mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-01 03:26:41 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc4b50f817 | ||
|
|
0a73123a66 | ||
|
|
2961c991a4 | ||
|
|
a6001dc76e | ||
|
|
9f23b89e90 | ||
|
|
a6096f4702 | ||
|
|
08ad8a0b2d | ||
|
|
3ea4cf0b63 | ||
|
|
9290c074f7 | ||
|
|
ca06ce6100 | ||
|
|
bc0cddf03d | ||
|
|
4b33efffbe | ||
|
|
80a614d330 | ||
|
|
61ff8a4da8 | ||
|
|
039831a27a | ||
|
|
ce9fbe58b2 | ||
|
|
ef1a141347 | ||
|
|
42bbe965fa | ||
|
|
a8e98593ea | ||
|
|
6b1cab7011 | ||
|
|
dc795b5db7 | ||
|
|
0fc107c8c1 | ||
|
|
09a4e0b209 | ||
|
|
6479c52fb2 | ||
|
|
3f3e942f60 | ||
|
|
e7cb469fd0 | ||
|
|
9511ed9d2c | ||
|
|
2a7ce19b73 | ||
|
|
908975c758 | ||
|
|
a83fc5e343 | ||
|
|
2c206c374d | ||
|
|
50785a2217 | ||
|
|
91d8c25183 | ||
|
|
3df894ec92 | ||
|
|
c440a208a6 | ||
|
|
32bd9e6029 | ||
|
|
823aa640b9 | ||
|
|
00d043d743 | ||
|
|
eebc39d3e9 | ||
|
|
92c9eb614f | ||
|
|
ef16867ff1 | ||
|
|
edcfce22f0 | ||
|
|
c87661dbee | ||
|
|
d82852a909 | ||
|
|
2b1f150129 | ||
|
|
5be97c6145 | ||
|
|
80e20417a8 | ||
|
|
ee62878e6b | ||
|
|
2a5970265d | ||
|
|
d60a843c23 | ||
|
|
36388e58b9 | ||
|
|
ca05a2ccea | ||
|
|
7aa426fa71 | ||
|
|
0471dbaa84 | ||
|
|
1e92d8b7f3 | ||
|
|
d9e299c1df | ||
|
|
9b54dc0b28 | ||
|
|
c4de94cff0 | ||
|
|
92093ce56a | ||
|
|
c7d0aaa786 | ||
|
|
4604227238 | ||
|
|
b7aecf5f17 | ||
|
|
8c574f888c | ||
|
|
7ebf5fff0f | ||
|
|
4439531906 | ||
|
|
e5b09f34e0 | ||
|
|
b12b1040c3 | ||
|
|
718cbdb48c | ||
|
|
03abdfcfe2 | ||
|
|
f95ac6780f | ||
|
|
61815c10e7 | ||
|
|
7a8937279a | ||
|
|
f07c08a071 | ||
|
|
75ee875194 | ||
|
|
9e05e4d810 | ||
|
|
ef6f96bda3 | ||
|
|
b5c2524d75 | ||
|
|
80887f5791 | ||
|
|
b5c284db39 | ||
|
|
159efdef9f | ||
|
|
206283d1f7 | ||
|
|
ec0f4d242e | ||
|
|
fd270768e8 | ||
|
|
5ee5d42511 | ||
|
|
4c92ffda60 | ||
|
|
80bbdf41fb | ||
|
|
fb2c5ea8b6 | ||
|
|
265557afa2 | ||
|
|
aa23a6e2b5 | ||
|
|
708b30ab8a | ||
|
|
aa331e52b8 | ||
|
|
c752f3529b | ||
|
|
3e26be9845 | ||
|
|
37b118f4fb | ||
|
|
9e64f7dd66 | ||
|
|
ac0c3db472 | ||
|
|
f185e1a5e3 | ||
|
|
ac7de7e8de | ||
|
|
706891e92b | ||
|
|
fa238809ae | ||
|
|
737a99b1c8 | ||
|
|
122d6e5f0d | ||
|
|
36de219eb7 | ||
|
|
bb6c883761 | ||
|
|
1e5d104524 | ||
|
|
946f5f130c | ||
|
|
a8c73f1ff2 | ||
|
|
d60fe437ef | ||
|
|
8280fdedb7 | ||
|
|
fdf9726c37 | ||
|
|
816d0c0820 | ||
|
|
5b4d009f55 | ||
|
|
fce4816a0d | ||
|
|
676b5c2287 | ||
|
|
d5bd30474c | ||
|
|
75a8c0c73e | ||
|
|
c4bfdaeaf3 | ||
|
|
5dbd91038a | ||
|
|
21b8b2827e | ||
|
|
3cff48437e | ||
|
|
ffb8586795 | ||
|
|
da32b59e11 | ||
|
|
2a8ef71e8f | ||
|
|
6705428a8d | ||
|
|
bbd17abc43 | ||
|
|
12ed50464b | ||
|
|
57991c1da8 | ||
|
|
eecb59dd8a | ||
|
|
b8fcaf01b8 | ||
|
|
c80b0e1910 | ||
|
|
f250dfb8de | ||
|
|
f3466e2525 | ||
|
|
7a24c5628a | ||
|
|
cabcd54508 | ||
|
|
a4f43cb275 | ||
|
|
caa8c921e6 | ||
|
|
48305c18f7 | ||
|
|
ffda51014d | ||
|
|
61822b73f0 | ||
|
|
df805f3a1a | ||
|
|
0249d4c8ab | ||
|
|
4f959da9f8 | ||
|
|
da5789ba5f | ||
|
|
bf13e0bc6a | ||
|
|
5686e63af3 | ||
|
|
1fbfefa625 | ||
|
|
01b63a25ba | ||
|
|
43efdded4a | ||
|
|
e67bc45d48 | ||
|
|
a0ec22c251 | ||
|
|
3bd8a923ee | ||
|
|
47f1d847f1 | ||
|
|
04bacbf9ce | ||
|
|
26e58b8def | ||
|
|
5d30409f33 | ||
|
|
450239d5c8 | ||
|
|
c3081ef02e | ||
|
|
636b94cf22 | ||
|
|
1a880f916c | ||
|
|
a27db87fb6 | ||
|
|
f6d13330dd | ||
|
|
5321101276 | ||
|
|
50b2aa5843 | ||
|
|
b02441cdca | ||
|
|
0c0f95168c | ||
|
|
200b34ff28 | ||
|
|
144565ee03 | ||
|
|
ef1da6f704 | ||
|
|
4e497eef8a | ||
|
|
e6bd9d0ae6 | ||
|
|
1028f8efb8 | ||
|
|
c16c9f982f | ||
|
|
252dda5220 | ||
|
|
a6a68778ea | ||
|
|
223978eb82 | ||
|
|
e8d365f1aa | ||
|
|
a9f9dee30d | ||
|
|
5f4be3bfb7 | ||
|
|
02611dad7c | ||
|
|
a199ed1396 | ||
|
|
8cf617b683 | ||
|
|
a400ef0079 | ||
|
|
4e3d09e9d8 | ||
|
|
e295477951 | ||
|
|
7137196788 |
@@ -11,6 +11,8 @@
|
||||
* [ ] You've read through your own file changes for silly mistakes etc
|
||||
|
||||
<!--
|
||||
Be sure to name your PR with an imperative e.g. 'Add worktrees view'
|
||||
Be sure to name your PR with an imperative e.g. 'Add worktrees view', and make sure the title
|
||||
is suitable to be included as a bullet point in release notes (i.e. phrased from a user's point
|
||||
of view).
|
||||
see https://github.com/jesseduffield/lazygit/releases/tag/v0.40.0 for examples
|
||||
-->
|
||||
|
||||
@@ -169,9 +169,9 @@ jobs:
|
||||
with:
|
||||
go-version: 1.24.x
|
||||
- name: Lint
|
||||
uses: golangci/golangci-lint-action@v6.5.0
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
with:
|
||||
version: v1.64.6
|
||||
version: v2.2.1
|
||||
- name: errors
|
||||
run: golangci-lint run
|
||||
if: ${{ failure() }}
|
||||
|
||||
+108
-34
@@ -1,36 +1,110 @@
|
||||
linters:
|
||||
enable:
|
||||
- gofumpt
|
||||
- thelper
|
||||
- goimports
|
||||
- tparallel
|
||||
- wastedassign
|
||||
- unparam
|
||||
- prealloc
|
||||
- unconvert
|
||||
- exhaustive
|
||||
- makezero
|
||||
- nakedret
|
||||
- copyloopvar
|
||||
fast: false
|
||||
|
||||
linters-settings:
|
||||
copyloopvar:
|
||||
# Check all assigning the loop variable to another variable.
|
||||
# Default: false
|
||||
# If true, an assignment like `a := x` will be detected as an error.
|
||||
check-alias: true
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
staticcheck:
|
||||
# SA1019 is for checking that we're not using fields marked as deprecated
|
||||
# in a comment. It decides this in a loose way so I'm silencing it. Also because
|
||||
# it's tripping on our own structs.
|
||||
checks: ["all", "-SA1019"]
|
||||
nakedret:
|
||||
# the gods will judge me but I just don't like naked returns at all
|
||||
max-func-lines: 0
|
||||
|
||||
version: "2"
|
||||
run:
|
||||
go: "1.24"
|
||||
timeout: 10m
|
||||
linters:
|
||||
enable:
|
||||
- copyloopvar
|
||||
- errorlint
|
||||
- exhaustive
|
||||
- intrange
|
||||
- makezero
|
||||
- nakedret
|
||||
- nolintlint
|
||||
- prealloc
|
||||
- revive
|
||||
- thelper
|
||||
- tparallel
|
||||
- unconvert
|
||||
- unparam
|
||||
- wastedassign
|
||||
settings:
|
||||
copyloopvar:
|
||||
check-alias: true
|
||||
exhaustive:
|
||||
default-signifies-exhaustive: true
|
||||
nakedret:
|
||||
# the gods will judge me but I just don't like naked returns at all
|
||||
max-func-lines: 0
|
||||
staticcheck:
|
||||
checks:
|
||||
- all
|
||||
|
||||
# SA1019 is for checking that we're not using fields marked as
|
||||
# deprecated in a comment. It decides this in a loose way so I'm
|
||||
# silencing it. Also because it's tripping on our own structs.
|
||||
- -SA1019
|
||||
|
||||
# ST1003 complains about names like remoteUrl or itemId (should be
|
||||
# remoteURL and itemID). While I like these suggestions, it also
|
||||
# complains about enum constants that are all caps, and we use these and
|
||||
# I like them, and also about camelCase identifiers that contain an
|
||||
# underscore, which we also use in a few places. Since it can't be
|
||||
# configured to ignore specific cases, and I don't want to use nolint
|
||||
# comments in the code, we have to disable it altogether.
|
||||
- -ST1003 # Poorly chosen identifier
|
||||
|
||||
# Probably a good idea, but we first have to review our error reporting
|
||||
# strategy to be able to use it everywhere.
|
||||
- -ST1005 # Error strings should not be capitalized
|
||||
|
||||
# Many of our classes use self as a receiver name, and we think that's fine.
|
||||
- -ST1006 # Use of self or this as receiver name
|
||||
|
||||
# De Morgan's law suggests to replace `!(a && b)` with `!a || !b`; but
|
||||
# sometimes I find one more readable than the other, so I want to decide
|
||||
# that myself.
|
||||
- -QF1001 # De Morgan's law
|
||||
|
||||
# QF1003 is about using a tagged switch instead of an if-else chain. In
|
||||
# many cases this is a useful suggestion; however, sometimes the change
|
||||
# is only possible by adding a default case to the switch (when there
|
||||
# was no `else` block in the original code), in which case I don't find
|
||||
# it to be an improvement.
|
||||
- -QF1003 # Could replace with tagged switch
|
||||
|
||||
# We need to review our use of embedded fields. I suspect that in some
|
||||
# cases the fix is not to remove the selector for the embedded field,
|
||||
# but to turn the embedded field into a named field.
|
||||
- -QF1008 # Could remove embedded field from selector
|
||||
|
||||
# The following checks are all disabled by default in golangci-lint, but
|
||||
# we disable them again explicitly here to make it easier to keep this
|
||||
# list in sync with the gopls config in .vscode/settings.json.
|
||||
- -ST1000, # At least one file in a package should have a package comment
|
||||
- -ST1020, # The documentation of an exported function should start with the function's name
|
||||
- -ST1021, # The documentation of an exported type should start with type's name
|
||||
- -ST1022, # The documentation of an exported variable or constant should start with variable's name
|
||||
|
||||
dot-import-whitelist:
|
||||
- github.com/jesseduffield/lazygit/pkg/integration/components
|
||||
revive:
|
||||
severity: warning
|
||||
rules:
|
||||
- name: atomic
|
||||
- name: context-as-argument
|
||||
- name: context-keys-type
|
||||
- name: error-naming
|
||||
- name: var-declaration
|
||||
- name: package-comments
|
||||
- name: range
|
||||
- name: time-naming
|
||||
- name: indent-error-flow
|
||||
- name: errorf
|
||||
- name: superfluous-else
|
||||
exclusions:
|
||||
generated: lax
|
||||
presets:
|
||||
- comments
|
||||
- common-false-positives
|
||||
- legacy
|
||||
- std-error-handling
|
||||
paths:
|
||||
- vendor/
|
||||
formatters:
|
||||
enable:
|
||||
- gofumpt
|
||||
- goimports
|
||||
exclusions:
|
||||
generated: lax
|
||||
paths:
|
||||
- vendor/
|
||||
|
||||
Vendored
+24
@@ -1,5 +1,29 @@
|
||||
{
|
||||
"gopls": {
|
||||
"formatting.gofumpt": true,
|
||||
"ui.diagnostic.staticcheck": true,
|
||||
"ui.diagnostic.analyses": {
|
||||
// This list must match the one in .golangci.yml
|
||||
"SA1019": false,
|
||||
"ST1003": false,
|
||||
"ST1005": false,
|
||||
"ST1006": false,
|
||||
"QF1001": false,
|
||||
"QF1003": false,
|
||||
"QF1008": false,
|
||||
"ST1000": false,
|
||||
"ST1020": false,
|
||||
"ST1021": false,
|
||||
"ST1022": false,
|
||||
// Dot imports; this warning is enabled in .golangci.yml, but with an
|
||||
// extra dot-import-whitelist config. Because I couldn't figure out how to
|
||||
// specify that extra config for gopls, I'm disabling the check altogether
|
||||
// here.
|
||||
"ST1001": false,
|
||||
},
|
||||
},
|
||||
"go.alternateTools": {
|
||||
"golangci-lint-v2": "${workspaceFolder}/.bin/golangci-lint",
|
||||
},
|
||||
"go.lintTool": "golangci-lint-v2",
|
||||
}
|
||||
|
||||
Vendored
+6
-3
@@ -24,22 +24,24 @@
|
||||
{
|
||||
"label": "Run current file integration test",
|
||||
"type": "shell",
|
||||
"command": "go generate pkg/integration/tests/tests.go && go run cmd/integration_test/main.go cli ${relativeFile}",
|
||||
"command": "go run cmd/integration_test/main.go cli ${relativeFile}",
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
"isDefault": true
|
||||
},
|
||||
"presentation": {
|
||||
"clear": true,
|
||||
"focus": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Run current file integration test (slow)",
|
||||
"type": "shell",
|
||||
"command": "go generate pkg/integration/tests/tests.go && go run cmd/integration_test/main.go cli --slow ${relativeFile}",
|
||||
"command": "go run cmd/integration_test/main.go cli --slow ${relativeFile}",
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
"clear": true,
|
||||
"kind": "test",
|
||||
},
|
||||
"presentation": {
|
||||
@@ -49,12 +51,13 @@
|
||||
{
|
||||
"label": "Run current file integration test (sandbox)",
|
||||
"type": "shell",
|
||||
"command": "go generate pkg/integration/tests/tests.go && go run cmd/integration_test/main.go cli --sandbox ${relativeFile}",
|
||||
"command": "go run cmd/integration_test/main.go cli --sandbox ${relativeFile}",
|
||||
"problemMatcher": [],
|
||||
"group": {
|
||||
"kind": "test",
|
||||
},
|
||||
"presentation": {
|
||||
"clear": true,
|
||||
"focus": true
|
||||
}
|
||||
},
|
||||
|
||||
@@ -40,7 +40,7 @@ format:
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
golangci-lint run
|
||||
./scripts/lint.sh
|
||||
|
||||
# For more details about integration test, see https://github.com/jesseduffield/lazygit/blob/master/pkg/integration/README.md.
|
||||
.PHONY: integration-test-tui
|
||||
|
||||
@@ -194,7 +194,7 @@ Say you're on a feature branch that was itself branched off of the develop branc
|
||||
|
||||
### Undo
|
||||
|
||||
You can undo the last action by pressing 'z' and redo with `ctrl+z`. Here we drop a couple of commits and then undo the actions.
|
||||
You can undo the last action by pressing `z` and redo with `ctrl+z`. Here we drop a couple of commits and then undo the actions.
|
||||
Undo uses the reflog which is specific to commits and branches so we can't undo changes to the working tree or stash.
|
||||
|
||||
[More info](/docs/Undoing.md)
|
||||
|
||||
+14
-3
@@ -72,6 +72,9 @@ gui:
|
||||
# When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS.
|
||||
mouseEvents: true
|
||||
|
||||
# If true, do not show a warning when amending a commit.
|
||||
skipAmendWarning: false
|
||||
|
||||
# If true, do not show a warning when discarding changes in the staging view.
|
||||
skipDiscardChangeWarning: false
|
||||
|
||||
@@ -113,6 +116,9 @@ gui:
|
||||
# paragraphs of markdown text.
|
||||
wrapLinesInStagingView: true
|
||||
|
||||
# If true, hunk selection mode will be enabled by default when entering the staging view.
|
||||
useHunkModeInStagingView: false
|
||||
|
||||
# One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
|
||||
language: auto
|
||||
|
||||
@@ -190,6 +196,9 @@ gui:
|
||||
# This can be toggled from within Lazygit with the '`' key, but that will not change the default.
|
||||
showFileTree: true
|
||||
|
||||
# If true, add a "/" root item in the file tree representing the root of the repository. It is only added when necessary, i.e. when there is more than one item at top level.
|
||||
showRootItemInFileTree: true
|
||||
|
||||
# If true, show the number of lines changed per file in the Files view
|
||||
showNumstatInFilesView: false
|
||||
|
||||
@@ -242,7 +251,7 @@ gui:
|
||||
screenMode: normal
|
||||
|
||||
# Window border style.
|
||||
# One of 'rounded' (default) | 'single' | 'double' | 'hidden'
|
||||
# One of 'rounded' (default) | 'single' | 'double' | 'hidden' | 'bold'
|
||||
border: rounded
|
||||
|
||||
# If true, show a seriously epic explosion animation when nuking the working tree.
|
||||
@@ -337,7 +346,7 @@ git:
|
||||
# If true, periodically refresh files and submodules
|
||||
autoRefresh: true
|
||||
|
||||
# If not "none", lazygit will automatically forward branches to their upstream after fetching. Applies to branches that are not the currently checked out branch, and only to those that are strictly behind their upstream (as opposed to diverged).
|
||||
# If not "none", lazygit will automatically fast-forward local branches to match their upstream after fetching. Applies to branches that are not the currently checked out branch, and only to those that are strictly behind their upstream (as opposed to diverged).
|
||||
# Possible values: 'none' | 'onlyMainBranches' | 'allBranches'
|
||||
autoForwardBranches: onlyMainBranches
|
||||
|
||||
@@ -354,7 +363,8 @@ git:
|
||||
branchLogCmd: git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --
|
||||
|
||||
# Commands used to display git log of all branches in the main window, they will be cycled in order of appearance (array of strings)
|
||||
allBranchesLogCmds: []
|
||||
allBranchesLogCmds:
|
||||
- git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium
|
||||
|
||||
# If true, do not spawn a separate process when using GPG
|
||||
overrideGpg: false
|
||||
@@ -515,6 +525,7 @@ keybinding:
|
||||
goInto: <enter>
|
||||
confirm: <enter>
|
||||
confirmInEditor: <a-enter>
|
||||
confirmInEditor-alt: <c-s>
|
||||
remove: d
|
||||
new: "n"
|
||||
edit: e
|
||||
|
||||
@@ -14,7 +14,7 @@ customCommands:
|
||||
- key: 'C'
|
||||
context: 'global'
|
||||
command: "git commit"
|
||||
subprocess: true
|
||||
output: terminal
|
||||
- key: 'n'
|
||||
context: 'localBranches'
|
||||
prompts:
|
||||
@@ -53,13 +53,11 @@ For a given custom command, here are the allowed fields:
|
||||
| key | The key to trigger the command. Use a single letter or one of the values from [here](https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md). Custom commands without a key specified can be triggered by selecting them from the keybindings (`?`) menu | no |
|
||||
| command | The command to run (using Go template syntax for placeholder values) | yes |
|
||||
| context | The context in which to listen for the key (see [below](#contexts)) | yes |
|
||||
| subprocess | Whether you want the command to run in a subprocess (e.g. if the command requires user input) | no |
|
||||
| prompts | A list of prompts that will request user input before running the final command | no |
|
||||
| loadingText | Text to display while waiting for command to finish | no |
|
||||
| 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 |
|
||||
| outputTitle | The title to display in the popup panel if showOutput is true. If left unset, the command will be used as the title. | no |
|
||||
| output | Where the output of the command should go. 'none' discards it, 'terminal' suspends lazygit and runs the command in the terminal (useful for commands that require user input), 'log' streams it to the command log, 'logWithPty' is like 'log' but runs the command in a pseudo terminal (can be useful for commands that produce colored output when the output is a terminal), and 'popup' shows it in a popup. | no |
|
||||
| outputTitle | The title to display in the popup panel if output is set to 'popup'. If left unset, the command will be used as the title. | no |
|
||||
| after | Actions to take after the command has completed | no |
|
||||
|
||||
Here are the options for the `after` key:
|
||||
@@ -365,7 +363,7 @@ If you use the commandMenu property, none of the other properties except key and
|
||||
|
||||
## Debugging
|
||||
|
||||
If you want to verify that your command actually does what you expect, you can wrap it in an 'echo' call and set `showOutput: true` so that it doesn't actually execute the command but you can see how the placeholders were resolved.
|
||||
If you want to verify that your command actually does what you expect, you can wrap it in an 'echo' call and set `output: popup` so that it doesn't actually execute the command but you can see how the placeholders were resolved.
|
||||
|
||||
## More Examples
|
||||
|
||||
|
||||
+309
-294
@@ -1,346 +1,314 @@
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
|
||||
|
||||
# Lazygit キーバインド
|
||||
# Lazygit キーバインディング
|
||||
|
||||
_Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
_凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味します_
|
||||
|
||||
## グローバルキーバインド
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-r> `` | 最近使用したリポジトリに切り替え | |
|
||||
| `` <pgup> (fn+up/shift+k) `` | メインパネルを上にスクロール | |
|
||||
| `` <pgdown> (fn+down/shift+j) `` | メインパネルを下にスクロール | |
|
||||
| `` @ `` | コマンドログメニューを開く | View options for the command log e.g. show/hide the command log and focus the command log. |
|
||||
| `` P `` | Push | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
|
||||
| `` p `` | Pull | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
|
||||
| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename. |
|
||||
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
|
||||
| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view. |
|
||||
| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view. |
|
||||
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
|
||||
| `` <c-p> `` | View custom patch options | |
|
||||
| `` m `` | View merge/rebase options | View options to abort/continue/skip the current merge/rebase. |
|
||||
| `` R `` | リフレッシュ | Refresh the git state (i.e. run `git status`, `git branch`, etc in background to update the contents of panels). This does not run `git fetch`. |
|
||||
| `` + `` | 次のスクリーンモード (normal/half/fullscreen) | |
|
||||
| `` _ `` | 前のスクリーンモード | |
|
||||
| `` ? `` | メニューを開く | |
|
||||
| `` <c-s> `` | View filter options | View options for filtering the commit log, so that only commits matching the filter are shown. |
|
||||
| `` W `` | 差分メニューを開く | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
|
||||
| `` <c-e> `` | 差分メニューを開く | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
|
||||
| `` <c-r> `` | 最近のリポジトリをチェックアウト | |
|
||||
| `` <pgup> (fn+up/shift+k) `` | メインウィンドウを上にスクロール | |
|
||||
| `` <pgdown> (fn+down/shift+j) `` | メインウィンドウを下にスクロール | |
|
||||
| `` @ `` | コマンドログオプションを表示 | コマンドログのオプションを表示します(例:コマンドログの表示/非表示、コマンドログへのフォーカスなど)。 |
|
||||
| `` P `` | プッシュ | 現在のブランチを対応するアップストリームブランチにプッシュします。アップストリームが設定されていない場合、アップストリームブランチの設定を求められます。 |
|
||||
| `` p `` | プル | 現在のブランチのリモートから変更をプルします。アップストリームが設定されていない場合、アップストリームブランチの設定を求められます。 |
|
||||
| `` ) `` | リネーム検出の類似度しきい値を上げる | ファイルの削除と追加をリネーム(ファイル名変更)と判断するための類似度しきい値を上げます。値を上げると、より似ているファイルのみがリネームと認識されます。 |
|
||||
| `` ( `` | リネーム検出の類似度しきい値を下げる | ファイルの削除と追加をリネーム(ファイル名変更)と判断するための類似度しきい値を下げます。値を下げると、より多くのファイルペアがリネームと認識されます。 |
|
||||
| `` } `` | 差分コンテキストサイズを増やす | 差分ビューで変更の周りに表示されるコンテキストの量を増やします。 |
|
||||
| `` { `` | 差分コンテキストサイズを減らす | 差分ビューで変更の周りに表示されるコンテキストの量を減らします。 |
|
||||
| `` : `` | シェルコマンドを実行 | 実行するシェルコマンドを入力するプロンプトを表示します。 |
|
||||
| `` <c-p> `` | カスタムパッチオプションを表示 | |
|
||||
| `` m `` | マージ/リベースオプションを表示 | 現在のマージ/リベースを中止/継続/スキップするオプションを表示します。 |
|
||||
| `` R `` | 更新 | Gitの状態を更新します(`git status`、`git branch`などをバックグラウンドで実行してパネルの内容を更新します)。これは`git fetch`を実行しません。 |
|
||||
| `` + `` | 次の画面モード(通常/半分/全画面) | |
|
||||
| `` _ `` | 前の画面モード | |
|
||||
| `` ? `` | キーバインディングメニューを開く | |
|
||||
| `` <c-s> `` | フィルターオプションを表示 | コミットログのフィルタリングオプションを表示し、フィルタに一致するコミットのみを表示します。 |
|
||||
| `` W `` | 差分オプションを表示 | 2つのrefの差分に関連するオプションを表示します(例:選択したrefとの差分表示、差分を取るrefの入力、差分方向の反転など)。 |
|
||||
| `` <c-e> `` | 差分オプションを表示 | 2つのrefの差分に関連するオプションを表示します(例:選択したrefとの差分表示、差分を取るrefの入力、差分方向の反転など)。 |
|
||||
| `` q `` | 終了 | |
|
||||
| `` <esc> `` | キャンセル | |
|
||||
| `` <c-w> `` | 空白文字の差分の表示有無を切り替え | Toggle whether or not whitespace changes are shown in the diff view. |
|
||||
| `` z `` | アンドゥ (via reflog) (experimental) | The reflog will be used to determine what git command to run to undo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
|
||||
| `` <c-z> `` | リドゥ (via reflog) (experimental) | The reflog will be used to determine what git command to run to redo the last git command. This does not include changes to the working tree; only commits are taken into consideration. |
|
||||
| `` <c-w> `` | 空白表示の切り替え | 差分ビューで空白の変更を表示するかどうかを切り替えます。 |
|
||||
| `` z `` | 元に戻す | 最後のgitコマンドを元に戻すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 |
|
||||
| `` <c-z> `` | やり直す | 最後のgitコマンドをやり直すために実行するgitコマンドを決定するためにreflogが使用されます。これにはワーキングツリーへの変更は含まれません。コミットのみが考慮されます。 |
|
||||
|
||||
## 一覧パネルの操作
|
||||
## リストパネルのナビゲーション
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` , `` | 前のページ | |
|
||||
| `` . `` | 次のページ | |
|
||||
| `` < (<home>) `` | 最上部までスクロール | |
|
||||
| `` > (<end>) `` | 最下部までスクロール | |
|
||||
| `` < (<home>) `` | 先頭にスクロール | |
|
||||
| `` > (<end>) `` | 末尾にスクロール | |
|
||||
| `` v `` | 範囲選択を切り替え | |
|
||||
| `` <s-down> `` | Range select down | |
|
||||
| `` <s-up> `` | Range select up | |
|
||||
| `` / `` | 検索を開始 | |
|
||||
| `` H `` | 左スクロール | |
|
||||
| `` L `` | 右スクロール | |
|
||||
| `` <s-down> `` | 範囲選択を下に | |
|
||||
| `` <s-up> `` | 範囲選択を上に | |
|
||||
| `` / `` | 現在のビューをテキストで検索 | |
|
||||
| `` H `` | 左にスクロール | |
|
||||
| `` L `` | 右にスクロール | |
|
||||
| `` ] `` | 次のタブ | |
|
||||
| `` [ `` | 前のタブ | |
|
||||
|
||||
## Secondary
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <tab> `` | パネルを切り替え | Switch to other view (staged/unstaged changes). |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` / `` | 検索を開始 | |
|
||||
|
||||
## Stash
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | 適用 | Apply the stash entry to your working directory. |
|
||||
| `` g `` | Pop | Apply the stash entry to your working directory and remove the stash entry. |
|
||||
| `` d `` | Drop | Remove the stash entry from the stash list. |
|
||||
| `` n `` | 新しいブランチを作成 | Create a new branch from the selected stash entry. This works by git checking out the commit that the stash entry was created from, creating a new branch from that commit, then applying the stash entry to the new branch as an additional commit. |
|
||||
| `` r `` | Stashを変更 | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | View files | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Sub-commits
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | コミットのhashをクリップボードにコピー | |
|
||||
| `` <space> `` | チェックアウト | Checkout the selected commit as a detached HEAD. |
|
||||
| `` y `` | コミットの情報をコピー | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
|
||||
| `` o `` | ブラウザでコミットを開く | |
|
||||
| `` n `` | コミットにブランチを作成 | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.
|
||||
|
||||
Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | コミットをコピー (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <c-r> `` | Reset copied (cherry-picked) commits selection | |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | View files | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | 検索を開始 | |
|
||||
|
||||
## Worktrees
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` n `` | New worktree | |
|
||||
| `` <space> `` | Switch | Switch to the selected worktree. |
|
||||
| `` o `` | Open in editor | |
|
||||
| `` d `` | Remove | Remove the selected worktree. This will both delete the worktree's directory, as well as metadata about the worktree in the .git directory. |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## コミット
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | コミットのhashをクリップボードにコピー | |
|
||||
| `` <c-r> `` | Reset copied (cherry-picked) commits selection | |
|
||||
| `` b `` | View bisect options | |
|
||||
| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
|
||||
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
|
||||
| `` r `` | コミットメッセージを変更 | Reword the selected commit's message. |
|
||||
| `` R `` | エディタでコミットメッセージを編集 | |
|
||||
| `` d `` | コミットを削除 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
|
||||
| `` e `` | Edit (start interactive rebase) | コミットを編集 |
|
||||
| `` i `` | Start interactive rebase | Start an interactive rebase for the commits on your branch. This will include all commits from the HEAD commit down to the first merge commit or main branch commit.
|
||||
If you would instead like to start an interactive rebase from the selected commit, press `e`. |
|
||||
| `` p `` | 選択 | Mark the selected commit to be picked (when mid-rebase). This means that the commit will be retained upon continuing the rebase. |
|
||||
| `` F `` | Fixupコミットを作成 | このコミットに対するfixupコミットを作成 |
|
||||
| `` S `` | Apply fixup commits | Squash all 'fixup!' commits, either above the selected commit, or all in current branch (autosquash). |
|
||||
| `` <c-o> `` | コミットハッシュをクリップボードにコピー | |
|
||||
| `` <c-r> `` | コピーされた(チェリーピックされた)コミットの選択をリセット | |
|
||||
| `` b `` | bisectオプションを表示 | |
|
||||
| `` s `` | スカッシュ | 選択したコミットをその下のコミットにスカッシュします。スカッシュとは複数のコミットを1つにまとめる操作です。選択したコミットのメッセージが下のコミットに追加されます。 |
|
||||
| `` f `` | フィックスアップ | 選択したコミットをその下のコミットにマージします。フィックスアップはスカッシュと似ていますが、選択したコミットのメッセージは破棄され、下のコミットのメッセージのみが保持されます。 |
|
||||
| `` r `` | メッセージ変更 | 選択したコミットのメッセージを変更します。 |
|
||||
| `` R `` | エディタでメッセージ変更 | |
|
||||
| `` d `` | 削除 | 選択したコミットを削除します。これはリベースを通じてブランチからコミットを削除します。コミットが後続のコミットが依存する変更を行っている場合、マージコンフリクトを解決する必要があるかもしれません。 |
|
||||
| `` e `` | 編集(対話型リベースを開始) | 選択したコミットを編集します。これを使用して、選択したコミットから対話型リベースを開始します。すでにリベース中の場合、これは選択したコミットを編集用にマークし、リベースを続行すると、リベースは選択したコミットで一時停止して変更を行えるようにします。 |
|
||||
| `` i `` | 対話的リベースを開始 | ブランチ上のコミットの対話的リベースを開始します。これには、HEADコミットから最初のマージコミットまたはメインブランチのコミットまでのすべてのコミットが含まれます。
|
||||
選択したコミットから対話的リベースを開始したい場合は、代わりに `e` を押してください。 |
|
||||
| `` p `` | ピック | 選択したコミットをピックするようにマークします(リベース中)。これは、リベースを続行すると、コミットが保持されることを意味します。 |
|
||||
| `` F `` | fixupコミットを作成 | 選択したコミットに対する「fixup!」コミットを作成します。fixupコミットは、選択したコミットの修正用コミットです。後で、同じコミットで `S` を押すと、上記のすべてのfixupコミットが適用されます。 |
|
||||
| `` S `` | fixupコミットを適用 | すべての「fixup!」コミットを、選択したコミットの上部または現在のブランチ内のすべてをスカッシュします(autosquash)。 |
|
||||
| `` <c-j> `` | コミットを1つ下に移動 | |
|
||||
| `` <c-k> `` | コミットを1つ上に移動 | |
|
||||
| `` V `` | コミットを貼り付け (cherry-pick) | |
|
||||
| `` B `` | Mark as base commit for rebase | Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command. |
|
||||
| `` A `` | Amend | ステージされた変更でamendコミット |
|
||||
| `` a `` | Amend commit attribute | Set/Reset commit author or set co-author. |
|
||||
| `` t `` | Revert | Create a revert commit for the selected commit, which applies the selected commit's changes in reverse. |
|
||||
| `` T `` | タグを作成 | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. |
|
||||
| `` <c-l> `` | ログメニューを開く | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. |
|
||||
| `` <space> `` | チェックアウト | Checkout the selected commit as a detached HEAD. |
|
||||
| `` y `` | コミットの情報をコピー | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
|
||||
| `` V `` | ペースト(チェリーピック) | |
|
||||
| `` B `` | リベース用のベースコミットとしてマーク | 次のリベース用のベースコミットを選択します。ブランチにリベースするとき、ベースコミットより上のコミットのみが持ち込まれます。これは `git rebase --onto` コマンドを使用します。 |
|
||||
| `` A `` | 修正 | ステージされた変更でコミットを修正します。選択したコミットがHEADコミットの場合、これは `git commit --amend` を実行します。それ以外の場合、コミットはリベースを通じて修正されます。 |
|
||||
| `` a `` | コミット属性を修正 | コミット作者の設定/リセットまたは共同作者の設定を行います。 |
|
||||
| `` t `` | 元に戻す | 選択したコミットの変更を逆に適用する、リバートコミットを作成します。 |
|
||||
| `` T `` | コミットにタグを付ける | 選択したコミットを指すタグを新規作成します。タグ名とオプションの説明を入力するよう促されます。 |
|
||||
| `` <c-l> `` | ログオプションを表示 | コミットログのオプションを表示します(例:並び順の変更、Gitグラフの非表示、Gitグラフ全体の表示)。 |
|
||||
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したコミットをデタッチドヘッド(特定のブランチに属さない状態)としてチェックアウトします。 |
|
||||
| `` y `` | コミット属性をクリップボードにコピー | コミット属性をクリップボードにコピーします(例:ハッシュ、URL、差分、メッセージ、作者)。 |
|
||||
| `` o `` | ブラウザでコミットを開く | |
|
||||
| `` n `` | コミットにブランチを作成 | |
|
||||
| `` n `` | コミットから新しいブランチを作成 | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.
|
||||
|
||||
Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | コミットをコピー (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:
|
||||
- ソフトリセット:変更を保持し、ステージされた状態にします
|
||||
- ミックスリセット:変更を保持し、ステージされていない状態にします
|
||||
- ハードリセット:すべての変更を破棄します |
|
||||
| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 |
|
||||
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
|
||||
| `` * `` | 現在のブランチのコミットを選択 | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | View files | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | 検索を開始 | |
|
||||
| `` <enter> `` | ファイルを表示 | |
|
||||
| `` w `` | ワークツリーオプションを表示 | |
|
||||
| `` / `` | 現在のビューをテキストで検索 | |
|
||||
|
||||
## コミットファイル
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | ファイル名をクリップボードにコピー | |
|
||||
| `` y `` | Copy to clipboard | |
|
||||
| `` c `` | チェックアウト | Checkout file. This replaces the file in your working tree with the version from the selected commit. |
|
||||
| `` d `` | Remove | Discard this commit's changes to this file. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes this file. |
|
||||
| `` o `` | ファイルを開く | Open file in default application. |
|
||||
| `` e `` | 編集 | Open file in external editor. |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <space> `` | Toggle file included in patch | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
|
||||
| `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
|
||||
| `` <enter> `` | Enter file / Toggle directory collapsed | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. |
|
||||
| `` ` `` | ファイルツリーの表示を切り替え | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. |
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` <c-o> `` | パスをクリップボードにコピー | |
|
||||
| `` y `` | クリップボードにコピー | |
|
||||
| `` c `` | チェックアウト(ブランチの切り替え) | ファイルをチェックアウトします。これにより、作業ツリー内のファイルが選択したコミットのバージョンに置き換えられます。 |
|
||||
| `` d `` | 削除 | このコミットのこのファイルへの変更を破棄します。これはバックグラウンドで対話的なリベースを実行するため、後のコミットでもこのファイルが変更されている場合、マージコンフリクトが発生する可能性があります。 |
|
||||
| `` o `` | ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 |
|
||||
| `` e `` | 編集 | 外部エディタでファイルを開きます。 |
|
||||
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
|
||||
| `` <space> `` | パッチに含めるファイルを切り替え | ファイルがカスタムパッチに含まれるかどうかを切り替えます。https://github.com/jesseduffield/lazygit#rebase-magic-custom-patchesを参照してください。 |
|
||||
| `` a `` | すべてのファイルを切り替え | コミットのすべてのファイルをカスタムパッチに追加/削除します。https://github.com/jesseduffield/lazygit#rebase-magic-custom-patchesを参照してください。 |
|
||||
| `` <enter> `` | ファイルに入る / ディレクトリの折りたたみを切り替える | ファイルが選択されている場合、そのファイルに入ってカスタムパッチに個々の行を追加/削除できます。ディレクトリが選択されている場合、ディレクトリを切り替えます。 |
|
||||
| `` ` `` | ファイルツリービューを切り替え | ファイルビューをフラットレイアウトとツリーレイアウトの間で切り替えます。フラットレイアウトは全てのファイルパスを単一のリストで表示し、ツリーレイアウトはディレクトリごとにファイルをグループ化します。 |
|
||||
| `` - `` | すべてのファイルを折りたたむ | ファイルツリー内のすべてのディレクトリを折りたたみます |
|
||||
| `` = `` | すべてのファイルを展開 | ファイルツリー内のすべてのディレクトリを展開します |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` / `` | 検索を開始 | |
|
||||
| `` / `` | 現在のビューをテキストで検索 | |
|
||||
|
||||
## コミットメッセージ
|
||||
## コミット概要
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <enter> `` | 確認 | |
|
||||
| `` <esc> `` | 閉じる | |
|
||||
|
||||
## サブコミット
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | コミットハッシュをクリップボードにコピー | |
|
||||
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したコミットをデタッチドヘッド(特定のブランチに属さない状態)としてチェックアウトします。 |
|
||||
| `` y `` | コミット属性をクリップボードにコピー | コミット属性をクリップボードにコピーします(例:ハッシュ、URL、差分、メッセージ、作者)。 |
|
||||
| `` o `` | ブラウザでコミットを開く | |
|
||||
| `` n `` | コミットから新しいブランチを作成 | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.
|
||||
|
||||
Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:
|
||||
- ソフトリセット:変更を保持し、ステージされた状態にします
|
||||
- ミックスリセット:変更を保持し、ステージされていない状態にします
|
||||
- ハードリセット:すべての変更を破棄します |
|
||||
| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 |
|
||||
| `` <c-r> `` | コピーされた(チェリーピックされた)コミットの選択をリセット | |
|
||||
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
|
||||
| `` * `` | 現在のブランチのコミットを選択 | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | ファイルを表示 | |
|
||||
| `` w `` | ワークツリーオプションを表示 | |
|
||||
| `` / `` | 現在のビューをテキストで検索 | |
|
||||
|
||||
## サブモジュール
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | サブモジュール名をクリップボードにコピー | |
|
||||
| `` <enter> `` | Enter | サブモジュールを開く |
|
||||
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. |
|
||||
| `` u `` | Update | サブモジュールを更新 |
|
||||
| `` n `` | サブモジュールを新規追加 | |
|
||||
| `` e `` | サブモジュールのURLを更新 | |
|
||||
| `` i `` | Initialize | サブモジュールを初期化 |
|
||||
| `` b `` | View bulk submodule options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
| `` <enter> `` | 入る | サブモジュールに入ります。サブモジュールに入った後、`<esc>`を押して親リポジトリに戻ることができます。 |
|
||||
| `` d `` | 削除 | 選択したサブモジュールとそれに対応するディレクトリを削除します。 |
|
||||
| `` u `` | 更新 | 選択したサブモジュールを更新します。 |
|
||||
| `` n `` | 新しいサブモジュール | |
|
||||
| `` e `` | サブモジュールURLを更新 | |
|
||||
| `` i `` | 初期化 | 選択したサブモジュールを初期化してフェッチの準備をします。おそらく、続いて「更新」アクションを呼び出してサブモジュールをフェッチしたいでしょう。 |
|
||||
| `` b `` | 一括サブモジュールオプションを表示 | |
|
||||
| `` / `` | 現在のビューをテキストでフィルタリング | |
|
||||
|
||||
## スタッシュ
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | 適用 | スタッシュエントリをワーキングディレクトリに適用します。 |
|
||||
| `` g `` | ポップ | スタッシュエントリをワーキングディレクトリに適用し、スタッシュエントリを削除します。 |
|
||||
| `` d `` | 削除 | スタッシュリストからスタッシュエントリを削除します。 |
|
||||
| `` n `` | 新しいブランチ | 選択したスタッシュエントリから新しいブランチを作成します。これは、スタッシュエントリが作成されたコミットをgitがチェックアウトし、そのコミットから新しいブランチを作成した後、スタッシュエントリを追加のコミットとして新しいブランチに適用することで機能します。 |
|
||||
| `` r `` | スタッシュの名前を変更 | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | ファイルを表示 | |
|
||||
| `` w `` | ワークツリーオプションを表示 | |
|
||||
| `` / `` | 現在のビューをテキストでフィルタリング | |
|
||||
|
||||
## ステータス
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` o `` | 設定ファイルを開く | Open file in default application. |
|
||||
| `` e `` | 設定ファイルを編集 | Open file in external editor. |
|
||||
| `` o `` | 設定ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 |
|
||||
| `` e `` | 設定ファイルを編集 | 外部エディタでファイルを開きます。 |
|
||||
| `` u `` | 更新を確認 | |
|
||||
| `` <enter> `` | 最近使用したリポジトリに切り替え | |
|
||||
| `` a `` | Show/cycle all branch logs | |
|
||||
| `` <enter> `` | 最近のリポジトリをチェックアウト | |
|
||||
| `` a `` | ブランチログの表示モードを順に切り替え | |
|
||||
|
||||
## セカンダリ
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <tab> `` | ビューを切り替え | 他のビュー(ステージされた変更/ステージされていない変更)に切り替えます。 |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` / `` | 現在のビューをテキストで検索 | |
|
||||
|
||||
## タグ
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | Copy tag to clipboard | |
|
||||
| `` <space> `` | チェックアウト | Checkout the selected tag as a detached HEAD. |
|
||||
| `` n `` | タグを作成 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
|
||||
| `` d `` | Delete | View delete options for local/remote tag. |
|
||||
| `` P `` | タグをpush | Push the selected tag to a remote. You'll be prompted to select a remote. |
|
||||
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <c-o> `` | タグをクリップボードにコピー | |
|
||||
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したタグをデタッチドHEADとしてチェックアウトします。 |
|
||||
| `` n `` | 新しいタグを作成 | 現在のコミットから新しいタグを作成します。タグ名とオプションの説明を入力するよう促されます。 |
|
||||
| `` d `` | 削除 | ローカル/リモートタグの削除オプションを表示します。 |
|
||||
| `` P `` | タグをプッシュ | 選択したタグをリモートにプッシュします。リモートを選択するよう促されます。 |
|
||||
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:
|
||||
- ソフトリセット:変更を保持し、ステージされた状態にします
|
||||
- ミックスリセット:変更を保持し、ステージされていない状態にします
|
||||
- ハードリセット:すべての変更を破棄します |
|
||||
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | コミットを閲覧 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
| `` <enter> `` | コミットを表示 | |
|
||||
| `` w `` | ワークツリーオプションを表示 | |
|
||||
| `` / `` | 現在のビューをテキストでフィルタリング | |
|
||||
|
||||
## ファイル
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | ファイル名をクリップボードにコピー | |
|
||||
| `` <space> `` | ステージ/アンステージ | Toggle staged for selected file. |
|
||||
| `` <c-b> `` | ファイルをフィルタ (ステージ/アンステージ) | |
|
||||
| `` y `` | Copy to clipboard | |
|
||||
| `` c `` | 変更をコミット | Commit staged changes. |
|
||||
| `` w `` | pre-commitフックを実行せずに変更をコミット | |
|
||||
| `` A `` | 最新のコミットにamend | |
|
||||
| `` C `` | gitエディタを使用して変更をコミット | |
|
||||
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
|
||||
| `` e `` | 編集 | Open file in external editor. |
|
||||
| `` o `` | ファイルを開く | Open file in default application. |
|
||||
| `` i `` | ファイルをignore | |
|
||||
| `` r `` | ファイルをリフレッシュ | |
|
||||
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. |
|
||||
| `` S `` | View stash options | View stash options (e.g. stash all, stash staged, stash unstaged). |
|
||||
| `` a `` | すべての変更をステージ/アンステージ | Toggle staged/unstaged for all files in working tree. |
|
||||
| `` <enter> `` | Stage lines / Collapse directory | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. |
|
||||
| `` d `` | Discard | View options for discarding changes to the selected file. |
|
||||
| `` g `` | View upstream reset options | |
|
||||
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). |
|
||||
| `` ` `` | ファイルツリーの表示を切り替え | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory. |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` M `` | Git mergetoolを開く | Run `git mergetool`. |
|
||||
| `` f `` | Fetch | Fetch changes from remote. |
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` <c-o> `` | パスをクリップボードにコピー | |
|
||||
| `` <space> `` | ステージ | 選択したファイルのステージ状態を切り替えます。 |
|
||||
| `` <c-b> `` | ステータスでファイルをフィルタリング | |
|
||||
| `` y `` | クリップボードにコピー | |
|
||||
| `` c `` | コミット | ステージされた変更をコミットします。 |
|
||||
| `` w `` | pre-commitフックなしで変更をコミット | |
|
||||
| `` A `` | 直前のコミットを修正 | |
|
||||
| `` C `` | Gitエディタを使用して変更をコミット | |
|
||||
| `` <c-f> `` | フィックスアップのベースコミットを検索 | 現在の変更が基づいているコミットを見つけて、コミットの修正/フィックスアップを行います。これにより、ブランチのコミットを一つずつ確認して、どのコミットを修正/フィックスアップすべきかを調べる手間が省けます。詳細はドキュメントを参照: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
|
||||
| `` e `` | 編集 | 外部エディタでファイルを開きます。 |
|
||||
| `` o `` | ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 |
|
||||
| `` i `` | ファイルを無視または除外 | |
|
||||
| `` r `` | ファイルを更新 | |
|
||||
| `` s `` | スタッシュ | すべての変更をスタッシュします。スタッシュの他のバリエーションについては、スタッシュオプションを表示するキーバインディングを使用してください。 |
|
||||
| `` S `` | スタッシュオプションを表示 | スタッシュオプション(すべてをスタッシュ、ステージされた変更をスタッシュ、ステージされていない変更をスタッシュなど)を表示します。 |
|
||||
| `` a `` | すべてステージ | ワーキングツリー内のすべてのファイルのステージ/アンステージを切り替えます。 |
|
||||
| `` <enter> `` | 行をステージ / ディレクトリを折りたたむ | 選択された項目がファイルの場合、個々のハンク/行をステージできるようにステージングビューにフォーカスします。選択された項目がディレクトリの場合、ディレクトリを折りたたむ/展開します。 |
|
||||
| `` d `` | 破棄 | 選択したファイルの変更を破棄するオプションを表示します。 |
|
||||
| `` g `` | アップストリームへのリセットオプションを表示 | |
|
||||
| `` D `` | リセット | 作業ツリーのリセットオプション(例:作業ツリーの完全破棄)を表示します。 |
|
||||
| `` ` `` | ファイルツリービューを切り替え | ファイルビューをフラットレイアウトとツリーレイアウトの間で切り替えます。フラットレイアウトは全てのファイルパスを単一のリストで表示し、ツリーレイアウトはディレクトリごとにファイルをグループ化します。 |
|
||||
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
|
||||
| `` M `` | 外部マージツールを開く | `git mergetool`を実行します。 |
|
||||
| `` f `` | フェッチ | リモートから変更をフェッチします。 |
|
||||
| `` - `` | すべてのファイルを折りたたむ | ファイルツリー内のすべてのディレクトリを折りたたみます |
|
||||
| `` = `` | すべてのファイルを展開 | ファイルツリー内のすべてのディレクトリを展開します |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` / `` | 検索を開始 | |
|
||||
| `` / `` | 現在のビューをテキストで検索 | |
|
||||
|
||||
## ブランチ
|
||||
## メインパネル(ステージング)
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | ブランチ名をクリップボードにコピー | |
|
||||
| `` i `` | Show git-flow options | |
|
||||
| `` <space> `` | チェックアウト | Checkout selected item. |
|
||||
| `` n `` | 新しいブランチを作成 | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.
|
||||
| `` <left> `` | 前のハンクに移動 | |
|
||||
| `` <right> `` | 次のハンクに移動 | |
|
||||
| `` v `` | 範囲選択を切り替え | |
|
||||
| `` a `` | ハンクを選択 | ハンク選択モードを切り替えます。 |
|
||||
| `` <c-o> `` | 選択したテキストをクリップボードにコピー | |
|
||||
| `` <space> `` | ステージ | 選択された部分のステージ / アンステージを切り替えます。 |
|
||||
| `` d `` | 破棄 | ステージされていない変更が選択されている場合、`git reset`を使用して変更を破棄します。ステージされた変更が選択されている場合、変更をアンステージします。 |
|
||||
| `` o `` | ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 |
|
||||
| `` e `` | ファイルを編集 | 外部エディタでファイルを開きます。 |
|
||||
| `` <esc> `` | ファイルパネルに戻る | |
|
||||
| `` <tab> `` | ビューを切り替え | 他のビュー(ステージされた変更/ステージされていない変更)に切り替えます。 |
|
||||
| `` E `` | ハンクを編集 | 選択したハンクを外部エディタで編集します。 |
|
||||
| `` c `` | コミット | ステージされた変更をコミットします。 |
|
||||
| `` w `` | pre-commitフックなしで変更をコミット | |
|
||||
| `` C `` | Gitエディタを使用して変更をコミット | |
|
||||
| `` <c-f> `` | フィックスアップのベースコミットを検索 | 現在の変更が基づいているコミットを見つけて、コミットの修正/フィックスアップを行います。これにより、ブランチのコミットを一つずつ確認して、どのコミットを修正/フィックスアップすべきかを調べる手間が省けます。詳細はドキュメントを参照: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
|
||||
| `` / `` | 現在のビューをテキストで検索 | |
|
||||
|
||||
Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` o `` | Pull Requestを作成 | |
|
||||
| `` O `` | View create pull request options | |
|
||||
| `` <c-y> `` | Pull RequestのURLをクリップボードにコピー | |
|
||||
| `` c `` | Checkout by name | Checkout by name. In the input box you can enter '-' to switch to the last branch. |
|
||||
| `` F `` | 強制的にチェックアウト | Force checkout selected branch. This will discard all local changes in your working directory before checking out the selected branch. |
|
||||
| `` d `` | Delete | View delete options for local/remote branch. |
|
||||
| `` r `` | Rebase | Rebase the checked-out branch onto the selected branch. |
|
||||
| `` M `` | 現在のブランチにマージ | View options for merging the selected item into the current branch (regular merge, squash merge) |
|
||||
| `` f `` | Fast-forward | Fast-forward selected branch from its upstream. |
|
||||
| `` T `` | タグを作成 | |
|
||||
| `` s `` | 並び替え | |
|
||||
| `` g `` | Reset | |
|
||||
| `` R `` | ブランチ名を変更 | |
|
||||
| `` u `` | View upstream options | View options relating to the branch's upstream e.g. setting/unsetting the upstream and resetting to the upstream. |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | コミットを閲覧 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## メインパネル (Merging)
|
||||
## メインパネル(パッチ作成)
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | Pick hunk | |
|
||||
| `` b `` | Pick all hunks | |
|
||||
| `` <up> `` | 前のhunkを選択 | |
|
||||
| `` <down> `` | 次のhunkを選択 | |
|
||||
| `` <left> `` | 前のコンフリクトを選択 | |
|
||||
| `` <right> `` | 次のコンフリクトを選択 | |
|
||||
| `` z `` | アンドゥ | Undo last merge conflict resolution. |
|
||||
| `` e `` | ファイルを編集 | Open file in external editor. |
|
||||
| `` o `` | ファイルを開く | Open file in default application. |
|
||||
| `` M `` | Git mergetoolを開く | Run `git mergetool`. |
|
||||
| `` <esc> `` | ファイル一覧に戻る | |
|
||||
| `` <left> `` | 前のハンクに移動 | |
|
||||
| `` <right> `` | 次のハンクに移動 | |
|
||||
| `` v `` | 範囲選択を切り替え | |
|
||||
| `` a `` | ハンクを選択 | ハンク選択モードを切り替えます。 |
|
||||
| `` <c-o> `` | 選択したテキストをクリップボードにコピー | |
|
||||
| `` o `` | ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 |
|
||||
| `` e `` | ファイルを編集 | 外部エディタでファイルを開きます。 |
|
||||
| `` <space> `` | パッチ内の行を切り替え | |
|
||||
| `` <esc> `` | カスタムパッチビルダーを終了 | |
|
||||
| `` / `` | 現在のビューをテキストで検索 | |
|
||||
|
||||
## メインパネル (Normal)
|
||||
## メインパネル(マージ中)
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | ハンクを選択 | |
|
||||
| `` b `` | すべてのハンクを選択 | |
|
||||
| `` <up> `` | 前のハンク | |
|
||||
| `` <down> `` | 次のハンク | |
|
||||
| `` <left> `` | 前のコンフリクト | |
|
||||
| `` <right> `` | 次のコンフリクト | |
|
||||
| `` z `` | 元に戻す | 最後のマージコンフリクト解決を元に戻します。 |
|
||||
| `` e `` | ファイルを編集 | 外部エディタでファイルを開きます。 |
|
||||
| `` o `` | ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 |
|
||||
| `` M `` | 外部マージツールを開く | `git mergetool`を実行します。 |
|
||||
| `` <esc> `` | ファイルパネルに戻る | |
|
||||
|
||||
## メインパネル(通常)
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` mouse wheel down (fn+up) `` | 下にスクロール | |
|
||||
| `` mouse wheel up (fn+down) `` | 上にスクロール | |
|
||||
| `` <tab> `` | パネルを切り替え | Switch to other view (staged/unstaged changes). |
|
||||
| `` <tab> `` | ビューを切り替え | 他のビュー(ステージされた変更/ステージされていない変更)に切り替えます。 |
|
||||
| `` <esc> `` | Exit back to side panel | |
|
||||
| `` / `` | 検索を開始 | |
|
||||
|
||||
## メインパネル (Patch Building)
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <left> `` | 前のhunkを選択 | |
|
||||
| `` <right> `` | 次のhunkを選択 | |
|
||||
| `` v `` | 範囲選択を切り替え | |
|
||||
| `` a `` | Hunk選択を切り替え | Toggle hunk selection mode. |
|
||||
| `` <c-o> `` | 選択されたテキストをクリップボードにコピー | |
|
||||
| `` o `` | ファイルを開く | Open file in default application. |
|
||||
| `` e `` | ファイルを編集 | Open file in external editor. |
|
||||
| `` <space> `` | 行をパッチに追加/削除 | |
|
||||
| `` <esc> `` | Exit custom patch builder | |
|
||||
| `` / `` | 検索を開始 | |
|
||||
|
||||
## メインパネル (Staging)
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <left> `` | 前のhunkを選択 | |
|
||||
| `` <right> `` | 次のhunkを選択 | |
|
||||
| `` v `` | 範囲選択を切り替え | |
|
||||
| `` a `` | Hunk選択を切り替え | Toggle hunk selection mode. |
|
||||
| `` <c-o> `` | 選択されたテキストをクリップボードにコピー | |
|
||||
| `` <space> `` | ステージ/アンステージ | 選択行をステージ/アンステージ |
|
||||
| `` d `` | 変更を削除 (git reset) | When unstaged change is selected, discard the change using `git reset`. When staged change is selected, unstage the change. |
|
||||
| `` o `` | ファイルを開く | Open file in default application. |
|
||||
| `` e `` | ファイルを編集 | Open file in external editor. |
|
||||
| `` <esc> `` | ファイル一覧に戻る | |
|
||||
| `` <tab> `` | パネルを切り替え | Switch to other view (staged/unstaged changes). |
|
||||
| `` E `` | Edit hunk | Edit selected hunk in external editor. |
|
||||
| `` c `` | 変更をコミット | Commit staged changes. |
|
||||
| `` w `` | pre-commitフックを実行せずに変更をコミット | |
|
||||
| `` C `` | gitエディタを使用して変更をコミット | |
|
||||
| `` <c-f> `` | Find base commit for fixup | Find the commit that your current changes are building upon, for the sake of amending/fixing up the commit. This spares you from having to look through your branch's commits one-by-one to see which commit should be amended/fixed up. See docs: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
|
||||
| `` / `` | 検索を開始 | |
|
||||
| `` / `` | 現在のビューをテキストで検索 | |
|
||||
|
||||
## メニュー
|
||||
|
||||
@@ -348,59 +316,106 @@ Note that this disregards the selection, the new branch is always created either
|
||||
|-----|--------|-------------|
|
||||
| `` <enter> `` | 実行 | |
|
||||
| `` <esc> `` | 閉じる | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
| `` / `` | 現在のビューをテキストでフィルタリング | |
|
||||
|
||||
## リフログ
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | コミットハッシュをクリップボードにコピー | |
|
||||
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したコミットをデタッチドヘッド(特定のブランチに属さない状態)としてチェックアウトします。 |
|
||||
| `` y `` | コミット属性をクリップボードにコピー | コミット属性をクリップボードにコピーします(例:ハッシュ、URL、差分、メッセージ、作者)。 |
|
||||
| `` o `` | ブラウザでコミットを開く | |
|
||||
| `` n `` | コミットから新しいブランチを作成 | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.
|
||||
|
||||
Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:
|
||||
- ソフトリセット:変更を保持し、ステージされた状態にします
|
||||
- ミックスリセット:変更を保持し、ステージされていない状態にします
|
||||
- ハードリセット:すべての変更を破棄します |
|
||||
| `` C `` | コピー(チェリーピック) | コミットをコピーとしてマークします。ローカルコミットビューで `V` を押すと、コピーしたコミットをチェックアウトしたブランチにペースト(チェリーピック)できます。いつでも `<esc>` を押して選択をキャンセルできます。 |
|
||||
| `` <c-r> `` | コピーされた(チェリーピックされた)コミットの選択をリセット | |
|
||||
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
|
||||
| `` * `` | 現在のブランチのコミットを選択 | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | コミットを表示 | |
|
||||
| `` w `` | ワークツリーオプションを表示 | |
|
||||
| `` / `` | 現在のビューをテキストでフィルタリング | |
|
||||
|
||||
## リモート
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <enter> `` | View branches | |
|
||||
| `` n `` | リモートを新規追加 | |
|
||||
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. |
|
||||
| `` e `` | 編集 | リモートを編集 |
|
||||
| `` f `` | Fetch | リモートをfetch |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
| `` <enter> `` | ブランチを表示 | |
|
||||
| `` n `` | 新しいリモート | |
|
||||
| `` d `` | 削除 | 選択したリモートを削除します。そのリモートからのリモートブランチを追跡しているローカルブランチは影響を受けません。 |
|
||||
| `` e `` | 編集 | 選択したリモートの名前またはURLを編集します。 |
|
||||
| `` f `` | フェッチ | リモートリポジトリから更新をフェッチします。これにより、ローカルブランチにマージせずに新しいコミットとブランチを取得します。 |
|
||||
| `` / `` | 現在のビューをテキストでフィルタリング | |
|
||||
|
||||
## リモートブランチ
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | ブランチ名をクリップボードにコピー | |
|
||||
| `` <space> `` | チェックアウト | Checkout a new local branch based on the selected remote branch, or the remote branch as a detached head. |
|
||||
| `` n `` | 新しいブランチを作成 | |
|
||||
| `` M `` | 現在のブランチにマージ | View options for merging the selected item into the current branch (regular merge, squash merge) |
|
||||
| `` r `` | Rebase | Rebase the checked-out branch onto the selected branch. |
|
||||
| `` d `` | Delete | Delete the remote branch from the remote. |
|
||||
| `` u `` | Set as upstream | Set the selected remote branch as the upstream of the checked-out branch. |
|
||||
| `` s `` | 並び替え | |
|
||||
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択したリモートブランチに基づいて新しいローカルブランチをチェックアウトするか、リモートブランチをデタッチドヘッドとしてチェックアウトします。 |
|
||||
| `` n `` | 新しいブランチ | |
|
||||
| `` M `` | マージ | 選択した項目を現在のブランチにマージするためのオプションを表示します(通常のマージ、スカッシュマージ) |
|
||||
| `` r `` | リベース | チェックアウトしたブランチを選択したブランチ上にリベースします。 |
|
||||
| `` d `` | 削除 | リモートからリモートブランチを削除します。 |
|
||||
| `` u `` | アップストリームとして設定 | 選択したリモートブランチをチェックアウトされたブランチのアップストリームとして設定します。 |
|
||||
| `` s `` | 並び順 | |
|
||||
| `` g `` | リセット | 選択した項目へのリセットオプション(ソフト/ミックス/ハード)を表示します。各リセットタイプの詳細は次の通りです:
|
||||
- ソフトリセット:変更を保持し、ステージされた状態にします
|
||||
- ミックスリセット:変更を保持し、ステージされていない状態にします
|
||||
- ハードリセット:すべての変更を破棄します |
|
||||
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | コミットを閲覧 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
| `` <enter> `` | コミットを表示 | |
|
||||
| `` w `` | ワークツリーオプションを表示 | |
|
||||
| `` / `` | 現在のビューをテキストでフィルタリング | |
|
||||
|
||||
## 参照ログ
|
||||
## ローカルブランチ
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | コミットのhashをクリップボードにコピー | |
|
||||
| `` <space> `` | チェックアウト | Checkout the selected commit as a detached HEAD. |
|
||||
| `` y `` | コミットの情報をコピー | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
|
||||
| `` o `` | ブラウザでコミットを開く | |
|
||||
| `` n `` | コミットにブランチを作成 | |
|
||||
| `` <c-o> `` | ブランチ名をクリップボードにコピー | |
|
||||
| `` i `` | git-flowオプションを表示 | |
|
||||
| `` <space> `` | チェックアウト(ブランチの切り替え) | 選択した項目をチェックアウトします。 |
|
||||
| `` n `` | 新しいブランチ | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.
|
||||
|
||||
Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | コミットをコピー (cherry-pick) | Mark commit as copied. Then, within the local commits view, you can press `V` to paste (cherry-pick) the copied commit(s) into your checked out branch. At any time you can press `<esc>` to cancel the selection. |
|
||||
| `` <c-r> `` | Reset copied (cherry-picked) commits selection | |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` o `` | プルリクエストを作成 | |
|
||||
| `` O `` | プルリクエスト作成オプションを表示 | |
|
||||
| `` <c-y> `` | プルリクエストURLをクリップボードにコピー | |
|
||||
| `` c `` | 名前でチェックアウト | 名前でチェックアウトします。入力ボックスに「-」を入力すると、最後のブランチをチェックアウトすることができます。 |
|
||||
| `` F `` | 強制チェックアウト | 選択したブランチを強制的にチェックアウトします。これにより、選択したブランチをチェックアウトする前にワーキングディレクトリ内のすべてのローカル変更が破棄されます。 |
|
||||
| `` d `` | 削除 | ローカル/リモートブランチの削除オプションを表示します。 |
|
||||
| `` r `` | リベース | チェックアウトしたブランチを選択したブランチ上にリベースします。 |
|
||||
| `` M `` | マージ | 選択した項目を現在のブランチにマージするためのオプションを表示します(通常のマージ、スカッシュマージ) |
|
||||
| `` f `` | ブランチを最新化(fast-forward) | 選択したブランチを対応するアップストリームの最新状態に追いつかせます(fast-forward)。 |
|
||||
| `` T `` | 新しいタグを作成 | |
|
||||
| `` s `` | 並び順 | |
|
||||
| `` g `` | リセット | |
|
||||
| `` R `` | ブランチ名を変更 | |
|
||||
| `` u `` | アップストリームオプションを表示 | ブランチのアップストリームに関連するオプションを表示します(例:アップストリームの設定/解除やアップストリームへのリセット)。 |
|
||||
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | コミットを閲覧 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
| `` <enter> `` | コミットを表示 | |
|
||||
| `` w `` | ワークツリーオプションを表示 | |
|
||||
| `` / `` | 現在のビューをテキストでフィルタリング | |
|
||||
|
||||
## ワークツリー
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` n `` | 新しいワークツリー | |
|
||||
| `` <space> `` | チェックアウト(切り替え) | 選択したワークツリーをチェックアウト(切り替え)します。 |
|
||||
| `` o `` | エディタで開く | |
|
||||
| `` d `` | 削除 | 選択したワークツリーを削除します。これはワークツリーのディレクトリとワークツリーに関するメタデータの両方を.gitディレクトリから削除します。 |
|
||||
| `` / `` | 現在のビューをテキストでフィルタリング | |
|
||||
|
||||
## 確認パネル
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ Veja a documentação:
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | Copy branch name to clipboard | |
|
||||
| `` i `` | Show git-flow options | |
|
||||
| `` i `` | Exibir opções do git-flow | |
|
||||
| `` <space> `` | Verificar | Checar item selecionado |
|
||||
| `` n `` | Nova branch | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.
|
||||
|
||||
@@ -12,15 +12,15 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
|
||||
| `` <pgup> (fn+up/shift+k) `` | 向上滚动主面板 | |
|
||||
| `` <pgdown> (fn+down/shift+j) `` | 向下滚动主面板 | |
|
||||
| `` @ `` | 打开命令日志菜单 | 查看命令日志的选项,例如显示/隐藏命令日志以及聚焦命令日志 |
|
||||
| `` P `` | 推送 | 推送当前分支到它的上游。如果上游未配置,你可以在弹窗中配置上游分支。 |
|
||||
| `` p `` | 拉取 | 从当前分支的远程分支获取改动。如果上游未配置,你可以在弹窗中配置上游分支。 |
|
||||
| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename. |
|
||||
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
|
||||
| `` P `` | 推送 | 推送当前分支到它的上游。如果上游未配置,您可以在弹窗中配置上游分支。 |
|
||||
| `` p `` | 拉取 | 从当前分支的远程分支获取改动。如果上游未配置,您可以在弹窗中配置上游分支。 |
|
||||
| `` ) `` | 提高重命名相似度阈值 | 增加删除和添加对被视作重命名的相似度阈值。 |
|
||||
| `` ( `` | 降低重命名相似度阈值 | 降低删除和添加对被视作重命名的相似度阈值。 |
|
||||
| `` } `` | 扩大差异视图中显示的上下文范围 | 增加diff视图中围绕更改显示的上下文数量 |
|
||||
| `` { `` | 缩小差异视图中显示的上下文范围 | 减少diff视图中围绕更改显示的上下文数量 |
|
||||
| `` : `` | 执行 Shell 命令 | Bring up a prompt where you can enter a shell command to execute. |
|
||||
| `` : `` | 执行 Shell 命令 | 调出可输入shell命令执行的提示符。 |
|
||||
| `` <c-p> `` | 查看自定义补丁选项 | |
|
||||
| `` m `` | 查看 合并/变基 选项 | 查看当前合并或变基的中止、继续、跳过选项 |
|
||||
| `` m `` | 查看合并/变基选项 | 查看当前合并或变基的中止、继续、跳过选项 |
|
||||
| `` R `` | 刷新 | 刷新git状态(即在后台上运行`git status`,`git branch`等命令以更新面板内容) 不会运行`git fetch` |
|
||||
| `` + `` | 下一屏模式(正常/半屏/全屏) | |
|
||||
| `` _ `` | 上一屏模式 | |
|
||||
@@ -55,19 +55,19 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
|
||||
| `` <c-o> `` | 复制提交哈希到剪贴板 | |
|
||||
| `` <space> `` | 检出 | 检出所选择的提交作为分离HEAD。 |
|
||||
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(例如,hash、URL、diff、消息、作者)。 |
|
||||
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(如hash、URL、diff、消息、作者)。 |
|
||||
| `` o `` | 在浏览器中打开提交 | |
|
||||
| `` n `` | 从提交创建新分支 | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.
|
||||
|
||||
Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
|
||||
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,你可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
|
||||
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
|
||||
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
|
||||
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` * `` | 选择当前分支的提交 | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | 查看提交 | |
|
||||
| `` w `` | 查看工作区选项 | |
|
||||
@@ -77,19 +77,19 @@ Note that this disregards the selection, the new branch is always created either
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
|
||||
| `` <c-o> `` | 复制提交哈希到剪贴板 | |
|
||||
| `` <space> `` | 检出 | 检出所选择的提交作为分离HEAD。 |
|
||||
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(例如,hash、URL、diff、消息、作者)。 |
|
||||
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(如hash、URL、diff、消息、作者)。 |
|
||||
| `` o `` | 在浏览器中打开提交 | |
|
||||
| `` n `` | 从提交创建新分支 | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.
|
||||
|
||||
Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
|
||||
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,你可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
|
||||
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
|
||||
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
|
||||
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` * `` | 选择当前分支的提交 | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | 查看提交的文件 | |
|
||||
| `` w `` | 查看工作区选项 | |
|
||||
@@ -99,7 +99,7 @@ Note that this disregards the selection, the new branch is always created either
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将子模块名称复制到剪贴板 | |
|
||||
| `` <c-o> `` | 复制子模块名称到剪贴板 | |
|
||||
| `` <enter> `` | 进入 | 输入子模块 |
|
||||
| `` d `` | 删除 | 删除选定的子模块及其相应的目录 |
|
||||
| `` u `` | 更新 | 更新子模块 |
|
||||
@@ -123,7 +123,7 @@ Note that this disregards the selection, the new branch is always created either
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
|
||||
| `` <c-o> `` | 复制提交哈希到剪贴板 | |
|
||||
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
|
||||
| `` b `` | 查看二分查找选项 | |
|
||||
| `` s `` | 压缩(Squash) | 将已选提交压缩到该提交之下。这些选定的提交的消息会附加到该提交的消息之下。 |
|
||||
@@ -144,19 +144,19 @@ Note that this disregards the selection, the new branch is always created either
|
||||
| `` A `` | 修补(Amend) | 用已暂存的变更来修补提交 |
|
||||
| `` a `` | 修补提交属性 | 设置或重置提交的作者,或添加其他作者。 |
|
||||
| `` t `` | 撤销(Revert) | 为所选提交创建还原提交,这会反向应用所选提交的更改。 |
|
||||
| `` T `` | 标签提交 | 创建一个新标签指向所选提交。你可以在弹窗中输入标签名称和描述(可选)。 |
|
||||
| `` T `` | 标签提交 | 创建一个新标签指向所选提交。您可以在弹窗中输入标签名称和描述(可选)。 |
|
||||
| `` <c-l> `` | 打开日志菜单 | 查看提交日志的选项,例如更改排序顺序、隐藏 git graph、显示整个 git graph。 |
|
||||
| `` <space> `` | 检出 | 检出所选择的提交作为分离HEAD。 |
|
||||
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(例如,hash、URL、diff、消息、作者)。 |
|
||||
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(如hash、URL、diff、消息、作者)。 |
|
||||
| `` o `` | 在浏览器中打开提交 | |
|
||||
| `` n `` | 从提交创建新分支 | |
|
||||
| `` N `` | Move commits to new branch | Create a new branch and move the unpushed commits of the current branch to it. Useful if you meant to start new work and forgot to create a new branch first.
|
||||
|
||||
Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
|
||||
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,你可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
|
||||
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,您可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
|
||||
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
|
||||
| `` * `` | Select commits of current branch | |
|
||||
| `` * `` | 选择当前分支的提交 | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` <enter> `` | 查看提交的文件 | |
|
||||
| `` w `` | 查看工作区选项 | |
|
||||
@@ -173,7 +173,7 @@ Note that this disregards the selection, the new branch is always created either
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将文件名复制到剪贴板 | |
|
||||
| `` <c-o> `` | 复制路径到剪贴板 | |
|
||||
| `` y `` | 复制到剪贴板 | |
|
||||
| `` c `` | 检出 | 检出文件 |
|
||||
| `` d `` | 删除 | 放弃对此文件的提交变更 |
|
||||
@@ -193,7 +193,7 @@ Note that this disregards the selection, the new branch is always created either
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将文件名复制到剪贴板 | |
|
||||
| `` <c-o> `` | 复制路径到剪贴板 | |
|
||||
| `` <space> `` | 切换暂存状态 | 为选定的文件切换暂存状态 |
|
||||
| `` <c-b> `` | 通过状态过滤文件 | |
|
||||
| `` y `` | 复制到剪贴板 | |
|
||||
@@ -226,7 +226,7 @@ Note that this disregards the selection, the new branch is always created either
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将分支名称复制到剪贴板 | |
|
||||
| `` <c-o> `` | 复制分支名称到剪贴板 | |
|
||||
| `` i `` | 显示 git-flow 选项 | |
|
||||
| `` <space> `` | 检出 | 检出选中的项目 |
|
||||
| `` n `` | 新分支 | |
|
||||
@@ -235,11 +235,11 @@ Note that this disregards the selection, the new branch is always created either
|
||||
Note that this disregards the selection, the new branch is always created either from the main branch or stacked on top of the current branch (you get to choose which). |
|
||||
| `` o `` | 创建拉取请求 | |
|
||||
| `` O `` | 创建拉取请求选项 | |
|
||||
| `` <c-y> `` | 将拉取请求 URL 复制到剪贴板 | |
|
||||
| `` <c-y> `` | 复制拉取请求 URL 到剪贴板 | |
|
||||
| `` c `` | 按名称检出 | 按名称检出。在输入框中,您可以输入'-' 来切换到最后一个分支。 |
|
||||
| `` F `` | 强制检出 | 强制检出所选分支。这将在检出所选分支之前放弃工作目录中的所有本地更改。 |
|
||||
| `` d `` | 删除 | 查看本地/远程分支的删除选项 |
|
||||
| `` r `` | 将已检出的分支变基到该分支 | 将检出的分支变基到所选的分支上。 |
|
||||
| `` r `` | 变基 | 将检出的分支变基到所选的分支上。 |
|
||||
| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. |
|
||||
| `` f `` | 从上游快进此分支 | 将当前分支直接移动到远程追踪分支的最新提交 |
|
||||
| `` T `` | 创建标签 | |
|
||||
@@ -261,7 +261,7 @@ Note that this disregards the selection, the new branch is always created either
|
||||
| `` <right> `` | 选择下一个区块 | |
|
||||
| `` v `` | 切换拖动选择 | |
|
||||
| `` a `` | 切换选择代码块 | 切换代码块选择模式 |
|
||||
| `` <c-o> `` | 将选中文本复制到剪贴板 | |
|
||||
| `` <c-o> `` | 复制选中文本到剪贴板 | |
|
||||
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
|
||||
| `` e `` | 编辑文件 | 使用外部编辑器打开文件 |
|
||||
| `` <space> `` | 添加/移除 行到补丁 | |
|
||||
@@ -272,11 +272,11 @@ Note that this disregards the selection, the new branch is always created either
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将标签复制到剪贴板 | |
|
||||
| `` <c-o> `` | 复制标签到剪贴板 | |
|
||||
| `` <space> `` | 检出 | 检出选择的标签作为分离的HEAD |
|
||||
| `` n `` | 创建标签 | 基于当前提交创建一个新标签。你将在弹窗中输入标签名称和描述(可选)。 |
|
||||
| `` n `` | 创建标签 | 基于当前提交创建一个新标签。您将在弹窗中输入标签名称和描述(可选)。 |
|
||||
| `` d `` | 删除 | 查看本地/远程标签的删除选项 |
|
||||
| `` P `` | 推送标签 | 推送选择的标签到远端。你将在弹窗中选择一个远端。 |
|
||||
| `` P `` | 推送标签 | 推送选择的标签到远端。您将在弹窗中选择一个远端。 |
|
||||
| `` g `` | 重置 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
|
||||
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
|
||||
| `` 0 `` | Focus main view | |
|
||||
@@ -316,7 +316,7 @@ Note that this disregards the selection, the new branch is always created either
|
||||
| `` <right> `` | 选择下一个区块 | |
|
||||
| `` v `` | 切换拖动选择 | |
|
||||
| `` a `` | 切换选择代码块 | 切换代码块选择模式 |
|
||||
| `` <c-o> `` | 将选中文本复制到剪贴板 | |
|
||||
| `` <c-o> `` | 复制选中文本到剪贴板 | |
|
||||
| `` <space> `` | 切换暂存状态 | 切换行暂存状态 |
|
||||
| `` d `` | 取消变更(git reset) | 当选择未暂存的变更时,使用git reset丢弃该变更。当选择已暂存的变更时,取消暂存该变更 |
|
||||
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
|
||||
@@ -348,7 +348,7 @@ Note that this disregards the selection, the new branch is always created either
|
||||
| `` e `` | 编辑配置文件 | 使用外部编辑器打开文件 |
|
||||
| `` u `` | 检查更新 | |
|
||||
| `` <enter> `` | 切换到最近的仓库 | |
|
||||
| `` a `` | Show/cycle all branch logs | |
|
||||
| `` a `` | 显示/循环所有分支日志 | |
|
||||
|
||||
## 确认面板
|
||||
|
||||
@@ -394,11 +394,11 @@ Note that this disregards the selection, the new branch is always created either
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将分支名称复制到剪贴板 | |
|
||||
| `` <c-o> `` | 复制分支名称到剪贴板 | |
|
||||
| `` <space> `` | 检出 | 基于当前选中的远程分支检出一个新的本地分支,或者将远程分支作分离的HEAD。 |
|
||||
| `` n `` | 新分支 | |
|
||||
| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. |
|
||||
| `` r `` | 将已检出的分支变基到该分支 | 将检出的分支变基到所选的分支上。 |
|
||||
| `` r `` | 变基 | 将检出的分支变基到所选的分支上。 |
|
||||
| `` d `` | 删除 | 从远程删除远程分支。 |
|
||||
| `` u `` | 设置为上游 | 设置为检出分支的上游 |
|
||||
| `` s `` | 排序 | |
|
||||
|
||||
@@ -13,9 +13,9 @@ require (
|
||||
github.com/go-errors/errors v1.5.1
|
||||
github.com/gookit/color v1.4.2
|
||||
github.com/integrii/flaggy v1.4.0
|
||||
github.com/jesseduffield/generics v0.0.0-20250406224309-4f541cb84918
|
||||
github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c
|
||||
github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20250421160159-82c9aaeba2b9
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20250605111917-fc5387961412
|
||||
github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
|
||||
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
|
||||
@@ -37,7 +37,7 @@ require (
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||
golang.org/x/sync v0.13.0
|
||||
golang.org/x/sync v0.14.0
|
||||
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
@@ -47,7 +47,7 @@ require (
|
||||
github.com/ProtonMail/go-crypto v1.1.6 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/cloudflare/circl v1.6.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
@@ -78,9 +78,9 @@ require (
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/crypto v0.37.0 // indirect
|
||||
golang.org/x/net v0.39.0 // indirect
|
||||
golang.org/x/sys v0.32.0 // indirect
|
||||
golang.org/x/term v0.31.0 // indirect
|
||||
golang.org/x/text v0.24.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
golang.org/x/term v0.32.0 // indirect
|
||||
golang.org/x/text v0.25.0 // indirect
|
||||
gopkg.in/fsnotify.v1 v1.4.7 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
|
||||
@@ -65,8 +65,8 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
|
||||
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21 h1:tuijfIjZyjZaHq9xDUh0tNitwXshJpbLkqMOJv4H3do=
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21/go.mod h1:po7NpZ/QiTKzBKyrsEAxwnTamCoh8uDk/egRpQ7siIc=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
@@ -190,12 +190,12 @@ github.com/invopop/jsonschema v0.10.0 h1:c1ktzNLBun3LyQQhyty5WE3lulbOdIIyOVlkmDL
|
||||
github.com/invopop/jsonschema v0.10.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||
github.com/jesseduffield/generics v0.0.0-20250406224309-4f541cb84918 h1:meoUDZGF6jZAbhW5IBwj92mTqGmrOn+Cuu0jM7/aUcs=
|
||||
github.com/jesseduffield/generics v0.0.0-20250406224309-4f541cb84918/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
|
||||
github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c h1:tC2PaiisXAC5sOjDPfMArSnbswDObtCssx+xn28edX4=
|
||||
github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c/go.mod h1:F2fEBk0ddf6ixrBrJjY7phfQ3hL9rXG0uSjvwYe50bE=
|
||||
github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd h1:ViKj6qth8FgcIWizn9KiACWwPemWSymx62OPN0tHT+Q=
|
||||
github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd/go.mod h1:lRhCiBr6XjQrvcQVa+UYsy/99d3wMXn/a0nSQlhnhlA=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20250421160159-82c9aaeba2b9 h1:k23sCKHCNpAvwJP8Yr16CBUItuarmUHBGH7FaAm2glc=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20250421160159-82c9aaeba2b9/go.mod h1:sLIyZ2J42R6idGdtemZzsiR3xY5EF0KsvYEGh3dQv3s=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20250605111917-fc5387961412 h1:8z1CpdCy9nzdj47lSLbDbCVmR5MgXsknYsuuHpzYk5M=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20250605111917-fc5387961412/go.mod h1:sLIyZ2J42R6idGdtemZzsiR3xY5EF0KsvYEGh3dQv3s=
|
||||
github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a h1:UDeJ3EBk04bXDLOPvuqM3on8HvyJfISw0+UMqW+0a4g=
|
||||
github.com/jesseduffield/kill v0.0.0-20250101124109-e216ddbe133a/go.mod h1:FSWDLKT0NQpntbDd1H3lbz51fhCVlMzy/J0S6nM727Q=
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
|
||||
@@ -436,8 +436,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
|
||||
golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
|
||||
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20170407050850-f3918c30c5c2/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -490,8 +490,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
|
||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -501,8 +501,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
|
||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
|
||||
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg=
|
||||
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -517,8 +517,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
|
||||
golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
|
||||
golang.org/x/text v0.25.0 h1:qVyWApTSYLk/drJRO5mDlNYskwQznZmkpV2c8q9zls4=
|
||||
golang.org/x/text v0.25.0/go.mod h1:WEdwpYrmk1qmdHvhkSTNPm3app7v4rsT8F2UD6+VHIA=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
|
||||
+2
-2
@@ -86,9 +86,9 @@ func newLogger(cfg config.AppConfigurer) *logrus.Entry {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return logs.NewDevelopmentLogger(logPath)
|
||||
} else {
|
||||
return logs.NewProductionLogger()
|
||||
}
|
||||
|
||||
return logs.NewProductionLogger()
|
||||
}
|
||||
|
||||
// NewApp bootstrap a new application
|
||||
|
||||
@@ -21,9 +21,8 @@ type TodoLine struct {
|
||||
func (self *TodoLine) ToString() string {
|
||||
if self.Action == "break" {
|
||||
return self.Action + "\n"
|
||||
} else {
|
||||
return self.Action + " " + self.Commit.Hash() + " " + self.Commit.Name + "\n"
|
||||
}
|
||||
return self.Action + " " + self.Commit.Hash() + " " + self.Commit.Name + "\n"
|
||||
}
|
||||
|
||||
func TodoLinesToString(todoLines []TodoLine) string {
|
||||
|
||||
@@ -30,11 +30,11 @@ func NewGitCmdObjBuilder(log *logrus.Entry, innerBuilder *oscommands.CmdObjBuild
|
||||
|
||||
var defaultEnvVar = "GIT_OPTIONAL_LOCKS=0"
|
||||
|
||||
func (self *gitCmdObjBuilder) New(args []string) oscommands.ICmdObj {
|
||||
func (self *gitCmdObjBuilder) New(args []string) *oscommands.CmdObj {
|
||||
return self.innerBuilder.New(args).AddEnvVars(defaultEnvVar)
|
||||
}
|
||||
|
||||
func (self *gitCmdObjBuilder) NewShell(cmdStr string, shellFunctionsFile string) oscommands.ICmdObj {
|
||||
func (self *gitCmdObjBuilder) NewShell(cmdStr string, shellFunctionsFile string) *oscommands.CmdObj {
|
||||
return self.innerBuilder.NewShell(cmdStr, shellFunctionsFile).AddEnvVars(defaultEnvVar)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,15 +20,15 @@ type gitCmdObjRunner struct {
|
||||
innerRunner oscommands.ICmdObjRunner
|
||||
}
|
||||
|
||||
func (self *gitCmdObjRunner) Run(cmdObj oscommands.ICmdObj) error {
|
||||
func (self *gitCmdObjRunner) Run(cmdObj *oscommands.CmdObj) error {
|
||||
_, err := self.RunWithOutput(cmdObj)
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *gitCmdObjRunner) RunWithOutput(cmdObj oscommands.ICmdObj) (string, error) {
|
||||
func (self *gitCmdObjRunner) RunWithOutput(cmdObj *oscommands.CmdObj) (string, error) {
|
||||
var output string
|
||||
var err error
|
||||
for i := 0; i < RetryCount; i++ {
|
||||
for range RetryCount {
|
||||
newCmdObj := cmdObj.Clone()
|
||||
output, err = self.innerRunner.RunWithOutput(newCmdObj)
|
||||
|
||||
@@ -44,10 +44,10 @@ func (self *gitCmdObjRunner) RunWithOutput(cmdObj oscommands.ICmdObj) (string, e
|
||||
return output, err
|
||||
}
|
||||
|
||||
func (self *gitCmdObjRunner) RunWithOutputs(cmdObj oscommands.ICmdObj) (string, string, error) {
|
||||
func (self *gitCmdObjRunner) RunWithOutputs(cmdObj *oscommands.CmdObj) (string, string, error) {
|
||||
var stdout, stderr string
|
||||
var err error
|
||||
for i := 0; i < RetryCount; i++ {
|
||||
for range RetryCount {
|
||||
newCmdObj := cmdObj.Clone()
|
||||
stdout, stderr, err = self.innerRunner.RunWithOutputs(newCmdObj)
|
||||
|
||||
@@ -64,6 +64,6 @@ func (self *gitCmdObjRunner) RunWithOutputs(cmdObj oscommands.ICmdObj) (string,
|
||||
}
|
||||
|
||||
// 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 {
|
||||
func (self *gitCmdObjRunner) RunAndProcessLines(cmdObj *oscommands.CmdObj, onLine func(line string) (bool, error)) error {
|
||||
return self.innerRunner.RunAndProcessLines(cmdObj, onLine)
|
||||
}
|
||||
|
||||
@@ -102,19 +102,18 @@ func (self *BranchCommands) CurrentBranchInfo() (BranchInfo, error) {
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CurrentBranchName get name of current branch
|
||||
// CurrentBranchName get name of current branch. Returns empty string if HEAD is detached.
|
||||
func (self *BranchCommands) CurrentBranchName() (string, error) {
|
||||
cmdArgs := NewGitCmd("rev-parse").
|
||||
Arg("--abbrev-ref").
|
||||
Arg("--verify").
|
||||
Arg("HEAD").
|
||||
cmdArgs := NewGitCmd("branch").
|
||||
Arg("--show-current").
|
||||
ToArgv()
|
||||
|
||||
output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
|
||||
if err == nil {
|
||||
return strings.TrimSpace(output), nil
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return "", err
|
||||
|
||||
return strings.TrimSpace(output), nil
|
||||
}
|
||||
|
||||
// LocalDelete delete branch locally
|
||||
@@ -154,7 +153,7 @@ func (self *BranchCommands) GetGraph(branchName string) (string, error) {
|
||||
return self.GetGraphCmdObj(branchName).DontLog().RunWithOutput()
|
||||
}
|
||||
|
||||
func (self *BranchCommands) GetGraphCmdObj(branchName string) oscommands.ICmdObj {
|
||||
func (self *BranchCommands) GetGraphCmdObj(branchName string) *oscommands.CmdObj {
|
||||
branchLogCmdTemplate := self.UserConfig().Git.BranchLogCmd
|
||||
templateValues := map[string]string{
|
||||
"branchName": self.cmd.Quote(branchName),
|
||||
@@ -255,22 +254,24 @@ func (self *BranchCommands) Merge(branchName string, opts MergeOpts) error {
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
}
|
||||
|
||||
func (self *BranchCommands) AllBranchesLogCmdObj() oscommands.ICmdObj {
|
||||
// Only choose between non-empty, non-identical commands
|
||||
candidates := lo.Uniq(lo.WithoutEmpty(append([]string{
|
||||
self.UserConfig().Git.AllBranchesLogCmd,
|
||||
},
|
||||
self.UserConfig().Git.AllBranchesLogCmds...,
|
||||
)))
|
||||
// Only choose between non-empty, non-identical commands
|
||||
func (self *BranchCommands) allBranchesLogCandidates() []string {
|
||||
return lo.Uniq(lo.WithoutEmpty(self.UserConfig().Git.AllBranchesLogCmds))
|
||||
}
|
||||
|
||||
n := len(candidates)
|
||||
func (self *BranchCommands) AllBranchesLogCmdObj() *oscommands.CmdObj {
|
||||
candidates := self.allBranchesLogCandidates()
|
||||
|
||||
i := self.allBranchesLogCmdIndex
|
||||
self.allBranchesLogCmdIndex = uint8((int(i) + 1) % n)
|
||||
|
||||
return self.cmd.New(str.ToArgv(candidates[i])).DontLog()
|
||||
}
|
||||
|
||||
func (self *BranchCommands) RotateAllBranchesLogIdx() {
|
||||
n := len(self.allBranchesLogCandidates())
|
||||
i := self.allBranchesLogCmdIndex
|
||||
self.allBranchesLogCmdIndex = uint8((int(i) + 1) % n)
|
||||
}
|
||||
|
||||
func (self *BranchCommands) IsBranchMerged(branch *models.Branch, mainBranches *MainBranches) (bool, error) {
|
||||
branchesToCheckAgainst := []string{"HEAD"}
|
||||
if branch.RemoteBranchStoredLocally() {
|
||||
|
||||
@@ -356,9 +356,8 @@ func parseDifference(track string, regexStr string) string {
|
||||
match := re.FindStringSubmatch(track)
|
||||
if len(match) > 1 {
|
||||
return match[1]
|
||||
} else {
|
||||
return "0"
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
// TODO: only look at the new reflog commits, and otherwise store the recencies in
|
||||
|
||||
@@ -85,7 +85,7 @@ func (self *CommitCommands) ResetToCommit(hash string, strength string, envVars
|
||||
Run()
|
||||
}
|
||||
|
||||
func (self *CommitCommands) CommitCmdObj(summary string, description string, forceSkipHooks bool) oscommands.ICmdObj {
|
||||
func (self *CommitCommands) CommitCmdObj(summary string, description string, forceSkipHooks bool) *oscommands.CmdObj {
|
||||
messageArgs := self.commitMessageArgs(summary, description)
|
||||
skipHookPrefix := self.UserConfig().Git.SkipHookPrefix
|
||||
cmdArgs := NewGitCmd("commit").
|
||||
@@ -97,16 +97,16 @@ func (self *CommitCommands) CommitCmdObj(summary string, description string, for
|
||||
return self.cmd.New(cmdArgs)
|
||||
}
|
||||
|
||||
func (self *CommitCommands) RewordLastCommitInEditorCmdObj() oscommands.ICmdObj {
|
||||
func (self *CommitCommands) RewordLastCommitInEditorCmdObj() *oscommands.CmdObj {
|
||||
return self.cmd.New(NewGitCmd("commit").Arg("--allow-empty", "--amend", "--only").ToArgv())
|
||||
}
|
||||
|
||||
func (self *CommitCommands) RewordLastCommitInEditorWithMessageFileCmdObj(tmpMessageFile string) oscommands.ICmdObj {
|
||||
func (self *CommitCommands) RewordLastCommitInEditorWithMessageFileCmdObj(tmpMessageFile string) *oscommands.CmdObj {
|
||||
return self.cmd.New(NewGitCmd("commit").
|
||||
Arg("--allow-empty", "--amend", "--only", "--edit", "--file="+tmpMessageFile).ToArgv())
|
||||
}
|
||||
|
||||
func (self *CommitCommands) CommitInEditorWithMessageFileCmdObj(tmpMessageFile string, forceSkipHooks bool) oscommands.ICmdObj {
|
||||
func (self *CommitCommands) CommitInEditorWithMessageFileCmdObj(tmpMessageFile string, forceSkipHooks bool) *oscommands.CmdObj {
|
||||
return self.cmd.New(NewGitCmd("commit").
|
||||
ArgIf(forceSkipHooks, "--no-verify").
|
||||
Arg("--edit").
|
||||
@@ -116,7 +116,7 @@ func (self *CommitCommands) CommitInEditorWithMessageFileCmdObj(tmpMessageFile s
|
||||
}
|
||||
|
||||
// RewordLastCommit rewords the topmost commit with the given message
|
||||
func (self *CommitCommands) RewordLastCommit(summary string, description string) oscommands.ICmdObj {
|
||||
func (self *CommitCommands) RewordLastCommit(summary string, description string) *oscommands.CmdObj {
|
||||
messageArgs := self.commitMessageArgs(summary, description)
|
||||
|
||||
cmdArgs := NewGitCmd("commit").
|
||||
@@ -138,7 +138,7 @@ func (self *CommitCommands) commitMessageArgs(summary string, description string
|
||||
}
|
||||
|
||||
// runs git commit without the -m argument meaning it will invoke the user's editor
|
||||
func (self *CommitCommands) CommitEditorCmdObj() oscommands.ICmdObj {
|
||||
func (self *CommitCommands) CommitEditorCmdObj() *oscommands.CmdObj {
|
||||
cmdArgs := NewGitCmd("commit").
|
||||
ArgIf(self.signoffFlag() != "", self.signoffFlag()).
|
||||
ToArgv()
|
||||
@@ -149,9 +149,8 @@ func (self *CommitCommands) CommitEditorCmdObj() oscommands.ICmdObj {
|
||||
func (self *CommitCommands) signoffFlag() string {
|
||||
if self.UserConfig().Git.Commit.SignOff {
|
||||
return "--signoff"
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *CommitCommands) GetCommitMessage(commitHash string) (string, error) {
|
||||
@@ -246,7 +245,7 @@ func (self *CommitCommands) AmendHead() error {
|
||||
return self.AmendHeadCmdObj().Run()
|
||||
}
|
||||
|
||||
func (self *CommitCommands) AmendHeadCmdObj() oscommands.ICmdObj {
|
||||
func (self *CommitCommands) AmendHeadCmdObj() *oscommands.CmdObj {
|
||||
cmdArgs := NewGitCmd("commit").
|
||||
Arg("--amend", "--no-edit", "--allow-empty").
|
||||
ToArgv()
|
||||
@@ -254,7 +253,7 @@ func (self *CommitCommands) AmendHeadCmdObj() oscommands.ICmdObj {
|
||||
return self.cmd.New(cmdArgs)
|
||||
}
|
||||
|
||||
func (self *CommitCommands) ShowCmdObj(hash string, filterPath string) oscommands.ICmdObj {
|
||||
func (self *CommitCommands) ShowCmdObj(hash string, filterPath string) *oscommands.CmdObj {
|
||||
contextSize := self.AppState.DiffContextSize
|
||||
|
||||
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
|
||||
@@ -278,7 +277,7 @@ func (self *CommitCommands) ShowCmdObj(hash string, filterPath string) oscommand
|
||||
return self.cmd.New(cmdArgs).DontLog()
|
||||
}
|
||||
|
||||
func (self *CommitCommands) ShowFileContentCmdObj(hash string, filePath string) oscommands.ICmdObj {
|
||||
func (self *CommitCommands) ShowFileContentCmdObj(hash string, filePath string) *oscommands.CmdObj {
|
||||
cmdArgs := NewGitCmd("show").
|
||||
Arg(fmt.Sprintf("%s:%s", hash, filePath)).
|
||||
ToArgv()
|
||||
|
||||
@@ -583,7 +583,7 @@ func (self *CommitLoader) getFirstPushedCommit(refName string) (string, error) {
|
||||
}
|
||||
|
||||
// getLog gets the git log.
|
||||
func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
|
||||
func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) *oscommands.CmdObj {
|
||||
gitLogOrder := self.AppState.GitLogOrder
|
||||
|
||||
refSpec := opts.RefName
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
@@ -15,16 +16,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
|
||||
var commitsOutput = strings.ReplaceAll(`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)
|
||||
053a66a7be3da43aacdc7aa78e1fe757b82c4dd2|1640739815|Jesse Duffield|jessedduffield@gmail.com||985fe482e806b172aea4|>|refactoring the config struct`, "|", "\x00")
|
||||
|
||||
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.ReplaceAll(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|>|better typing for rebase mode`, "|", "\x00")
|
||||
|
||||
func TestGetCommits(t *testing.T) {
|
||||
type scenario struct {
|
||||
@@ -296,7 +297,7 @@ func TestGetCommits(t *testing.T) {
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.testName, func(t *testing.T) {
|
||||
common := utils.NewDummyCommon()
|
||||
common := common.NewDummyCommon()
|
||||
common.AppState = &config.AppState{}
|
||||
common.AppState.GitLogOrder = scenario.logOrder
|
||||
cmd := oscommands.NewDummyCmdObjBuilder(scenario.runner)
|
||||
@@ -516,7 +517,7 @@ func TestCommitLoader_getConflictedCommitImpl(t *testing.T) {
|
||||
}
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.testName, func(t *testing.T) {
|
||||
common := utils.NewDummyCommon()
|
||||
common := common.NewDummyCommon()
|
||||
|
||||
builder := &CommitLoader{
|
||||
Common: common,
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
@@ -32,7 +31,7 @@ func buildGitCommon(deps commonDeps) *GitCommon {
|
||||
|
||||
gitCommon.Common = deps.common
|
||||
if gitCommon.Common == nil {
|
||||
gitCommon.Common = utils.NewDummyCommonWithUserConfigAndAppState(deps.userConfig, deps.appState)
|
||||
gitCommon.Common = common.NewDummyCommonWithUserConfigAndAppState(deps.userConfig, deps.appState)
|
||||
}
|
||||
|
||||
if deps.fs != nil {
|
||||
@@ -98,7 +97,7 @@ func buildGitCommon(deps commonDeps) *GitCommon {
|
||||
|
||||
func buildRepo() *gogit.Repository {
|
||||
// TODO: think of a way to actually mock this out
|
||||
var repo *gogit.Repository = nil
|
||||
var repo *gogit.Repository
|
||||
return repo
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ func NewDiffCommands(gitCommon *GitCommon) *DiffCommands {
|
||||
|
||||
// This is for generating diffs to be shown in the UI (e.g. rendering a range
|
||||
// diff to the main view). It uses a custom pager if one is configured.
|
||||
func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj {
|
||||
func (self *DiffCommands) DiffCmdObj(diffArgs []string) *oscommands.CmdObj {
|
||||
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
|
||||
useExtDiff := extDiffCmd != ""
|
||||
ignoreWhitespace := self.AppState.IgnoreWhitespaceInDiffView
|
||||
@@ -83,7 +83,7 @@ type DiffToolCmdOptions struct {
|
||||
Staged bool
|
||||
}
|
||||
|
||||
func (self *DiffCommands) OpenDiffToolCmdObj(opts DiffToolCmdOptions) oscommands.ICmdObj {
|
||||
func (self *DiffCommands) OpenDiffToolCmdObj(opts DiffToolCmdOptions) *oscommands.CmdObj {
|
||||
return self.cmd.New(NewGitCmd("difftool").
|
||||
Arg("--no-prompt").
|
||||
ArgIf(opts.IsDirectory, "--dir-diff").
|
||||
@@ -95,7 +95,7 @@ func (self *DiffCommands) OpenDiffToolCmdObj(opts DiffToolCmdOptions) oscommands
|
||||
ToArgv())
|
||||
}
|
||||
|
||||
func (self *DiffCommands) DiffIndexCmdObj(diffArgs ...string) oscommands.ICmdObj {
|
||||
func (self *DiffCommands) DiffIndexCmdObj(diffArgs ...string) *oscommands.CmdObj {
|
||||
return self.cmd.New(
|
||||
NewGitCmd("diff-index").
|
||||
Config("diff.noprefix=false").
|
||||
|
||||
@@ -110,8 +110,8 @@ type FileDiff struct {
|
||||
LinesDeleted int
|
||||
}
|
||||
|
||||
func (fileLoader *FileLoader) getFileDiffs() (map[string]FileDiff, error) {
|
||||
diffs, err := fileLoader.gitDiffNumStat()
|
||||
func (self *FileLoader) getFileDiffs() (map[string]FileDiff, error) {
|
||||
diffs, err := self.gitDiffNumStat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -157,8 +157,8 @@ type FileStatus struct {
|
||||
PreviousPath string
|
||||
}
|
||||
|
||||
func (fileLoader *FileLoader) gitDiffNumStat() (string, error) {
|
||||
return fileLoader.cmd.New(
|
||||
func (self *FileLoader) gitDiffNumStat() (string, error) {
|
||||
return self.cmd.New(
|
||||
NewGitCmd("diff").
|
||||
Arg("--numstat").
|
||||
Arg("-z").
|
||||
|
||||
@@ -24,7 +24,7 @@ func (self *FlowCommands) GitFlowEnabled() bool {
|
||||
return self.config.GetGitFlowPrefixes() != ""
|
||||
}
|
||||
|
||||
func (self *FlowCommands) FinishCmdObj(branchName string) (oscommands.ICmdObj, error) {
|
||||
func (self *FlowCommands) FinishCmdObj(branchName string) (*oscommands.CmdObj, error) {
|
||||
prefixes := self.config.GetGitFlowPrefixes()
|
||||
|
||||
// need to find out what kind of branch this is
|
||||
@@ -54,7 +54,7 @@ func (self *FlowCommands) FinishCmdObj(branchName string) (oscommands.ICmdObj, e
|
||||
return self.cmd.New(cmdArgs), nil
|
||||
}
|
||||
|
||||
func (self *FlowCommands) StartCmdObj(branchType string, name string) oscommands.ICmdObj {
|
||||
func (self *FlowCommands) StartCmdObj(branchType string, name string) *oscommands.CmdObj {
|
||||
cmdArgs := NewGitCmd("flow").Arg(branchType, "start", name).ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs)
|
||||
|
||||
@@ -32,9 +32,8 @@ func (self *GitCommandBuilder) ArgIf(condition bool, ifTrue ...string) *GitComma
|
||||
func (self *GitCommandBuilder) ArgIfElse(condition bool, ifTrue string, ifFalse string) *GitCommandBuilder {
|
||||
if condition {
|
||||
return self.Arg(ifTrue)
|
||||
} else {
|
||||
return self.Arg(ifFalse)
|
||||
}
|
||||
return self.Arg(ifFalse)
|
||||
}
|
||||
|
||||
func (self *GitCommandBuilder) Config(value string) *GitCommandBuilder {
|
||||
|
||||
@@ -21,7 +21,7 @@ type MainBranches struct {
|
||||
previousMainBranches []string
|
||||
|
||||
cmd oscommands.ICmdObjBuilder
|
||||
mutex *deadlock.Mutex
|
||||
mutex deadlock.Mutex
|
||||
}
|
||||
|
||||
func NewMainBranches(
|
||||
@@ -32,7 +32,6 @@ func NewMainBranches(
|
||||
c: cmn,
|
||||
existingMainBranches: nil,
|
||||
cmd: cmd,
|
||||
mutex: &deadlock.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
@@ -162,7 +163,7 @@ func (self *PatchCommands) MovePatchToSelectedCommit(commits []*models.Commit, s
|
||||
self.os.LogCommand(logTodoChanges(changes), false)
|
||||
|
||||
err := self.rebase.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseHashOrRoot: commits[baseIndex].Hash(),
|
||||
baseHashOrRoot: getBaseHashOrRoot(commits, baseIndex),
|
||||
overrideEditor: true,
|
||||
instruction: daemon.NewChangeTodoActionsInstruction(changes),
|
||||
}).Run()
|
||||
@@ -218,7 +219,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.Push(self.Tr.StashPrefix + commits[commitIdx].Hash()); err != nil {
|
||||
if err := self.stash.Push(fmt.Sprintf(self.Tr.AutoStashForMovingPatchToIndex, commits[commitIdx].ShortHash())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -259,7 +260,7 @@ func (self *PatchCommands) MovePatchIntoIndex(commits []*models.Commit, commitId
|
||||
}
|
||||
|
||||
if stash {
|
||||
if err := self.stash.Apply(0); err != nil {
|
||||
if err := self.stash.Pop(0); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -314,6 +315,29 @@ func (self *PatchCommands) PullPatchIntoNewCommit(
|
||||
return self.rebase.ContinueRebase()
|
||||
}
|
||||
|
||||
func (self *PatchCommands) PullPatchIntoNewCommitBefore(
|
||||
commits []*models.Commit,
|
||||
commitIdx int,
|
||||
commitSummary string,
|
||||
commitDescription string,
|
||||
) error {
|
||||
if err := self.rebase.BeginInteractiveRebaseForCommit(commits, commitIdx+1, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.ApplyCustomPatch(false, false); err != nil {
|
||||
_ = self.rebase.AbortRebase()
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.commit.CommitCmdObj(commitSummary, commitDescription, false).Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.PatchBuilder.Reset()
|
||||
return self.rebase.ContinueRebase()
|
||||
}
|
||||
|
||||
// We have just applied a patch in reverse to discard it from a commit; if we
|
||||
// now try to apply the patch again to move it to a later commit, or to the
|
||||
// index, then this would conflict "with itself" in case the patch contained
|
||||
|
||||
@@ -35,6 +35,8 @@ func NewRebaseCommands(
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, summary string, description string) error {
|
||||
// This check is currently unreachable (handled in LocalCommitsController.reword),
|
||||
// but kept as a safeguard in case this method is used elsewhere.
|
||||
if self.config.NeedsGpgSubprocessForCommit() {
|
||||
return errors.New(self.Tr.DisabledForGPG)
|
||||
}
|
||||
@@ -53,7 +55,7 @@ func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, su
|
||||
return self.ContinueRebase()
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index int) (oscommands.ICmdObj, error) {
|
||||
func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index int) (*oscommands.CmdObj, error) {
|
||||
changes := []daemon.ChangeTodoAction{{
|
||||
Hash: commits[index].Hash(),
|
||||
NewAction: todo.Reword,
|
||||
@@ -209,7 +211,7 @@ type PrepareInteractiveRebaseCommandOpts struct {
|
||||
// PrepareInteractiveRebaseCommand returns the cmd for an interactive rebase
|
||||
// we tell git to run lazygit to edit the todo list, and we pass the client
|
||||
// lazygit instructions what to do with the todo file
|
||||
func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteractiveRebaseCommandOpts) oscommands.ICmdObj {
|
||||
func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteractiveRebaseCommandOpts) *oscommands.CmdObj {
|
||||
ex := oscommands.GetLazygitPath()
|
||||
|
||||
cmdArgs := NewGitCmd("rebase").
|
||||
@@ -325,9 +327,8 @@ func (self *RebaseCommands) MoveFixupCommitDown(commits []*models.Commit, target
|
||||
func todoFromCommit(commit *models.Commit) utils.Todo {
|
||||
if commit.Action == todo.UpdateRef {
|
||||
return utils.Todo{Ref: commit.Name}
|
||||
} else {
|
||||
return utils.Todo{Hash: commit.Hash()}
|
||||
}
|
||||
return utils.Todo{Hash: commit.Hash()}
|
||||
}
|
||||
|
||||
// Sets the action for the given commits in the git-rebase-todo file
|
||||
@@ -400,6 +401,18 @@ func (self *RebaseCommands) SquashAllAboveFixupCommits(commit *models.Commit) er
|
||||
func (self *RebaseCommands) BeginInteractiveRebaseForCommit(
|
||||
commits []*models.Commit, commitIndex int, keepCommitsThatBecomeEmpty bool,
|
||||
) error {
|
||||
if commitIndex < len(commits) && commits[commitIndex].IsMerge() {
|
||||
if self.config.NeedsGpgSubprocessForCommit() {
|
||||
return errors.New(self.Tr.DisabledForGPG)
|
||||
}
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseHashOrRoot: getBaseHashOrRoot(commits, commitIndex),
|
||||
instruction: daemon.NewInsertBreakInstruction(),
|
||||
keepCommitsThatBecomeEmpty: keepCommitsThatBecomeEmpty,
|
||||
}).Run()
|
||||
}
|
||||
|
||||
return self.BeginInteractiveRebaseForCommitRange(commits, commitIndex, commitIndex, keepCommitsThatBecomeEmpty)
|
||||
}
|
||||
|
||||
@@ -446,7 +459,7 @@ func (self *RebaseCommands) RebaseBranchFromBaseCommit(targetBranchName string,
|
||||
}).Run()
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) oscommands.ICmdObj {
|
||||
func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) *oscommands.CmdObj {
|
||||
cmdArgs := NewGitCmd(commandType).Arg("--" + command).ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs)
|
||||
@@ -485,7 +498,7 @@ func (self *RebaseCommands) GenericMergeOrRebaseAction(commandType string, comma
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) runSkipEditorCommand(cmdObj oscommands.ICmdObj) error {
|
||||
func (self *RebaseCommands) runSkipEditorCommand(cmdObj *oscommands.CmdObj) error {
|
||||
instruction := daemon.NewExitImmediatelyInstruction()
|
||||
lazyGitPath := oscommands.GetLazygitPath()
|
||||
return cmdObj.
|
||||
@@ -560,7 +573,7 @@ func getBaseHashOrRoot(commits []*models.Commit, index int) string {
|
||||
// at time of writing)
|
||||
if index < len(commits) {
|
||||
return commits[index].Hash()
|
||||
} else {
|
||||
return "--root"
|
||||
}
|
||||
|
||||
return "--root"
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ func TestRebaseRebaseBranch(t *testing.T) {
|
||||
// environment variables that suppress an interactive editor
|
||||
func TestRebaseSkipEditorCommand(t *testing.T) {
|
||||
cmdArgs := []string{"git", "blah"}
|
||||
runner := oscommands.NewFakeRunner(t).ExpectFunc("matches editor env var", func(cmdObj oscommands.ICmdObj) bool {
|
||||
runner := oscommands.NewFakeRunner(t).ExpectFunc("matches editor env var", func(cmdObj *oscommands.CmdObj) bool {
|
||||
assert.EqualValues(t, cmdArgs, cmdObj.Args())
|
||||
envVars := cmdObj.GetEnvVars()
|
||||
for _, regexStr := range []string{
|
||||
|
||||
@@ -7,18 +7,19 @@ import (
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
"github.com/sanity-io/litter"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var reflogOutput = strings.Replace(`c3c4b66b64c97ffeecde|1643150483|checkout: moving from A to B|51baa8c1
|
||||
var reflogOutput = strings.ReplaceAll(`c3c4b66b64c97ffeecde|1643150483|checkout: moving from A to B|51baa8c1
|
||||
c3c4b66b64c97ffeecde|1643150483|checkout: moving from B to A|51baa8c1
|
||||
c3c4b66b64c97ffeecde|1643150483|checkout: moving from A to B|51baa8c1
|
||||
c3c4b66b64c97ffeecde|1643150483|checkout: moving from master to A|51baa8c1
|
||||
f4ddf2f0d4be4ccc7efa|1643149435|checkout: moving from A to master|51baa8c1
|
||||
`, "|", "\x00", -1)
|
||||
`, "|", "\x00")
|
||||
|
||||
func TestGetReflogCommits(t *testing.T) {
|
||||
type scenario struct {
|
||||
@@ -181,7 +182,7 @@ func TestGetReflogCommits(t *testing.T) {
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.testName, func(t *testing.T) {
|
||||
builder := &ReflogCommitLoader{
|
||||
Common: utils.NewDummyCommon(),
|
||||
Common: common.NewDummyCommon(),
|
||||
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
|
||||
}
|
||||
|
||||
|
||||
@@ -96,19 +96,23 @@ func (self *RemoteLoader) getRemoteBranchesByRemoteName() (map[string][]*models.
|
||||
|
||||
cmdArgs := NewGitCmd("for-each-ref").
|
||||
Arg(fmt.Sprintf("--sort=%s", sortOrder)).
|
||||
Arg("--format=%(refname:short)").
|
||||
Arg("--format=%(refname)").
|
||||
Arg("refs/remotes").
|
||||
ToArgv()
|
||||
|
||||
err := self.cmd.New(cmdArgs).DontLog().RunAndProcessLines(func(line string) (bool, error) {
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
split := strings.SplitN(line, "/", 2)
|
||||
if len(split) != 2 {
|
||||
split := strings.SplitN(line, "/", 4)
|
||||
if len(split) != 4 {
|
||||
return false, nil
|
||||
}
|
||||
remoteName := split[2]
|
||||
name := split[3]
|
||||
|
||||
if name == "HEAD" {
|
||||
return false, nil
|
||||
}
|
||||
remoteName := split[0]
|
||||
name := split[1]
|
||||
|
||||
_, ok := remoteBranchesByRemoteName[remoteName]
|
||||
if !ok {
|
||||
|
||||
@@ -21,7 +21,7 @@ type RepoPaths struct {
|
||||
isBareRepo bool
|
||||
}
|
||||
|
||||
var gitPathFormatVersion GitVersion = GitVersion{2, 31, 0, ""}
|
||||
var gitPathFormatVersion = GitVersion{2, 31, 0, ""}
|
||||
|
||||
// Path to the current worktree. If we're in the main worktree, this will
|
||||
// be the same as RepoPath()
|
||||
|
||||
@@ -184,9 +184,7 @@ func TestGetRepoPaths(t *testing.T) {
|
||||
Expected: nil,
|
||||
Err: func(getRevParseArgs argFn) error {
|
||||
args := strings.Join(getRevParseArgs(), " ")
|
||||
return errors.New(
|
||||
fmt.Sprintf("'git %v --show-toplevel --absolute-git-dir --git-common-dir --is-bare-repository --show-superproject-working-tree' failed: fatal: invalid gitfile format: /path/to/repo/worktree2/.git", args),
|
||||
)
|
||||
return fmt.Errorf("'git %v --show-toplevel --absolute-git-dir --git-common-dir --is-bare-repository --show-superproject-working-tree' failed: fatal: invalid gitfile format: /path/to/repo/worktree2/.git", args)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -32,21 +32,21 @@ func (self *StashCommands) DropNewest() error {
|
||||
}
|
||||
|
||||
func (self *StashCommands) Drop(index int) error {
|
||||
cmdArgs := NewGitCmd("stash").Arg("drop", fmt.Sprintf("stash@{%d}", index)).
|
||||
cmdArgs := NewGitCmd("stash").Arg("drop", fmt.Sprintf("refs/stash@{%d}", index)).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
}
|
||||
|
||||
func (self *StashCommands) Pop(index int) error {
|
||||
cmdArgs := NewGitCmd("stash").Arg("pop", fmt.Sprintf("stash@{%d}", index)).
|
||||
cmdArgs := NewGitCmd("stash").Arg("pop", fmt.Sprintf("refs/stash@{%d}", index)).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
}
|
||||
|
||||
func (self *StashCommands) Apply(index int) error {
|
||||
cmdArgs := NewGitCmd("stash").Arg("apply", fmt.Sprintf("stash@{%d}", index)).
|
||||
cmdArgs := NewGitCmd("stash").Arg("apply", fmt.Sprintf("refs/stash@{%d}", index)).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
@@ -80,7 +80,7 @@ func (self *StashCommands) Hash(index int) (string, error) {
|
||||
return strings.Trim(hash, "\r\n"), err
|
||||
}
|
||||
|
||||
func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj {
|
||||
func (self *StashCommands) ShowStashEntryCmdObj(index int) *oscommands.CmdObj {
|
||||
// "-u" is the same as "--include-untracked", but the latter fails in older git versions for some reason
|
||||
cmdArgs := NewGitCmd("stash").Arg("show").
|
||||
Arg("-p").
|
||||
@@ -90,7 +90,7 @@ func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj {
|
||||
Arg(fmt.Sprintf("--unified=%d", self.AppState.DiffContextSize)).
|
||||
ArgIf(self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space").
|
||||
Arg(fmt.Sprintf("--find-renames=%d%%", self.AppState.RenameSimilarityThreshold)).
|
||||
Arg(fmt.Sprintf("stash@{%d}", index)).
|
||||
Arg(fmt.Sprintf("refs/stash@{%d}", index)).
|
||||
Dir(self.repoPaths.worktreePath).
|
||||
ToArgv()
|
||||
|
||||
@@ -152,7 +152,7 @@ func (self *StashCommands) SaveStagedChanges(message string) error {
|
||||
}
|
||||
|
||||
if err := self.cmd.New(
|
||||
NewGitCmd("stash").Arg("apply", "stash@{1}").ToArgv(),
|
||||
NewGitCmd("stash").Arg("apply", "refs/stash@{1}").ToArgv(),
|
||||
).Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -165,7 +165,7 @@ func (self *StashCommands) SaveStagedChanges(message string) error {
|
||||
}
|
||||
|
||||
if err := self.cmd.New(
|
||||
NewGitCmd("stash").Arg("drop", "stash@{1}").ToArgv(),
|
||||
NewGitCmd("stash").Arg("drop", "refs/stash@{1}").ToArgv(),
|
||||
).Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ outer:
|
||||
if err != nil {
|
||||
return self.getUnfilteredStashEntries()
|
||||
}
|
||||
currentStashEntry = self.stashEntryFromLine(lines[i], idx)
|
||||
currentStashEntry = stashEntryFromLine(lines[i], idx)
|
||||
for i+1 < len(lines) && !isAStash(lines[i+1]) {
|
||||
i++
|
||||
if lines[i] == filterPath {
|
||||
@@ -70,11 +70,11 @@ func (self *StashLoader) getUnfilteredStashEntries() []*models.StashEntry {
|
||||
|
||||
rawString, _ := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
|
||||
return lo.Map(utils.SplitNul(rawString), func(line string, index int) *models.StashEntry {
|
||||
return self.stashEntryFromLine(line, index)
|
||||
return stashEntryFromLine(line, index)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *StashLoader) stashEntryFromLine(line string, index int) *models.StashEntry {
|
||||
func stashEntryFromLine(line string, index int) *models.StashEntry {
|
||||
model := &models.StashEntry{
|
||||
Name: line,
|
||||
Index: index,
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -50,7 +50,7 @@ func TestGetStashEntries(t *testing.T) {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
cmd := oscommands.NewDummyCmdObjBuilder(s.runner)
|
||||
|
||||
loader := NewStashLoader(utils.NewDummyCommon(), cmd)
|
||||
loader := NewStashLoader(common.NewDummyCommon(), cmd)
|
||||
|
||||
assert.EqualValues(t, s.expectedStashEntries, loader.GetStashEntries(s.filterPath))
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
|
||||
func TestStashDrop(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"stash", "drop", "stash@{1}"}, "Dropped refs/stash@{1} (98e9cca532c37c766107093010c72e26f2c24c04)\n", nil)
|
||||
ExpectGitArgs([]string{"stash", "drop", "refs/stash@{1}"}, "Dropped refs/stash@{1} (98e9cca532c37c766107093010c72e26f2c24c04)\n", nil)
|
||||
instance := buildStashCommands(commonDeps{runner: runner})
|
||||
|
||||
assert.NoError(t, instance.Drop(1))
|
||||
@@ -19,7 +19,7 @@ func TestStashDrop(t *testing.T) {
|
||||
|
||||
func TestStashApply(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"stash", "apply", "stash@{1}"}, "", nil)
|
||||
ExpectGitArgs([]string{"stash", "apply", "refs/stash@{1}"}, "", nil)
|
||||
instance := buildStashCommands(commonDeps{runner: runner})
|
||||
|
||||
assert.NoError(t, instance.Apply(1))
|
||||
@@ -28,7 +28,7 @@ func TestStashApply(t *testing.T) {
|
||||
|
||||
func TestStashPop(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"stash", "pop", "stash@{1}"}, "", nil)
|
||||
ExpectGitArgs([]string{"stash", "pop", "refs/stash@{1}"}, "", nil)
|
||||
instance := buildStashCommands(commonDeps{runner: runner})
|
||||
|
||||
assert.NoError(t, instance.Pop(1))
|
||||
@@ -113,7 +113,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
|
||||
contextSize: 3,
|
||||
similarityThreshold: 50,
|
||||
ignoreWhitespace: false,
|
||||
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=3", "--find-renames=50%", "stash@{5}"},
|
||||
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=3", "--find-renames=50%", "refs/stash@{5}"},
|
||||
},
|
||||
{
|
||||
testName: "Show diff with custom context size",
|
||||
@@ -121,7 +121,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
|
||||
contextSize: 77,
|
||||
similarityThreshold: 50,
|
||||
ignoreWhitespace: false,
|
||||
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=77", "--find-renames=50%", "stash@{5}"},
|
||||
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=77", "--find-renames=50%", "refs/stash@{5}"},
|
||||
},
|
||||
{
|
||||
testName: "Show diff with custom similarity threshold",
|
||||
@@ -129,7 +129,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
|
||||
contextSize: 3,
|
||||
similarityThreshold: 33,
|
||||
ignoreWhitespace: false,
|
||||
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=3", "--find-renames=33%", "stash@{5}"},
|
||||
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=3", "--find-renames=33%", "refs/stash@{5}"},
|
||||
},
|
||||
{
|
||||
testName: "Default case",
|
||||
@@ -137,7 +137,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
|
||||
contextSize: 3,
|
||||
similarityThreshold: 50,
|
||||
ignoreWhitespace: true,
|
||||
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=3", "--ignore-all-space", "--find-renames=50%", "stash@{5}"},
|
||||
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=3", "--ignore-all-space", "--find-renames=50%", "refs/stash@{5}"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ func TestStashRename(t *testing.T) {
|
||||
message: "New message",
|
||||
expectedHashCmd: []string{"rev-parse", "refs/stash@{3}"},
|
||||
hashResult: "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd\n",
|
||||
expectedDropCmd: []string{"stash", "drop", "stash@{3}"},
|
||||
expectedDropCmd: []string{"stash", "drop", "refs/stash@{3}"},
|
||||
expectedStoreCmd: []string{"stash", "store", "-m", "New message", "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd"},
|
||||
},
|
||||
{
|
||||
@@ -186,7 +186,7 @@ func TestStashRename(t *testing.T) {
|
||||
message: "",
|
||||
expectedHashCmd: []string{"rev-parse", "refs/stash@{4}"},
|
||||
hashResult: "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd\n",
|
||||
expectedDropCmd: []string{"stash", "drop", "stash@{4}"},
|
||||
expectedDropCmd: []string{"stash", "drop", "refs/stash@{4}"},
|
||||
expectedStoreCmd: []string{"stash", "store", "f0d0f20f2f61ffd6d6bfe0752deffa38845a3edd"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -49,9 +49,8 @@ func (self *SubmoduleCommands) GetConfigs(parentModule *models.SubmoduleConfig)
|
||||
|
||||
if len(matches) > 0 {
|
||||
return matches[1], true
|
||||
} else {
|
||||
return "", false
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
configs := []*models.SubmoduleConfig{}
|
||||
@@ -237,35 +236,35 @@ func (self *SubmoduleCommands) Update(path string) error {
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
}
|
||||
|
||||
func (self *SubmoduleCommands) BulkInitCmdObj() oscommands.ICmdObj {
|
||||
func (self *SubmoduleCommands) BulkInitCmdObj() *oscommands.CmdObj {
|
||||
cmdArgs := NewGitCmd("submodule").Arg("init").
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs)
|
||||
}
|
||||
|
||||
func (self *SubmoduleCommands) BulkUpdateCmdObj() oscommands.ICmdObj {
|
||||
func (self *SubmoduleCommands) BulkUpdateCmdObj() *oscommands.CmdObj {
|
||||
cmdArgs := NewGitCmd("submodule").Arg("update").
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs)
|
||||
}
|
||||
|
||||
func (self *SubmoduleCommands) ForceBulkUpdateCmdObj() oscommands.ICmdObj {
|
||||
func (self *SubmoduleCommands) ForceBulkUpdateCmdObj() *oscommands.CmdObj {
|
||||
cmdArgs := NewGitCmd("submodule").Arg("update", "--force").
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs)
|
||||
}
|
||||
|
||||
func (self *SubmoduleCommands) BulkUpdateRecursivelyCmdObj() oscommands.ICmdObj {
|
||||
func (self *SubmoduleCommands) BulkUpdateRecursivelyCmdObj() *oscommands.CmdObj {
|
||||
cmdArgs := NewGitCmd("submodule").Arg("update", "--init", "--recursive").
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs)
|
||||
}
|
||||
|
||||
func (self *SubmoduleCommands) BulkDeinitCmdObj() oscommands.ICmdObj {
|
||||
func (self *SubmoduleCommands) BulkDeinitCmdObj() *oscommands.CmdObj {
|
||||
cmdArgs := NewGitCmd("submodule").Arg("deinit", "--all", "--force").
|
||||
ToArgv()
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ type PushOpts struct {
|
||||
SetUpstream bool
|
||||
}
|
||||
|
||||
func (self *SyncCommands) PushCmdObj(task gocui.Task, opts PushOpts) (oscommands.ICmdObj, error) {
|
||||
func (self *SyncCommands) PushCmdObj(task gocui.Task, opts PushOpts) (*oscommands.CmdObj, error) {
|
||||
if opts.UpstreamBranch != "" && opts.UpstreamRemote == "" {
|
||||
return nil, errors.New(self.Tr.MustSpecifyOriginError)
|
||||
}
|
||||
@@ -62,7 +62,7 @@ func (self *SyncCommands) fetchCommandBuilder(fetchAll bool) *GitCommandBuilder
|
||||
ArgIf(self.version.IsAtLeast(2, 29, 0), "--no-write-fetch-head")
|
||||
}
|
||||
|
||||
func (self *SyncCommands) FetchCmdObj(task gocui.Task) oscommands.ICmdObj {
|
||||
func (self *SyncCommands) FetchCmdObj(task gocui.Task) *oscommands.CmdObj {
|
||||
cmdArgs := self.fetchCommandBuilder(self.UserConfig().Git.FetchAll).ToArgv()
|
||||
|
||||
cmdObj := self.cmd.New(cmdArgs)
|
||||
@@ -74,7 +74,7 @@ func (self *SyncCommands) Fetch(task gocui.Task) error {
|
||||
return self.FetchCmdObj(task).Run()
|
||||
}
|
||||
|
||||
func (self *SyncCommands) FetchBackgroundCmdObj() oscommands.ICmdObj {
|
||||
func (self *SyncCommands) FetchBackgroundCmdObj() *oscommands.CmdObj {
|
||||
cmdArgs := self.fetchCommandBuilder(self.UserConfig().Git.FetchAll).ToArgv()
|
||||
|
||||
cmdObj := self.cmd.New(cmdArgs)
|
||||
|
||||
@@ -12,14 +12,14 @@ func TestSyncPush(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
opts PushOpts
|
||||
test func(oscommands.ICmdObj, error)
|
||||
test func(*oscommands.CmdObj, error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "Push with force disabled",
|
||||
opts: PushOpts{ForceWithLease: false},
|
||||
test: func(cmdObj oscommands.ICmdObj, err error) {
|
||||
test: func(cmdObj *oscommands.CmdObj, err error) {
|
||||
assert.Equal(t, cmdObj.Args(), []string{"git", "push"})
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
@@ -27,7 +27,7 @@ func TestSyncPush(t *testing.T) {
|
||||
{
|
||||
testName: "Push with force-with-lease enabled",
|
||||
opts: PushOpts{ForceWithLease: true},
|
||||
test: func(cmdObj oscommands.ICmdObj, err error) {
|
||||
test: func(cmdObj *oscommands.CmdObj, err error) {
|
||||
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease"})
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
@@ -35,7 +35,7 @@ func TestSyncPush(t *testing.T) {
|
||||
{
|
||||
testName: "Push with force enabled",
|
||||
opts: PushOpts{Force: true},
|
||||
test: func(cmdObj oscommands.ICmdObj, err error) {
|
||||
test: func(cmdObj *oscommands.CmdObj, err error) {
|
||||
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force"})
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
@@ -48,7 +48,7 @@ func TestSyncPush(t *testing.T) {
|
||||
UpstreamRemote: "origin",
|
||||
UpstreamBranch: "master",
|
||||
},
|
||||
test: func(cmdObj oscommands.ICmdObj, err error) {
|
||||
test: func(cmdObj *oscommands.CmdObj, err error) {
|
||||
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "origin", "refs/heads/master:master"})
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
@@ -62,7 +62,7 @@ func TestSyncPush(t *testing.T) {
|
||||
UpstreamBranch: "master",
|
||||
SetUpstream: true,
|
||||
},
|
||||
test: func(cmdObj oscommands.ICmdObj, err error) {
|
||||
test: func(cmdObj *oscommands.CmdObj, err error) {
|
||||
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--set-upstream", "origin", "refs/heads/master-local:master"})
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
@@ -76,7 +76,7 @@ func TestSyncPush(t *testing.T) {
|
||||
UpstreamBranch: "master",
|
||||
SetUpstream: true,
|
||||
},
|
||||
test: func(cmdObj oscommands.ICmdObj, err error) {
|
||||
test: func(cmdObj *oscommands.CmdObj, err error) {
|
||||
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease", "--set-upstream", "origin", "refs/heads/master:master"})
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
@@ -89,7 +89,7 @@ func TestSyncPush(t *testing.T) {
|
||||
UpstreamBranch: "master",
|
||||
SetUpstream: true,
|
||||
},
|
||||
test: func(cmdObj oscommands.ICmdObj, err error) {
|
||||
test: func(cmdObj *oscommands.CmdObj, err error) {
|
||||
assert.Error(t, err)
|
||||
assert.EqualValues(t, "Must specify a remote if specifying a branch", err.Error())
|
||||
},
|
||||
@@ -109,14 +109,14 @@ func TestSyncFetch(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
fetchAllConfig bool
|
||||
test func(oscommands.ICmdObj)
|
||||
test func(*oscommands.CmdObj)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "Fetch in foreground (all=false)",
|
||||
fetchAllConfig: false,
|
||||
test: func(cmdObj oscommands.ICmdObj) {
|
||||
test: func(cmdObj *oscommands.CmdObj) {
|
||||
assert.True(t, cmdObj.ShouldLog())
|
||||
assert.Equal(t, cmdObj.GetCredentialStrategy(), oscommands.PROMPT)
|
||||
assert.Equal(t, cmdObj.Args(), []string{"git", "fetch"})
|
||||
@@ -125,7 +125,7 @@ func TestSyncFetch(t *testing.T) {
|
||||
{
|
||||
testName: "Fetch in foreground (all=true)",
|
||||
fetchAllConfig: true,
|
||||
test: func(cmdObj oscommands.ICmdObj) {
|
||||
test: func(cmdObj *oscommands.CmdObj) {
|
||||
assert.True(t, cmdObj.ShouldLog())
|
||||
assert.Equal(t, cmdObj.GetCredentialStrategy(), oscommands.PROMPT)
|
||||
assert.Equal(t, cmdObj.Args(), []string{"git", "fetch", "--all"})
|
||||
@@ -147,14 +147,14 @@ func TestSyncFetchBackground(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
fetchAllConfig bool
|
||||
test func(oscommands.ICmdObj)
|
||||
test func(*oscommands.CmdObj)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "Fetch in background (all=false)",
|
||||
fetchAllConfig: false,
|
||||
test: func(cmdObj oscommands.ICmdObj) {
|
||||
test: func(cmdObj *oscommands.CmdObj) {
|
||||
assert.False(t, cmdObj.ShouldLog())
|
||||
assert.Equal(t, cmdObj.GetCredentialStrategy(), oscommands.FAIL)
|
||||
assert.Equal(t, cmdObj.Args(), []string{"git", "fetch"})
|
||||
@@ -163,7 +163,7 @@ func TestSyncFetchBackground(t *testing.T) {
|
||||
{
|
||||
testName: "Fetch in background (all=true)",
|
||||
fetchAllConfig: true,
|
||||
test: func(cmdObj oscommands.ICmdObj) {
|
||||
test: func(cmdObj *oscommands.CmdObj) {
|
||||
assert.False(t, cmdObj.ShouldLog())
|
||||
assert.Equal(t, cmdObj.GetCredentialStrategy(), oscommands.FAIL)
|
||||
assert.Equal(t, cmdObj.Args(), []string{"git", "fetch", "--all"})
|
||||
|
||||
@@ -15,7 +15,7 @@ func NewTagCommands(gitCommon *GitCommon) *TagCommands {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *TagCommands) CreateLightweightObj(tagName string, ref string, force bool) oscommands.ICmdObj {
|
||||
func (self *TagCommands) CreateLightweightObj(tagName string, ref string, force bool) *oscommands.CmdObj {
|
||||
cmdArgs := NewGitCmd("tag").
|
||||
ArgIf(force, "--force").
|
||||
Arg("--", tagName).
|
||||
@@ -25,7 +25,7 @@ func (self *TagCommands) CreateLightweightObj(tagName string, ref string, force
|
||||
return self.cmd.New(cmdArgs)
|
||||
}
|
||||
|
||||
func (self *TagCommands) CreateAnnotatedObj(tagName, ref, msg string, force bool) oscommands.ICmdObj {
|
||||
func (self *TagCommands) CreateAnnotatedObj(tagName, ref, msg string, force bool) *oscommands.CmdObj {
|
||||
cmdArgs := NewGitCmd("tag").Arg(tagName).
|
||||
ArgIf(force, "--force").
|
||||
ArgIf(len(ref) > 0, ref).
|
||||
@@ -57,3 +57,20 @@ func (self *TagCommands) Push(task gocui.Task, remoteName string, tagName string
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
|
||||
}
|
||||
|
||||
// Return info about an annotated tag in the format:
|
||||
//
|
||||
// Tagger: tagger name <tagger email>
|
||||
// TaggerDate: tagger date
|
||||
//
|
||||
// Tag message
|
||||
//
|
||||
// Should only be called for annotated tags.
|
||||
func (self *TagCommands) ShowAnnotationInfo(tagName string) (string, error) {
|
||||
cmdArgs := NewGitCmd("for-each-ref").
|
||||
Arg("--format=Tagger: %(taggername) %(taggeremail)%0aTaggerDate: %(taggerdate)%0a%0a%(contents)").
|
||||
Arg("refs/tags/" + tagName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).RunWithOutput()
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
@@ -26,9 +26,13 @@ func NewTagLoader(
|
||||
}
|
||||
|
||||
func (self *TagLoader) GetTags() ([]*models.Tag, error) {
|
||||
// get remote branches, sorted by creation date (descending)
|
||||
// get tags, sorted by creation date (descending)
|
||||
// see: https://git-scm.com/docs/git-tag#Documentation/git-tag.txt---sortltkeygt
|
||||
cmdArgs := NewGitCmd("tag").Arg("--list", "-n", "--sort=-creatordate").ToArgv()
|
||||
cmdArgs := NewGitCmd("for-each-ref").
|
||||
Arg("--sort=-creatordate").
|
||||
Arg("--format=%(refname)%00%(objecttype)%00%(contents:subject)").
|
||||
Arg("refs/tags").
|
||||
ToArgv()
|
||||
tagsOutput, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -36,20 +40,20 @@ func (self *TagLoader) GetTags() ([]*models.Tag, error) {
|
||||
|
||||
split := utils.SplitLines(tagsOutput)
|
||||
|
||||
lineRegex := regexp.MustCompile(`^([^\s]+)(\s+)?(.*)$`)
|
||||
|
||||
tags := lo.Map(split, func(line string, _ int) *models.Tag {
|
||||
matches := lineRegex.FindStringSubmatch(line)
|
||||
tagName := matches[1]
|
||||
message := ""
|
||||
if len(matches) > 3 {
|
||||
message = matches[3]
|
||||
tags := lo.FilterMap(split, func(line string, _ int) (*models.Tag, bool) {
|
||||
fields := strings.SplitN(line, "\x00", 3)
|
||||
if len(fields) != 3 {
|
||||
return nil, false
|
||||
}
|
||||
tagName := fields[0]
|
||||
objectType := fields[1]
|
||||
message := fields[2]
|
||||
|
||||
return &models.Tag{
|
||||
Name: tagName,
|
||||
Message: message,
|
||||
}
|
||||
Name: strings.TrimPrefix(tagName, "refs/tags/"),
|
||||
Message: message,
|
||||
IsAnnotated: objectType == "tag",
|
||||
}, true
|
||||
})
|
||||
|
||||
return tags, nil
|
||||
|
||||
@@ -5,14 +5,13 @@ import (
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const tagsOutput = `tag1 this is my message
|
||||
tag2
|
||||
tag3 this is my other message
|
||||
`
|
||||
const tagsOutput = "refs/tags/tag1\x00tag\x00this is my message\n" +
|
||||
"refs/tags/tag2\x00commit\x00\n" +
|
||||
"refs/tags/tag3\x00tag\x00this is my other message\n"
|
||||
|
||||
func TestGetTags(t *testing.T) {
|
||||
type scenario struct {
|
||||
@@ -26,18 +25,18 @@ func TestGetTags(t *testing.T) {
|
||||
{
|
||||
testName: "should return no tags if there are none",
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"tag", "--list", "-n", "--sort=-creatordate"}, "", nil),
|
||||
ExpectGitArgs([]string{"for-each-ref", "--sort=-creatordate", "--format=%(refname)%00%(objecttype)%00%(contents:subject)", "refs/tags"}, "", nil),
|
||||
expectedTags: []*models.Tag{},
|
||||
expectedError: nil,
|
||||
},
|
||||
{
|
||||
testName: "should return tags if present",
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"tag", "--list", "-n", "--sort=-creatordate"}, tagsOutput, nil),
|
||||
ExpectGitArgs([]string{"for-each-ref", "--sort=-creatordate", "--format=%(refname)%00%(objecttype)%00%(contents:subject)", "refs/tags"}, tagsOutput, nil),
|
||||
expectedTags: []*models.Tag{
|
||||
{Name: "tag1", Message: "this is my message"},
|
||||
{Name: "tag2", Message: ""},
|
||||
{Name: "tag3", Message: "this is my other message"},
|
||||
{Name: "tag1", Message: "this is my message", IsAnnotated: true},
|
||||
{Name: "tag2", Message: "", IsAnnotated: false},
|
||||
{Name: "tag3", Message: "this is my other message", IsAnnotated: true},
|
||||
},
|
||||
expectedError: nil,
|
||||
},
|
||||
@@ -46,7 +45,7 @@ func TestGetTags(t *testing.T) {
|
||||
for _, scenario := range scenarios {
|
||||
t.Run(scenario.testName, func(t *testing.T) {
|
||||
loader := &TagLoader{
|
||||
Common: utils.NewDummyCommon(),
|
||||
Common: common.NewDummyCommon(),
|
||||
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ func NewWorkingTreeCommands(
|
||||
}
|
||||
}
|
||||
|
||||
func (self *WorkingTreeCommands) OpenMergeToolCmdObj() oscommands.ICmdObj {
|
||||
func (self *WorkingTreeCommands) OpenMergeToolCmdObj() *oscommands.CmdObj {
|
||||
return self.cmd.New(NewGitCmd("mergetool").ToArgv())
|
||||
}
|
||||
|
||||
@@ -66,9 +66,8 @@ func (self *WorkingTreeCommands) UnstageAll() error {
|
||||
func (self *WorkingTreeCommands) UnStageFile(paths []string, tracked bool) error {
|
||||
if tracked {
|
||||
return self.UnstageTrackedFiles(paths)
|
||||
} else {
|
||||
return self.UnstageUntrackedFiles(paths)
|
||||
}
|
||||
return self.UnstageUntrackedFiles(paths)
|
||||
}
|
||||
|
||||
func (self *WorkingTreeCommands) UnstageTrackedFiles(paths []string) error {
|
||||
@@ -255,7 +254,7 @@ func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool,
|
||||
return s
|
||||
}
|
||||
|
||||
func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool) oscommands.ICmdObj {
|
||||
func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool) *oscommands.CmdObj {
|
||||
colorArg := self.UserConfig().Git.Paging.ColorArg
|
||||
if plain {
|
||||
colorArg = "never"
|
||||
@@ -293,7 +292,7 @@ func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bo
|
||||
return self.ShowFileDiffCmdObj(from, to, reverse, fileName, plain).RunWithOutput()
|
||||
}
|
||||
|
||||
func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool) oscommands.ICmdObj {
|
||||
func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool) *oscommands.CmdObj {
|
||||
contextSize := self.AppState.DiffContextSize
|
||||
|
||||
colorArg := self.UserConfig().Git.Paging.ColorArg
|
||||
|
||||
@@ -2,6 +2,7 @@ package git_config
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
@@ -39,7 +40,8 @@ func runGitConfigCmd(cmd *exec.Cmd) (string, error) {
|
||||
cmd.Stderr = io.Discard
|
||||
|
||||
err := cmd.Run()
|
||||
if exitError, ok := err.(*exec.ExitError); ok {
|
||||
var exitError *exec.ExitError
|
||||
if errors.As(err, &exitError) {
|
||||
if waitStatus, ok := exitError.Sys().(syscall.WaitStatus); ok {
|
||||
if waitStatus.ExitStatus() == 1 {
|
||||
return "", fmt.Errorf("the key is not found for %s", cmd.Args)
|
||||
|
||||
@@ -46,9 +46,8 @@ func (self *HostingServiceMgr) GetPullRequestURL(from string, to string) (string
|
||||
|
||||
if to == "" {
|
||||
return gitService.getPullRequestURLIntoDefaultBranch(url.QueryEscape(from)), nil
|
||||
} else {
|
||||
return gitService.getPullRequestURLIntoTargetBranch(url.QueryEscape(from), url.QueryEscape(to)), nil
|
||||
}
|
||||
return gitService.getPullRequestURLIntoTargetBranch(url.QueryEscape(from), url.QueryEscape(to)), nil
|
||||
}
|
||||
|
||||
func (self *HostingServiceMgr) GetCommitURL(commitHash string) (string, error) {
|
||||
|
||||
@@ -10,7 +10,7 @@ type StashEntry struct {
|
||||
}
|
||||
|
||||
func (s *StashEntry) FullRefName() string {
|
||||
return s.RefName()
|
||||
return "refs/" + s.RefName()
|
||||
}
|
||||
|
||||
func (s *StashEntry) RefName() string {
|
||||
|
||||
@@ -3,9 +3,13 @@ package models
|
||||
// Tag : A git tag
|
||||
type Tag struct {
|
||||
Name string
|
||||
|
||||
// this is either the first line of the message of an annotated tag, or the
|
||||
// first line of a commit message for a lightweight tag
|
||||
Message string
|
||||
|
||||
// true if this is an annotated tag, false if it's a lightweight tag
|
||||
IsAnnotated bool
|
||||
}
|
||||
|
||||
func (t *Tag) FullRefName() string {
|
||||
|
||||
@@ -11,70 +11,6 @@ import (
|
||||
|
||||
// A command object is a general way to represent a command to be run on the
|
||||
// command line.
|
||||
type ICmdObj interface {
|
||||
GetCmd() *exec.Cmd
|
||||
// outputs string representation of command. Note that if the command was built
|
||||
// using NewFromArgs, the output won't be quite the same as what you would type
|
||||
// into a terminal e.g. 'sh -c git commit' as opposed to 'sh -c "git commit"'
|
||||
ToString() string
|
||||
|
||||
// outputs args vector e.g. ["git", "commit", "-m", "my message"]
|
||||
Args() []string
|
||||
|
||||
// Set a string to be used as stdin for the command.
|
||||
SetStdin(input string) ICmdObj
|
||||
|
||||
AddEnvVars(...string) ICmdObj
|
||||
GetEnvVars() []string
|
||||
|
||||
// sets the working directory
|
||||
SetWd(string) ICmdObj
|
||||
|
||||
// runs the command and returns an error if any
|
||||
Run() error
|
||||
// runs the command and returns the output as a string, and an error if any
|
||||
RunWithOutput() (string, error)
|
||||
// runs the command and returns stdout and stderr as a string, and an error if any
|
||||
RunWithOutputs() (string, string, error)
|
||||
// runs the command and runs a callback function on each line of the output. If the callback returns true for the boolean value, we kill the process and return.
|
||||
RunAndProcessLines(onLine func(line string) (bool, error)) error
|
||||
|
||||
// Be calling DontLog(), we're saying that once we call Run(), we don't want to
|
||||
// log the command in the UI (it'll still be logged in the log file). The general rule
|
||||
// is that if a command doesn't change the git state (e.g. read commands like `git diff`)
|
||||
// then we don't want to log it. If we are changing something (e.g. `git add .`) then
|
||||
// we do. The only exception is if we're running a command in the background periodically
|
||||
// like `git fetch`, which technically does mutate stuff but isn't something we need
|
||||
// to notify the user about.
|
||||
DontLog() ICmdObj
|
||||
|
||||
// This returns false if DontLog() was called
|
||||
ShouldLog() bool
|
||||
|
||||
// when you call this, then call Run(), we'll stream the output to the cmdWriter (i.e. the command log panel)
|
||||
StreamOutput() ICmdObj
|
||||
// returns true if StreamOutput() was called
|
||||
ShouldStreamOutput() bool
|
||||
|
||||
// if you call this before ShouldStreamOutput we'll consider an error with no
|
||||
// stderr content as a non-error. Not yet supported for Run or RunWithOutput (
|
||||
// but adding support is trivial)
|
||||
IgnoreEmptyError() ICmdObj
|
||||
// returns true if IgnoreEmptyError() was called
|
||||
ShouldIgnoreEmptyError() bool
|
||||
|
||||
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 {
|
||||
cmd *exec.Cmd
|
||||
|
||||
@@ -86,6 +22,9 @@ type CmdObj struct {
|
||||
// see StreamOutput()
|
||||
streamOutput bool
|
||||
|
||||
// see UsePty()
|
||||
usePty bool
|
||||
|
||||
// see IgnoreEmptyError()
|
||||
ignoreEmptyError bool
|
||||
|
||||
@@ -112,12 +51,13 @@ const (
|
||||
FAIL
|
||||
)
|
||||
|
||||
var _ ICmdObj = &CmdObj{}
|
||||
|
||||
func (self *CmdObj) GetCmd() *exec.Cmd {
|
||||
return self.cmd
|
||||
}
|
||||
|
||||
// outputs string representation of command. Note that if the command was built
|
||||
// using NewFromArgs, the output won't be quite the same as what you would type
|
||||
// into a terminal e.g. 'sh -c git commit' as opposed to 'sh -c "git commit"'
|
||||
func (self *CmdObj) ToString() string {
|
||||
// if a given arg contains a space, we need to wrap it in quotes
|
||||
quotedArgs := lo.Map(self.cmd.Args, func(arg string, _ int) string {
|
||||
@@ -130,17 +70,19 @@ func (self *CmdObj) ToString() string {
|
||||
return strings.Join(quotedArgs, " ")
|
||||
}
|
||||
|
||||
// outputs args vector e.g. ["git", "commit", "-m", "my message"]
|
||||
func (self *CmdObj) Args() []string {
|
||||
return self.cmd.Args
|
||||
}
|
||||
|
||||
func (self *CmdObj) SetStdin(input string) ICmdObj {
|
||||
// Set a string to be used as stdin for the command.
|
||||
func (self *CmdObj) SetStdin(input string) *CmdObj {
|
||||
self.cmd.Stdin = strings.NewReader(input)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *CmdObj) AddEnvVars(vars ...string) ICmdObj {
|
||||
func (self *CmdObj) AddEnvVars(vars ...string) *CmdObj {
|
||||
self.cmd.Env = append(self.cmd.Env, vars...)
|
||||
|
||||
return self
|
||||
@@ -150,76 +92,111 @@ func (self *CmdObj) GetEnvVars() []string {
|
||||
return self.cmd.Env
|
||||
}
|
||||
|
||||
func (self *CmdObj) SetWd(wd string) ICmdObj {
|
||||
// sets the working directory
|
||||
func (self *CmdObj) SetWd(wd string) *CmdObj {
|
||||
self.cmd.Dir = wd
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *CmdObj) DontLog() ICmdObj {
|
||||
// By calling DontLog(), we're saying that once we call Run(), we don't want to
|
||||
// log the command in the UI (it'll still be logged in the log file). The general rule
|
||||
// is that if a command doesn't change the git state (e.g. read commands like `git diff`)
|
||||
// then we don't want to log it. If we are changing something (e.g. `git add .`) then
|
||||
// we do. The only exception is if we're running a command in the background periodically
|
||||
// like `git fetch`, which technically does mutate stuff but isn't something we need
|
||||
// to notify the user about.
|
||||
func (self *CmdObj) DontLog() *CmdObj {
|
||||
self.dontLog = true
|
||||
return self
|
||||
}
|
||||
|
||||
// This returns false if DontLog() was called
|
||||
func (self *CmdObj) ShouldLog() bool {
|
||||
return !self.dontLog
|
||||
}
|
||||
|
||||
func (self *CmdObj) StreamOutput() ICmdObj {
|
||||
// when you call this, then call Run(), we'll stream the output to the cmdWriter (i.e. the command log panel)
|
||||
func (self *CmdObj) StreamOutput() *CmdObj {
|
||||
self.streamOutput = true
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// returns true if StreamOutput() was called
|
||||
func (self *CmdObj) ShouldStreamOutput() bool {
|
||||
return self.streamOutput
|
||||
}
|
||||
|
||||
func (self *CmdObj) IgnoreEmptyError() ICmdObj {
|
||||
// when you call this, then call Run(), we'll use a PTY to run the command. Only
|
||||
// has an effect if StreamOutput() was also called. Ignored on Windows.
|
||||
func (self *CmdObj) UsePty() *CmdObj {
|
||||
self.usePty = true
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// returns true if UsePty() was called
|
||||
func (self *CmdObj) ShouldUsePty() bool {
|
||||
return self.usePty
|
||||
}
|
||||
|
||||
// if you call this before ShouldStreamOutput we'll consider an error with no
|
||||
// stderr content as a non-error. Not yet supported for Run or RunWithOutput (
|
||||
// but adding support is trivial)
|
||||
func (self *CmdObj) IgnoreEmptyError() *CmdObj {
|
||||
self.ignoreEmptyError = true
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// returns true if IgnoreEmptyError() was called
|
||||
func (self *CmdObj) ShouldIgnoreEmptyError() bool {
|
||||
return self.ignoreEmptyError
|
||||
}
|
||||
|
||||
func (self *CmdObj) Mutex() *deadlock.Mutex {
|
||||
return self.mutex
|
||||
}
|
||||
|
||||
func (self *CmdObj) WithMutex(mutex *deadlock.Mutex) ICmdObj {
|
||||
func (self *CmdObj) WithMutex(mutex *deadlock.Mutex) *CmdObj {
|
||||
self.mutex = mutex
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *CmdObj) ShouldIgnoreEmptyError() bool {
|
||||
return self.ignoreEmptyError
|
||||
}
|
||||
|
||||
// runs the command and returns an error if any
|
||||
func (self *CmdObj) Run() error {
|
||||
return self.runner.Run(self)
|
||||
}
|
||||
|
||||
// runs the command and returns the output as a string, and an error if any
|
||||
func (self *CmdObj) RunWithOutput() (string, error) {
|
||||
return self.runner.RunWithOutput(self)
|
||||
}
|
||||
|
||||
// runs the command and returns stdout and stderr as a string, and an error if any
|
||||
func (self *CmdObj) RunWithOutputs() (string, string, error) {
|
||||
return self.runner.RunWithOutputs(self)
|
||||
}
|
||||
|
||||
// runs the command and runs a callback function on each line of the output. If the callback
|
||||
// returns true for the boolean value, we kill the process and return.
|
||||
func (self *CmdObj) RunAndProcessLines(onLine func(line string) (bool, error)) error {
|
||||
return self.runner.RunAndProcessLines(self, onLine)
|
||||
}
|
||||
|
||||
func (self *CmdObj) PromptOnCredentialRequest(task gocui.Task) ICmdObj {
|
||||
func (self *CmdObj) PromptOnCredentialRequest(task gocui.Task) *CmdObj {
|
||||
self.credentialStrategy = PROMPT
|
||||
self.usePty = true
|
||||
self.task = task
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *CmdObj) FailOnCredentialRequest() ICmdObj {
|
||||
func (self *CmdObj) FailOnCredentialRequest() *CmdObj {
|
||||
self.credentialStrategy = FAIL
|
||||
self.usePty = true
|
||||
|
||||
return self
|
||||
}
|
||||
@@ -232,7 +209,7 @@ func (self *CmdObj) GetTask() gocui.Task {
|
||||
return self.task
|
||||
}
|
||||
|
||||
func (self *CmdObj) Clone() ICmdObj {
|
||||
func (self *CmdObj) Clone() *CmdObj {
|
||||
clone := &CmdObj{}
|
||||
*clone = *self
|
||||
clone.cmd = cloneCmd(self.cmd)
|
||||
|
||||
@@ -11,11 +11,11 @@ import (
|
||||
|
||||
type ICmdObjBuilder interface {
|
||||
// NewFromArgs takes a slice of strings like []string{"git", "commit"} and returns a new command object.
|
||||
New(args []string) ICmdObj
|
||||
New(args []string) *CmdObj
|
||||
// NewShell takes a string like `git commit` and returns an executable shell command for it e.g. `sh -c 'git commit'`
|
||||
// shellFunctionsFile is an optional file path that will be sourced before executing the command. Callers should pass
|
||||
// the value of UserConfig.OS.ShellFunctionsFile.
|
||||
NewShell(commandStr string, shellFunctionsFile string) ICmdObj
|
||||
NewShell(commandStr string, shellFunctionsFile string) *CmdObj
|
||||
// Quote wraps a string in quotes with any necessary escaping applied. The reason for bundling this up with the other methods in this interface is that we basically always need to make use of this when creating new command objects.
|
||||
Quote(str string) string
|
||||
}
|
||||
@@ -28,13 +28,13 @@ type CmdObjBuilder struct {
|
||||
// poor man's version of explicitly saying that struct X implements interface Y
|
||||
var _ ICmdObjBuilder = &CmdObjBuilder{}
|
||||
|
||||
func (self *CmdObjBuilder) New(args []string) ICmdObj {
|
||||
func (self *CmdObjBuilder) New(args []string) *CmdObj {
|
||||
cmdObj := self.NewWithEnviron(args, os.Environ())
|
||||
return cmdObj
|
||||
}
|
||||
|
||||
// A command with explicit environment from env
|
||||
func (self *CmdObjBuilder) NewWithEnviron(args []string, env []string) ICmdObj {
|
||||
func (self *CmdObjBuilder) NewWithEnviron(args []string, env []string) *CmdObj {
|
||||
cmd := exec.Command(args[0], args[1:]...)
|
||||
cmd.Env = env
|
||||
|
||||
@@ -44,7 +44,7 @@ func (self *CmdObjBuilder) NewWithEnviron(args []string, env []string) ICmdObj {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CmdObjBuilder) NewShell(commandStr string, shellFunctionsFile string) ICmdObj {
|
||||
func (self *CmdObjBuilder) NewShell(commandStr string, shellFunctionsFile string) *CmdObj {
|
||||
if len(shellFunctionsFile) > 0 {
|
||||
commandStr = fmt.Sprintf("%ssource %s\n%s", self.platform.PrefixForShellFunctionsFile, shellFunctionsFile, commandStr)
|
||||
}
|
||||
|
||||
@@ -4,21 +4,22 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"io"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type ICmdObjRunner interface {
|
||||
Run(cmdObj ICmdObj) error
|
||||
RunWithOutput(cmdObj ICmdObj) (string, error)
|
||||
RunWithOutputs(cmdObj ICmdObj) (string, string, error)
|
||||
RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error
|
||||
Run(cmdObj *CmdObj) error
|
||||
RunWithOutput(cmdObj *CmdObj) (string, error)
|
||||
RunWithOutputs(cmdObj *CmdObj) (string, string, error)
|
||||
RunAndProcessLines(cmdObj *CmdObj, onLine func(line string) (bool, error)) error
|
||||
}
|
||||
|
||||
type cmdObjRunner struct {
|
||||
@@ -28,7 +29,7 @@ type cmdObjRunner struct {
|
||||
|
||||
var _ ICmdObjRunner = &cmdObjRunner{}
|
||||
|
||||
func (self *cmdObjRunner) Run(cmdObj ICmdObj) error {
|
||||
func (self *cmdObjRunner) Run(cmdObj *CmdObj) error {
|
||||
if cmdObj.Mutex() != nil {
|
||||
cmdObj.Mutex().Lock()
|
||||
defer cmdObj.Mutex().Unlock()
|
||||
@@ -46,7 +47,7 @@ func (self *cmdObjRunner) Run(cmdObj ICmdObj) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
|
||||
func (self *cmdObjRunner) RunWithOutput(cmdObj *CmdObj) (string, error) {
|
||||
if cmdObj.Mutex() != nil {
|
||||
cmdObj.Mutex().Lock()
|
||||
defer cmdObj.Mutex().Unlock()
|
||||
@@ -71,7 +72,7 @@ func (self *cmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
|
||||
return self.RunWithOutputAux(cmdObj)
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) RunWithOutputs(cmdObj ICmdObj) (string, string, error) {
|
||||
func (self *cmdObjRunner) RunWithOutputs(cmdObj *CmdObj) (string, string, error) {
|
||||
if cmdObj.Mutex() != nil {
|
||||
cmdObj.Mutex().Lock()
|
||||
defer cmdObj.Mutex().Unlock()
|
||||
@@ -96,7 +97,7 @@ func (self *cmdObjRunner) RunWithOutputs(cmdObj ICmdObj) (string, string, error)
|
||||
return self.RunWithOutputsAux(cmdObj)
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) RunWithOutputAux(cmdObj ICmdObj) (string, error) {
|
||||
func (self *cmdObjRunner) RunWithOutputAux(cmdObj *CmdObj) (string, error) {
|
||||
self.log.WithField("command", cmdObj.ToString()).Debug("RunCommand")
|
||||
|
||||
if cmdObj.ShouldLog() {
|
||||
@@ -114,7 +115,7 @@ func (self *cmdObjRunner) RunWithOutputAux(cmdObj ICmdObj) (string, error) {
|
||||
return output, err
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) RunWithOutputsAux(cmdObj ICmdObj) (string, string, error) {
|
||||
func (self *cmdObjRunner) RunWithOutputsAux(cmdObj *CmdObj) (string, string, error) {
|
||||
self.log.WithField("command", cmdObj.ToString()).Debug("RunCommand")
|
||||
|
||||
if cmdObj.ShouldLog() {
|
||||
@@ -139,7 +140,7 @@ func (self *cmdObjRunner) RunWithOutputsAux(cmdObj ICmdObj) (string, string, err
|
||||
return stdout, stderr, err
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error {
|
||||
func (self *cmdObjRunner) RunAndProcessLines(cmdObj *CmdObj, onLine func(line string) (bool, error)) error {
|
||||
if cmdObj.Mutex() != nil {
|
||||
cmdObj.Mutex().Lock()
|
||||
defer cmdObj.Mutex().Unlock()
|
||||
@@ -190,7 +191,7 @@ func (self *cmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line st
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) logCmdObj(cmdObj ICmdObj) {
|
||||
func (self *cmdObjRunner) logCmdObj(cmdObj *CmdObj) {
|
||||
self.guiIO.logCommandFn(cmdObj.ToString(), true)
|
||||
}
|
||||
|
||||
@@ -213,7 +214,7 @@ type cmdHandler struct {
|
||||
close func() error
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) runAndStream(cmdObj ICmdObj) error {
|
||||
func (self *cmdObjRunner) runAndStream(cmdObj *CmdObj) error {
|
||||
return self.runAndStreamAux(cmdObj, func(handler *cmdHandler, cmdWriter io.Writer) {
|
||||
go func() {
|
||||
_, _ = io.Copy(cmdWriter, handler.stdoutPipe)
|
||||
@@ -222,7 +223,7 @@ func (self *cmdObjRunner) runAndStream(cmdObj ICmdObj) error {
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) runAndStreamAux(
|
||||
cmdObj ICmdObj,
|
||||
cmdObj *CmdObj,
|
||||
onRun func(*cmdHandler, io.Writer),
|
||||
) error {
|
||||
cmdWriter := self.guiIO.newCmdWriterFn()
|
||||
@@ -236,7 +237,13 @@ func (self *cmdObjRunner) runAndStreamAux(
|
||||
var stderr bytes.Buffer
|
||||
cmd.Stderr = io.MultiWriter(cmdWriter, &stderr)
|
||||
|
||||
handler, err := self.getCmdHandler(cmd)
|
||||
var handler *cmdHandler
|
||||
var err error
|
||||
if cmdObj.ShouldUsePty() {
|
||||
handler, err = self.getCmdHandlerPty(cmd)
|
||||
} else {
|
||||
handler, err = self.getCmdHandlerNonPty(cmd)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -287,17 +294,13 @@ const (
|
||||
Token
|
||||
)
|
||||
|
||||
// Whenever we're asked for a password we just enter a newline, which will
|
||||
// eventually cause the command to fail.
|
||||
// Whenever we're asked for a password we return a nil channel to tell the
|
||||
// caller to kill the process.
|
||||
var failPromptFn = func(CredentialType) <-chan string {
|
||||
ch := make(chan string)
|
||||
go func() {
|
||||
ch <- "\n"
|
||||
}()
|
||||
return ch
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) runWithCredentialHandling(cmdObj ICmdObj) error {
|
||||
func (self *cmdObjRunner) runWithCredentialHandling(cmdObj *CmdObj) error {
|
||||
promptFn, err := self.getCredentialPromptFn(cmdObj)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -306,7 +309,7 @@ func (self *cmdObjRunner) runWithCredentialHandling(cmdObj ICmdObj) error {
|
||||
return self.runAndDetectCredentialRequest(cmdObj, promptFn)
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) getCredentialPromptFn(cmdObj ICmdObj) (func(CredentialType) <-chan string, error) {
|
||||
func (self *cmdObjRunner) getCredentialPromptFn(cmdObj *CmdObj) (func(CredentialType) <-chan string, error) {
|
||||
switch cmdObj.GetCredentialStrategy() {
|
||||
case PROMPT:
|
||||
return self.guiIO.promptForCredentialFn, nil
|
||||
@@ -322,7 +325,7 @@ func (self *cmdObjRunner) getCredentialPromptFn(cmdObj ICmdObj) (func(Credential
|
||||
// promptUserForCredential is a function that gets executed when this function detect you need to fill in 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,
|
||||
cmdObj *CmdObj,
|
||||
promptUserForCredential func(CredentialType) <-chan string,
|
||||
) error {
|
||||
// setting the output to english so we can parse it for a username/password request
|
||||
@@ -332,7 +335,7 @@ func (self *cmdObjRunner) runAndDetectCredentialRequest(
|
||||
tr := io.TeeReader(handler.stdoutPipe, cmdWriter)
|
||||
|
||||
go utils.Safe(func() {
|
||||
self.processOutput(tr, handler.stdinPipe, promptUserForCredential, cmdObj.GetTask())
|
||||
self.processOutput(tr, handler.stdinPipe, promptUserForCredential, cmdObj)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -341,9 +344,10 @@ func (self *cmdObjRunner) processOutput(
|
||||
reader io.Reader,
|
||||
writer io.Writer,
|
||||
promptUserForCredential func(CredentialType) <-chan string,
|
||||
task gocui.Task,
|
||||
cmdObj *CmdObj,
|
||||
) {
|
||||
checkForCredentialRequest := self.getCheckForCredentialRequestFunc()
|
||||
task := cmdObj.GetTask()
|
||||
|
||||
scanner := bufio.NewScanner(reader)
|
||||
scanner.Split(bufio.ScanBytes)
|
||||
@@ -352,16 +356,26 @@ func (self *cmdObjRunner) processOutput(
|
||||
askFor, ok := checkForCredentialRequest(newBytes)
|
||||
if ok {
|
||||
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))
|
||||
if responseChan == nil {
|
||||
// Returning a nil channel means we should kill the process.
|
||||
// Note that we don't break the loop after this, because we
|
||||
// still need to drain the output, otherwise the Wait() call
|
||||
// later might block.
|
||||
if err := Kill(cmdObj.GetCmd()); err != nil {
|
||||
self.log.Error(err)
|
||||
}
|
||||
} else {
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -377,6 +391,7 @@ func (self *cmdObjRunner) getCheckForCredentialRequestFunc() func([]byte) (Crede
|
||||
`Username\s*for\s*'.+':`: Username,
|
||||
`Enter\s*passphrase\s*for\s*key\s*'.+':`: Passphrase,
|
||||
`Enter\s*PIN\s*for\s*.+\s*key\s*.+:`: PIN,
|
||||
`Enter\s*PIN\s*for\s*'.+':`: PIN,
|
||||
`.*2FA Token.*`: Token,
|
||||
}
|
||||
|
||||
@@ -410,3 +425,38 @@ func (self *cmdObjRunner) getCheckForCredentialRequestFunc() func([]byte) (Crede
|
||||
return 0, false
|
||||
}
|
||||
}
|
||||
|
||||
type Buffer struct {
|
||||
b bytes.Buffer
|
||||
m deadlock.Mutex
|
||||
}
|
||||
|
||||
func (b *Buffer) Read(p []byte) (n int, err error) {
|
||||
b.m.Lock()
|
||||
defer b.m.Unlock()
|
||||
return b.b.Read(p)
|
||||
}
|
||||
|
||||
func (b *Buffer) Write(p []byte) (n int, err error) {
|
||||
b.m.Lock()
|
||||
defer b.m.Unlock()
|
||||
return b.b.Write(p)
|
||||
}
|
||||
|
||||
func (self *cmdObjRunner) getCmdHandlerNonPty(cmd *exec.Cmd) (*cmdHandler, error) {
|
||||
stdoutReader, stdoutWriter := io.Pipe()
|
||||
cmd.Stdout = stdoutWriter
|
||||
|
||||
buf := &Buffer{}
|
||||
cmd.Stdin = buf
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cmdHandler{
|
||||
stdoutPipe: stdoutReader,
|
||||
stdinPipe: buf,
|
||||
close: func() error { return nil },
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
// we define this separately for windows and non-windows given that windows does
|
||||
// not have great PTY support and we need a PTY to handle a credential request
|
||||
func (self *cmdObjRunner) getCmdHandler(cmd *exec.Cmd) (*cmdHandler, error) {
|
||||
func (self *cmdObjRunner) getCmdHandlerPty(cmd *exec.Cmd) (*cmdHandler, error) {
|
||||
ptmx, err := pty.Start(cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -89,11 +89,17 @@ func TestProcessOutput(t *testing.T) {
|
||||
expectedToWrite: "passphrase",
|
||||
},
|
||||
{
|
||||
name: "pin prompt",
|
||||
name: "security key pin prompt",
|
||||
promptUserForCredential: defaultPromptUserForCredential,
|
||||
output: "Enter PIN for key '123':",
|
||||
expectedToWrite: "pin",
|
||||
},
|
||||
{
|
||||
name: "pkcs11 key pin prompt",
|
||||
promptUserForCredential: defaultPromptUserForCredential,
|
||||
output: "Enter PIN for '123':",
|
||||
expectedToWrite: "pin",
|
||||
},
|
||||
{
|
||||
name: "2FA token prompt",
|
||||
promptUserForCredential: defaultPromptUserForCredential,
|
||||
@@ -120,8 +126,8 @@ func TestProcessOutput(t *testing.T) {
|
||||
reader := strings.NewReader(scenario.output)
|
||||
writer := &strings.Builder{}
|
||||
|
||||
task := gocui.NewFakeTask()
|
||||
runner.processOutput(reader, writer, toChanFn(scenario.promptUserForCredential), task)
|
||||
cmdObj := &CmdObj{task: gocui.NewFakeTask()}
|
||||
runner.processOutput(reader, writer, toChanFn(scenario.promptUserForCredential), cmdObj)
|
||||
|
||||
if writer.String() != scenario.expectedToWrite {
|
||||
t.Errorf("expected to write '%s' but got '%s'", scenario.expectedToWrite, writer.String())
|
||||
|
||||
@@ -4,48 +4,10 @@
|
||||
package oscommands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"os/exec"
|
||||
|
||||
"github.com/sasha-s/go-deadlock"
|
||||
)
|
||||
|
||||
type Buffer struct {
|
||||
b bytes.Buffer
|
||||
m deadlock.Mutex
|
||||
}
|
||||
|
||||
func (b *Buffer) Read(p []byte) (n int, err error) {
|
||||
b.m.Lock()
|
||||
defer b.m.Unlock()
|
||||
return b.b.Read(p)
|
||||
}
|
||||
|
||||
func (b *Buffer) Write(p []byte) (n int, err error) {
|
||||
b.m.Lock()
|
||||
defer b.m.Unlock()
|
||||
return b.b.Write(p)
|
||||
}
|
||||
|
||||
// TODO: Remove this hack and replace it with a proper way to run commands live on windows. We still have an issue where if a password is requested, the request for a password is written straight to stdout because we can't control the stdout of a subprocess of a subprocess. Keep an eye on https://github.com/creack/pty/pull/109
|
||||
func (self *cmdObjRunner) getCmdHandler(cmd *exec.Cmd) (*cmdHandler, error) {
|
||||
stdoutReader, stdoutWriter := io.Pipe()
|
||||
cmd.Stdout = stdoutWriter
|
||||
|
||||
buf := &Buffer{}
|
||||
cmd.Stdin = buf
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// because we don't yet have windows support for a pty, we instead just
|
||||
// pass our standard stream handlers and because there's no pty to close
|
||||
// we pass a no-op function for that.
|
||||
return &cmdHandler{
|
||||
stdoutPipe: stdoutReader,
|
||||
stdinPipe: buf,
|
||||
close: func() error { return nil },
|
||||
}, nil
|
||||
func (self *cmdObjRunner) getCmdHandlerPty(cmd *exec.Cmd) (*cmdHandler, error) {
|
||||
// We don't have PTY support on Windows yet, so we just return a non-PTY handler.
|
||||
return self.getCmdHandlerNonPty(cmd)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
// NewDummyOSCommand creates a new dummy OSCommand for testing
|
||||
func NewDummyOSCommand() *OSCommand {
|
||||
osCmd := NewOSCommand(utils.NewDummyCommon(), config.NewDummyAppConfig(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog()))
|
||||
osCmd := NewOSCommand(common.NewDummyCommon(), config.NewDummyAppConfig(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog()))
|
||||
|
||||
return osCmd
|
||||
}
|
||||
@@ -23,9 +23,9 @@ type OSCommandDeps struct {
|
||||
}
|
||||
|
||||
func NewDummyOSCommandWithDeps(deps OSCommandDeps) *OSCommand {
|
||||
common := deps.Common
|
||||
if common == nil {
|
||||
common = utils.NewDummyCommon()
|
||||
cmn := deps.Common
|
||||
if cmn == nil {
|
||||
cmn = common.NewDummyCommon()
|
||||
}
|
||||
|
||||
platform := deps.Platform
|
||||
@@ -34,7 +34,7 @@ func NewDummyOSCommandWithDeps(deps OSCommandDeps) *OSCommand {
|
||||
}
|
||||
|
||||
return &OSCommand{
|
||||
Common: common,
|
||||
Common: cmn,
|
||||
Platform: platform,
|
||||
getenvFn: deps.GetenvFn,
|
||||
removeFileFn: deps.RemoveFileFn,
|
||||
@@ -59,7 +59,7 @@ var dummyPlatform = &Platform{
|
||||
}
|
||||
|
||||
func NewDummyOSCommandWithRunner(runner *FakeCmdObjRunner) *OSCommand {
|
||||
osCommand := NewOSCommand(utils.NewDummyCommon(), config.NewDummyAppConfig(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog()))
|
||||
osCommand := NewOSCommand(common.NewDummyCommon(), config.NewDummyAppConfig(), dummyPlatform, NewNullGuiIO(utils.NewDummyLog()))
|
||||
osCommand.Cmd = NewDummyCmdObjBuilder(runner)
|
||||
|
||||
return osCommand
|
||||
|
||||
@@ -28,7 +28,7 @@ type FakeCmdObjRunner struct {
|
||||
type CmdObjMatcher struct {
|
||||
description string
|
||||
// returns true if the matcher matches the command object
|
||||
test func(ICmdObj) bool
|
||||
test func(*CmdObj) bool
|
||||
|
||||
// output of the command
|
||||
output string
|
||||
@@ -48,12 +48,12 @@ func (self *FakeCmdObjRunner) remainingExpectedCmds() []CmdObjMatcher {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *FakeCmdObjRunner) Run(cmdObj ICmdObj) error {
|
||||
func (self *FakeCmdObjRunner) Run(cmdObj *CmdObj) error {
|
||||
_, err := self.RunWithOutput(cmdObj)
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *FakeCmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
|
||||
func (self *FakeCmdObjRunner) RunWithOutput(cmdObj *CmdObj) (string, error) {
|
||||
self.mutex.Lock()
|
||||
defer self.mutex.Unlock()
|
||||
|
||||
@@ -78,12 +78,12 @@ func (self *FakeCmdObjRunner) RunWithOutput(cmdObj ICmdObj) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (self *FakeCmdObjRunner) RunWithOutputs(cmdObj ICmdObj) (string, string, error) {
|
||||
func (self *FakeCmdObjRunner) RunWithOutputs(cmdObj *CmdObj) (string, string, error) {
|
||||
output, err := self.RunWithOutput(cmdObj)
|
||||
return output, "", err
|
||||
}
|
||||
|
||||
func (self *FakeCmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line string) (bool, error)) error {
|
||||
func (self *FakeCmdObjRunner) RunAndProcessLines(cmdObj *CmdObj, onLine func(line string) (bool, error)) error {
|
||||
output, err := self.RunWithOutput(cmdObj)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -105,7 +105,7 @@ func (self *FakeCmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(lin
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *FakeCmdObjRunner) ExpectFunc(description string, fn func(cmdObj ICmdObj) bool, output string, err error) *FakeCmdObjRunner {
|
||||
func (self *FakeCmdObjRunner) ExpectFunc(description string, fn func(cmdObj *CmdObj) bool, output string, err error) *FakeCmdObjRunner {
|
||||
self.mutex.Lock()
|
||||
defer self.mutex.Unlock()
|
||||
|
||||
@@ -121,7 +121,7 @@ func (self *FakeCmdObjRunner) ExpectFunc(description string, fn func(cmdObj ICmd
|
||||
|
||||
func (self *FakeCmdObjRunner) ExpectArgs(expectedArgs []string, output string, err error) *FakeCmdObjRunner {
|
||||
description := fmt.Sprintf("matches args %s", strings.Join(expectedArgs, " "))
|
||||
self.ExpectFunc(description, func(cmdObj ICmdObj) bool {
|
||||
self.ExpectFunc(description, func(cmdObj *CmdObj) bool {
|
||||
return slices.Equal(expectedArgs, cmdObj.GetCmd().Args)
|
||||
}, output, err)
|
||||
|
||||
@@ -130,7 +130,7 @@ func (self *FakeCmdObjRunner) ExpectArgs(expectedArgs []string, output string, e
|
||||
|
||||
func (self *FakeCmdObjRunner) ExpectGitArgs(expectedArgs []string, output string, err error) *FakeCmdObjRunner {
|
||||
description := fmt.Sprintf("matches git args %s", strings.Join(expectedArgs, " "))
|
||||
self.ExpectFunc(description, func(cmdObj ICmdObj) bool {
|
||||
self.ExpectFunc(description, func(cmdObj *CmdObj) bool {
|
||||
return slices.Equal(expectedArgs, cmdObj.GetCmd().Args[1:])
|
||||
}, output, err)
|
||||
|
||||
|
||||
@@ -208,13 +208,13 @@ func (c *OSCommand) FileExists(path string) (bool, error) {
|
||||
}
|
||||
|
||||
// PipeCommands runs a heap of commands and pipes their inputs/outputs together like A | B | C
|
||||
func (c *OSCommand) PipeCommands(cmdObjs ...ICmdObj) error {
|
||||
cmds := lo.Map(cmdObjs, func(cmdObj ICmdObj, _ int) *exec.Cmd {
|
||||
func (c *OSCommand) PipeCommands(cmdObjs ...*CmdObj) error {
|
||||
cmds := lo.Map(cmdObjs, func(cmdObj *CmdObj, _ int) *exec.Cmd {
|
||||
return cmdObj.GetCmd()
|
||||
})
|
||||
|
||||
logCmdStr := strings.Join(
|
||||
lo.Map(cmdObjs, func(cmdObj ICmdObj, _ int) string {
|
||||
lo.Map(cmdObjs, func(cmdObj *CmdObj, _ int) string {
|
||||
return cmdObj.ToString()
|
||||
}),
|
||||
" | ",
|
||||
@@ -222,7 +222,7 @@ func (c *OSCommand) PipeCommands(cmdObjs ...ICmdObj) error {
|
||||
|
||||
c.LogCommand(logCmdStr, true)
|
||||
|
||||
for i := 0; i < len(cmds)-1; i++ {
|
||||
for i := range len(cmds) - 1 {
|
||||
stdout, err := cmds[i].StdoutPipe()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -283,7 +283,7 @@ func PrepareForChildren(cmd *exec.Cmd) {
|
||||
}
|
||||
|
||||
func (c *OSCommand) CopyToClipboard(str string) error {
|
||||
escaped := strings.Replace(str, "\n", "\\n", -1)
|
||||
escaped := strings.ReplaceAll(str, "\n", "\\n")
|
||||
truncated := utils.TruncateWithEllipsis(escaped, 40)
|
||||
|
||||
msg := utils.ResolvePlaceholderString(
|
||||
|
||||
@@ -72,30 +72,22 @@ func (self *patchPresenter) format() string {
|
||||
|
||||
lineIdx++
|
||||
}
|
||||
appendFormattedLine := func(line string, style style.TextStyle) {
|
||||
formattedLine := self.formatLine(
|
||||
line,
|
||||
style,
|
||||
lineIdx,
|
||||
)
|
||||
|
||||
appendLine(formattedLine)
|
||||
}
|
||||
|
||||
for _, line := range self.patch.header {
|
||||
appendFormattedLine(line, theme.DefaultTextColor.SetBold())
|
||||
// always passing false for 'included' here because header lines are not part of the patch
|
||||
appendLine(self.formatLineAux(line, theme.DefaultTextColor.SetBold(), false))
|
||||
}
|
||||
|
||||
for _, hunk := range self.patch.hunks {
|
||||
appendLine(
|
||||
self.formatLine(
|
||||
self.formatLineAux(
|
||||
hunk.formatHeaderStart(),
|
||||
style.FgCyan,
|
||||
lineIdx,
|
||||
false,
|
||||
) +
|
||||
// we're splitting the line into two parts: the diff header and the context
|
||||
// We explicitly pass 'included' as false here so that we're only tagging the
|
||||
// first half of the line as included if the line is indeed included.
|
||||
// We explicitly pass 'included' as false for both because these are not part
|
||||
// of the actual patch
|
||||
self.formatLineAux(
|
||||
hunk.headerContext,
|
||||
theme.DefaultTextColor,
|
||||
@@ -104,7 +96,12 @@ func (self *patchPresenter) format() string {
|
||||
)
|
||||
|
||||
for _, line := range hunk.bodyLines {
|
||||
appendFormattedLine(line.Content, self.patchLineStyle(line))
|
||||
style := self.patchLineStyle(line)
|
||||
if line.IsChange() {
|
||||
appendLine(self.formatLine(line.Content, style, lineIdx))
|
||||
} else {
|
||||
appendLine(self.formatLineAux(line.Content, style, false))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ func (self *Patch) HunkStartIdx(hunkIndex int) int {
|
||||
hunkIndex = lo.Clamp(hunkIndex, 0, len(self.hunks)-1)
|
||||
|
||||
result := len(self.header)
|
||||
for i := 0; i < hunkIndex; i++ {
|
||||
for i := range hunkIndex {
|
||||
result += self.hunks[i].lineCount()
|
||||
}
|
||||
return result
|
||||
@@ -115,15 +115,22 @@ func (self *Patch) HunkContainingLine(idx int) int {
|
||||
return -1
|
||||
}
|
||||
|
||||
// Returns the patch line index of the next change (i.e. addition or deletion).
|
||||
func (self *Patch) GetNextChangeIdx(idx int) int {
|
||||
// Returns the patch line index of the next change (i.e. addition or deletion)
|
||||
// that matches the same "included" state, given the includedLines. If you don't
|
||||
// care about included states, pass nil for includedLines and false for included.
|
||||
func (self *Patch) GetNextChangeIdxOfSameIncludedState(idx int, includedLines []int, included bool) (int, bool) {
|
||||
idx = lo.Clamp(idx, 0, self.LineCount()-1)
|
||||
|
||||
lines := self.Lines()
|
||||
|
||||
isMatch := func(i int, line *PatchLine) bool {
|
||||
sameIncludedState := lo.Contains(includedLines, i) == included
|
||||
return line.IsChange() && sameIncludedState
|
||||
}
|
||||
|
||||
for i, line := range lines[idx:] {
|
||||
if line.isChange() {
|
||||
return i + idx
|
||||
if isMatch(i+idx, line) {
|
||||
return i + idx, true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,13 +138,18 @@ func (self *Patch) GetNextChangeIdx(idx int) int {
|
||||
// return the index of the last change
|
||||
for i := len(lines) - 1; i >= 0; i-- {
|
||||
line := lines[i]
|
||||
if line.isChange() {
|
||||
return i
|
||||
if isMatch(i, line) {
|
||||
return i, true
|
||||
}
|
||||
}
|
||||
|
||||
// should not be possible
|
||||
return 0
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Returns the patch line index of the next change (i.e. addition or deletion).
|
||||
func (self *Patch) GetNextChangeIdx(idx int) int {
|
||||
result, _ := self.GetNextChangeIdxOfSameIncludedState(idx, nil, false)
|
||||
return result
|
||||
}
|
||||
|
||||
// Returns the length of the patch in lines
|
||||
|
||||
@@ -91,7 +91,7 @@ func (p *PatchBuilder) addFileWhole(info *fileInfo) {
|
||||
// add every line index
|
||||
// TODO: add tests and then use lo.Range to simplify
|
||||
info.includedLineIndices = make([]int, lineCount)
|
||||
for i := 0; i < lineCount; i++ {
|
||||
for i := range lineCount {
|
||||
info.includedLineIndices[i] = i
|
||||
}
|
||||
}
|
||||
@@ -124,14 +124,6 @@ func (p *PatchBuilder) RemoveFile(filename string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func getIndicesForRange(first, last int) []int {
|
||||
indices := []int{}
|
||||
for i := first; i <= last; i++ {
|
||||
indices = append(indices, i)
|
||||
}
|
||||
return indices
|
||||
}
|
||||
|
||||
func (p *PatchBuilder) getFileInfo(filename string) (*fileInfo, error) {
|
||||
info, ok := p.fileInfoMap[filename]
|
||||
if ok {
|
||||
@@ -152,24 +144,24 @@ func (p *PatchBuilder) getFileInfo(filename string) (*fileInfo, error) {
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func (p *PatchBuilder) AddFileLineRange(filename string, firstLineIdx, lastLineIdx int) error {
|
||||
func (p *PatchBuilder) AddFileLineRange(filename string, lineIndices []int) error {
|
||||
info, err := p.getFileInfo(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info.mode = PART
|
||||
info.includedLineIndices = lo.Union(info.includedLineIndices, getIndicesForRange(firstLineIdx, lastLineIdx))
|
||||
info.includedLineIndices = lo.Union(info.includedLineIndices, lineIndices)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *PatchBuilder) RemoveFileLineRange(filename string, firstLineIdx, lastLineIdx int) error {
|
||||
func (p *PatchBuilder) RemoveFileLineRange(filename string, lineIndices []int) error {
|
||||
info, err := p.getFileInfo(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info.mode = PART
|
||||
info.includedLineIndices, _ = lo.Difference(info.includedLineIndices, getIndicesForRange(firstLineIdx, lastLineIdx))
|
||||
info.includedLineIndices, _ = lo.Difference(info.includedLineIndices, lineIndices)
|
||||
if len(info.includedLineIndices) == 0 {
|
||||
p.removeFile(info)
|
||||
}
|
||||
@@ -211,9 +203,8 @@ func (p *PatchBuilder) RenderPatchForFile(opts RenderPatchForFileOpts) string {
|
||||
|
||||
if opts.Plain {
|
||||
return patch.FormatPlain()
|
||||
} else {
|
||||
return patch.FormatView(FormatViewOpts{})
|
||||
}
|
||||
return patch.FormatView(FormatViewOpts{})
|
||||
}
|
||||
|
||||
func (p *PatchBuilder) renderEachFilePatch(plain bool) []string {
|
||||
@@ -287,11 +278,5 @@ func (p *PatchBuilder) NewPatchRequired(from string, to string, reverse bool) bo
|
||||
}
|
||||
|
||||
func (p *PatchBuilder) AllFilesInPatch() []string {
|
||||
files := make([]string, 0, len(p.fileInfoMap))
|
||||
|
||||
for filename := range p.fileInfoMap {
|
||||
files = append(files, filename)
|
||||
}
|
||||
|
||||
return files
|
||||
return lo.Keys(p.fileInfoMap)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ type PatchLine struct {
|
||||
Content string // something like '+ hello' (note the first character is not removed)
|
||||
}
|
||||
|
||||
func (self *PatchLine) isChange() bool {
|
||||
func (self *PatchLine) IsChange() bool {
|
||||
return self.Kind == ADDITION || self.Kind == DELETION
|
||||
}
|
||||
|
||||
|
||||
@@ -84,9 +84,9 @@ func (self *patchTransformer) transformHeader() []string {
|
||||
result = append(result, line)
|
||||
}
|
||||
return result
|
||||
} else {
|
||||
return self.patch.header
|
||||
}
|
||||
|
||||
return self.patch.header
|
||||
}
|
||||
|
||||
func (self *patchTransformer) transformHunks() []*Hunk {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
func NewDummyCommon() *Common {
|
||||
tr := i18n.EnglishTranslationSet()
|
||||
cmn := &Common{
|
||||
Log: utils.NewDummyLog(),
|
||||
Tr: tr,
|
||||
Fs: afero.NewOsFs(),
|
||||
}
|
||||
cmn.SetUserConfig(config.GetDefaultConfig())
|
||||
return cmn
|
||||
}
|
||||
|
||||
func NewDummyCommonWithUserConfigAndAppState(userConfig *config.UserConfig, appState *config.AppState) *Common {
|
||||
tr := i18n.EnglishTranslationSet()
|
||||
cmn := &Common{
|
||||
Log: utils.NewDummyLog(),
|
||||
Tr: tr,
|
||||
AppState: appState,
|
||||
// TODO: remove dependency on actual filesystem in tests and switch to using
|
||||
// in-memory for everything
|
||||
Fs: afero.NewOsFs(),
|
||||
}
|
||||
cmn.SetUserConfig(userConfig)
|
||||
return cmn
|
||||
}
|
||||
+179
-41
@@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
@@ -10,6 +11,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/adrg/xdg"
|
||||
"github.com/jesseduffield/generics/orderedset"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils/yaml_utils"
|
||||
"github.com/samber/lo"
|
||||
"gopkg.in/yaml.v3"
|
||||
@@ -94,7 +97,7 @@ func NewAppConfig(
|
||||
configFiles = []*ConfigFile{configFile}
|
||||
}
|
||||
|
||||
userConfig, err := loadUserConfigWithDefaults(configFiles)
|
||||
userConfig, err := loadUserConfigWithDefaults(configFiles, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -133,7 +136,7 @@ func NewAppConfig(
|
||||
}
|
||||
|
||||
func ConfigDir() string {
|
||||
_, filePath := findConfigFile("config.yml")
|
||||
_, filePath := findConfigFile(ConfigFilename)
|
||||
|
||||
return filepath.Dir(filePath)
|
||||
}
|
||||
@@ -143,11 +146,11 @@ func findOrCreateConfigDir() (string, error) {
|
||||
return folder, os.MkdirAll(folder, 0o755)
|
||||
}
|
||||
|
||||
func loadUserConfigWithDefaults(configFiles []*ConfigFile) (*UserConfig, error) {
|
||||
return loadUserConfig(configFiles, GetDefaultConfig())
|
||||
func loadUserConfigWithDefaults(configFiles []*ConfigFile, isGuiInitialized bool) (*UserConfig, error) {
|
||||
return loadUserConfig(configFiles, GetDefaultConfig(), isGuiInitialized)
|
||||
}
|
||||
|
||||
func loadUserConfig(configFiles []*ConfigFile, base *UserConfig) (*UserConfig, error) {
|
||||
func loadUserConfig(configFiles []*ConfigFile, base *UserConfig, isGuiInitialized bool) (*UserConfig, error) {
|
||||
for _, configFile := range configFiles {
|
||||
path := configFile.Path
|
||||
statInfo, err := os.Stat(path)
|
||||
@@ -192,7 +195,7 @@ func loadUserConfig(configFiles []*ConfigFile, base *UserConfig) (*UserConfig, e
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content, err = migrateUserConfig(path, content)
|
||||
content, err = migrateUserConfig(path, content, isGuiInitialized)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -213,41 +216,64 @@ func loadUserConfig(configFiles []*ConfigFile, base *UserConfig) (*UserConfig, e
|
||||
return base, nil
|
||||
}
|
||||
|
||||
type ChangesSet = orderedset.OrderedSet[string]
|
||||
|
||||
func NewChangesSet() *ChangesSet {
|
||||
return orderedset.New[string]()
|
||||
}
|
||||
|
||||
// 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 := computeMigratedConfig(path, content)
|
||||
func migrateUserConfig(path string, content []byte, isGuiInitialized bool) ([]byte, error) {
|
||||
changes := NewChangesSet()
|
||||
|
||||
changedContent, didChange, err := computeMigratedConfig(path, content, changes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Write config back if changed
|
||||
if string(changedContent) != string(content) {
|
||||
fmt.Println("Provided user config is deprecated but auto-fixable. Attempting to write fixed version back to file...")
|
||||
if err := os.WriteFile(path, changedContent, 0o644); err != nil {
|
||||
return nil, fmt.Errorf("While attempting to write back fixed user config to %s, an error occurred: %s", path, err)
|
||||
}
|
||||
fmt.Printf("Success. New config written to %s\n", path)
|
||||
return changedContent, nil
|
||||
// Nothing to do if config didn't change
|
||||
if !didChange {
|
||||
return content, nil
|
||||
}
|
||||
|
||||
return content, nil
|
||||
changesText := "The following changes were made:\n\n"
|
||||
changesText += strings.Join(lo.Map(changes.ToSliceFromOldest(), func(change string, _ int) string {
|
||||
return fmt.Sprintf("- %s\n", change)
|
||||
}), "")
|
||||
|
||||
// Write config back
|
||||
if !isGuiInitialized {
|
||||
fmt.Printf("The user config file %s must be migrated. Attempting to do this automatically.\n", path)
|
||||
fmt.Println(changesText)
|
||||
}
|
||||
if err := os.WriteFile(path, changedContent, 0o644); err != nil {
|
||||
errorMsg := fmt.Sprintf("While attempting to write back migrated user config to %s, an error occurred: %s", path, err)
|
||||
if isGuiInitialized {
|
||||
errorMsg += "\n\n" + changesText
|
||||
}
|
||||
return nil, errors.New(errorMsg)
|
||||
}
|
||||
if !isGuiInitialized {
|
||||
fmt.Printf("Config file saved successfully to %s\n", path)
|
||||
}
|
||||
return changedContent, nil
|
||||
}
|
||||
|
||||
// A pure function helper for testing purposes
|
||||
func computeMigratedConfig(path string, content []byte) ([]byte, error) {
|
||||
func computeMigratedConfig(path string, content []byte, changes *ChangesSet) ([]byte, bool, error) {
|
||||
var err error
|
||||
var rootNode yaml.Node
|
||||
err = yaml.Unmarshal(content, &rootNode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse YAML: %w", err)
|
||||
return nil, false, fmt.Errorf("failed to parse YAML: %w", err)
|
||||
}
|
||||
var originalCopy yaml.Node
|
||||
err = yaml.Unmarshal(content, &originalCopy)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse YAML, but only the second time!?!? How did that happen: %w", err)
|
||||
return nil, false, fmt.Errorf("failed to parse YAML, but only the second time!?!? How did that happen: %w", err)
|
||||
}
|
||||
|
||||
pathsToReplace := []struct {
|
||||
@@ -260,50 +286,64 @@ func computeMigratedConfig(path string, content []byte) ([]byte, error) {
|
||||
}
|
||||
|
||||
for _, pathToReplace := range pathsToReplace {
|
||||
err := yaml_utils.RenameYamlKey(&rootNode, pathToReplace.oldPath, pathToReplace.newName)
|
||||
err, didReplace := yaml_utils.RenameYamlKey(&rootNode, pathToReplace.oldPath, pathToReplace.newName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't migrate config file at `%s` for key %s: %s", path, strings.Join(pathToReplace.oldPath, "."), err)
|
||||
return nil, false, fmt.Errorf("Couldn't migrate config file at `%s` for key %s: %w", path, strings.Join(pathToReplace.oldPath, "."), err)
|
||||
}
|
||||
if didReplace {
|
||||
changes.Add(fmt.Sprintf("Renamed '%s' to '%s'", strings.Join(pathToReplace.oldPath, "."), pathToReplace.newName))
|
||||
}
|
||||
}
|
||||
|
||||
err = changeNullKeybindingsToDisabled(&rootNode)
|
||||
err = changeNullKeybindingsToDisabled(&rootNode, changes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
|
||||
return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err)
|
||||
}
|
||||
|
||||
err = changeElementToSequence(&rootNode, []string{"git", "commitPrefix"})
|
||||
err = changeElementToSequence(&rootNode, []string{"git", "commitPrefix"}, changes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
|
||||
return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err)
|
||||
}
|
||||
|
||||
err = changeCommitPrefixesMap(&rootNode)
|
||||
err = changeCommitPrefixesMap(&rootNode, changes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
|
||||
return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err)
|
||||
}
|
||||
|
||||
err = changeCustomCommandStreamAndOutputToOutputEnum(&rootNode, changes)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err)
|
||||
}
|
||||
|
||||
err = migrateAllBranchesLogCmd(&rootNode, changes)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("Couldn't migrate config file at `%s`: %w", path, err)
|
||||
}
|
||||
|
||||
// Add more migrations here...
|
||||
|
||||
if !reflect.DeepEqual(rootNode, originalCopy) {
|
||||
newContent, err := yaml_utils.YamlMarshal(&rootNode)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to remarsal!\n %w", err)
|
||||
}
|
||||
return newContent, nil
|
||||
} else {
|
||||
return content, nil
|
||||
if reflect.DeepEqual(rootNode, originalCopy) {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
newContent, err := yaml_utils.YamlMarshal(&rootNode)
|
||||
if err != nil {
|
||||
return nil, false, fmt.Errorf("Failed to remarsal!\n %w", err)
|
||||
}
|
||||
return newContent, true, nil
|
||||
}
|
||||
|
||||
func changeNullKeybindingsToDisabled(rootNode *yaml.Node) error {
|
||||
func changeNullKeybindingsToDisabled(rootNode *yaml.Node, changes *ChangesSet) error {
|
||||
return yaml_utils.Walk(rootNode, func(node *yaml.Node, path string) {
|
||||
if strings.HasPrefix(path, "keybinding.") && node.Kind == yaml.ScalarNode && node.Tag == "!!null" {
|
||||
node.Value = "<disabled>"
|
||||
node.Tag = "!!str"
|
||||
changes.Add(fmt.Sprintf("Changed 'null' to '<disabled>' for keybinding '%s'", path))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func changeElementToSequence(rootNode *yaml.Node, path []string) error {
|
||||
func changeElementToSequence(rootNode *yaml.Node, path []string, changes *ChangesSet) error {
|
||||
return yaml_utils.TransformNode(rootNode, path, func(node *yaml.Node) error {
|
||||
if node.Kind == yaml.MappingNode {
|
||||
nodeContentCopy := node.Content
|
||||
@@ -315,13 +355,15 @@ func changeElementToSequence(rootNode *yaml.Node, path []string) error {
|
||||
Content: nodeContentCopy,
|
||||
}}
|
||||
|
||||
changes.Add(fmt.Sprintf("Changed '%s' to an array of strings", strings.Join(path, ".")))
|
||||
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func changeCommitPrefixesMap(rootNode *yaml.Node) error {
|
||||
func changeCommitPrefixesMap(rootNode *yaml.Node, changes *ChangesSet) error {
|
||||
return yaml_utils.TransformNode(rootNode, []string{"git", "commitPrefixes"}, func(prefixesNode *yaml.Node) error {
|
||||
if prefixesNode.Kind == yaml.MappingNode {
|
||||
for _, contentNode := range prefixesNode.Content {
|
||||
@@ -334,6 +376,7 @@ func changeCommitPrefixesMap(rootNode *yaml.Node) error {
|
||||
Kind: yaml.MappingNode,
|
||||
Content: nodeContentCopy,
|
||||
}}
|
||||
changes.Add("Changed 'git.commitPrefixes' elements to arrays of strings")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -341,6 +384,101 @@ func changeCommitPrefixesMap(rootNode *yaml.Node) error {
|
||||
})
|
||||
}
|
||||
|
||||
func changeCustomCommandStreamAndOutputToOutputEnum(rootNode *yaml.Node, changes *ChangesSet) error {
|
||||
return yaml_utils.Walk(rootNode, func(node *yaml.Node, path string) {
|
||||
// We are being lazy here and rely on the fact that the only mapping
|
||||
// nodes in the tree under customCommands are actual custom commands. If
|
||||
// this ever changes (e.g. because we add a struct field to
|
||||
// customCommand), then we need to change this to iterate properly.
|
||||
if strings.HasPrefix(path, "customCommands[") && node.Kind == yaml.MappingNode {
|
||||
output := ""
|
||||
if streamKey, streamValue := yaml_utils.RemoveKey(node, "subprocess"); streamKey != nil {
|
||||
if streamValue.Kind == yaml.ScalarNode && streamValue.Value == "true" {
|
||||
output = "terminal"
|
||||
changes.Add("Changed 'subprocess: true' to 'output: terminal' in custom command")
|
||||
} else {
|
||||
changes.Add("Deleted redundant 'subprocess: false' in custom command")
|
||||
}
|
||||
}
|
||||
if streamKey, streamValue := yaml_utils.RemoveKey(node, "stream"); streamKey != nil {
|
||||
if streamValue.Kind == yaml.ScalarNode && streamValue.Value == "true" && output == "" {
|
||||
output = "log"
|
||||
changes.Add("Changed 'stream: true' to 'output: log' in custom command")
|
||||
} else {
|
||||
changes.Add(fmt.Sprintf("Deleted redundant 'stream: %v' property in custom command", streamValue.Value))
|
||||
}
|
||||
}
|
||||
if streamKey, streamValue := yaml_utils.RemoveKey(node, "showOutput"); streamKey != nil {
|
||||
if streamValue.Kind == yaml.ScalarNode && streamValue.Value == "true" && output == "" {
|
||||
changes.Add("Changed 'showOutput: true' to 'output: popup' in custom command")
|
||||
output = "popup"
|
||||
} else {
|
||||
changes.Add(fmt.Sprintf("Deleted redundant 'showOutput: %v' property in custom command", streamValue.Value))
|
||||
}
|
||||
}
|
||||
if output != "" {
|
||||
outputKeyNode := &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Value: "output",
|
||||
Tag: "!!str",
|
||||
}
|
||||
outputValueNode := &yaml.Node{
|
||||
Kind: yaml.ScalarNode,
|
||||
Value: output,
|
||||
Tag: "!!str",
|
||||
}
|
||||
node.Content = append(node.Content, outputKeyNode, outputValueNode)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// This migration is special because users have already defined
|
||||
// a single element at `allBranchesLogCmd` and the sequence at `allBranchesLogCmds`.
|
||||
// Some users have explicitly set `allBranchesLogCmd` to be an empty string in order
|
||||
// to remove it, so in that case we just delete the element, and add nothing to the list
|
||||
func migrateAllBranchesLogCmd(rootNode *yaml.Node, changes *ChangesSet) error {
|
||||
return yaml_utils.TransformNode(rootNode, []string{"git"}, func(gitNode *yaml.Node) error {
|
||||
cmdKeyNode, cmdValueNode := yaml_utils.LookupKey(gitNode, "allBranchesLogCmd")
|
||||
// Nothing to do if they do not have the deprecated item
|
||||
if cmdKeyNode == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
cmdsKeyNode, cmdsValueNode := yaml_utils.LookupKey(gitNode, "allBranchesLogCmds")
|
||||
var change string
|
||||
if cmdsKeyNode == nil {
|
||||
// Create empty sequence node and attach it onto the root git node
|
||||
// We will later populate it with the individual allBranchesLogCmd record
|
||||
cmdsKeyNode = &yaml.Node{Kind: yaml.ScalarNode, Value: "allBranchesLogCmds"}
|
||||
cmdsValueNode = &yaml.Node{Kind: yaml.SequenceNode, Content: []*yaml.Node{}}
|
||||
gitNode.Content = append(gitNode.Content,
|
||||
cmdsKeyNode,
|
||||
cmdsValueNode,
|
||||
)
|
||||
change = "Created git.allBranchesLogCmds array containing value of git.allBranchesLogCmd"
|
||||
} else {
|
||||
if cmdsValueNode.Kind != yaml.SequenceNode {
|
||||
return errors.New("You should have an allBranchesLogCmds defined as a sequence!")
|
||||
}
|
||||
|
||||
change = "Prepended git.allBranchesLogCmd value to git.allBranchesLogCmds array"
|
||||
}
|
||||
|
||||
if cmdValueNode.Value != "" {
|
||||
// Prepending the individual element to make it show up first in the list, which was prior behavior
|
||||
cmdsValueNode.Content = utils.Prepend(cmdsValueNode.Content, &yaml.Node{Kind: yaml.ScalarNode, Value: cmdValueNode.Value})
|
||||
changes.Add(change)
|
||||
}
|
||||
|
||||
// Clear out the existing allBranchesLogCmd, now that we have migrated it into the list
|
||||
_, _ = yaml_utils.RemoveKey(gitNode, "allBranchesLogCmd")
|
||||
changes.Add("Removed obsolete git.allBranchesLogCmd")
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (c *AppConfig) GetDebug() bool {
|
||||
return c.debug
|
||||
}
|
||||
@@ -381,7 +519,7 @@ func (c *AppConfig) GetUserConfigDir() string {
|
||||
|
||||
func (c *AppConfig) ReloadUserConfigForRepo(repoConfigFiles []*ConfigFile) error {
|
||||
configFiles := append(c.globalUserConfigFiles, repoConfigFiles...)
|
||||
userConfig, err := loadUserConfigWithDefaults(configFiles)
|
||||
userConfig, err := loadUserConfigWithDefaults(configFiles, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -406,7 +544,7 @@ func (c *AppConfig) ReloadChangedUserConfigFiles() (error, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
userConfig, err := loadUserConfigWithDefaults(c.userConfigFiles)
|
||||
userConfig, err := loadUserConfigWithDefaults(c.userConfigFiles, true)
|
||||
if err != nil {
|
||||
return err, false
|
||||
}
|
||||
|
||||
+425
-31
@@ -6,17 +6,165 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCommitPrefixMigrations(t *testing.T) {
|
||||
func TestMigrationOfRenamedKeys(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
expectedDidChange bool
|
||||
expectedChanges []string
|
||||
}{
|
||||
{
|
||||
name: "Empty String",
|
||||
input: "",
|
||||
expected: "",
|
||||
}, {
|
||||
name: "Empty String",
|
||||
input: "",
|
||||
expectedDidChange: false,
|
||||
expectedChanges: []string{},
|
||||
},
|
||||
{
|
||||
name: "No rename needed",
|
||||
input: `foo:
|
||||
bar: 5
|
||||
`,
|
||||
expectedDidChange: false,
|
||||
expectedChanges: []string{},
|
||||
},
|
||||
{
|
||||
name: "Rename one",
|
||||
input: `gui:
|
||||
skipUnstageLineWarning: true
|
||||
`,
|
||||
expected: `gui:
|
||||
skipDiscardChangeWarning: true
|
||||
`,
|
||||
expectedDidChange: true,
|
||||
expectedChanges: []string{"Renamed 'gui.skipUnstageLineWarning' to 'skipDiscardChangeWarning'"},
|
||||
},
|
||||
{
|
||||
name: "Rename several",
|
||||
input: `gui:
|
||||
windowSize: half
|
||||
skipUnstageLineWarning: true
|
||||
keybinding:
|
||||
universal:
|
||||
executeCustomCommand: a
|
||||
`,
|
||||
expected: `gui:
|
||||
screenMode: half
|
||||
skipDiscardChangeWarning: true
|
||||
keybinding:
|
||||
universal:
|
||||
executeShellCommand: a
|
||||
`,
|
||||
expectedDidChange: true,
|
||||
expectedChanges: []string{
|
||||
"Renamed 'gui.skipUnstageLineWarning' to 'skipDiscardChangeWarning'",
|
||||
"Renamed 'keybinding.universal.executeCustomCommand' to 'executeShellCommand'",
|
||||
"Renamed 'gui.windowSize' to 'screenMode'",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
changes := NewChangesSet()
|
||||
actual, didChange, err := computeMigratedConfig("path doesn't matter", []byte(s.input), changes)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, s.expectedDidChange, didChange)
|
||||
if didChange {
|
||||
assert.Equal(t, s.expected, string(actual))
|
||||
}
|
||||
assert.Equal(t, s.expectedChanges, changes.ToSliceFromOldest())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMigrateNullKeybindingsToDisabled(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
expectedDidChange bool
|
||||
expectedChanges []string
|
||||
}{
|
||||
{
|
||||
name: "Empty String",
|
||||
input: "",
|
||||
expectedDidChange: false,
|
||||
expectedChanges: []string{},
|
||||
},
|
||||
{
|
||||
name: "No change needed",
|
||||
input: `keybinding:
|
||||
universal:
|
||||
quit: q
|
||||
`,
|
||||
expectedDidChange: false,
|
||||
expectedChanges: []string{},
|
||||
},
|
||||
{
|
||||
name: "Change one",
|
||||
input: `keybinding:
|
||||
universal:
|
||||
quit: null
|
||||
`,
|
||||
expected: `keybinding:
|
||||
universal:
|
||||
quit: <disabled>
|
||||
`,
|
||||
expectedDidChange: true,
|
||||
expectedChanges: []string{"Changed 'null' to '<disabled>' for keybinding 'keybinding.universal.quit'"},
|
||||
},
|
||||
{
|
||||
name: "Change several",
|
||||
input: `keybinding:
|
||||
universal:
|
||||
quit: null
|
||||
return: <esc>
|
||||
new: null
|
||||
`,
|
||||
expected: `keybinding:
|
||||
universal:
|
||||
quit: <disabled>
|
||||
return: <esc>
|
||||
new: <disabled>
|
||||
`,
|
||||
expectedDidChange: true,
|
||||
expectedChanges: []string{
|
||||
"Changed 'null' to '<disabled>' for keybinding 'keybinding.universal.quit'",
|
||||
"Changed 'null' to '<disabled>' for keybinding 'keybinding.universal.new'",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
changes := NewChangesSet()
|
||||
actual, didChange, err := computeMigratedConfig("path doesn't matter", []byte(s.input), changes)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, s.expectedDidChange, didChange)
|
||||
if didChange {
|
||||
assert.Equal(t, s.expected, string(actual))
|
||||
}
|
||||
assert.Equal(t, s.expectedChanges, changes.ToSliceFromOldest())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitPrefixMigrations(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
expectedDidChange bool
|
||||
expectedChanges []string
|
||||
}{
|
||||
{
|
||||
name: "Empty String",
|
||||
input: "",
|
||||
expectedDidChange: false,
|
||||
expectedChanges: []string{},
|
||||
},
|
||||
{
|
||||
name: "Single CommitPrefix Rename",
|
||||
input: `git:
|
||||
commitPrefix:
|
||||
@@ -28,7 +176,10 @@ func TestCommitPrefixMigrations(t *testing.T) {
|
||||
- pattern: "^\\w+-\\w+.*"
|
||||
replace: '[JIRA $0] '
|
||||
`,
|
||||
}, {
|
||||
expectedDidChange: true,
|
||||
expectedChanges: []string{"Changed 'git.commitPrefix' to an array of strings"},
|
||||
},
|
||||
{
|
||||
name: "Complicated CommitPrefixes Rename",
|
||||
input: `git:
|
||||
commitPrefixes:
|
||||
@@ -48,13 +199,16 @@ func TestCommitPrefixMigrations(t *testing.T) {
|
||||
- pattern: "^foo.bar*"
|
||||
replace: '[FUN $0] '
|
||||
`,
|
||||
}, {
|
||||
name: "Incomplete Configuration",
|
||||
input: "git:",
|
||||
expected: "git:",
|
||||
}, {
|
||||
// This test intentionally uses non-standard indentation to test that the migration
|
||||
// does not change the input.
|
||||
expectedDidChange: true,
|
||||
expectedChanges: []string{"Changed 'git.commitPrefixes' elements to arrays of strings"},
|
||||
},
|
||||
{
|
||||
name: "Incomplete Configuration",
|
||||
input: "git:",
|
||||
expectedDidChange: false,
|
||||
expectedChanges: []string{},
|
||||
},
|
||||
{
|
||||
name: "No changes made when already migrated",
|
||||
input: `
|
||||
git:
|
||||
@@ -65,25 +219,144 @@ git:
|
||||
foo:
|
||||
- pattern: "^\\w+-\\w+.*"
|
||||
replace: '[JIRA $0] '`,
|
||||
expected: `
|
||||
git:
|
||||
commitPrefix:
|
||||
- pattern: "Hello World"
|
||||
replace: "Goodbye"
|
||||
commitPrefixes:
|
||||
foo:
|
||||
- pattern: "^\\w+-\\w+.*"
|
||||
replace: '[JIRA $0] '`,
|
||||
expectedDidChange: false,
|
||||
expectedChanges: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
actual, err := computeMigratedConfig("path doesn't matter", []byte(s.input))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
changes := NewChangesSet()
|
||||
actual, didChange, err := computeMigratedConfig("path doesn't matter", []byte(s.input), changes)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, s.expectedDidChange, didChange)
|
||||
if didChange {
|
||||
assert.Equal(t, s.expected, string(actual))
|
||||
}
|
||||
assert.Equal(t, s.expected, string(actual))
|
||||
assert.Equal(t, s.expectedChanges, changes.ToSliceFromOldest())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCustomCommandsOutputMigration(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
expectedDidChange bool
|
||||
expectedChanges []string
|
||||
}{
|
||||
{
|
||||
name: "Empty String",
|
||||
input: "",
|
||||
expectedDidChange: false,
|
||||
expectedChanges: []string{},
|
||||
},
|
||||
{
|
||||
name: "Convert subprocess to output=terminal",
|
||||
input: `customCommands:
|
||||
- command: echo 'hello'
|
||||
subprocess: true
|
||||
`,
|
||||
expected: `customCommands:
|
||||
- command: echo 'hello'
|
||||
output: terminal
|
||||
`,
|
||||
expectedDidChange: true,
|
||||
expectedChanges: []string{"Changed 'subprocess: true' to 'output: terminal' in custom command"},
|
||||
},
|
||||
{
|
||||
name: "Convert stream to output=log",
|
||||
input: `customCommands:
|
||||
- command: echo 'hello'
|
||||
stream: true
|
||||
`,
|
||||
expected: `customCommands:
|
||||
- command: echo 'hello'
|
||||
output: log
|
||||
`,
|
||||
expectedDidChange: true,
|
||||
expectedChanges: []string{"Changed 'stream: true' to 'output: log' in custom command"},
|
||||
},
|
||||
{
|
||||
name: "Convert showOutput to output=popup",
|
||||
input: `customCommands:
|
||||
- command: echo 'hello'
|
||||
showOutput: true
|
||||
`,
|
||||
expected: `customCommands:
|
||||
- command: echo 'hello'
|
||||
output: popup
|
||||
`,
|
||||
expectedDidChange: true,
|
||||
expectedChanges: []string{"Changed 'showOutput: true' to 'output: popup' in custom command"},
|
||||
},
|
||||
{
|
||||
name: "Subprocess wins over the other two",
|
||||
input: `customCommands:
|
||||
- command: echo 'hello'
|
||||
subprocess: true
|
||||
stream: true
|
||||
showOutput: true
|
||||
`,
|
||||
expected: `customCommands:
|
||||
- command: echo 'hello'
|
||||
output: terminal
|
||||
`,
|
||||
expectedDidChange: true,
|
||||
expectedChanges: []string{
|
||||
"Changed 'subprocess: true' to 'output: terminal' in custom command",
|
||||
"Deleted redundant 'stream: true' property in custom command",
|
||||
"Deleted redundant 'showOutput: true' property in custom command",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Stream wins over showOutput",
|
||||
input: `customCommands:
|
||||
- command: echo 'hello'
|
||||
stream: true
|
||||
showOutput: true
|
||||
`,
|
||||
expected: `customCommands:
|
||||
- command: echo 'hello'
|
||||
output: log
|
||||
`,
|
||||
expectedDidChange: true,
|
||||
expectedChanges: []string{
|
||||
"Changed 'stream: true' to 'output: log' in custom command",
|
||||
"Deleted redundant 'showOutput: true' property in custom command",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Explicitly setting to false doesn't create an output=none key",
|
||||
input: `customCommands:
|
||||
- command: echo 'hello'
|
||||
subprocess: false
|
||||
stream: false
|
||||
showOutput: false
|
||||
`,
|
||||
expected: `customCommands:
|
||||
- command: echo 'hello'
|
||||
`,
|
||||
expectedDidChange: true,
|
||||
expectedChanges: []string{
|
||||
"Deleted redundant 'subprocess: false' in custom command",
|
||||
"Deleted redundant 'stream: false' property in custom command",
|
||||
"Deleted redundant 'showOutput: false' property in custom command",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
changes := NewChangesSet()
|
||||
actual, didChange, err := computeMigratedConfig("path doesn't matter", []byte(s.input), changes)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, s.expectedDidChange, didChange)
|
||||
if didChange {
|
||||
assert.Equal(t, s.expected, string(actual))
|
||||
}
|
||||
assert.Equal(t, s.expectedChanges, changes.ToSliceFromOldest())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -111,6 +384,9 @@ gui:
|
||||
# When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS.
|
||||
mouseEvents: true
|
||||
|
||||
# If true, do not show a warning when amending a commit.
|
||||
skipAmendWarning: false
|
||||
|
||||
# If true, do not show a warning when discarding changes in the staging view.
|
||||
skipDiscardChangeWarning: false
|
||||
|
||||
@@ -284,7 +560,7 @@ gui:
|
||||
screenMode: normal
|
||||
|
||||
# Window border style.
|
||||
# One of 'rounded' (default) | 'single' | 'double' | 'hidden'
|
||||
# One of 'rounded' (default) | 'single' | 'double' | 'hidden' | 'bold'
|
||||
border: rounded
|
||||
|
||||
# If true, show a seriously epic explosion animation when nuking the working tree.
|
||||
@@ -692,6 +968,124 @@ keybinding:
|
||||
|
||||
func BenchmarkMigrationOnLargeConfiguration(b *testing.B) {
|
||||
for b.Loop() {
|
||||
_, _ = computeMigratedConfig("path doesn't matter", largeConfiguration)
|
||||
changes := NewChangesSet()
|
||||
_, _, _ = computeMigratedConfig("path doesn't matter", largeConfiguration, changes)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllBranchesLogCmdMigrations(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
expectedDidChange bool
|
||||
expectedChanges []string
|
||||
}{
|
||||
{
|
||||
name: "Incomplete Configuration Passes uneventfully",
|
||||
input: "git:",
|
||||
expectedDidChange: false,
|
||||
expectedChanges: []string{},
|
||||
},
|
||||
{
|
||||
name: "Single Cmd with no Cmds",
|
||||
input: `git:
|
||||
allBranchesLogCmd: git log --graph --oneline
|
||||
`,
|
||||
expected: `git:
|
||||
allBranchesLogCmds:
|
||||
- git log --graph --oneline
|
||||
`,
|
||||
expectedDidChange: true,
|
||||
expectedChanges: []string{
|
||||
"Created git.allBranchesLogCmds array containing value of git.allBranchesLogCmd",
|
||||
"Removed obsolete git.allBranchesLogCmd",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Cmd with one existing Cmds",
|
||||
input: `git:
|
||||
allBranchesLogCmd: git log --graph --oneline
|
||||
allBranchesLogCmds:
|
||||
- git log --graph --oneline --pretty
|
||||
`,
|
||||
expected: `git:
|
||||
allBranchesLogCmds:
|
||||
- git log --graph --oneline
|
||||
- git log --graph --oneline --pretty
|
||||
`,
|
||||
expectedDidChange: true,
|
||||
expectedChanges: []string{
|
||||
"Prepended git.allBranchesLogCmd value to git.allBranchesLogCmds array",
|
||||
"Removed obsolete git.allBranchesLogCmd",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Only Cmds set have no changes",
|
||||
input: `git:
|
||||
allBranchesLogCmds:
|
||||
- git log
|
||||
`,
|
||||
expected: "",
|
||||
expectedChanges: []string{},
|
||||
},
|
||||
{
|
||||
name: "Removes Empty Cmd When at end of yaml",
|
||||
input: `git:
|
||||
allBranchesLogCmds:
|
||||
- git log --graph --oneline
|
||||
allBranchesLogCmd:
|
||||
`,
|
||||
expected: `git:
|
||||
allBranchesLogCmds:
|
||||
- git log --graph --oneline
|
||||
`,
|
||||
expectedDidChange: true,
|
||||
expectedChanges: []string{"Removed obsolete git.allBranchesLogCmd"},
|
||||
},
|
||||
{
|
||||
name: "Migrates when sequence defined inline",
|
||||
input: `git:
|
||||
allBranchesLogCmds: [foo, bar]
|
||||
allBranchesLogCmd: baz
|
||||
`,
|
||||
expected: `git:
|
||||
allBranchesLogCmds: [baz, foo, bar]
|
||||
`,
|
||||
expectedDidChange: true,
|
||||
expectedChanges: []string{
|
||||
"Prepended git.allBranchesLogCmd value to git.allBranchesLogCmds array",
|
||||
"Removed obsolete git.allBranchesLogCmd",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Removes Empty Cmd With Keys Afterwards",
|
||||
input: `git:
|
||||
allBranchesLogCmds:
|
||||
- git log --graph --oneline
|
||||
allBranchesLogCmd:
|
||||
foo: bar
|
||||
`,
|
||||
expected: `git:
|
||||
allBranchesLogCmds:
|
||||
- git log --graph --oneline
|
||||
foo: bar
|
||||
`,
|
||||
expectedDidChange: true,
|
||||
expectedChanges: []string{"Removed obsolete git.allBranchesLogCmd"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
changes := NewChangesSet()
|
||||
actual, didChange, err := computeMigratedConfig("path doesn't matter", []byte(s.input), changes)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, s.expectedDidChange, didChange)
|
||||
if didChange {
|
||||
assert.Equal(t, s.expected, string(actual))
|
||||
}
|
||||
assert.Equal(t, s.expectedChanges, changes.ToSliceFromOldest())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+18
-17
@@ -74,6 +74,8 @@ type GuiConfig struct {
|
||||
// If true, capture mouse events.
|
||||
// When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS.
|
||||
MouseEvents bool `yaml:"mouseEvents"`
|
||||
// If true, do not show a warning when amending a commit.
|
||||
SkipAmendWarning bool `yaml:"skipAmendWarning"`
|
||||
// If true, do not show a warning when discarding changes in the staging view.
|
||||
SkipDiscardChangeWarning bool `yaml:"skipDiscardChangeWarning"`
|
||||
// If true, do not show warning when applying/popping the stash
|
||||
@@ -105,6 +107,8 @@ type GuiConfig struct {
|
||||
// makes it much easier to work with diffs that have long lines, e.g.
|
||||
// paragraphs of markdown text.
|
||||
WrapLinesInStagingView bool `yaml:"wrapLinesInStagingView"`
|
||||
// If true, hunk selection mode will be enabled by default when entering the staging view.
|
||||
UseHunkModeInStagingView bool `yaml:"useHunkModeInStagingView"`
|
||||
// One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
|
||||
Language string `yaml:"language" jsonschema:"enum=auto,enum=en,enum=zh-TW,enum=zh-CN,enum=pl,enum=nl,enum=ja,enum=ko,enum=ru"`
|
||||
// Format used when displaying time e.g. commit time.
|
||||
@@ -123,6 +127,8 @@ type GuiConfig struct {
|
||||
// If true, display the files in the file views as a tree. If false, display the files as a flat list.
|
||||
// This can be toggled from within Lazygit with the '`' key, but that will not change the default.
|
||||
ShowFileTree bool `yaml:"showFileTree"`
|
||||
// If true, add a "/" root item in the file tree representing the root of the repository. It is only added when necessary, i.e. when there is more than one item at top level.
|
||||
ShowRootItemInFileTree bool `yaml:"showRootItemInFileTree"`
|
||||
// If true, show the number of lines changed per file in the Files view
|
||||
ShowNumstatInFilesView bool `yaml:"showNumstatInFilesView"`
|
||||
// If true, show a random tip in the command log when Lazygit starts
|
||||
@@ -162,8 +168,8 @@ type GuiConfig struct {
|
||||
// One of: 'normal' (default) | 'half' | 'full'
|
||||
ScreenMode string `yaml:"screenMode" jsonschema:"enum=normal,enum=half,enum=full"`
|
||||
// Window border style.
|
||||
// One of 'rounded' (default) | 'single' | 'double' | 'hidden'
|
||||
Border string `yaml:"border" jsonschema:"enum=single,enum=double,enum=rounded,enum=hidden"`
|
||||
// One of 'rounded' (default) | 'single' | 'double' | 'hidden' | 'bold'
|
||||
Border string `yaml:"border" jsonschema:"enum=single,enum=double,enum=rounded,enum=hidden,enum=bold"`
|
||||
// If true, show a seriously epic explosion animation when nuking the working tree.
|
||||
AnimateExplosion bool `yaml:"animateExplosion"`
|
||||
// Whether to stack UI components on top of each other.
|
||||
@@ -244,7 +250,7 @@ type GitConfig struct {
|
||||
AutoFetch bool `yaml:"autoFetch"`
|
||||
// If true, periodically refresh files and submodules
|
||||
AutoRefresh bool `yaml:"autoRefresh"`
|
||||
// If not "none", lazygit will automatically forward branches to their upstream after fetching. Applies to branches that are not the currently checked out branch, and only to those that are strictly behind their upstream (as opposed to diverged).
|
||||
// If not "none", lazygit will automatically fast-forward local branches to match their upstream after fetching. Applies to branches that are not the currently checked out branch, and only to those that are strictly behind their upstream (as opposed to diverged).
|
||||
// Possible values: 'none' | 'onlyMainBranches' | 'allBranches'
|
||||
AutoForwardBranches string `yaml:"autoForwardBranches" jsonschema:"enum=none,enum=onlyMainBranches,enum=allBranches"`
|
||||
// If true, pass the --all arg to git fetch
|
||||
@@ -256,9 +262,6 @@ type GitConfig struct {
|
||||
AutoStageResolvedConflicts bool `yaml:"autoStageResolvedConflicts"`
|
||||
// Command used when displaying the current branch git log in the main window
|
||||
BranchLogCmd string `yaml:"branchLogCmd"`
|
||||
// Command used to display git log of all branches in the main window.
|
||||
// Deprecated: Use `allBranchesLogCmds` instead.
|
||||
AllBranchesLogCmd string `yaml:"allBranchesLogCmd" jsonschema:"deprecated"`
|
||||
// Commands used to display git log of all branches in the main window, they will be cycled in order of appearance (array of strings)
|
||||
AllBranchesLogCmds []string `yaml:"allBranchesLogCmds"`
|
||||
// If true, do not spawn a separate process when using GPG
|
||||
@@ -408,6 +411,7 @@ type KeybindingUniversalConfig struct {
|
||||
GoInto string `yaml:"goInto"`
|
||||
Confirm string `yaml:"confirm"`
|
||||
ConfirmInEditor string `yaml:"confirmInEditor"`
|
||||
ConfirmInEditorAlt string `yaml:"confirmInEditor-alt"`
|
||||
Remove string `yaml:"remove"`
|
||||
New string `yaml:"new"`
|
||||
Edit string `yaml:"edit"`
|
||||
@@ -637,22 +641,15 @@ type CustomCommand struct {
|
||||
Context string `yaml:"context" jsonschema:"example=status,example=files,example=worktrees,example=localBranches,example=remotes,example=remoteBranches,example=tags,example=commits,example=reflogCommits,example=subCommits,example=commitFiles,example=stash,example=global"`
|
||||
// The command to run (using Go template syntax for placeholder values)
|
||||
Command string `yaml:"command" jsonschema:"example=git fetch {{.Form.Remote}} {{.Form.Branch}} && git checkout FETCH_HEAD"`
|
||||
// If true, run the command in a subprocess (e.g. if the command requires user input)
|
||||
// [dev] Pointer to bool so that we can distinguish unset (nil) from false.
|
||||
Subprocess *bool `yaml:"subprocess"`
|
||||
// A list of prompts that will request user input before running the final command
|
||||
Prompts []CustomCommandPrompt `yaml:"prompts"`
|
||||
// Text to display while waiting for command to finish
|
||||
LoadingText string `yaml:"loadingText" jsonschema:"example=Loading..."`
|
||||
// Label for the custom command when displayed in the keybindings menu
|
||||
Description string `yaml:"description"`
|
||||
// If true, stream the command's output to the Command Log panel
|
||||
// [dev] Pointer to bool so that we can distinguish unset (nil) from false.
|
||||
Stream *bool `yaml:"stream"`
|
||||
// If true, show the command's output in a popup within Lazygit
|
||||
// [dev] Pointer to bool so that we can distinguish unset (nil) from false.
|
||||
ShowOutput *bool `yaml:"showOutput"`
|
||||
// The title to display in the popup panel if showOutput is true. If left unset, the command will be used as the title.
|
||||
// Where the output of the command should go. 'none' discards it, 'terminal' suspends lazygit and runs the command in the terminal (useful for commands that require user input), 'log' streams it to the command log, 'logWithPty' is like 'log' but runs the command in a pseudo terminal (can be useful for commands that produce colored output when the output is a terminal), and 'popup' shows it in a popup.
|
||||
Output string `yaml:"output" jsonschema:"enum=none,enum=terminal,enum=log,enum=logWithPty,enum=popup"`
|
||||
// The title to display in the popup panel if output is set to 'popup'. If left unset, the command will be used as the title.
|
||||
OutputTitle string `yaml:"outputTitle"`
|
||||
// Actions to take after the command has completed
|
||||
// [dev] Pointer so that we can tell whether it appears in the config file
|
||||
@@ -741,6 +738,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
ScrollOffBehavior: "margin",
|
||||
TabWidth: 4,
|
||||
MouseEvents: true,
|
||||
SkipAmendWarning: false,
|
||||
SkipDiscardChangeWarning: false,
|
||||
SkipStashWarning: false,
|
||||
SidePanelWidth: 0.3333,
|
||||
@@ -749,6 +747,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
MainPanelSplitMode: "flexible",
|
||||
EnlargedSideViewLocation: "left",
|
||||
WrapLinesInStagingView: true,
|
||||
UseHunkModeInStagingView: false,
|
||||
Language: "auto",
|
||||
TimeFormat: "02 Jan 06",
|
||||
ShortTimeFormat: time.Kitchen,
|
||||
@@ -773,6 +772,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
ShowBottomLine: true,
|
||||
ShowPanelJumps: true,
|
||||
ShowFileTree: true,
|
||||
ShowRootItemInFileTree: true,
|
||||
ShowNumstatInFilesView: false,
|
||||
ShowRandomTip: true,
|
||||
ShowIcons: false,
|
||||
@@ -830,7 +830,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
FetchAll: true,
|
||||
AutoStageResolvedConflicts: true,
|
||||
BranchLogCmd: "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --",
|
||||
AllBranchesLogCmd: "git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium",
|
||||
AllBranchesLogCmds: []string{"git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium"},
|
||||
DisableForcePushing: false,
|
||||
CommitPrefixes: map[string][]CommitPrefixConfig(nil),
|
||||
BranchPrefix: "",
|
||||
@@ -892,6 +892,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
GoInto: "<enter>",
|
||||
Confirm: "<enter>",
|
||||
ConfirmInEditor: "<a-enter>",
|
||||
ConfirmInEditorAlt: "<c-s>",
|
||||
Remove: "d",
|
||||
New: "n",
|
||||
Edit: "e",
|
||||
|
||||
@@ -56,7 +56,7 @@ func validateKeybindingsRecurse(path string, node any) error {
|
||||
}
|
||||
}
|
||||
} else if value.Kind() == reflect.Slice {
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
for i := range value.Len() {
|
||||
if err := validateKeybindingsRecurse(
|
||||
fmt.Sprintf("%s[%d]", path, i), value.Index(i).Interface()); err != nil {
|
||||
return err
|
||||
@@ -101,21 +101,29 @@ func validateCustomCommands(customCommands []CustomCommand) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(customCommand.CommandMenu) > 0 &&
|
||||
(len(customCommand.Context) > 0 ||
|
||||
if len(customCommand.CommandMenu) > 0 {
|
||||
if len(customCommand.Context) > 0 ||
|
||||
len(customCommand.Command) > 0 ||
|
||||
customCommand.Subprocess != nil ||
|
||||
len(customCommand.Prompts) > 0 ||
|
||||
len(customCommand.LoadingText) > 0 ||
|
||||
customCommand.Stream != nil ||
|
||||
customCommand.ShowOutput != nil ||
|
||||
len(customCommand.Output) > 0 ||
|
||||
len(customCommand.OutputTitle) > 0 ||
|
||||
customCommand.After != nil) {
|
||||
commandRef := ""
|
||||
if len(customCommand.Key) > 0 {
|
||||
commandRef = fmt.Sprintf(" with key '%s'", customCommand.Key)
|
||||
customCommand.After != nil {
|
||||
commandRef := ""
|
||||
if len(customCommand.Key) > 0 {
|
||||
commandRef = fmt.Sprintf(" with key '%s'", customCommand.Key)
|
||||
}
|
||||
return fmt.Errorf("Error with custom command%s: it is not allowed to use both commandMenu and any of the other fields except key and description.", commandRef)
|
||||
}
|
||||
|
||||
if err := validateCustomCommands(customCommand.CommandMenu); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := validateEnum("customCommand.output", customCommand.Output,
|
||||
[]string{"", "none", "terminal", "log", "logWithPty", "popup"}); err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("Error with custom command%s: it is not allowed to use both commandMenu and any of the other fields except key and description.", commandRef)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -74,6 +74,46 @@ func TestUserConfigValidate_enums(t *testing.T) {
|
||||
{value: "invalid_value", valid: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Custom command keybinding in sub menu",
|
||||
setup: func(config *UserConfig, value string) {
|
||||
config.CustomCommands = []CustomCommand{
|
||||
{
|
||||
Key: "X",
|
||||
Description: "My Custom Commands",
|
||||
CommandMenu: []CustomCommand{
|
||||
{Key: value, Command: "echo 'hello'", Context: "global"},
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
testCases: []testCase{
|
||||
{value: "", valid: true},
|
||||
{value: "<disabled>", valid: true},
|
||||
{value: "q", valid: true},
|
||||
{value: "<c-c>", valid: true},
|
||||
{value: "invalid_value", valid: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Custom command output",
|
||||
setup: func(config *UserConfig, value string) {
|
||||
config.CustomCommands = []CustomCommand{
|
||||
{
|
||||
Output: value,
|
||||
},
|
||||
}
|
||||
},
|
||||
testCases: []testCase{
|
||||
{value: "", valid: true},
|
||||
{value: "none", valid: true},
|
||||
{value: "terminal", valid: true},
|
||||
{value: "log", valid: true},
|
||||
{value: "logWithPty", valid: true},
|
||||
{value: "popup", valid: true},
|
||||
{value: "invalid_value", valid: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Custom command sub menu",
|
||||
setup: func(config *UserConfig, _ string) {
|
||||
@@ -97,7 +137,7 @@ func TestUserConfigValidate_enums(t *testing.T) {
|
||||
config.CustomCommands = []CustomCommand{
|
||||
{
|
||||
Key: "X",
|
||||
Context: "global",
|
||||
Context: "global", // context is not allowed for submenus
|
||||
CommandMenu: []CustomCommand{
|
||||
{Key: "1", Command: "echo 'hello'", Context: "global"},
|
||||
},
|
||||
@@ -111,11 +151,10 @@ func TestUserConfigValidate_enums(t *testing.T) {
|
||||
{
|
||||
name: "Custom command sub menu",
|
||||
setup: func(config *UserConfig, _ string) {
|
||||
falseVal := false
|
||||
config.CustomCommands = []CustomCommand{
|
||||
{
|
||||
Key: "X",
|
||||
Subprocess: &falseVal,
|
||||
Key: "X",
|
||||
LoadingText: "loading", // other properties are not allowed for submenus (using loadingText as an example)
|
||||
CommandMenu: []CustomCommand{
|
||||
{Key: "1", Command: "echo 'hello'", Context: "global"},
|
||||
},
|
||||
|
||||
@@ -93,7 +93,8 @@ func (self *BackgroundRoutineMgr) startBackgroundFilesRefresh(refreshInterval in
|
||||
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}})
|
||||
self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -125,7 +126,7 @@ func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan stru
|
||||
func (self *BackgroundRoutineMgr) backgroundFetch() (err error) {
|
||||
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.SYNC})
|
||||
self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.SYNC})
|
||||
|
||||
if err == nil {
|
||||
err = self.gui.helpers.BranchesHelper.AutoForwardBranches()
|
||||
|
||||
@@ -48,7 +48,7 @@ func (gui *Gui) LogCommand(cmdStr string, commandLine bool) {
|
||||
textStyle = style.FgMagenta
|
||||
}
|
||||
gui.GuiLog = append(gui.GuiLog, cmdStr)
|
||||
indentedCmdStr := " " + strings.Replace(cmdStr, "\n", "\n ", -1)
|
||||
indentedCmdStr := " " + strings.ReplaceAll(cmdStr, "\n", "\n ")
|
||||
fmt.Fprint(gui.Views.Extras, "\n"+textStyle.Sprint(indentedCmdStr))
|
||||
}
|
||||
|
||||
|
||||
@@ -357,3 +357,19 @@ func (self *ContextMgr) CurrentPopup() []types.Context {
|
||||
return context.GetKind() == types.TEMPORARY_POPUP || context.GetKind() == types.PERSISTENT_POPUP
|
||||
})
|
||||
}
|
||||
|
||||
func (self *ContextMgr) NextInStack(c types.Context) types.Context {
|
||||
self.RLock()
|
||||
defer self.RUnlock()
|
||||
|
||||
for i := range self.ContextStack {
|
||||
if self.ContextStack[i].GetKey() == c.GetKey() {
|
||||
if i == 0 {
|
||||
return nil
|
||||
}
|
||||
return self.ContextStack[i-1]
|
||||
}
|
||||
}
|
||||
|
||||
panic("context not in stack")
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ var (
|
||||
func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
|
||||
viewModel := filetree.NewCommitFileTreeViewModel(
|
||||
func() []*models.CommitFile { return c.Model().CommitFiles },
|
||||
c.Log,
|
||||
c.Common,
|
||||
c.UserConfig().Gui.ShowFileTree,
|
||||
)
|
||||
|
||||
|
||||
@@ -16,14 +16,13 @@ type FilteredList[T any] struct {
|
||||
getFilterFields func(T) []string
|
||||
filter string
|
||||
|
||||
mutex *deadlock.Mutex
|
||||
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{},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -149,3 +149,7 @@ func (self *ListContextTrait) TotalContentHeight() int {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (self *ListContextTrait) IndexForGotoBottom() int {
|
||||
return self.list.Len() - 1
|
||||
}
|
||||
|
||||
@@ -257,11 +257,11 @@ func TestListRenderer_ModelIndexToViewIndex_and_back(t *testing.T) {
|
||||
// Need to render first so that it knows the non-model items
|
||||
self.renderLines(-1, -1)
|
||||
|
||||
for i := 0; i < len(s.modelIndices); i++ {
|
||||
for i := range len(s.modelIndices) {
|
||||
assert.Equal(t, s.expectedViewIndices[i], self.ModelIndexToViewIndex(s.modelIndices[i]))
|
||||
}
|
||||
|
||||
for i := 0; i < len(s.viewIndices); i++ {
|
||||
for i := range len(s.viewIndices) {
|
||||
assert.Equal(t, s.expectedModelIndices[i], self.ViewIndexToModelIndex(s.viewIndices[i]))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -289,3 +289,20 @@ func searchModelCommits(caseSensitive bool, commits []*models.Commit, columnPosi
|
||||
strings.Contains(normalize(commit.ExtraInfo), searchStr) // allow searching for tags
|
||||
})
|
||||
}
|
||||
|
||||
func (self *LocalCommitsContext) IndexForGotoBottom() int {
|
||||
commits := self.GetCommits()
|
||||
selectedIdx := self.GetSelectedLineIdx()
|
||||
if selectedIdx >= 0 && selectedIdx < len(commits)-1 {
|
||||
if commits[selectedIdx+1].Status != models.StatusMerged {
|
||||
_, idx, found := lo.FindIndexOf(commits, func(c *models.Commit) bool {
|
||||
return c.Status == models.StatusMerged
|
||||
})
|
||||
if found {
|
||||
return idx - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self.list.Len() - 1
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ func (self *MenuViewModel) GetNonModelItems() []*NonModelItem {
|
||||
}
|
||||
|
||||
menuItems := self.FilteredListViewModel.GetItems()
|
||||
var prevSection *types.MenuSection = nil
|
||||
var prevSection *types.MenuSection
|
||||
for i, menuItem := range menuItems {
|
||||
if menuItem.Section != nil && menuItem.Section != prevSection {
|
||||
if prevSection != nil {
|
||||
|
||||
@@ -12,7 +12,7 @@ type MergeConflictsContext struct {
|
||||
types.Context
|
||||
viewModel *ConflictsViewModel
|
||||
c *ContextCommon
|
||||
mutex *deadlock.Mutex
|
||||
mutex deadlock.Mutex
|
||||
}
|
||||
|
||||
type ConflictsViewModel struct {
|
||||
@@ -33,7 +33,6 @@ func NewMergeConflictsContext(
|
||||
|
||||
return &MergeConflictsContext{
|
||||
viewModel: viewModel,
|
||||
mutex: &deadlock.Mutex{},
|
||||
Context: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
@@ -57,7 +56,7 @@ func (self *MergeConflictsContext) SetState(state *mergeconflicts.State) {
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) GetMutex() *deadlock.Mutex {
|
||||
return self.mutex
|
||||
return &self.mutex
|
||||
}
|
||||
|
||||
func (self *MergeConflictsContext) SetUserScrolling(isScrolling bool) {
|
||||
|
||||
@@ -15,7 +15,7 @@ type PatchExplorerContext struct {
|
||||
viewTrait *ViewTrait
|
||||
getIncludedLineIndices func() []int
|
||||
c *ContextCommon
|
||||
mutex *deadlock.Mutex
|
||||
mutex deadlock.Mutex
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -36,7 +36,6 @@ func NewPatchExplorerContext(
|
||||
state: nil,
|
||||
viewTrait: NewViewTrait(view),
|
||||
c: c,
|
||||
mutex: &deadlock.Mutex{},
|
||||
getIncludedLineIndices: getIncludedLineIndices,
|
||||
SimpleContext: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
@@ -137,7 +136,7 @@ func (self *PatchExplorerContext) NavigateTo(selectedLineIdx int) {
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) GetMutex() *deadlock.Mutex {
|
||||
return self.mutex
|
||||
return &self.mutex
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
|
||||
|
||||
@@ -227,3 +227,20 @@ func (self *SubCommitsContext) RefForAdjustingLineNumberInDiff() string {
|
||||
func (self *SubCommitsContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
|
||||
return searchModelCommits(caseSensitive, self.GetCommits(), self.ColumnPositions(), searchStr)
|
||||
}
|
||||
|
||||
func (self *SubCommitsContext) IndexForGotoBottom() int {
|
||||
commits := self.GetCommits()
|
||||
selectedIdx := self.GetSelectedLineIdx()
|
||||
if selectedIdx >= 0 && selectedIdx < len(commits)-1 {
|
||||
if commits[selectedIdx+1].Status != models.StatusMerged {
|
||||
_, idx, found := lo.FindIndexOf(commits, func(c *models.Commit) bool {
|
||||
return c.Status == models.StatusMerged
|
||||
})
|
||||
if found {
|
||||
return idx - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self.list.Len() - 1
|
||||
}
|
||||
|
||||
@@ -79,9 +79,8 @@ func (self *SuggestionsContext) RefreshSuggestions() {
|
||||
if findSuggestionsFn != nil {
|
||||
suggestions := findSuggestionsFn(self.c.GetPromptInput())
|
||||
return func() { self.SetSuggestions(suggestions) }
|
||||
} else {
|
||||
return func() {}
|
||||
}
|
||||
return func() {}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -77,9 +77,8 @@ func (self *ListCursor) SetSelectionRangeAndMode(selectedIdx, rangeStartIdx int,
|
||||
func (self *ListCursor) GetSelectionRangeAndMode() (int, int, RangeSelectMode) {
|
||||
if self.IsSelectingRange() {
|
||||
return self.selectedIdx, self.rangeStartIdx, self.rangeSelectMode
|
||||
} else {
|
||||
return self.selectedIdx, self.selectedIdx, self.rangeSelectMode
|
||||
}
|
||||
return self.selectedIdx, self.selectedIdx, self.rangeSelectMode
|
||||
}
|
||||
|
||||
func (self *ListCursor) clampValue(value int) int {
|
||||
@@ -134,10 +133,19 @@ func (self *ListCursor) GetRangeStartIdx() (int, bool) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Cancel range select mode, but keep the "moving end" of the range selected.
|
||||
// Used when pressing 'v' or escape to toggle range select mode, for example.
|
||||
func (self *ListCursor) CancelRangeSelect() {
|
||||
self.rangeSelectMode = RangeSelectModeNone
|
||||
}
|
||||
|
||||
// Cancel range select mode, but keep the top of the range selected. Note that
|
||||
// this is different from CancelRangeSelect. Useful after deleting a range of items.
|
||||
func (self *ListCursor) CollapseRangeSelectionToTop() {
|
||||
start, _ := self.GetSelectionRange()
|
||||
self.SetSelection(start)
|
||||
}
|
||||
|
||||
// Returns true if we are in range select mode. Note that we may be in range select
|
||||
// mode and still only selecting a single item. See AreMultipleItemsSelected below.
|
||||
func (self *ListCursor) IsSelectingRange() bool {
|
||||
|
||||
@@ -24,14 +24,14 @@ var (
|
||||
func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
|
||||
viewModel := filetree.NewFileTreeViewModel(
|
||||
func() []*models.File { return c.Model().Files },
|
||||
c.Log,
|
||||
c.Common,
|
||||
c.UserConfig().Gui.ShowFileTree,
|
||||
)
|
||||
|
||||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
showFileIcons := icons.IsIconEnabled() && c.UserConfig().Gui.ShowFileIcons
|
||||
showNumstat := c.UserConfig().Gui.ShowNumstatInFilesView
|
||||
lines := presentation.RenderFileTree(viewModel, c.Model().Submodules, showFileIcons, showNumstat, &c.UserConfig().Gui.CustomIcons)
|
||||
lines := presentation.RenderFileTree(viewModel, c.Model().Submodules, showFileIcons, showNumstat, &c.UserConfig().Gui.CustomIcons, c.UserConfig().Gui.ShowRootItemInFileTree)
|
||||
return lo.Map(lines, func(line string, _ int) []string {
|
||||
return []string{line}
|
||||
})
|
||||
|
||||
@@ -24,7 +24,7 @@ func OnFocusWrapper(f func() error) func(opts types.OnFocusOpts) error {
|
||||
func (gui *Gui) defaultSideContext() types.Context {
|
||||
if gui.State.Modes.Filtering.Active() {
|
||||
return gui.State.Contexts.LocalCommits
|
||||
} else {
|
||||
return gui.State.Contexts.Files
|
||||
}
|
||||
|
||||
return gui.State.Contexts.Files
|
||||
}
|
||||
|
||||
@@ -361,7 +361,7 @@ func (self *BasicCommitsController) newBranch(commit *models.Commit) error {
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) createResetMenu(commit *models.Commit) error {
|
||||
return self.c.Helpers().Refs.CreateGitResetMenu(commit.Hash())
|
||||
return self.c.Helpers().Refs.CreateGitResetMenu(commit.Hash(), commit.Hash())
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) checkout(commit *models.Commit) error {
|
||||
|
||||
@@ -54,9 +54,8 @@ func (self *BisectController) openMenu(commit *models.Commit) error {
|
||||
info := self.c.Git().Bisect.GetInfo()
|
||||
if info.Started() {
|
||||
return self.openMidBisectMenu(info, commit)
|
||||
} else {
|
||||
return self.openStartBisectMenu(info, commit)
|
||||
}
|
||||
return self.openStartBisectMenu(info, commit)
|
||||
}
|
||||
|
||||
func (self *BisectController) openMidBisectMenu(info *git_commands.BisectInfo, commit *models.Commit) error {
|
||||
@@ -176,7 +175,8 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
return nil
|
||||
},
|
||||
DisabledReason: self.require(self.singleItemSelected())(),
|
||||
Key: 'b',
|
||||
@@ -193,7 +193,8 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
return nil
|
||||
},
|
||||
DisabledReason: self.require(self.singleItemSelected())(),
|
||||
Key: 'g',
|
||||
@@ -212,7 +213,8 @@ func (self *BisectController) openStartBisectMenu(info *git_commands.BisectInfo,
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
return nil
|
||||
@@ -246,7 +248,8 @@ func (self *BisectController) showBisectCompleteMessage(candidateHashes []string
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
@@ -271,20 +274,21 @@ func (self *BisectController) afterMark(selectCurrent bool, waitToReselect bool)
|
||||
}
|
||||
|
||||
func (self *BisectController) afterBisectMarkRefresh(selectCurrent bool, waitToReselect bool) error {
|
||||
selectFn := func() error {
|
||||
selectFn := func() {
|
||||
if selectCurrent {
|
||||
self.selectCurrentBisectCommit()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if waitToReselect {
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{}, Then: selectFn})
|
||||
} else {
|
||||
_ = selectFn()
|
||||
|
||||
return self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.SYNC, Scope: []types.RefreshableView{}, Then: selectFn})
|
||||
return nil
|
||||
}
|
||||
|
||||
selectFn()
|
||||
|
||||
self.c.Helpers().Bisect.PostBisectCommandRefresh()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BisectController) selectCurrentBisectCommit() {
|
||||
|
||||
@@ -263,15 +263,13 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
|
||||
if err := self.c.Git().Branch.UnsetUpstream(selectedBranch.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := self.c.Refresh(types.RefreshOptions{
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.SYNC,
|
||||
Scope: []types.RefreshableView{
|
||||
types.BRANCHES,
|
||||
types.COMMITS,
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
})
|
||||
return nil
|
||||
},
|
||||
Key: 'u',
|
||||
@@ -289,15 +287,13 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
|
||||
if err := self.c.Git().Branch.SetUpstream(upstreamRemote, upstreamBranch, selectedBranch.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := self.c.Refresh(types.RefreshOptions{
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.SYNC,
|
||||
Scope: []types.RefreshableView{
|
||||
types.BRANCHES,
|
||||
types.COMMITS,
|
||||
},
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
})
|
||||
return nil
|
||||
})
|
||||
},
|
||||
@@ -326,7 +322,8 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
|
||||
LabelColumns: []string{upstreamResetOptions},
|
||||
OpensMenu: true,
|
||||
OnPress: func() error {
|
||||
err := self.c.Helpers().Refs.CreateGitResetMenu(upstream)
|
||||
// We only can invoke this when the remote branch is stored locally, so using the selectedBranch here is fine.
|
||||
err := self.c.Helpers().Refs.CreateGitResetMenu(upstream, selectedBranch.FullUpstreamRefName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -477,7 +474,8 @@ func (self *BranchesController) forceCheckout() error {
|
||||
if err := self.c.Git().Branch.Checkout(branch.Name, git_commands.CheckoutOptions{Force: true}); err != nil {
|
||||
return err
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
@@ -525,7 +523,8 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
|
||||
}
|
||||
|
||||
self.context().SetSelection(0)
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, KeepBranchSelectionIndex: true})
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, KeepBranchSelectionIndex: true})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesController) localDelete(branches []*models.Branch) error {
|
||||
@@ -536,7 +535,7 @@ func (self *BranchesController) remoteDelete(branches []*models.Branch) error {
|
||||
remoteBranches := lo.Map(branches, func(branch *models.Branch, _ int) *models.RemoteBranch {
|
||||
return &models.RemoteBranch{Name: branch.UpstreamBranch, RemoteName: branch.UpstreamRemote}
|
||||
})
|
||||
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(remoteBranches)
|
||||
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(remoteBranches, false)
|
||||
}
|
||||
|
||||
func (self *BranchesController) localAndRemoteDelete(branches []*models.Branch) error {
|
||||
@@ -654,17 +653,17 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
WorktreePath: worktreePath,
|
||||
},
|
||||
)
|
||||
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return err
|
||||
} else {
|
||||
self.c.LogAction(action)
|
||||
|
||||
err := self.c.Git().Sync.FastForward(
|
||||
task, branch.Name, branch.UpstreamRemote, branch.UpstreamBranch,
|
||||
)
|
||||
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.LogAction(action)
|
||||
|
||||
err := self.c.Git().Sync.FastForward(
|
||||
task, branch.Name, branch.UpstreamRemote, branch.UpstreamBranch,
|
||||
)
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
@@ -678,7 +677,8 @@ func (self *BranchesController) createSortMenu() error {
|
||||
self.c.GetAppState().LocalBranchSortOrder = sortOrder
|
||||
self.c.SaveAppStateAndLogError()
|
||||
self.c.Contexts().Branches.SetSelection(0)
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
},
|
||||
@@ -686,7 +686,7 @@ func (self *BranchesController) createSortMenu() error {
|
||||
}
|
||||
|
||||
func (self *BranchesController) createResetMenu(selectedBranch *models.Branch) error {
|
||||
return self.c.Helpers().Refs.CreateGitResetMenu(selectedBranch.Name)
|
||||
return self.c.Helpers().Refs.CreateGitResetMenu(selectedBranch.Name, selectedBranch.FullRefName())
|
||||
}
|
||||
|
||||
func (self *BranchesController) rename(branch *models.Branch) error {
|
||||
@@ -701,7 +701,7 @@ func (self *BranchesController) rename(branch *models.Branch) error {
|
||||
}
|
||||
|
||||
// need to find where the branch is now so that we can re-select it. That means we need to refetch the branches synchronously and then find our branch
|
||||
_ = self.c.Refresh(types.RefreshOptions{
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.SYNC,
|
||||
Scope: []types.RefreshableView{types.BRANCHES, types.WORKTREES},
|
||||
})
|
||||
|
||||
@@ -38,6 +38,10 @@ func (self *CommitDescriptionController) GetKeybindings(opts types.KeybindingsOp
|
||||
Key: opts.GetKey(opts.Config.Universal.ConfirmInEditor),
|
||||
Handler: self.confirm,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.ConfirmInEditorAlt),
|
||||
Handler: self.confirm,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.CommitMessage.CommitMenu),
|
||||
Handler: self.openCommitMenu,
|
||||
@@ -63,10 +67,27 @@ func (self *CommitDescriptionController) GetMouseKeybindings(opts types.Keybindi
|
||||
|
||||
func (self *CommitDescriptionController) GetOnFocus() func(types.OnFocusOpts) {
|
||||
return func(types.OnFocusOpts) {
|
||||
self.c.Views().CommitDescription.Footer = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionFooter,
|
||||
map[string]string{
|
||||
"confirmInEditorKeybinding": keybindings.Label(self.c.UserConfig().Keybinding.Universal.ConfirmInEditor),
|
||||
})
|
||||
footer := ""
|
||||
if self.c.UserConfig().Keybinding.Universal.ConfirmInEditor != "<disabled>" || self.c.UserConfig().Keybinding.Universal.ConfirmInEditorAlt != "<disabled>" {
|
||||
if self.c.UserConfig().Keybinding.Universal.ConfirmInEditor == "<disabled>" {
|
||||
footer = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionFooter,
|
||||
map[string]string{
|
||||
"confirmInEditorKeybinding": keybindings.Label(self.c.UserConfig().Keybinding.Universal.ConfirmInEditorAlt),
|
||||
})
|
||||
} else if self.c.UserConfig().Keybinding.Universal.ConfirmInEditorAlt == "<disabled>" {
|
||||
footer = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionFooter,
|
||||
map[string]string{
|
||||
"confirmInEditorKeybinding": keybindings.Label(self.c.UserConfig().Keybinding.Universal.ConfirmInEditor),
|
||||
})
|
||||
} else {
|
||||
footer = utils.ResolvePlaceholderString(self.c.Tr.CommitDescriptionFooterTwoBindings,
|
||||
map[string]string{
|
||||
"confirmInEditorKeybinding1": keybindings.Label(self.c.UserConfig().Keybinding.Universal.ConfirmInEditor),
|
||||
"confirmInEditorKeybinding2": keybindings.Label(self.c.UserConfig().Keybinding.Universal.ConfirmInEditorAlt),
|
||||
})
|
||||
}
|
||||
}
|
||||
self.c.Views().CommitDescription.Footer = footer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ func (self *CommitMessageController) handleCommitIndexChange(value int) error {
|
||||
func (self *CommitMessageController) setCommitMessageAtIndex(index int) (bool, error) {
|
||||
commitMessage, err := self.c.Git().Commit.GetCommitMessageFromHistory(index)
|
||||
if err != nil {
|
||||
if err == git_commands.ErrInvalidCommitIndex {
|
||||
if errors.Is(err, git_commands.ErrInvalidCommitIndex) {
|
||||
return false, nil
|
||||
}
|
||||
return false, errors.New(self.c.Tr.CommitWithoutMessageErr)
|
||||
|
||||
@@ -289,7 +289,8 @@ func (self *CommitFilesController) checkout(node *filetree.CommitFileNode) error
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) discard(selectedNodes []*filetree.CommitFileNode) error {
|
||||
@@ -313,19 +314,13 @@ func (self *CommitFilesController) discard(selectedNodes []*filetree.CommitFileN
|
||||
// Reset the current patch if there is one.
|
||||
if self.c.Git().Patch.PatchBuilder.Active() {
|
||||
self.c.Git().Patch.PatchBuilder.Reset()
|
||||
if err := self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, node := range selectedNodes {
|
||||
err := node.ForEachFile(func(file *models.CommitFile) error {
|
||||
_ = node.ForEachFile(func(file *models.CommitFile) error {
|
||||
filePaths = append(filePaths, file.GetPath())
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err := self.c.Git().Rebase.DiscardOldFileChanges(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), filePaths)
|
||||
@@ -336,7 +331,8 @@ func (self *CommitFilesController) discard(selectedNodes []*filetree.CommitFileN
|
||||
if self.context().RangeSelectEnabled() {
|
||||
self.context().GetList().CancelRangeSelect()
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC})
|
||||
|
||||
return nil
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -103,13 +103,13 @@ func (self *ContextLinesController) applyChange() error {
|
||||
switch currentContext.GetKey() {
|
||||
// we make an exception for our staging and patch building contexts because they actually need to refresh their state afterwards.
|
||||
case context.PATCH_BUILDING_MAIN_CONTEXT_KEY:
|
||||
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.PATCH_BUILDING}})
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.PATCH_BUILDING}})
|
||||
case context.STAGING_MAIN_CONTEXT_KEY, context.STAGING_SECONDARY_CONTEXT_KEY:
|
||||
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STAGING}})
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STAGING}})
|
||||
default:
|
||||
currentContext.HandleRenderToMain()
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *ContextLinesController) checkCanChangeContext() error {
|
||||
@@ -131,7 +131,9 @@ func (self *ContextLinesController) currentSidePanel() types.Context {
|
||||
currentContext := self.c.Context().CurrentStatic()
|
||||
if currentContext.GetKey() == context.NORMAL_MAIN_CONTEXT_KEY ||
|
||||
currentContext.GetKey() == context.NORMAL_SECONDARY_CONTEXT_KEY {
|
||||
return currentContext.GetParentContext()
|
||||
if sidePanelContext := self.c.Context().NextInStack(currentContext); sidePanelContext != nil {
|
||||
return sidePanelContext
|
||||
}
|
||||
}
|
||||
|
||||
return currentContext
|
||||
|
||||
@@ -4,9 +4,13 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/generics/set"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type CustomPatchOptionsMenuAction struct {
|
||||
@@ -46,7 +50,7 @@ func (self *CustomPatchOptionsMenuAction) Call() error {
|
||||
if self.c.Git().Patch.PatchBuilder.CanRebase && self.c.Git().Status.WorkingTreeState().None() {
|
||||
menuItems = append(menuItems, []*types.MenuItem{
|
||||
{
|
||||
Label: fmt.Sprintf(self.c.Tr.RemovePatchFromOriginalCommit, self.c.Git().Patch.PatchBuilder.To),
|
||||
Label: fmt.Sprintf(self.c.Tr.RemovePatchFromOriginalCommit, utils.ShortHash(self.c.Git().Patch.PatchBuilder.To)),
|
||||
Tooltip: self.c.Tr.RemovePatchFromOriginalCommitTooltip,
|
||||
OnPress: self.handleDeletePatchFromCommit,
|
||||
Key: 'd',
|
||||
@@ -63,6 +67,12 @@ func (self *CustomPatchOptionsMenuAction) Call() error {
|
||||
OnPress: self.handlePullPatchIntoNewCommit,
|
||||
Key: 'n',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.MovePatchIntoNewCommitBefore,
|
||||
Tooltip: self.c.Tr.MovePatchIntoNewCommitBeforeTooltip,
|
||||
OnPress: self.handlePullPatchIntoNewCommitBefore,
|
||||
Key: 'N',
|
||||
},
|
||||
}...)
|
||||
|
||||
if self.c.Context().Current().GetKey() == self.c.Contexts().LocalCommits.GetKey() {
|
||||
@@ -182,9 +192,9 @@ func (self *CustomPatchOptionsMenuAction) handleMovePatchIntoWorkingTree() error
|
||||
})
|
||||
|
||||
return nil
|
||||
} else {
|
||||
return pull(false)
|
||||
}
|
||||
|
||||
return pull(false)
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommit() error {
|
||||
@@ -222,18 +232,80 @@ func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommit() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) handlePullPatchIntoNewCommitBefore() error {
|
||||
if ok, err := self.validateNormalWorkingTreeState(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
self.returnFocusFromPatchExplorerIfNecessary()
|
||||
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.Helpers().Commits.OpenCommitMessagePanel(
|
||||
&helpers.OpenCommitMessagePanelOpts{
|
||||
// Pass a commit index of one less than the moved-from commit, so that
|
||||
// you can press up arrow once to recall the original commit message:
|
||||
CommitIndex: commitIndex - 1,
|
||||
InitialMessage: "",
|
||||
SummaryTitle: self.c.Tr.CommitSummaryTitle,
|
||||
DescriptionTitle: self.c.Tr.CommitDescriptionTitle,
|
||||
PreserveMessage: false,
|
||||
OnConfirm: func(summary string, description string) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
self.c.Helpers().Commits.CloseCommitMessagePanel()
|
||||
self.c.LogAction(self.c.Tr.Actions.MovePatchIntoNewCommit)
|
||||
err := self.c.Git().Patch.PullPatchIntoNewCommitBefore(self.c.Model().Commits, commitIndex, summary, description)
|
||||
if err := self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{})
|
||||
return nil
|
||||
})
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) handleApplyPatch(reverse bool) error {
|
||||
self.returnFocusFromPatchExplorerIfNecessary()
|
||||
|
||||
action := self.c.Tr.Actions.ApplyPatch
|
||||
if reverse {
|
||||
action = "Apply patch in reverse"
|
||||
affectedUnstagedFiles := self.getAffectedUnstagedFiles()
|
||||
|
||||
apply := func() error {
|
||||
action := self.c.Tr.Actions.ApplyPatch
|
||||
if reverse {
|
||||
action = "Apply patch in reverse"
|
||||
}
|
||||
self.c.LogAction(action)
|
||||
|
||||
if len(affectedUnstagedFiles) > 0 {
|
||||
if err := self.c.Git().WorkingTree.StageFiles(affectedUnstagedFiles, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := self.c.Git().Patch.ApplyCustomPatch(reverse, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return nil
|
||||
}
|
||||
self.c.LogAction(action)
|
||||
if err := self.c.Git().Patch.ApplyCustomPatch(reverse, true); err != nil {
|
||||
return err
|
||||
|
||||
if len(affectedUnstagedFiles) > 0 {
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.MustStageFilesAffectedByPatchTitle,
|
||||
Prompt: self.c.Tr.MustStageFilesAffectedByPatchWarning,
|
||||
HandleConfirm: func() error {
|
||||
return apply()
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
|
||||
return apply()
|
||||
}
|
||||
|
||||
func (self *CustomPatchOptionsMenuAction) copyPatchToClipboard() error {
|
||||
@@ -248,3 +320,17 @@ func (self *CustomPatchOptionsMenuAction) copyPatchToClipboard() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Returns a list of files that have unstaged changes and are contained in the patch.
|
||||
func (self *CustomPatchOptionsMenuAction) getAffectedUnstagedFiles() []string {
|
||||
unstagedFiles := set.NewFromSlice(lo.FilterMap(self.c.Model().Files, func(f *models.File, _ int) (string, bool) {
|
||||
if f.GetHasUnstagedChanges() {
|
||||
return f.GetPath(), true
|
||||
}
|
||||
return "", false
|
||||
}))
|
||||
|
||||
return lo.Filter(self.c.Git().Patch.PatchBuilder.AllFilesInPatch(), func(patchFile string, _ int) bool {
|
||||
return unstagedFiles.Includes(patchFile)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -23,7 +23,8 @@ func (self *DiffingMenuAction) Call() error {
|
||||
OnPress: func() error {
|
||||
self.c.Modes().Diffing.Ref = name
|
||||
// can scope this down based on current view but too lazy right now
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}...)
|
||||
@@ -38,7 +39,8 @@ func (self *DiffingMenuAction) Call() error {
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRefsSuggestionsFunc(),
|
||||
HandleConfirm: func(response string) error {
|
||||
self.c.Modes().Diffing.Ref = strings.TrimSpace(response)
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
@@ -53,14 +55,16 @@ func (self *DiffingMenuAction) Call() error {
|
||||
Label: self.c.Tr.SwapDiff,
|
||||
OnPress: func() error {
|
||||
self.c.Modes().Diffing.Reverse = !self.c.Modes().Diffing.Reverse
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.ExitDiffMode,
|
||||
OnPress: func() error {
|
||||
self.c.Modes().Diffing = diffing.New()
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}...)
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
type FilesController struct {
|
||||
baseController // nolint: unused
|
||||
baseController
|
||||
*ListControllerTrait[*filetree.FileNode]
|
||||
c *ControllerCommon
|
||||
}
|
||||
@@ -262,15 +262,14 @@ func (self *FilesController) GetOnRenderToMain() func() {
|
||||
}
|
||||
if node.File.ShortStatus == "DU" || node.File.ShortStatus == "UD" {
|
||||
cmdObj := self.c.Git().Diff.DiffCmdObj([]string{"--base", "--", node.GetPath()})
|
||||
task := types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
task.Prefix = message + "\n\n"
|
||||
prefix := message + "\n\n"
|
||||
if node.File.ShortStatus == "DU" {
|
||||
task.Prefix += self.c.Tr.MergeConflictIncomingDiff
|
||||
prefix += self.c.Tr.MergeConflictIncomingDiff
|
||||
} else {
|
||||
task.Prefix += self.c.Tr.MergeConflictCurrentDiff
|
||||
prefix += self.c.Tr.MergeConflictCurrentDiff
|
||||
}
|
||||
task.Prefix += "\n\n"
|
||||
opts.Main.Task = task
|
||||
prefix += "\n\n"
|
||||
opts.Main.Task = types.NewRunPtyTaskWithPrefix(cmdObj.GetCmd(), prefix)
|
||||
} else {
|
||||
opts.Main.Task = types.NewRenderStringTask(message)
|
||||
}
|
||||
@@ -486,9 +485,7 @@ func (self *FilesController) press(nodes []*filetree.FileNode) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}, Mode: types.ASYNC}); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}, Mode: types.ASYNC})
|
||||
|
||||
self.context().HandleFocus(types.OnFocusOpts{})
|
||||
return nil
|
||||
@@ -566,7 +563,8 @@ func (self *FilesController) handleNonInlineConflict(file *models.File) error {
|
||||
if err := command(file.GetPath()); err != nil {
|
||||
return err
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||
return nil
|
||||
}
|
||||
keepItem := &types.MenuItem{
|
||||
Label: self.c.Tr.MergeConflictKeepFile,
|
||||
@@ -611,9 +609,7 @@ func (self *FilesController) toggleStagedAll() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}, Mode: types.ASYNC}); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}, Mode: types.ASYNC})
|
||||
|
||||
self.context().HandleFocus(types.OnFocusOpts{})
|
||||
return nil
|
||||
@@ -683,7 +679,8 @@ func (self *FilesController) ignoreOrExcludeTracked(node *filetree.FileNode, trA
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *FilesController) ignoreOrExcludeUntracked(node *filetree.FileNode, trAction string, f func(string) error) error {
|
||||
@@ -693,7 +690,8 @@ func (self *FilesController) ignoreOrExcludeUntracked(node *filetree.FileNode, t
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *FilesController) ignoreOrExcludeFile(node *filetree.FileNode, trText string, trPrompt string, trAction string, f func(string) error) error {
|
||||
@@ -755,7 +753,8 @@ func (self *FilesController) ignoreOrExcludeMenu(node *filetree.FileNode) error
|
||||
}
|
||||
|
||||
func (self *FilesController) refresh() error {
|
||||
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *FilesController) handleAmendCommitPress() error {
|
||||
@@ -795,7 +794,7 @@ func (self *FilesController) handleAmendCommitPress() error {
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
} else if !self.c.UserConfig().Gui.SkipAmendWarning {
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.AmendLastCommitTitle,
|
||||
Prompt: self.c.Tr.SureToAmend,
|
||||
@@ -803,9 +802,11 @@ func (self *FilesController) handleAmendCommitPress() error {
|
||||
return doAmend()
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
return doAmend()
|
||||
}
|
||||
|
||||
func (self *FilesController) isResolvingConflicts() bool {
|
||||
@@ -898,12 +899,11 @@ func (self *FilesController) setStatusFiltering(filter filetree.FileTreeDisplayF
|
||||
// Whenever we switch between untracked and other filters, we need to refresh the files view
|
||||
// because the untracked files filter applies when running `git status`.
|
||||
if previousFilter != filter && (previousFilter == filetree.DisplayUntracked || filter == filetree.DisplayUntracked) {
|
||||
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}, Mode: types.ASYNC})
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}, Mode: types.ASYNC})
|
||||
} else {
|
||||
self.c.PostRefreshUpdate(self.context())
|
||||
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *FilesController) edit(nodes []*filetree.FileNode) error {
|
||||
@@ -1144,7 +1144,7 @@ func (self *FilesController) stash() error {
|
||||
}
|
||||
|
||||
func (self *FilesController) createResetToUpstreamMenu() error {
|
||||
return self.c.Helpers().Refs.CreateGitResetMenu("@{upstream}")
|
||||
return self.c.Helpers().Refs.CreateGitResetMenu("@{upstream}", "@{upstream}")
|
||||
}
|
||||
|
||||
func (self *FilesController) handleToggleDirCollapsed() error {
|
||||
@@ -1176,7 +1176,8 @@ func (self *FilesController) handleStashSave(stashFunc func(message string) erro
|
||||
if err := stashFunc(stashComment); err != nil {
|
||||
return err
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH, types.FILES}})
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.STASH, types.FILES}})
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1196,7 +1197,7 @@ func (self *FilesController) fetch() error {
|
||||
return errors.New(self.c.Tr.PassUnameWrong)
|
||||
}
|
||||
|
||||
_ = self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.SYNC})
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.SYNC})
|
||||
|
||||
if err == nil {
|
||||
err = self.c.Helpers().BranchesHelper.AutoForwardBranches()
|
||||
@@ -1321,7 +1322,8 @@ func (self *FilesController) remove(selectedNodes []*filetree.FileNode) error {
|
||||
}
|
||||
}
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.WORKTREES}})
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.WORKTREES}})
|
||||
return nil
|
||||
},
|
||||
Key: self.c.KeybindingsOpts().GetKey(self.c.UserConfig().Keybinding.Files.ConfirmDiscard),
|
||||
Tooltip: utils.ResolvePlaceholderString(
|
||||
@@ -1347,7 +1349,8 @@ func (self *FilesController) remove(selectedNodes []*filetree.FileNode) error {
|
||||
}
|
||||
}
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.WORKTREES}})
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.WORKTREES}})
|
||||
return nil
|
||||
},
|
||||
Key: 'u',
|
||||
Tooltip: utils.ResolvePlaceholderString(
|
||||
@@ -1388,7 +1391,8 @@ func (self *FilesController) ResetSubmodule(submodule *models.SubmoduleConfig) e
|
||||
return err
|
||||
}
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.SUBMODULES}})
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.FILES, types.SUBMODULES}})
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -122,9 +122,10 @@ func (self *FilteringMenuAction) setFiltering() error {
|
||||
|
||||
self.c.Context().Push(self.c.Contexts().LocalCommits, types.OnFocusOpts{})
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}, Then: func() error {
|
||||
self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.COMMITS}, Then: func() {
|
||||
self.c.Contexts().LocalCommits.SetSelection(0)
|
||||
self.c.Contexts().LocalCommits.FocusLine()
|
||||
return nil
|
||||
}})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -143,7 +143,8 @@ func (self *GlobalController) createCustomPatchOptionsMenu() error {
|
||||
}
|
||||
|
||||
func (self *GlobalController) refresh() error {
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *GlobalController) nextScreenMode() error {
|
||||
|
||||
@@ -22,13 +22,14 @@ func (self *BisectHelper) Reset() error {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.PostBisectCommandRefresh()
|
||||
self.PostBisectCommandRefresh()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BisectHelper) PostBisectCommandRefresh() error {
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{}})
|
||||
func (self *BisectHelper) PostBisectCommandRefresh() {
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{}})
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user