mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-29 02:35:39 -04:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ab7b5f6d84 | ||
|
|
4065175a58 | ||
|
|
43106b6c7f | ||
|
|
7bea41534b | ||
|
|
14a91d9829 | ||
|
|
274e24d75e | ||
|
|
a1a8cd114d | ||
|
|
c64a7904b7 | ||
|
|
3e623cd1ce | ||
|
|
6da99a49a4 | ||
|
|
977a01172f | ||
|
|
91cb1ff29a | ||
|
|
28d10c26a4 | ||
|
|
2c321011db | ||
|
|
dbd407c01d | ||
|
|
b8d5e481bb | ||
|
|
2b3525bfd6 | ||
|
|
b2c46c33b6 | ||
|
|
c44231a7d7 | ||
|
|
3518ec9f72 | ||
|
|
ec19fcf134 | ||
|
|
2d193cb296 | ||
|
|
64cd7cd9f6 | ||
|
|
eaaf123238 | ||
|
|
1c5fe8ff17 | ||
|
|
49ca7f6a84 | ||
|
|
80c212faba | ||
|
|
ae53059ed2 | ||
|
|
13829d8ff7 | ||
|
|
53b1e12110 | ||
|
|
009062534e | ||
|
|
33e81f717d | ||
|
|
ef718f3386 | ||
|
|
bf9339557e | ||
|
|
ebfc7ff7c6 | ||
|
|
928e76a82f | ||
|
|
9de8d17d84 | ||
|
|
9776be3131 | ||
|
|
7f7d9b166f | ||
|
|
3dd2e25dfa | ||
|
|
9790a7e00c | ||
|
|
a5c6a6120d | ||
|
|
bf9f9b6c04 | ||
|
|
d520d5e404 | ||
|
|
3241a9c251 | ||
|
|
4c4a2c3ff4 | ||
|
|
508cdb40a8 | ||
|
|
ae459b2377 | ||
|
|
c834e132c7 | ||
|
|
5e9ba82d7b | ||
|
|
fc78082e81 | ||
|
|
d7d5733a71 | ||
|
|
5240b2862f | ||
|
|
e1c18226bf | ||
|
|
c4e59aea66 | ||
|
|
75311750c8 | ||
|
|
16a158a025 | ||
|
|
621229bb09 | ||
|
|
536ef82866 | ||
|
|
54680e0836 | ||
|
|
fc69945a80 | ||
|
|
69a048c0ff | ||
|
|
6c6d835d50 | ||
|
|
2a72e96011 | ||
|
|
a507c836b2 | ||
|
|
8b90cca521 | ||
|
|
3573289452 | ||
|
|
87b54a4107 | ||
|
|
1b0d3829f8 | ||
|
|
aad3dc42a7 | ||
|
|
f884cc2af9 | ||
|
|
f4c8287143 | ||
|
|
03d7bc854e | ||
|
|
fdf1643f63 | ||
|
|
d93be027b6 | ||
|
|
fd5be11370 | ||
|
|
1e1fba4ce6 | ||
|
|
b174791490 | ||
|
|
a4ecd293f9 | ||
|
|
5fac40c129 | ||
|
|
ec410b2ae6 | ||
|
|
3a30211099 | ||
|
|
ec92d92bf4 | ||
|
|
87d5da511e | ||
|
|
e29ddf46d2 | ||
|
|
15288b7bf4 | ||
|
|
2828fb94fb | ||
|
|
5213a9de32 | ||
|
|
b7444b9a49 | ||
|
|
5d3b3c6656 | ||
|
|
65a28c4c3b | ||
|
|
1f2cb35cc9 | ||
|
|
2b49865d0d | ||
|
|
da47498066 | ||
|
|
2f1564d288 | ||
|
|
3610f13418 | ||
|
|
2417b70acd | ||
|
|
b61c395278 | ||
|
|
a9ef69b9c7 | ||
|
|
cf27974ea3 | ||
|
|
83356d441f | ||
|
|
49c50fc95c | ||
|
|
b719dc4d8e | ||
|
|
75121384a3 | ||
|
|
078445db63 | ||
|
|
64eb3d560b | ||
|
|
2823a7cff0 | ||
|
|
d5f2fb6003 | ||
|
|
bd0d9ef259 | ||
|
|
75d2fb1df2 | ||
|
|
13e9e1de83 | ||
|
|
93a37cf83e | ||
|
|
0f269183de | ||
|
|
4268701606 | ||
|
|
799827ee0e | ||
|
|
4a7cf6040e | ||
|
|
f455f99705 | ||
|
|
f3a5c184e1 | ||
|
|
1543b83d10 | ||
|
|
4cfeb18632 | ||
|
|
3a4422fc68 | ||
|
|
6da42b07cd | ||
|
|
954347c5bd | ||
|
|
7fb9e8fa9a | ||
|
|
5cca4c7063 | ||
|
|
53dc23a037 | ||
|
|
debfe1a21f | ||
|
|
d84986880e | ||
|
|
17bb3970c1 | ||
|
|
016d46526c | ||
|
|
0766b14afd | ||
|
|
4624d496a2 | ||
|
|
ea03ae5ee3 | ||
|
|
51e5816dd7 | ||
|
|
c1b4201726 | ||
|
|
0b0910573b | ||
|
|
92bce7de43 | ||
|
|
e98cc4d016 | ||
|
|
2ffd52acd1 | ||
|
|
24e98d1792 | ||
|
|
a50712b6c2 | ||
|
|
64cebfc0a8 | ||
|
|
b07109de4d | ||
|
|
59303981f9 | ||
|
|
62e31efd0f | ||
|
|
f6f2a52dee | ||
|
|
579053d5a3 | ||
|
|
10db72d223 | ||
|
|
c3cf48cc49 | ||
|
|
949e131ebe | ||
|
|
b62546c391 | ||
|
|
de8dc935a3 | ||
|
|
dd765801db | ||
|
|
111407d9a6 | ||
|
|
9f03f9e95d | ||
|
|
181b00b758 | ||
|
|
e1e4e1be1f | ||
|
|
fdeaf9cea0 | ||
|
|
b0a766cc95 | ||
|
|
f858460ab9 | ||
|
|
b2cbd93619 | ||
|
|
8da43af924 | ||
|
|
7edf629eeb | ||
|
|
1e8b8471ca | ||
|
|
095eb130e9 | ||
|
|
f08b3e9e1d | ||
|
|
8f3e59b78e | ||
|
|
eaf3bf0971 | ||
|
|
4883c867bb | ||
|
|
a58770ee1b | ||
|
|
7655f6864e | ||
|
|
286e5f4849 | ||
|
|
052974be65 | ||
|
|
85523402d6 | ||
|
|
f473d23d65 | ||
|
|
59a937ee7a | ||
|
|
53f8249ee1 | ||
|
|
f71274b601 | ||
|
|
f2fd435c05 | ||
|
|
4e361e1a87 | ||
|
|
696e78fcc8 | ||
|
|
b71aa5e23b | ||
|
|
72cf3efb1d | ||
|
|
ae610dcbb7 | ||
|
|
d11e11d179 | ||
|
|
825f5c0a91 | ||
|
|
26e3a93fc3 | ||
|
|
1ceb5a6b37 | ||
|
|
65b731f484 | ||
|
|
c6a7722066 | ||
|
|
1ab70ec645 | ||
|
|
e181de1180 | ||
|
|
c712b1d0fe | ||
|
|
c4e5995cb9 | ||
|
|
be3683ccc8 | ||
|
|
7e7309f97e | ||
|
|
d2b6f93858 | ||
|
|
a91fe517d3 | ||
|
|
8a328a553a | ||
|
|
3d56357294 | ||
|
|
9b2a0c4538 | ||
|
|
a04ad24a60 | ||
|
|
0d633896ae | ||
|
|
611fabde11 | ||
|
|
f8073c7188 | ||
|
|
4dadcd2ace | ||
|
|
b18f12ca0f | ||
|
|
c67979abbb | ||
|
|
0e489bb5cc | ||
|
|
647f533e71 | ||
|
|
b22149d832 | ||
|
|
396215a5c9 | ||
|
|
42c157a5e6 | ||
|
|
a793f709b6 | ||
|
|
f74551e464 | ||
|
|
e06b1cef60 |
@@ -21,6 +21,9 @@ changelog:
|
||||
- title: I18n 🌎
|
||||
labels:
|
||||
- i18n
|
||||
- title: Performance Improvements 📊
|
||||
labels:
|
||||
- performance
|
||||
- title: Other Changes
|
||||
labels:
|
||||
- "*"
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
mkdir -p /tmp/code_coverage
|
||||
go test ./... -short -cover -args "-test.gocoverdir=/tmp/code_coverage"
|
||||
- name: Upload code coverage artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-unit-${{ matrix.os }}-${{ github.run_id }}
|
||||
path: /tmp/code_coverage
|
||||
@@ -100,7 +100,7 @@ jobs:
|
||||
mkdir -p /tmp/code_coverage
|
||||
./scripts/run_integration_tests.sh
|
||||
- name: Upload code coverage artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-integration-${{ matrix.git-version }}-${{ github.run_id }}
|
||||
path: /tmp/code_coverage
|
||||
@@ -184,7 +184,7 @@ jobs:
|
||||
with:
|
||||
mode: exactly
|
||||
count: 1
|
||||
labels: "ignore-for-release, feature, enhancement, bug, maintenance, docs, i18n"
|
||||
labels: "ignore-for-release, feature, enhancement, bug, maintenance, docs, i18n, performance"
|
||||
upload-coverage:
|
||||
# List all jobs that produce coverage files
|
||||
needs: [unit-tests, integration-tests]
|
||||
@@ -200,7 +200,7 @@ jobs:
|
||||
go-version: 1.22.x
|
||||
|
||||
- name: Download all coverage artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: /tmp/code_coverage
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
name: Automated Release
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Runs at 2:00 AM UTC on the first Saturday of every month
|
||||
- cron: '0 2 * * 6'
|
||||
workflow_dispatch: # Allow manual triggering of the workflow
|
||||
|
||||
jobs:
|
||||
check-and-release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Check for changes since last release
|
||||
run: |
|
||||
if [ -z "$(git diff --name-only ${{ env.latest_tag }})" ]; then
|
||||
echo "No changes detected since last release"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Check for Blocking Issues/PRs
|
||||
id: check_blocks
|
||||
run: |
|
||||
gh auth setup-git
|
||||
gh auth status
|
||||
|
||||
echo "Checking for blocking issues and PRs..."
|
||||
|
||||
# Check for blocking issues
|
||||
blocking_issues=$(gh issue list -l blocks-release --json number,title --jq '.[] | "- \(.title) (#\(.number))"')
|
||||
|
||||
# Check for blocking PRs
|
||||
blocking_prs=$(gh pr list -l blocks-release --json number,title --jq '.[] | "- \(.title) (#\(.number)) (PR)"')
|
||||
|
||||
# Combine the results
|
||||
blocking_items="$blocking_issues"$'\n'"$blocking_prs"
|
||||
|
||||
# Remove empty lines
|
||||
blocking_items=$(echo "$blocking_items" | grep . || true)
|
||||
|
||||
if [ -n "$blocking_items" ]; then
|
||||
echo "Blocking issues/PRs detected:"
|
||||
echo "$blocking_items"
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Calculate next version
|
||||
run: |
|
||||
latest_tag=$(git describe --tags $(git rev-list --tags --max-count=1) || echo "v0.0.0")
|
||||
echo "Latest tag: $latest_tag"
|
||||
IFS='.' read -r major minor patch <<< "$latest_tag"
|
||||
new_minor=$((minor + 1))
|
||||
new_tag="$major.$new_minor.0"
|
||||
echo "New tag: $new_tag"
|
||||
echo "new_tag=$new_tag" >> $GITHUB_ENV
|
||||
|
||||
# This will trigger a deploy via .github/workflows/cd.yml
|
||||
- name: Push New Tag
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git tag ${{ env.new_tag }}
|
||||
git push origin ${{ env.new_tag }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -10,6 +10,10 @@ before making a change.
|
||||
|
||||
[This video](https://www.youtube.com/watch?v=kNavnhzZHtk) walks through the process of adding a small feature to lazygit. If you have no idea where to start, watching that video is a good first step.
|
||||
|
||||
## Design principles
|
||||
|
||||
See [here](./VISION.md) for a set of design principles that we want to consider when building a feature or making a change.
|
||||
|
||||
## Codebase guide
|
||||
|
||||
[This doc](./docs/dev/Codebase_Guide.md) explains:
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
# Vision and Design Principles
|
||||
|
||||
## Vision
|
||||
|
||||
Lazygit's vision is to be the most enjoyable UI for git.
|
||||
|
||||
## Design Principles
|
||||
|
||||
There are seven (sometimes contradictory) design principles we follow:
|
||||
|
||||
- Dicoverability
|
||||
- Simplicity
|
||||
- Safety
|
||||
- Power
|
||||
- Speed
|
||||
- Conformity with git
|
||||
- Think of the codebase
|
||||
|
||||
### Discoverability
|
||||
|
||||
TUI's are notoriously hard to learn, thanks to limited screen real-estate to provide contextual help and a general lack of effort on the part of developers to make things obvious. We want Lazygit to buck the trend and be easy for a new user to grok.
|
||||
|
||||
Examples:
|
||||
|
||||
- Clearly document all the features/configuration options
|
||||
- e.g. gifs in the README
|
||||
- Document how to solve various git problems with Lazygit
|
||||
- This is something we don't have yet but should: a section in the docs explaining how Lazygit can help you in various scenarios
|
||||
- Use tooltips to explain what actions will do
|
||||
- Make it easy for users to ask questions and get answers from the community
|
||||
- Make it easy to find entities and actions from within Lazygit
|
||||
- Use visual elements to make things obvious
|
||||
- e.g. '<-- YOU ARE HERE' label when rebasing
|
||||
- Don't require the user to memorise keybindings
|
||||
- e.g. when the user is mid-rebase, we prominently show that the keybinding for viewing rebase options is 'm'
|
||||
- When the user performs an action in Lazygit, make the impact obvious
|
||||
- If the affected entity isn't visible, show a toast notification
|
||||
- If a keybinding is disabled, give a reason why
|
||||
|
||||
### Simplicity
|
||||
|
||||
The git CLI is very complex but most git use cases are simple. Lazygit needs to ensure that simple use cases are easy to satisfy.
|
||||
|
||||
- Make the most common use cases dead-simple (staging files, committing, pulling/pushing)
|
||||
- Don't overwhelm the user with options
|
||||
- Use sensible defaults
|
||||
- We already have too many configuration options: think hard before adding any new ones
|
||||
|
||||
### Safety
|
||||
|
||||
It's easy to screw things up in git so Lazygit should try to protect the user from screwing things up.
|
||||
|
||||
- Prompt for a confirmation before doing anything that's hard to reverse
|
||||
- Make it easy to correct mistakes
|
||||
- e.g. undo action
|
||||
- the escape key should get you out of most transient situations (rebasing, diffing, etc)
|
||||
|
||||
## Power
|
||||
|
||||
Users shouldn't have to drop down the CLI _too_ often. Lazygit should be able to handle some complex use cases.
|
||||
|
||||
- Make complex (but common) CLI flows simple
|
||||
- e.g. interactive rebasing
|
||||
- Use the custom commands system to handle the really rare complex edge-cases
|
||||
|
||||
### Speed
|
||||
|
||||
Pro users should be able to move at lightning speed with Lazygit.
|
||||
|
||||
- Always think about the number of keypresses involved in a given UX flow
|
||||
- Make lazygit performant and responsive
|
||||
- Think about the individual commands being run and how fast they are
|
||||
- Startup should be FAST. If you want to run something at startup that is slow, make it non-blocking.
|
||||
- Support muscle-memory
|
||||
- Prefer disabling menu items instead of hiding them so that muscle memory can be used to select the desired menu item
|
||||
- Try to make keybinding intuitions to transfer across contexts (e.g. 'd' for destroy)
|
||||
- When changing keybindings in a new release, always consider what will happen if a user does not read the release notes and relies on muscle memory.
|
||||
|
||||
### Conformity with git
|
||||
|
||||
Satisfying the use-cases of git users is more important than perfectly conforming to git's API, but even obscure parts of git's API were motivated by real use-cases.
|
||||
|
||||
- Users should only have to drop down to the git CLI in rare circumstances
|
||||
- Honour the git config
|
||||
- Don't override anything set in the git config without the user's permission
|
||||
- Work with git, not against it.
|
||||
- Too much magic will get us into trouble
|
||||
- Avoid storing Lazygit-specific session state that could instead be stored in git
|
||||
- Ensure that Lazygit can represent the state of any repo
|
||||
- Sometimes git's default behaviour is just silly and we'll make the call to override but it should be a well-considered decision.
|
||||
|
||||
### Think of the codebase
|
||||
|
||||
Will somebody PLEASE think of the codebase!
|
||||
|
||||
Some features are not worth the added complexity in the codebase. The more this codebase grows, the harder it will be to make the changes that everybody wants.
|
||||
|
||||
## Resolving conflicts
|
||||
|
||||
Many of the above objectives are directly antithetical to one another. If you add an extra confirmation prompt for the sake of _safety_, you're sacrificing _speed_. If you support toggling various git flags in the name of _power_, you're sacrificing _simplicity_. There are a few things to say here.
|
||||
|
||||
When there are conflicts, we need to make a judgement call. In general we should err on the side of safety and simplicity as the default, with the ability for users to make things faster / more powerful either through configuration or separate keybindings.
|
||||
|
||||
This does not mean for example that force pushes should be impossible without being manually enabled: force pushes are table stakes for anybody who rebases. But it does mean that a confirmation popup should appear when force pushing.
|
||||
+62
-8
@@ -87,6 +87,11 @@ gui:
|
||||
# - 'top': split the window vertically (side panel on top, main view below)
|
||||
enlargedSideViewLocation: left
|
||||
|
||||
# If true, wrap lines in the staging view to the width of the view. This
|
||||
# makes it much easier to work with diffs that have long lines, e.g.
|
||||
# paragraphs of markdown text.
|
||||
wrapLinesInStagingView: true
|
||||
|
||||
# One of 'auto' (default) | 'en' | 'zh-CN' | 'zh-TW' | 'pl' | 'nl' | 'ja' | 'ko' | 'ru'
|
||||
language: auto
|
||||
|
||||
@@ -164,6 +169,9 @@ gui:
|
||||
# This can be toggled from within Lazygit with the '~' key, but that will not change the default.
|
||||
showFileTree: true
|
||||
|
||||
# If true, show the number of lines changed per file in the Files view
|
||||
showNumstatInFilesView: false
|
||||
|
||||
# If true, show a random tip in the command log when Lazygit starts
|
||||
showRandomTip: true
|
||||
|
||||
@@ -211,9 +219,9 @@ gui:
|
||||
# If 'auto', only split the main window when a file has both staged and unstaged changes
|
||||
splitDiff: auto
|
||||
|
||||
# Default size for focused window. Window size can be changed from within Lazygit with '+' and '_' (but this won't change the default).
|
||||
# Default size for focused window. Can be changed from within Lazygit with '+' and '_' (but this won't change the default).
|
||||
# One of: 'normal' (default) | 'half' | 'full'
|
||||
windowSize: normal
|
||||
screenMode: normal
|
||||
|
||||
# Window border style.
|
||||
# One of 'rounded' (default) | 'single' | 'double' | 'hidden'
|
||||
@@ -246,6 +254,15 @@ gui:
|
||||
# One of 'dashboard' (default) | 'allBranchesLog'
|
||||
statusPanelView: dashboard
|
||||
|
||||
# If true, jump to the Files panel after popping a stash
|
||||
switchToFilesAfterStashPop: true
|
||||
|
||||
# If true, jump to the Files panel after applying a stash
|
||||
switchToFilesAfterStashApply: true
|
||||
|
||||
# If true, when using the panel jump keys (default 1 through 5) and target panel is already active, go to next tab instead
|
||||
switchTabsWithPanelJumpKeys: false
|
||||
|
||||
# Config relating to git
|
||||
git:
|
||||
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Custom_Pagers.md
|
||||
@@ -315,7 +332,7 @@ git:
|
||||
branchLogCmd: git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --
|
||||
|
||||
# Command used to display git log of all branches in the main window.
|
||||
# Deprecated: User `allBranchesLogCmds` instead.
|
||||
# Deprecated: Use `allBranchesLogCmds` instead.
|
||||
allBranchesLogCmd: git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium
|
||||
|
||||
# If true, do not spawn a separate process when using GPG
|
||||
@@ -554,6 +571,8 @@ keybinding:
|
||||
openMergeTool: M
|
||||
openStatusFilter: <c-b>
|
||||
copyFileInfoToClipboard: "y"
|
||||
collapseAll: '-'
|
||||
expandAll: =
|
||||
branches:
|
||||
createPullRequest: o
|
||||
viewPullRequestOptions: O
|
||||
@@ -642,6 +661,13 @@ os:
|
||||
open: 'open {{filename}}'
|
||||
```
|
||||
|
||||
## Custom Command for Opening a Link
|
||||
```yaml
|
||||
os:
|
||||
openLink: 'bash -C /path/to/your/shell-script.sh {{link}}'
|
||||
```
|
||||
Specify the external command to invoke when opening URL links (i.e. creating MR/PR in GitLab, BitBucket or GitHub). `{{link}}` will be replaced by the URL to be opened. A simple shell script can be used to further mangle the passed URL.
|
||||
|
||||
## Custom Command for Copying to and Pasting from Clipboard
|
||||
```yaml
|
||||
os:
|
||||
@@ -650,9 +676,26 @@ os:
|
||||
Specify an external command to invoke when copying to clipboard is requested. `{{text}` will be replaced by text to be copied. Default is to copy to system clipboard.
|
||||
|
||||
If you are working on a terminal that supports OSC52, the following command will let you take advantage of it:
|
||||
```
|
||||
```yaml
|
||||
os:
|
||||
copyToClipboardCmd: printf "\033]52;c;$(printf {{text}} | base64)\a" > /dev/tty
|
||||
copyToClipboardCmd: printf "\033]52;c;$(printf {{text}} | base64 -w 0)\a" > /dev/tty
|
||||
```
|
||||
|
||||
For tmux you need to wrap it with the [tmux escape sequence](https://github.com/tmux/tmux/wiki/FAQ#what-is-the-passthrough-escape-sequence-and-how-do-i-use-it), and enable passthrough in tmux config with `set -g allow-passthrough on`:
|
||||
```yaml
|
||||
os:
|
||||
copyToClipboardCmd: printf "\033Ptmux;\033\033]52;c;$(printf {{text}} | base64 -w 0)\a\033\\" > /dev/tty
|
||||
```
|
||||
|
||||
For the best of both worlds, we can let the command determine if we are running in a tmux session and send the correct sequence:
|
||||
```yaml
|
||||
os:
|
||||
copyToClipboardCmd: >
|
||||
if [[ "$TERM" =~ ^(screen|tmux) ]]; then
|
||||
printf "\033Ptmux;\033\033]52;c;$(printf {{text}} | base64 -w 0)\a\033\\" > /dev/tty
|
||||
else
|
||||
printf "\033]52;c;$(printf {{text}} | base64 -w 0)\a" > /dev/tty
|
||||
fi
|
||||
```
|
||||
|
||||
A custom command for reading from the clipboard can be set using
|
||||
@@ -791,14 +834,17 @@ gui:
|
||||
|
||||
## Custom Branch Color
|
||||
|
||||
You can customize the color of branches based on the branch prefix:
|
||||
You can customize the color of branches based on branch patterns (regular expressions):
|
||||
|
||||
```yaml
|
||||
gui:
|
||||
branchColors:
|
||||
'docs': '#11aaff' # use a light blue for branches beginning with 'docs/'
|
||||
branchColorPatterns:
|
||||
'^docs/': '#11aaff' # use a light blue for branches beginning with 'docs/'
|
||||
'ISSUE-\d+': '#ff5733' # use a bright orange for branches containing 'ISSUE-<some-number>'
|
||||
```
|
||||
|
||||
Note that the regular expressions are not implicitly anchored to the beginning/end of the branch name. If you want to do that, add leading `^` and/or trailing `$` as needed.
|
||||
|
||||
## Example Coloring
|
||||
|
||||

|
||||
@@ -899,6 +945,14 @@ git:
|
||||
replace: '[$1] '
|
||||
```
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The way golang regex works is when you use `$n` in the replacement string, where `n` is a number, it puts the nth captured subgroup at that place. If `n` is out of range because there aren't that many capture groups in the regex, it puts an empty string there.
|
||||
>
|
||||
> So make sure you are capturing group or groups in your regex.
|
||||
>
|
||||
> For example `^[A-Z]+-\d+$` won't work on branch name like BRANCH-1111
|
||||
> But `^([A-Z]+-\d+)$` will
|
||||
|
||||
## Predefined branch name prefix
|
||||
|
||||
In situations where certain naming pattern is used for branches, this can be used to populate new branch creation with a static prefix.
|
||||
|
||||
@@ -50,7 +50,7 @@ Custom command keybindings will appear alongside inbuilt keybindings when you vi
|
||||
For a given custom command, here are the allowed fields:
|
||||
| _field_ | _description_ | required |
|
||||
|-----------------|----------------------|-|
|
||||
| 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) | yes |
|
||||
| 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 |
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
## The use-case
|
||||
|
||||
This topic deserves its own doc because there there are a few touch points for it. We have a use-case for knowing when Lazygit is idle or busy because integration tests follow the following process:
|
||||
This topic deserves its own doc because there are a few touch points for it. We have a use-case for knowing when Lazygit is idle or busy because integration tests follow the following process:
|
||||
1) press a key
|
||||
2) wait until Lazygit is idle
|
||||
3) run assertion / press another key
|
||||
|
||||
@@ -65,6 +65,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` 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 tree view | 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 |
|
||||
| `` / `` | Search the current view by text | |
|
||||
|
||||
## Commit summary
|
||||
@@ -147,6 +149,8 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` M `` | Open external merge tool | 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 |
|
||||
| `` / `` | Search the current view by text | |
|
||||
|
||||
## Local branches
|
||||
@@ -348,7 +352,7 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | Checkout | Checkout the selected tag tag as a detached HEAD. |
|
||||
| `` <space> `` | Checkout | Checkout the selected tag as a detached HEAD. |
|
||||
| `` n `` | New tag | 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 tag | Push the selected tag to a remote. You'll be prompted to select a remote. |
|
||||
|
||||
@@ -143,6 +143,8 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| `` 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 |
|
||||
| `` / `` | 検索を開始 | |
|
||||
|
||||
## コミットメッセージ
|
||||
@@ -180,7 +182,7 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | チェックアウト | Checkout the selected tag tag as a detached HEAD. |
|
||||
| `` <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. |
|
||||
@@ -218,6 +220,8 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| `` <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 |
|
||||
| `` / `` | 検索を開始 | |
|
||||
|
||||
## ブランチ
|
||||
|
||||
@@ -308,6 +308,8 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| `` a `` | Toggle all files included in patch | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
|
||||
| `` <enter> `` | Enter file to add selected lines to the patch (or 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 |
|
||||
| `` / `` | 검색 시작 | |
|
||||
|
||||
## 커밋메시지
|
||||
@@ -321,7 +323,7 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | 체크아웃 | Checkout the selected tag tag as a detached HEAD. |
|
||||
| `` <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. |
|
||||
@@ -359,6 +361,8 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| `` <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 |
|
||||
| `` / `` | 검색 시작 | |
|
||||
|
||||
## 확인 패널
|
||||
|
||||
@@ -79,6 +79,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` M `` | Open external merge tool | 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 |
|
||||
| `` / `` | Start met zoeken | |
|
||||
|
||||
## Bevestigingspaneel
|
||||
@@ -136,6 +138,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` 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 bestand om geselecteerde regels toe te voegen aan de patch | 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 bestandsboom weergave | 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 |
|
||||
| `` / `` | Start met zoeken | |
|
||||
|
||||
## Commits
|
||||
@@ -348,7 +352,7 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | Uitchecken | Checkout the selected tag tag as a detached HEAD. |
|
||||
| `` <space> `` | Uitchecken | Checkout the selected tag as a detached HEAD. |
|
||||
| `` n `` | Creëer tag | 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 tag | Push the selected tag to a remote. You'll be prompted to select a remote. |
|
||||
|
||||
@@ -229,6 +229,8 @@ Jeśli chcesz zamiast tego rozpocząć interaktywny rebase od wybranego commita,
|
||||
| `` <c-t> `` | Otwórz zewnętrzne narzędzie różnic (git difftool) | |
|
||||
| `` M `` | Otwórz zewnętrzne narzędzie scalania | Uruchom `git mergetool`. |
|
||||
| `` f `` | Pobierz | Pobierz zmiany ze zdalnego serwera. |
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` / `` | Szukaj w bieżącym widoku po tekście | |
|
||||
|
||||
## Pliki commita
|
||||
@@ -245,6 +247,8 @@ Jeśli chcesz zamiast tego rozpocząć interaktywny rebase od wybranego commita,
|
||||
| `` a `` | Przełącz wszystkie pliki | Dodaj/usuń wszystkie pliki commita do niestandardowej łatki. Zobacz https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
|
||||
| `` <enter> `` | Wejdź do pliku / Przełącz zwiń katalog | Jeśli plik jest wybrany, wejdź do pliku, aby móc dodawać/usuwać poszczególne linie do niestandardowej łatki. Jeśli wybrany jest katalog, przełącz katalog. |
|
||||
| `` ` `` | Przełącz widok drzewa plików | Przełącz widok plików między płaskim a drzewem. Płaski układ pokazuje wszystkie ścieżki plików na jednej liście, układ drzewa grupuje pliki według katalogów. |
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` / `` | Szukaj w bieżącym widoku po tekście | |
|
||||
|
||||
## Podsumowanie commita
|
||||
|
||||
@@ -270,6 +270,8 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| `` a `` | Переключить все файлы, включённые в патч | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
|
||||
| `` <enter> `` | Введите файл, чтобы добавить выбранные строки в патч (или свернуть каталог переключения) | 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 |
|
||||
| `` / `` | Найти | |
|
||||
|
||||
## Статус
|
||||
@@ -286,7 +288,7 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | Переключить | Checkout the selected tag tag as a detached HEAD. |
|
||||
| `` <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 the selected tag to a remote. You'll be prompted to select a remote. |
|
||||
@@ -353,6 +355,8 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` M `` | Открыть внешний инструмент слияния (git mergetool) | Run `git mergetool`. |
|
||||
| `` f `` | Получить изменения | Fetch changes from remote. |
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` / `` | Найти | |
|
||||
|
||||
## Хранилище
|
||||
|
||||
@@ -2,7 +2,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct
|
||||
|
||||
# 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_
|
||||
|
||||
## 全局键绑定
|
||||
|
||||
@@ -11,28 +11,28 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` <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 the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
|
||||
| `` p `` | 拉取 | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
|
||||
| `` @ `` | 打开命令日志菜单 | 查看命令日志的选项,例如显示/隐藏命令日志以及聚焦命令日志 |
|
||||
| `` 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. |
|
||||
| `` } `` | 扩大差异视图中显示的上下文范围 | Increase the amount of the context shown around changes in the diff view. |
|
||||
| `` { `` | 缩小差异视图中显示的上下文范围 | Decrease the amount of the context shown around changes in the diff view. |
|
||||
| `` } `` | 扩大差异视图中显示的上下文范围 | 增加diff视图中围绕更改显示的上下文数量 |
|
||||
| `` { `` | 缩小差异视图中显示的上下文范围 | 减少diff视图中围绕更改显示的上下文数量 |
|
||||
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
|
||||
| `` <c-p> `` | 查看自定义补丁选项 | |
|
||||
| `` m `` | 查看 合并/变基 选项 | 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`. |
|
||||
| `` + `` | 下一屏模式(正常/半屏/全屏) | |
|
||||
| `` m `` | 查看 合并/变基 选项 | 查看当前合并或变基的中止、继续、跳过选项 |
|
||||
| `` R `` | 刷新 | 刷新git状态(即在后台上运行`git status`,`git branch`等命令以更新面板内容) 不会运行`git fetch` |
|
||||
| `` + `` | 下一屏模式(正常/半屏/全屏) | |
|
||||
| `` _ `` | 上一屏模式 | |
|
||||
| `` ? `` | 打开菜单 | |
|
||||
| `` <c-s> `` | 查看按路径过滤选项 | View options for filtering the commit log, so that only commits matching the filter are shown. |
|
||||
| `` W `` | 打开 diff 菜单 | 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> `` | 打开 diff 菜单 | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
|
||||
| `` <c-s> `` | 查看按路径过滤选项 | 查看用于过滤提交日志的选项,以便仅显示与过滤器匹配的提交。 |
|
||||
| `` W `` | 打开 diff 菜单 | 查看与比较两个引用相关的选项,例如与选定的 ref 进行比较,输入要比较的 ref,然后反转比较方向。 |
|
||||
| `` <c-e> `` | 打开 diff 菜单 | 查看与比较两个引用相关的选项,例如与选定的 ref 进行比较,输入要比较的 ref,然后反转比较方向。 |
|
||||
| `` q `` | 退出 | |
|
||||
| `` <esc> `` | 取消 | |
|
||||
| `` <c-w> `` | 切换是否在差异视图中显示空白字符差异 | Toggle whether or not whitespace changes are shown in the diff view. |
|
||||
| `` z `` | (通过 reflog)撤销「实验功能」 | 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> `` | (通过 reflog)重做「实验功能」 | 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> `` | 切换是否在差异视图中显示空白字符差异 | 切换是否在diff视图中显示空白更改 |
|
||||
| `` z `` | (通过 reflog)撤销「实验功能」 | Reflog将用于确定运行哪个git命令来撤消最后一个git命令。这并不包括对工作树的更改,只考虑提交。 |
|
||||
| `` <c-z> `` | (通过 reflog)重做「实验功能」 | Reflog将用于确定运行哪个git命令来重做上一个git命令。这并不包括对工作树的更改,只考虑提交。 |
|
||||
|
||||
## 列表面板导航
|
||||
|
||||
@@ -43,8 +43,8 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` < `` | 滚动到顶部 | |
|
||||
| `` > `` | 滚动到底部 | |
|
||||
| `` v `` | 切换拖动选择 | |
|
||||
| `` <s-down> `` | Range select down | |
|
||||
| `` <s-up> `` | Range select up | |
|
||||
| `` <s-down> `` | 向下扩展选择范围 | |
|
||||
| `` <s-up> `` | 向上扩展选择范围 | |
|
||||
| `` / `` | 开始搜索 | |
|
||||
| `` H `` | 向左滚动 | |
|
||||
| `` L `` | 向右滚动 | |
|
||||
@@ -56,27 +56,17 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
|
||||
| `` <space> `` | 检出 | Checkout the selected commit as a detached HEAD. |
|
||||
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
|
||||
| `` <space> `` | 检出 | 检出所选择的提交作为分离HEAD。 |
|
||||
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(例如,hash、URL、diff、消息、作者)。 |
|
||||
| `` o `` | 在浏览器中打开提交 | |
|
||||
| `` n `` | 从提交创建新分支 | |
|
||||
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | 复制提交(拣选) | 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> `` | 重置已拣选(复制)的提交 | |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
|
||||
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,你可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
|
||||
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
|
||||
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
|
||||
| `` <enter> `` | 查看提交 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## 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 | |
|
||||
| `` w `` | 查看工作区选项 | |
|
||||
| `` / `` | 通过文本过滤当前视图 | |
|
||||
|
||||
## 分支页面
|
||||
|
||||
@@ -84,42 +74,42 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将分支名称复制到剪贴板 | |
|
||||
| `` i `` | 显示 git-flow 选项 | |
|
||||
| `` <space> `` | 检出 | Checkout selected item. |
|
||||
| `` <space> `` | 检出 | 检出选中的项目 |
|
||||
| `` n `` | 新分支 | |
|
||||
| `` o `` | 创建抓取请求 | |
|
||||
| `` O `` | 创建抓取请求选项 | |
|
||||
| `` <c-y> `` | 将抓取请求 URL 复制到剪贴板 | |
|
||||
| `` c `` | 按名称检出 | 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 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 selected branch from its upstream. |
|
||||
| `` o `` | 创建拉取请求 | |
|
||||
| `` O `` | 创建拉取请求选项 | |
|
||||
| `` <c-y> `` | 将拉取请求 URL 复制到剪贴板 | |
|
||||
| `` c `` | 按名称检出 | 按名称检出。在输入框中,您可以输入'-' 来切换到最后一个分支。 |
|
||||
| `` F `` | 强制检出 | 强制检出所选分支。这将在检出所选分支之前放弃工作目录中的所有本地更改。 |
|
||||
| `` d `` | 删除 | 查看本地/远程分支的删除选项 |
|
||||
| `` r `` | 将已检出的分支变基到该分支 | 将检出的分支变基到所选的分支上。 |
|
||||
| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. |
|
||||
| `` f `` | 从上游快进此分支 | 将当前分支直接移动到远程追踪分支的最新提交 |
|
||||
| `` T `` | 创建标签 | |
|
||||
| `` s `` | Sort order | |
|
||||
| `` s `` | 排序 | |
|
||||
| `` g `` | 查看重置选项 | |
|
||||
| `` 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) | |
|
||||
| `` u `` | 查看上游选项 | 查看与分支上游相关的选项,例如设置/取消设置上游和重置为上游。 |
|
||||
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
|
||||
| `` <enter> `` | 查看提交 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
| `` w `` | 查看工作区选项 | |
|
||||
| `` / `` | 通过文本过滤当前视图 | |
|
||||
|
||||
## 子提交
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
|
||||
| `` <space> `` | 检出 | Checkout the selected commit as a detached HEAD. |
|
||||
| `` y `` | Copy commit attribute to clipboard | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
|
||||
| `` <space> `` | 检出 | 检出所选择的提交作为分离HEAD。 |
|
||||
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(例如,hash、URL、diff、消息、作者)。 |
|
||||
| `` o `` | 在浏览器中打开提交 | |
|
||||
| `` n `` | 从提交创建新分支 | |
|
||||
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | 复制提交(拣选) | 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> `` | 重置已拣选(复制)的提交 | |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
|
||||
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,你可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
|
||||
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
|
||||
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
|
||||
| `` <enter> `` | 查看提交的文件 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` w `` | 查看工作区选项 | |
|
||||
| `` / `` | 开始搜索 | |
|
||||
|
||||
## 子模块
|
||||
@@ -127,104 +117,118 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将子模块名称复制到剪贴板 | |
|
||||
| `` <enter> `` | Enter | 输入子模块 |
|
||||
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. |
|
||||
| `` u `` | Update | 更新子模块 |
|
||||
| `` <enter> `` | 进入 | 输入子模块 |
|
||||
| `` d `` | 删除 | 删除选定的子模块及其相应的目录 |
|
||||
| `` u `` | 更新 | 更新子模块 |
|
||||
| `` n `` | 添加新的子模块 | |
|
||||
| `` e `` | 更新子模块 URL | |
|
||||
| `` i `` | Initialize | 初始化子模块 |
|
||||
| `` i `` | 初始化 | 初始化子模块 |
|
||||
| `` b `` | 查看批量子模块选项 | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
| `` / `` | 通过文本过滤当前视图 | |
|
||||
|
||||
## 工作区
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` n `` | 新建工作树 | |
|
||||
| `` <space> `` | 切换 | 切换到选中的工作树 |
|
||||
| `` o `` | 在编辑器中编写 | |
|
||||
| `` d `` | 删除 | 删除选定的工作树。这将删除工作树的目录以及 .git 目录中有关工作树的元数据。 |
|
||||
| `` / `` | 通过文本过滤当前视图 | |
|
||||
|
||||
## 提交
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将提交的 hash 复制到剪贴板 | |
|
||||
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
|
||||
| `` <c-r> `` | 重置已拣选(复制)的提交 | |
|
||||
| `` b `` | 查看二分查找选项 | |
|
||||
| `` s `` | 压缩 | 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. |
|
||||
| `` s `` | 压缩(Squash) | 将已选提交压缩到该提交之下。这些选定的提交的消息会附加到该提交的消息之下。 |
|
||||
| `` f `` | 修正(fixup) | 将选定的提交合并到其下面的提交中。与压缩类似,但所选提交的消息将被丢弃。 |
|
||||
| `` r `` | 改写提交 | 重写所选提交的消息。 |
|
||||
| `` 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 `` | Pick | 选择提交(变基过程中) |
|
||||
| `` d `` | 删除提交 | 删除选中的提交。这将通过变基从分支中删除该提交,如果该提交修改的内容依赖于后续的提交,则需要解决合并冲突。 |
|
||||
| `` e `` | 编辑(开始交互式变基) | 编辑提交 |
|
||||
| `` i `` | 开始交互式变基 | 为分支上的提交启动交互式变基。这将包括从 HEAD 提交到第一个合并提交或主分支提交的所有提交。
|
||||
如果您想从所选提交启动交互式变基,请按 `e`。 |
|
||||
| `` p `` | 拣选(Pick) | 选择提交(变基过程中) |
|
||||
| `` F `` | 为此提交创建修正 | 创建修正提交 |
|
||||
| `` S `` | Apply fixup commits | 压缩在所选提交之上的所有“fixup!”提交(自动压缩) |
|
||||
| `` S `` | 应用该修复提交 | 压缩在所选提交之上的所有“fixup!”提交(自动压缩) |
|
||||
| `` <c-j> `` | 下移提交 | |
|
||||
| `` <c-k> `` | 上移提交 | |
|
||||
| `` V `` | 粘贴提交(拣选) | |
|
||||
| `` 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 | 用已暂存的更改来修补提交 |
|
||||
| `` 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 | Copy commit attribute to clipboard (e.g. hash, URL, diff, message, author). |
|
||||
| `` V `` | 粘贴提交(拣选) | |
|
||||
| `` B `` | 标记一个主提交用于变基 | 选择下一次变基的主提交。当您变基到一个分支时,只有高于主提交的提交才会被引入。这使用“git rebase --onto”命令。 |
|
||||
| `` A `` | 修补(Amend) | 用已暂存的变更来修补提交 |
|
||||
| `` a `` | 修补提交属性 | 设置或重置提交的作者,或添加其他作者。 |
|
||||
| `` t `` | 撤销(Revert) | 为所选提交创建还原提交,这会反向应用所选提交的更改。 |
|
||||
| `` T `` | 标签提交 | 创建一个新标签指向所选提交。你可以在弹窗中输入标签名称和描述(可选)。 |
|
||||
| `` <c-l> `` | 打开日志菜单 | 查看提交日志的选项,例如更改排序顺序、隐藏 git graph、显示整个 git graph。 |
|
||||
| `` <space> `` | 检出 | 检出所选择的提交作为分离HEAD。 |
|
||||
| `` y `` | 复制提交属性到剪贴板 | 复制提交属性到剪贴板(例如,hash、URL、diff、消息、作者)。 |
|
||||
| `` o `` | 在浏览器中打开提交 | |
|
||||
| `` n `` | 从提交创建新分支 | |
|
||||
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | 复制提交(拣选) | 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) | |
|
||||
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
|
||||
| `` C `` | 复制提交(拣选) | 标记提交为已复制。然后,在本地提交视图中,你可以按 `V` (Cherry-Pick) 将已复制的提交粘贴到已检出的分支中。任何时候都可以按 `<esc>` 来取消选择。 |
|
||||
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
|
||||
| `` <enter> `` | 查看提交的文件 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` w `` | 查看工作区选项 | |
|
||||
| `` / `` | 开始搜索 | |
|
||||
|
||||
## 提交信息
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <enter> `` | 确认 | |
|
||||
| `` <esc> `` | 关闭 | |
|
||||
|
||||
## 提交文件
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将文件名复制到剪贴板 | |
|
||||
| `` c `` | 检出 | 检出文件 |
|
||||
| `` d `` | Remove | 放弃对此文件的提交更改 |
|
||||
| `` o `` | 打开文件 | Open file in default application. |
|
||||
| `` e `` | Edit | Open file in external editor. |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <space> `` | 补丁中包含的切换文件 | 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> `` | 输入文件以将所选行添加到补丁中(或切换目录折叠) | 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. |
|
||||
| `` 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> `` | 输入文件以将所选行添加到补丁中(或切换目录折叠) | 如果已选择一个文件,则Enter进入该文件,以便您可以向自定义补丁添加/删除单独的行。如果选择了目录,则切换目录。 |
|
||||
| `` ` `` | 切换文件树视图 | 在平铺部署与树布局之间切换文件视图。平铺布局在一个列表中展示所有文件路径,树布局则根据目录分组展示。 |
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` / `` | 开始搜索 | |
|
||||
|
||||
## 提交讯息
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <enter> `` | 确认 | |
|
||||
| `` <esc> `` | 关闭 | |
|
||||
|
||||
## 文件
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将文件名复制到剪贴板 | |
|
||||
| `` <space> `` | 切换暂存状态 | Toggle staged for selected file. |
|
||||
| `` <c-b> `` | Filter files by status | |
|
||||
| `` y `` | Copy to clipboard | |
|
||||
| `` c `` | 提交更改 | Commit staged changes. |
|
||||
| `` w `` | 提交更改而无需预先提交钩子 | |
|
||||
| `` <space> `` | 切换暂存状态 | 为选定的文件切换暂存状态 |
|
||||
| `` <c-b> `` | 通过状态过滤文件 | |
|
||||
| `` y `` | 复制到剪贴板 | |
|
||||
| `` c `` | 提交变更 | 提交暂存文件 |
|
||||
| `` w `` | 提交变更而无需预先提交钩子 | |
|
||||
| `` A `` | 修补最后一次提交 | |
|
||||
| `` C `` | 提交更改(使用编辑器编辑提交信息) | |
|
||||
| `` <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 `` | Edit | Open file in external editor. |
|
||||
| `` o `` | 打开文件 | Open file in default application. |
|
||||
| `` C `` | 提交变更(使用编辑器编辑提交信息) | |
|
||||
| `` <c-f> `` | 找到用于修复的基准提交 | 找到您当前变更所基于的提交,以便于修正/改进该提交。这样做可以省去您逐一查看分支提交来确定应该修正/改进哪个提交的麻烦。请参阅文档: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
|
||||
| `` e `` | 编辑 | 使用外部编辑器打开文件 |
|
||||
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
|
||||
| `` i `` | 忽略文件 | |
|
||||
| `` r `` | 刷新文件 | |
|
||||
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. |
|
||||
| `` S `` | 查看贮藏选项 | View stash options (e.g. stash all, stash staged, stash unstaged). |
|
||||
| `` a `` | 切换所有文件的暂存状态 | Toggle staged/unstaged for all files in working tree. |
|
||||
| `` <enter> `` | 暂存单个 块/行 用于文件, 或 折叠/展开 目录 | 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 `` | 查看'放弃更改'选项 | View options for discarding changes to the selected file. |
|
||||
| `` s `` | 贮藏 | 贮藏所有变更.若要使用其他贮藏变体,请使用查看贮藏选项快捷键 |
|
||||
| `` S `` | 查看贮藏选项 | 查看贮藏选项(例如:贮藏所有、贮藏已暂存变更、贮藏未暂存变更) |
|
||||
| `` a `` | 切换所有文件的暂存状态 | 切换工作区中所有文件的已暂存/未暂存状态 |
|
||||
| `` <enter> `` | 暂存单个 块/行 用于文件, 或 折叠/展开 目录 | 如果选中的是一个文件,则会进入到暂存视图,以便可以暂存单个代码块/行。如果选中的是一个目录,则会折叠/展开这个目录 |
|
||||
| `` d `` | 查看'放弃变更'选项 | 查看选中文件的放弃变更选项 |
|
||||
| `` g `` | 查看上游重置选项 | |
|
||||
| `` 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 changes from remote. |
|
||||
| `` D `` | 重置 | 查看工作树的重置选项(例如:清除工作树)。 |
|
||||
| `` ` `` | 切换文件树视图 | 在平铺部署与树布局之间切换文件视图。平铺布局在一个列表中展示所有文件路径,树布局则根据目录分组展示。 |
|
||||
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
|
||||
| `` M `` | 打开外部合并工具(git mergetool) | 执行 `git mergetool`. |
|
||||
| `` f `` | 抓取 | 从远程获取变更 |
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` / `` | 开始搜索 | |
|
||||
|
||||
## 构建补丁中
|
||||
@@ -234,10 +238,10 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| `` <left> `` | 选择上一个区块 | |
|
||||
| `` <right> `` | 选择下一个区块 | |
|
||||
| `` v `` | 切换拖动选择 | |
|
||||
| `` a `` | 切换选择区块 | Toggle hunk selection mode. |
|
||||
| `` a `` | 切换选择代码块 | 切换代码块选择模式 |
|
||||
| `` <c-o> `` | 将选中文本复制到剪贴板 | |
|
||||
| `` o `` | 打开文件 | Open file in default application. |
|
||||
| `` e `` | 编辑文件 | Open file in external editor. |
|
||||
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
|
||||
| `` e `` | 编辑文件 | 使用外部编辑器打开文件 |
|
||||
| `` <space> `` | 添加/移除 行到补丁 | |
|
||||
| `` <esc> `` | 退出逐行模式 | |
|
||||
| `` / `` | 开始搜索 | |
|
||||
@@ -246,15 +250,15 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | 检出 | Checkout the selected tag 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 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) | |
|
||||
| `` <space> `` | 检出 | 检出选择的标签作为分离的HEAD |
|
||||
| `` n `` | 创建标签 | 基于当前提交创建一个新标签。你将在弹窗中输入标签名称和描述(可选)。 |
|
||||
| `` d `` | 删除 | 查看本地/远程标签的删除选项 |
|
||||
| `` P `` | 推送标签 | 推送选择的标签到远端。你将在弹窗中选择一个远端。 |
|
||||
| `` g `` | 重置 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
|
||||
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
|
||||
| `` <enter> `` | 查看提交 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
| `` w `` | 查看工作区选项 | |
|
||||
| `` / `` | 通过文本过滤当前视图 | |
|
||||
|
||||
## 正在合并
|
||||
|
||||
@@ -266,10 +270,10 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| `` <down> `` | 选择底部块 | |
|
||||
| `` <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`. |
|
||||
| `` z `` | 撤销 | 撤消上次合并冲突解决 |
|
||||
| `` e `` | 编辑文件 | 使用外部编辑器打开文件 |
|
||||
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
|
||||
| `` M `` | 打开外部合并工具(git mergetool) | 执行 `git mergetool`. |
|
||||
| `` <esc> `` | 返回文件面板 | |
|
||||
|
||||
## 正在暂存
|
||||
@@ -279,19 +283,19 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| `` <left> `` | 选择上一个区块 | |
|
||||
| `` <right> `` | 选择下一个区块 | |
|
||||
| `` v `` | 切换拖动选择 | |
|
||||
| `` a `` | 切换选择区块 | Toggle hunk selection mode. |
|
||||
| `` a `` | 切换选择代码块 | 切换代码块选择模式 |
|
||||
| `` <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. |
|
||||
| `` d `` | 取消变更(git reset) | 当选择未暂存的变更时,使用git reset丢弃该变更。当选择已暂存的变更时,取消暂存该变更 |
|
||||
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
|
||||
| `` e `` | 编辑文件 | 使用外部编辑器打开文件 |
|
||||
| `` <esc> `` | 返回文件面板 | |
|
||||
| `` <tab> `` | 切换到其他面板 | Switch to other view (staged/unstaged changes). |
|
||||
| `` E `` | Edit hunk | Edit selected hunk in external editor. |
|
||||
| `` c `` | 提交更改 | Commit staged changes. |
|
||||
| `` w `` | 提交更改而无需预先提交钩子 | |
|
||||
| `` C `` | 提交更改(使用编辑器编辑提交信息) | |
|
||||
| `` <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> |
|
||||
| `` <tab> `` | 切换到其他面板 | 切换到其他视图(已暂存/未暂存的变更) |
|
||||
| `` E `` | 编辑代码块 | 在外部编辑器中编辑选中的代码块 |
|
||||
| `` c `` | 提交变更 | 提交暂存文件 |
|
||||
| `` w `` | 提交变更而无需预先提交钩子 | |
|
||||
| `` C `` | 提交变更(使用编辑器编辑提交信息) | |
|
||||
| `` <c-f> `` | 找到用于修复的基准提交 | 找到您当前变更所基于的提交,以便于修正/改进该提交。这样做可以省去您逐一查看分支提交来确定应该修正/改进哪个提交的麻烦。请参阅文档: <https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
|
||||
| `` / `` | 开始搜索 | |
|
||||
|
||||
## 正常
|
||||
@@ -305,8 +309,8 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` o `` | 打开配置文件 | Open file in default application. |
|
||||
| `` e `` | 编辑配置文件 | Open file in external editor. |
|
||||
| `` o `` | 打开配置文件 | 使用默认程序打开该文件 |
|
||||
| `` e `` | 编辑配置文件 | 使用外部编辑器打开文件 |
|
||||
| `` u `` | 检查更新 | |
|
||||
| `` <enter> `` | 切换到最近的仓库 | |
|
||||
| `` a `` | 显示所有分支的日志 | |
|
||||
@@ -324,46 +328,46 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
|-----|--------|-------------|
|
||||
| `` <enter> `` | 执行 | |
|
||||
| `` <esc> `` | 关闭 | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
| `` / `` | 通过文本过滤当前视图 | |
|
||||
|
||||
## 贮藏
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | 应用 | Apply the stash entry to your working directory. |
|
||||
| `` g `` | 应用并删除 | Apply the stash entry to your working directory and remove the stash entry. |
|
||||
| `` d `` | 删除 | 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 `` | Rename stash | |
|
||||
| `` <space> `` | 应用 | 将贮藏项应用到您的工作目录。 |
|
||||
| `` g `` | 应用并删除 | 将存储项应用到工作目录并删除存储项。 |
|
||||
| `` d `` | 删除 | 从贮藏列表中删除该贮藏项 |
|
||||
| `` n `` | 新分支 | 从选定的贮藏项创建一个新分支。这是通过 git 检查创建贮藏项的提交,从该提交创建一个新分支,然后将贮藏项作为附加提交应用到新分支来实现的。 |
|
||||
| `` r `` | 重命名贮藏 | |
|
||||
| `` <enter> `` | 查看提交的文件 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
| `` w `` | 查看工作区选项 | |
|
||||
| `` / `` | 通过文本过滤当前视图 | |
|
||||
|
||||
## 远程分支
|
||||
|
||||
| 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. |
|
||||
| `` <space> `` | 检出 | 基于当前选中的远程分支检出一个新的本地分支,或者将远程分支作分离的HEAD。 |
|
||||
| `` n `` | 新分支 | |
|
||||
| `` M `` | 合并到当前检出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) |
|
||||
| `` r `` | 将已检出的分支变基到该分支 | Rebase the checked-out branch onto the selected branch. |
|
||||
| `` d `` | Delete | Delete the remote branch from the remote. |
|
||||
| `` u `` | Set as upstream | 设置为检出分支的上游 |
|
||||
| `` s `` | Sort order | |
|
||||
| `` g `` | 查看重置选项 | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. |
|
||||
| `` r `` | 将已检出的分支变基到该分支 | 将检出的分支变基到所选的分支上。 |
|
||||
| `` d `` | 删除 | 从远程删除远程分支。 |
|
||||
| `` u `` | 设置为上游 | 设置为检出分支的上游 |
|
||||
| `` s `` | 排序 | |
|
||||
| `` g `` | 查看重置选项 | 查看重置选项 (soft/mixed/hard) 用于重置到选择项 |
|
||||
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
|
||||
| `` <enter> `` | 查看提交 | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
| `` w `` | 查看工作区选项 | |
|
||||
| `` / `` | 通过文本过滤当前视图 | |
|
||||
|
||||
## 远程页面
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <enter> `` | View branches | |
|
||||
| `` <enter> `` | 查看分支 | |
|
||||
| `` n `` | 添加新的远程仓库 | |
|
||||
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. |
|
||||
| `` e `` | Edit | 编辑远程仓库 |
|
||||
| `` d `` | 删除 | 删除选中的远程。从远程跟踪远程分支的任何本地分支都不会受到影响。 |
|
||||
| `` e `` | 编辑 | 编辑远程仓库 |
|
||||
| `` f `` | 抓取 | 抓取远程仓库 |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
| `` / `` | 通过文本过滤当前视图 | |
|
||||
|
||||
@@ -12,8 +12,8 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
|
||||
| `` <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 the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
|
||||
| `` p `` | 拉取 | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. |
|
||||
| `` 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. |
|
||||
| `` } `` | 增加差異檢視中顯示變更周圍上下文的大小 | Increase the amount of the context shown around changes in the diff view. |
|
||||
@@ -60,8 +60,8 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
|
||||
| `` v `` | 切換拖曳選擇 | |
|
||||
| `` a `` | 切換選擇程式碼塊 | Toggle hunk selection mode. |
|
||||
| `` <c-o> `` | 複製所選文本至剪貼簿 | |
|
||||
| `` o `` | 開啟檔案 | Open file in default application. |
|
||||
| `` e `` | 編輯檔案 | Open file in external editor. |
|
||||
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
|
||||
| `` e `` | 編輯檔案 | 使用外部編輯器開啟 |
|
||||
| `` <space> `` | 向 (或從) 補丁中添加/刪除行 | |
|
||||
| `` <esc> `` | 退出自訂補丁建立器 | |
|
||||
| `` / `` | 搜尋 | |
|
||||
@@ -84,9 +84,9 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
|
||||
| `` <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`. |
|
||||
| `` e `` | 編輯檔案 | 使用外部編輯器開啟 |
|
||||
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
|
||||
| `` M `` | 開啟外部合併工具 | 執行 `git mergetool`。 |
|
||||
| `` <esc> `` | 返回檔案面板 | |
|
||||
|
||||
## 主面板(預存)
|
||||
@@ -100,12 +100,12 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
|
||||
| `` <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. |
|
||||
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
|
||||
| `` e `` | 編輯檔案 | 使用外部編輯器開啟 |
|
||||
| `` <esc> `` | 返回檔案面板 | |
|
||||
| `` <tab> `` | 切換至另一個面板 (已預存/未預存更改) | Switch to other view (staged/unstaged changes). |
|
||||
| `` E `` | 編輯程式碼塊 | Edit selected hunk in external editor. |
|
||||
| `` c `` | 提交變更 | Commit staged changes. |
|
||||
| `` c `` | 提交變更 | 提交暫存區變更 |
|
||||
| `` w `` | 沒有預提交 hook 就提交更改 | |
|
||||
| `` 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> |
|
||||
@@ -131,7 +131,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
|
||||
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | 複製提交 (揀選) | 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> `` | 重設選定的揀選 (複製) 提交 | |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
|
||||
| `` <enter> `` | 檢視所選項目的檔案 | |
|
||||
| `` w `` | 檢視工作目錄選項 | |
|
||||
| `` / `` | 搜尋 | |
|
||||
@@ -156,7 +156,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
|
||||
|-----|--------|-------------|
|
||||
| `` n `` | New worktree | |
|
||||
| `` <space> `` | Switch | Switch to the selected worktree. |
|
||||
| `` o `` | Open in editor | |
|
||||
| `` o `` | 在編輯器中開啟 | |
|
||||
| `` 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. |
|
||||
| `` / `` | 搜尋 | |
|
||||
|
||||
@@ -169,22 +169,22 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
|
||||
| `` b `` | 查看二分選項 | |
|
||||
| `` 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 `` | 改寫提交 | 改寫選中的提交訊息 |
|
||||
| `` 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.
|
||||
| `` e `` | 編輯(開始互動變基) | 編輯提交 |
|
||||
| `` i `` | 開始互動變基 | 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 `` | Pick | 挑選提交 (於變基過程中) |
|
||||
| `` p `` | 挑選 | 挑選提交 (於變基過程中) |
|
||||
| `` F `` | 建立修復提交 | 為此提交建立修復提交 |
|
||||
| `` S `` | 壓縮上方所有「fixup」提交(自動壓縮) | 是否壓縮上方 {{.commit}} 所有「fixup」提交? |
|
||||
| `` <c-j> `` | 向下移動提交 | |
|
||||
| `` <c-k> `` | 向上移動提交 | |
|
||||
| `` V `` | 貼上提交 (揀選) | |
|
||||
| `` B `` | 為了變基已標注提交為基準提交 | 請為了下一次變基選擇一項基準提交;此將執行 `git rebase --onto`。 |
|
||||
| `` A `` | Amend | 使用已預存的更改修正提交 |
|
||||
| `` A `` | 修改 | 使用已預存的更改修正提交 |
|
||||
| `` a `` | 設定/重設提交作者 | 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 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. |
|
||||
@@ -193,7 +193,7 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| `` n `` | 從提交建立新分支 | |
|
||||
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | 複製提交 (揀選) | 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) | |
|
||||
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
|
||||
| `` <enter> `` | 檢視所選項目的檔案 | |
|
||||
| `` w `` | 檢視工作目錄選項 | |
|
||||
| `` / `` | 搜尋 | |
|
||||
@@ -212,13 +212,15 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| `` <c-o> `` | 複製檔案名稱到剪貼簿 | |
|
||||
| `` c `` | 檢出 | 檢出檔案 |
|
||||
| `` 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 `` | Edit | Open file in external editor. |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
|
||||
| `` e `` | 編輯 | 使用外部編輯器開啟 |
|
||||
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
|
||||
| `` <space> `` | 切換檔案是否包含在補丁中 | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
|
||||
| `` a `` | 切換所有檔案是否包含在補丁中 | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
|
||||
| `` <enter> `` | 輸入檔案以將選定的行添加至補丁(或切換目錄折疊) | 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 |
|
||||
| `` / `` | 搜尋 | |
|
||||
|
||||
## 收藏 (Stash)
|
||||
@@ -246,7 +248,7 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | 複製提交 (揀選) | 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> `` | 重設選定的揀選 (複製) 提交 | |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
|
||||
| `` <enter> `` | 檢視提交 | |
|
||||
| `` w `` | 檢視工作目錄選項 | |
|
||||
| `` / `` | 搜尋 | |
|
||||
@@ -257,23 +259,23 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 複製分支名稱到剪貼簿 | |
|
||||
| `` i `` | 顯示 git-flow 選項 | |
|
||||
| `` <space> `` | 檢出 | Checkout selected item. |
|
||||
| `` <space> `` | 檢出 | 檢出選定的項目。 |
|
||||
| `` n `` | 新分支 | |
|
||||
| `` o `` | 建立拉取請求 | |
|
||||
| `` O `` | 建立拉取請求選項 | |
|
||||
| `` <c-y> `` | 複製拉取請求的 URL 到剪貼板 | |
|
||||
| `` c `` | 根據名稱檢出 | 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. |
|
||||
| `` d `` | 刪除 | View delete options for local/remote branch. |
|
||||
| `` r `` | 將已檢出的分支變基至此分支 | 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 selected branch from its upstream. |
|
||||
| `` f `` | 從上游快進此分支 | 從遠端快進所選的分支 |
|
||||
| `` T `` | 建立標籤 | |
|
||||
| `` s `` | Sort order | |
|
||||
| `` s `` | 排序規則 | |
|
||||
| `` g `` | 檢視重設選項 | |
|
||||
| `` R `` | 重新命名分支 | |
|
||||
| `` u `` | 檢視上游設定 | 檢視有關上游分支的設定(例如重設至上游) |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` u `` | 檢視遠端設定 | 檢視有關遠端分支的設定(例如重設至遠端) |
|
||||
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
|
||||
| `` <enter> `` | 檢視提交 | |
|
||||
| `` w `` | 檢視工作目錄選項 | |
|
||||
| `` / `` | 搜尋 | |
|
||||
@@ -282,12 +284,12 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | 檢出 | Checkout the selected tag tag as a detached HEAD. |
|
||||
| `` <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. |
|
||||
| `` d `` | 刪除 | View delete options for local/remote tag. |
|
||||
| `` P `` | 推送標籤 | 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) | |
|
||||
| `` g `` | 重設 | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
|
||||
| `` <enter> `` | 檢視提交 | |
|
||||
| `` w `` | 檢視工作目錄選項 | |
|
||||
| `` / `` | 搜尋 | |
|
||||
@@ -299,35 +301,37 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| `` <c-o> `` | 複製檔案名稱到剪貼簿 | |
|
||||
| `` <space> `` | 切換預存 | Toggle staged for selected file. |
|
||||
| `` <c-b> `` | 篩選檔案 (預存/未預存) | |
|
||||
| `` y `` | Copy to clipboard | |
|
||||
| `` c `` | 提交變更 | Commit staged changes. |
|
||||
| `` y `` | 複製到剪貼簿 | |
|
||||
| `` c `` | 提交變更 | 提交暫存區變更 |
|
||||
| `` w `` | 沒有預提交 hook 就提交更改 | |
|
||||
| `` A `` | 修改上次提交 | |
|
||||
| `` 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 `` | Edit | Open file in external editor. |
|
||||
| `` o `` | 開啟檔案 | Open file in default application. |
|
||||
| `` e `` | 編輯 | 使用外部編輯器開啟 |
|
||||
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
|
||||
| `` i `` | 忽略或排除檔案 | |
|
||||
| `` r `` | 重新整理檔案 | |
|
||||
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. |
|
||||
| `` s `` | 收藏 | Stash all changes. For other variations of stashing, use the view stash options keybinding. |
|
||||
| `` S `` | 檢視收藏選項 | View stash options (e.g. stash all, stash staged, stash unstaged). |
|
||||
| `` a `` | 全部預存/取消預存 | Toggle staged/unstaged for all files in working tree. |
|
||||
| `` <enter> `` | 選擇檔案中的單個程式碼塊/行,或展開/折疊目錄 | 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 `` | 檢視上游重設選項 | |
|
||||
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). |
|
||||
| `` d `` | 捨棄 | 檢視選中變動進行捨棄復原 |
|
||||
| `` g `` | 檢視遠端重設選項 | |
|
||||
| `` D `` | 重設 | 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 changes from remote. |
|
||||
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
|
||||
| `` M `` | 開啟外部合併工具 | 執行 `git mergetool`。 |
|
||||
| `` f `` | 擷取 | 同步遠端異動 |
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` / `` | 搜尋 | |
|
||||
|
||||
## 狀態
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` o `` | 開啟設定檔案 | Open file in default application. |
|
||||
| `` e `` | 編輯設定檔案 | Open file in external editor. |
|
||||
| `` o `` | 開啟設定檔案 | 使用預設軟體開啟 |
|
||||
| `` e `` | 編輯設定檔案 | 使用外部編輯器開啟 |
|
||||
| `` u `` | 檢查更新 | |
|
||||
| `` <enter> `` | 切換到最近使用的版本庫 | |
|
||||
| `` a `` | 顯示所有分支日誌 | |
|
||||
@@ -346,7 +350,7 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| `` <enter> `` | View branches | |
|
||||
| `` n `` | 新增遠端 | |
|
||||
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. |
|
||||
| `` e `` | Edit | 編輯遠端 |
|
||||
| `` e `` | 編輯 | 編輯遠端 |
|
||||
| `` f `` | 擷取 | 擷取遠端 |
|
||||
| `` / `` | 搜尋 | |
|
||||
|
||||
@@ -359,11 +363,11 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| `` n `` | 新分支 | |
|
||||
| `` M `` | 合併到當前檢出的分支 | View options for merging the selected item into the current branch (regular merge, squash merge) |
|
||||
| `` r `` | 將已檢出的分支變基至此分支 | Rebase the checked-out branch onto the selected branch. |
|
||||
| `` d `` | Delete | Delete the remote branch from the remote. |
|
||||
| `` u `` | Set as upstream | 將此分支設為當前分支之上游 |
|
||||
| `` s `` | Sort order | |
|
||||
| `` d `` | 刪除 | Delete the remote branch from the remote. |
|
||||
| `` u `` | 設置為遠端 | 將此分支設為當前分支之遠端 |
|
||||
| `` s `` | 排序規則 | |
|
||||
| `` g `` | 檢視重設選項 | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <c-t> `` | 開啟外部差異工具 (git difftool) | |
|
||||
| `` <enter> `` | 檢視提交 | |
|
||||
| `` w `` | 檢視工作目錄選項 | |
|
||||
| `` / `` | 搜尋 | |
|
||||
|
||||
@@ -8,7 +8,7 @@ require (
|
||||
github.com/aybabtme/humanlog v0.4.1
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
|
||||
github.com/creack/pty v1.1.11
|
||||
github.com/gdamore/tcell/v2 v2.7.4
|
||||
github.com/gdamore/tcell/v2 v2.8.0
|
||||
github.com/go-errors/errors v1.5.1
|
||||
github.com/gookit/color v1.4.2
|
||||
github.com/iancoleman/orderedmap v0.3.0
|
||||
@@ -16,8 +16,8 @@ require (
|
||||
github.com/integrii/flaggy v1.4.0
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240906064314-bfab49c720d7
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20250111205211-82d518436b5a
|
||||
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
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||
@@ -38,7 +38,7 @@ require (
|
||||
github.com/stretchr/testify v1.8.1
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
|
||||
golang.org/x/exp v0.0.0-20220318154914-8dddf5d87bd8
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/sync v0.10.0
|
||||
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
@@ -54,7 +54,6 @@ require (
|
||||
github.com/go-git/go-billy/v5 v5.0.0 // indirect
|
||||
github.com/go-logfmt/logfmt v0.5.0 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/google/go-cmp v0.5.6 // indirect
|
||||
github.com/invopop/jsonschema v0.10.0 // indirect
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect
|
||||
@@ -73,10 +72,10 @@ require (
|
||||
github.com/sergi/go-diff v1.1.0 // indirect
|
||||
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
|
||||
github.com/xanzy/ssh-agent v0.2.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
|
||||
golang.org/x/net v0.7.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/term v0.24.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
golang.org/x/crypto v0.31.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/term v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
@@ -85,11 +85,10 @@ github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/encoding v1.0.1 h1:YzKZckdBL6jVt2Gc+5p82qhrGiqMdG/eNs6Wy0u3Uhw=
|
||||
github.com/gdamore/encoding v1.0.1/go.mod h1:0Z0cMFinngz9kS1QfMjCP8TY7em3bZYeeklsSDPivEo=
|
||||
github.com/gdamore/tcell/v2 v2.7.4 h1:sg6/UnTM9jGpZU+oFYAsDahfchWAFW8Xx2yFinNSAYU=
|
||||
github.com/gdamore/tcell/v2 v2.7.4/go.mod h1:dSXtXTSK0VsW1biw65DZLZ2NKr7j0qP/0J7ONmsraWg=
|
||||
github.com/gdamore/tcell/v2 v2.8.0 h1:IDclow1j6kKpU/gOhjmc+7Pj5Dxnukb74pfKN4Cxrfg=
|
||||
github.com/gdamore/tcell/v2 v2.8.0/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw=
|
||||
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
|
||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs=
|
||||
@@ -145,8 +144,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
|
||||
@@ -188,10 +187,10 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
|
||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
|
||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240906064314-bfab49c720d7 h1:QeLCKRAt4T6sBg5tSrOc4OojCuAcPxUA+4vNMPY4aH4=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20240906064314-bfab49c720d7/go.mod h1:XtEbqCbn45keRXEu+OMZkjN5gw6AEob59afsgHjokZ8=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
|
||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20250111205211-82d518436b5a h1:GLFWB8rESraTt2eIe2yssy4d4VEkCnmKbPeeZ5vCT2s=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20250111205211-82d518436b5a/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=
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5/go.mod h1:qxN4mHOAyeIDLP7IK7defgPClM/z1Kze8VVQiaEjzsQ=
|
||||
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e h1:uw/oo+kg7t/oeMs6sqlAwr85ND/9cpO3up3VxphxY0U=
|
||||
@@ -235,7 +234,6 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mgutz/str v1.2.0 h1:4IzWSdIz9qPQWLfKZ0rJcV0jcUDpxvP4JVZ4GXQyvSw=
|
||||
@@ -327,8 +325,12 @@ golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPh
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
|
||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
@@ -366,6 +368,9 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
@@ -401,8 +406,12 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
@@ -424,8 +433,11 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
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 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
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-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@@ -474,15 +486,22 @@ golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
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.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.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 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
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=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
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.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
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=
|
||||
@@ -492,9 +511,12 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
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.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
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=
|
||||
@@ -547,6 +569,8 @@ golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4f
|
||||
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
|
||||
+42
-18
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
"github.com/stefanhaller/git-todo-parser/todo"
|
||||
)
|
||||
|
||||
// Sometimes lazygit will be invoked in daemon mode from a parent lazygit process.
|
||||
@@ -39,6 +38,7 @@ const (
|
||||
DaemonKindMoveTodosDown
|
||||
DaemonKindInsertBreak
|
||||
DaemonKindChangeTodoActions
|
||||
DaemonKindDropMergeCommit
|
||||
DaemonKindMoveFixupCommitDown
|
||||
DaemonKindWriteRebaseTodo
|
||||
)
|
||||
@@ -58,6 +58,7 @@ func getInstruction() Instruction {
|
||||
DaemonKindRemoveUpdateRefsForCopiedBranch: deserializeInstruction[*RemoveUpdateRefsForCopiedBranchInstruction],
|
||||
DaemonKindCherryPick: deserializeInstruction[*CherryPickCommitsInstruction],
|
||||
DaemonKindChangeTodoActions: deserializeInstruction[*ChangeTodoActionsInstruction],
|
||||
DaemonKindDropMergeCommit: deserializeInstruction[*DropMergeCommitInstruction],
|
||||
DaemonKindMoveFixupCommitDown: deserializeInstruction[*MoveFixupCommitDownInstruction],
|
||||
DaemonKindMoveTodosUp: deserializeInstruction[*MoveTodosUpInstruction],
|
||||
DaemonKindMoveTodosDown: deserializeInstruction[*MoveTodosDownInstruction],
|
||||
@@ -235,7 +236,6 @@ func (self *ChangeTodoActionsInstruction) run(common *common.Common) error {
|
||||
changes := lo.Map(self.Changes, func(c ChangeTodoAction, _ int) utils.TodoChange {
|
||||
return utils.TodoChange{
|
||||
Hash: c.Hash,
|
||||
OldAction: todo.Pick,
|
||||
NewAction: c.NewAction,
|
||||
}
|
||||
})
|
||||
@@ -244,18 +244,44 @@ func (self *ChangeTodoActionsInstruction) run(common *common.Common) error {
|
||||
})
|
||||
}
|
||||
|
||||
// Takes the hash of some commit, and the hash of a fixup commit that was created
|
||||
// at the end of the branch, then moves the fixup commit down to right after the
|
||||
// original commit, changing its type to "fixup"
|
||||
type MoveFixupCommitDownInstruction struct {
|
||||
OriginalHash string
|
||||
FixupHash string
|
||||
type DropMergeCommitInstruction struct {
|
||||
Hash string
|
||||
}
|
||||
|
||||
func NewMoveFixupCommitDownInstruction(originalHash string, fixupHash string) Instruction {
|
||||
func NewDropMergeCommitInstruction(hash string) Instruction {
|
||||
return &DropMergeCommitInstruction{
|
||||
Hash: hash,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *DropMergeCommitInstruction) Kind() DaemonKind {
|
||||
return DaemonKindDropMergeCommit
|
||||
}
|
||||
|
||||
func (self *DropMergeCommitInstruction) SerializedInstructions() string {
|
||||
return serializeInstruction(self)
|
||||
}
|
||||
|
||||
func (self *DropMergeCommitInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return utils.DropMergeCommit(path, self.Hash, getCommentChar())
|
||||
})
|
||||
}
|
||||
|
||||
// Takes the hash of some commit, and the hash of a fixup commit that was created
|
||||
// at the end of the branch, then moves the fixup commit down to right after the
|
||||
// original commit, changing its type to "fixup" (only if ChangeToFixup is true)
|
||||
type MoveFixupCommitDownInstruction struct {
|
||||
OriginalHash string
|
||||
FixupHash string
|
||||
ChangeToFixup bool
|
||||
}
|
||||
|
||||
func NewMoveFixupCommitDownInstruction(originalHash string, fixupHash string, changeToFixup bool) Instruction {
|
||||
return &MoveFixupCommitDownInstruction{
|
||||
OriginalHash: originalHash,
|
||||
FixupHash: fixupHash,
|
||||
OriginalHash: originalHash,
|
||||
FixupHash: fixupHash,
|
||||
ChangeToFixup: changeToFixup,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +295,7 @@ func (self *MoveFixupCommitDownInstruction) SerializedInstructions() string {
|
||||
|
||||
func (self *MoveFixupCommitDownInstruction) run(common *common.Common) error {
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return utils.MoveFixupCommitDown(path, self.OriginalHash, self.FixupHash, getCommentChar())
|
||||
return utils.MoveFixupCommitDown(path, self.OriginalHash, self.FixupHash, self.ChangeToFixup, getCommentChar())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -294,13 +320,12 @@ func (self *MoveTodosUpInstruction) SerializedInstructions() string {
|
||||
func (self *MoveTodosUpInstruction) run(common *common.Common) error {
|
||||
todosToMove := lo.Map(self.Hashes, func(hash string, _ int) utils.Todo {
|
||||
return utils.Todo{
|
||||
Hash: hash,
|
||||
Action: todo.Pick,
|
||||
Hash: hash,
|
||||
}
|
||||
})
|
||||
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return utils.MoveTodosUp(path, todosToMove, getCommentChar())
|
||||
return utils.MoveTodosUp(path, todosToMove, false, getCommentChar())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -325,13 +350,12 @@ func (self *MoveTodosDownInstruction) SerializedInstructions() string {
|
||||
func (self *MoveTodosDownInstruction) run(common *common.Common) error {
|
||||
todosToMove := lo.Map(self.Hashes, func(hash string, _ int) utils.Todo {
|
||||
return utils.Todo{
|
||||
Hash: hash,
|
||||
Action: todo.Pick,
|
||||
Hash: hash,
|
||||
}
|
||||
})
|
||||
|
||||
return handleInteractiveRebase(common, func(path string) error {
|
||||
return utils.MoveTodosDown(path, todosToMove, getCommentChar())
|
||||
return utils.MoveTodosDown(path, todosToMove, false, getCommentChar())
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+10
-5
@@ -29,16 +29,17 @@ type cliArgs struct {
|
||||
RepoPath string
|
||||
FilterPath string
|
||||
GitArg string
|
||||
UseConfigDir string
|
||||
WorkTree string
|
||||
GitDir string
|
||||
CustomConfigFile string
|
||||
ScreenMode string
|
||||
PrintVersionInfo bool
|
||||
Debug bool
|
||||
TailLogs bool
|
||||
Profile bool
|
||||
PrintDefaultConfig bool
|
||||
PrintConfigDir bool
|
||||
UseConfigDir string
|
||||
WorkTree string
|
||||
GitDir string
|
||||
CustomConfigFile string
|
||||
}
|
||||
|
||||
type BuildInfo struct {
|
||||
@@ -164,7 +165,7 @@ func Start(buildInfo *BuildInfo, integrationTest integrationTypes.IntegrationTes
|
||||
|
||||
parsedGitArg := parseGitArg(cliArgs.GitArg)
|
||||
|
||||
Run(appConfig, common, appTypes.NewStartArgs(cliArgs.FilterPath, parsedGitArg, integrationTest))
|
||||
Run(appConfig, common, appTypes.NewStartArgs(cliArgs.FilterPath, parsedGitArg, cliArgs.ScreenMode, integrationTest))
|
||||
}
|
||||
|
||||
func parseCliArgsAndEnvVars() *cliArgs {
|
||||
@@ -209,6 +210,9 @@ func parseCliArgsAndEnvVars() *cliArgs {
|
||||
customConfigFile := ""
|
||||
flaggy.String(&customConfigFile, "ucf", "use-config-file", "Comma separated list to custom config file(s)")
|
||||
|
||||
screenMode := ""
|
||||
flaggy.String(&screenMode, "sm", "screen-mode", "The initial screen-mode, which determines the size of the focused panel. Valid options: 'normal' (default), 'half', 'full'")
|
||||
|
||||
flaggy.Parse()
|
||||
|
||||
if os.Getenv("DEBUG") == "TRUE" {
|
||||
@@ -229,6 +233,7 @@ func parseCliArgsAndEnvVars() *cliArgs {
|
||||
WorkTree: workTree,
|
||||
GitDir: gitDir,
|
||||
CustomConfigFile: customConfigFile,
|
||||
ScreenMode: screenMode,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,12 +6,14 @@ import (
|
||||
|
||||
// StartArgs is the struct that represents some things we want to do on program start
|
||||
type StartArgs struct {
|
||||
// FilterPath determines which path we're going to filter on so that we only see commits from that file.
|
||||
FilterPath string
|
||||
// GitArg determines what context we open in
|
||||
GitArg GitArg
|
||||
// integration test (only relevant when invoking lazygit in the context of an integration test)
|
||||
IntegrationTest integrationTypes.IntegrationTest
|
||||
// FilterPath determines which path we're going to filter on so that we only see commits from that file.
|
||||
FilterPath string
|
||||
// ScreenMode determines the initial Screen Mode (normal, half or full) to use
|
||||
ScreenMode string
|
||||
}
|
||||
|
||||
type GitArg string
|
||||
@@ -24,10 +26,11 @@ const (
|
||||
GitArgStash GitArg = "stash"
|
||||
)
|
||||
|
||||
func NewStartArgs(filterPath string, gitArg GitArg, test integrationTypes.IntegrationTest) StartArgs {
|
||||
func NewStartArgs(filterPath string, gitArg GitArg, screenMode string, test integrationTypes.IntegrationTest) StartArgs {
|
||||
return StartArgs{
|
||||
FilterPath: filterPath,
|
||||
GitArg: gitArg,
|
||||
ScreenMode: screenMode,
|
||||
IntegrationTest: test,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/mgutz/str"
|
||||
@@ -108,10 +109,10 @@ func (self *BranchCommands) CurrentBranchName() (string, error) {
|
||||
}
|
||||
|
||||
// LocalDelete delete branch locally
|
||||
func (self *BranchCommands) LocalDelete(branch string, force bool) error {
|
||||
func (self *BranchCommands) LocalDelete(branches []string, force bool) error {
|
||||
cmdArgs := NewGitCmd("branch").
|
||||
ArgIfElse(force, "-D", "-d").
|
||||
Arg(branch).
|
||||
Arg(branches...).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
@@ -260,3 +261,26 @@ func (self *BranchCommands) AllBranchesLogCmdObj() oscommands.ICmdObj {
|
||||
|
||||
return self.cmd.New(str.ToArgv(candidates[i])).DontLog()
|
||||
}
|
||||
|
||||
func (self *BranchCommands) IsBranchMerged(branch *models.Branch, mainBranches *MainBranches) (bool, error) {
|
||||
branchesToCheckAgainst := []string{"HEAD"}
|
||||
if branch.RemoteBranchStoredLocally() {
|
||||
branchesToCheckAgainst = append(branchesToCheckAgainst, fmt.Sprintf("%s@{upstream}", branch.Name))
|
||||
}
|
||||
branchesToCheckAgainst = append(branchesToCheckAgainst, mainBranches.Get()...)
|
||||
|
||||
cmdArgs := NewGitCmd("rev-list").
|
||||
Arg("--max-count=1").
|
||||
Arg(branch.Name).
|
||||
Arg(lo.Map(branchesToCheckAgainst, func(branch string, _ int) string {
|
||||
return fmt.Sprintf("^%s", branch)
|
||||
})...).
|
||||
ToArgv()
|
||||
|
||||
stdout, _, err := self.cmd.New(cmdArgs).RunWithOutputs()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return stdout == "", nil
|
||||
}
|
||||
|
||||
@@ -62,36 +62,57 @@ func TestBranchNewBranch(t *testing.T) {
|
||||
|
||||
func TestBranchDeleteBranch(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
force bool
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
test func(error)
|
||||
testName string
|
||||
branchNames []string
|
||||
force bool
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
test func(error)
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"Delete a branch",
|
||||
[]string{"test"},
|
||||
false,
|
||||
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-d", "test"}, "", nil),
|
||||
func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Delete multiple branches",
|
||||
[]string{"test1", "test2", "test3"},
|
||||
false,
|
||||
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-d", "test1", "test2", "test3"}, "", nil),
|
||||
func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Force delete a branch",
|
||||
[]string{"test"},
|
||||
true,
|
||||
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-D", "test"}, "", nil),
|
||||
func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
"Force delete multiple branches",
|
||||
[]string{"test1", "test2", "test3"},
|
||||
true,
|
||||
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"branch", "-D", "test1", "test2", "test3"}, "", nil),
|
||||
func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildBranchCommands(commonDeps{runner: s.runner})
|
||||
|
||||
s.test(instance.LocalDelete("test", s.force))
|
||||
s.test(instance.LocalDelete(s.branchNames, s.force))
|
||||
s.runner.CheckForMissingCalls()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ type Author struct {
|
||||
|
||||
func (self *CommitCommands) GetCommitAuthor(commitHash string) (Author, error) {
|
||||
cmdArgs := NewGitCmd("show").
|
||||
Arg("--no-patch", "--pretty=format:'%an%x00%ae'", commitHash).
|
||||
Arg("--no-patch", "--pretty=format:%an%x00%ae", commitHash).
|
||||
ToArgv()
|
||||
|
||||
output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
|
||||
|
||||
@@ -230,7 +230,7 @@ func TestCommitShowCmdObj(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
filterPath string
|
||||
contextSize int
|
||||
contextSize uint64
|
||||
similarityThreshold int
|
||||
ignoreWhitespace bool
|
||||
extDiffCmd string
|
||||
|
||||
@@ -16,9 +16,12 @@ 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 {
|
||||
extDiffCmd := self.UserConfig().Git.Paging.ExternalDiffCommand
|
||||
useExtDiff := extDiffCmd != ""
|
||||
ignoreWhitespace := self.AppState.IgnoreWhitespaceInDiffView
|
||||
|
||||
return self.cmd.New(
|
||||
NewGitCmd("diff").
|
||||
@@ -27,33 +30,29 @@ func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj {
|
||||
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
|
||||
Arg("--submodule").
|
||||
Arg(fmt.Sprintf("--color=%s", self.UserConfig().Git.Paging.ColorArg)).
|
||||
ArgIf(ignoreWhitespace, "--ignore-all-space").
|
||||
Arg(fmt.Sprintf("--unified=%d", self.AppState.DiffContextSize)).
|
||||
Arg(diffArgs...).
|
||||
Dir(self.repoPaths.worktreePath).
|
||||
ToArgv(),
|
||||
)
|
||||
}
|
||||
|
||||
func (self *DiffCommands) internalDiffCmdObj(diffArgs ...string) *GitCommandBuilder {
|
||||
return NewGitCmd("diff").
|
||||
Config("diff.noprefix=false").
|
||||
Arg("--no-ext-diff", "--no-color").
|
||||
Arg(diffArgs...).
|
||||
Dir(self.repoPaths.worktreePath)
|
||||
}
|
||||
|
||||
func (self *DiffCommands) GetPathDiff(path string, staged bool) (string, error) {
|
||||
// This is a basic generic diff command that can be used for any diff operation
|
||||
// (e.g. copying a diff to the clipboard). It will not use a custom pager, and
|
||||
// does not use user configs such as ignore whitespace.
|
||||
// If you want to diff specific refs (one or two), you need to add them yourself
|
||||
// in additionalArgs; it is recommended to also pass `--` after that. If you
|
||||
// want to restrict the diff to specific paths, pass them in additionalArgs
|
||||
// after the `--`.
|
||||
func (self *DiffCommands) GetDiff(staged bool, additionalArgs ...string) (string, error) {
|
||||
return self.cmd.New(
|
||||
self.internalDiffCmdObj().
|
||||
ArgIf(staged, "--staged").
|
||||
Arg(path).
|
||||
ToArgv(),
|
||||
).RunWithOutput()
|
||||
}
|
||||
|
||||
func (self *DiffCommands) GetAllDiff(staged bool) (string, error) {
|
||||
return self.cmd.New(
|
||||
self.internalDiffCmdObj().
|
||||
NewGitCmd("diff").
|
||||
Config("diff.noprefix=false").
|
||||
Arg("--no-ext-diff", "--no-color").
|
||||
ArgIf(staged, "--staged").
|
||||
Dir(self.repoPaths.worktreePath).
|
||||
Arg(additionalArgs...).
|
||||
ToArgv(),
|
||||
).RunWithOutput()
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package git_commands
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
@@ -48,6 +49,14 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
|
||||
}
|
||||
files := []*models.File{}
|
||||
|
||||
fileDiffs := map[string]FileDiff{}
|
||||
if self.GitCommon.Common.UserConfig().Gui.ShowNumstatInFilesView {
|
||||
fileDiffs, err = self.getFileDiffs()
|
||||
if err != nil {
|
||||
self.Log.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, status := range statuses {
|
||||
if strings.HasPrefix(status.StatusString, "warning") {
|
||||
self.Log.Warningf("warning when calling git status: %s", status.StatusString)
|
||||
@@ -60,6 +69,11 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
|
||||
DisplayString: status.StatusString,
|
||||
}
|
||||
|
||||
if diff, ok := fileDiffs[status.Name]; ok {
|
||||
file.LinesAdded = diff.LinesAdded
|
||||
file.LinesDeleted = diff.LinesDeleted
|
||||
}
|
||||
|
||||
models.SetStatusFields(file, status.Change)
|
||||
files = append(files, file)
|
||||
}
|
||||
@@ -87,6 +101,45 @@ func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File
|
||||
return files
|
||||
}
|
||||
|
||||
type FileDiff struct {
|
||||
LinesAdded int
|
||||
LinesDeleted int
|
||||
}
|
||||
|
||||
func (fileLoader *FileLoader) getFileDiffs() (map[string]FileDiff, error) {
|
||||
diffs, err := fileLoader.gitDiffNumStat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
splitLines := strings.Split(diffs, "\x00")
|
||||
|
||||
fileDiffs := map[string]FileDiff{}
|
||||
for _, line := range splitLines {
|
||||
splitLine := strings.Split(line, "\t")
|
||||
if len(splitLine) != 3 {
|
||||
continue
|
||||
}
|
||||
|
||||
linesAdded, err := strconv.Atoi(splitLine[0])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
linesDeleted, err := strconv.Atoi(splitLine[1])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
fileName := splitLine[2]
|
||||
fileDiffs[fileName] = FileDiff{
|
||||
LinesAdded: linesAdded,
|
||||
LinesDeleted: linesDeleted,
|
||||
}
|
||||
}
|
||||
|
||||
return fileDiffs, nil
|
||||
}
|
||||
|
||||
// GitStatus returns the file status of the repo
|
||||
type GitStatusOptions struct {
|
||||
NoRenames bool
|
||||
@@ -100,6 +153,16 @@ type FileStatus struct {
|
||||
PreviousName string
|
||||
}
|
||||
|
||||
func (fileLoader *FileLoader) gitDiffNumStat() (string, error) {
|
||||
return fileLoader.cmd.New(
|
||||
NewGitCmd("diff").
|
||||
Arg("--numstat").
|
||||
Arg("-z").
|
||||
Arg("HEAD").
|
||||
ToArgv(),
|
||||
).DontLog().RunWithOutput()
|
||||
}
|
||||
|
||||
func (self *FileLoader) gitStatus(opts GitStatusOptions) ([]FileStatus, error) {
|
||||
cmdArgs := NewGitCmd("status").
|
||||
Arg(opts.UntrackedFilesArg).
|
||||
|
||||
@@ -11,29 +11,35 @@ import (
|
||||
|
||||
func TestFileGetStatusFiles(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
similarityThreshold int
|
||||
runner oscommands.ICmdObjRunner
|
||||
expectedFiles []*models.File
|
||||
testName string
|
||||
similarityThreshold int
|
||||
runner oscommands.ICmdObjRunner
|
||||
showNumstatInFilesView bool
|
||||
expectedFiles []*models.File
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"No files found",
|
||||
50,
|
||||
oscommands.NewFakeRunner(t).
|
||||
testName: "No files found",
|
||||
similarityThreshold: 50,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"}, "", nil),
|
||||
[]*models.File{},
|
||||
expectedFiles: []*models.File{},
|
||||
},
|
||||
{
|
||||
"Several files found",
|
||||
50,
|
||||
oscommands.NewFakeRunner(t).
|
||||
testName: "Several files found",
|
||||
similarityThreshold: 50,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"},
|
||||
"MM file1.txt\x00A file3.txt\x00AM file2.txt\x00?? file4.txt\x00UU file5.txt",
|
||||
nil,
|
||||
).
|
||||
ExpectGitArgs([]string{"diff", "--numstat", "-z", "HEAD"},
|
||||
"4\t1\tfile1.txt\x001\t0\tfile2.txt\x002\t2\tfile3.txt\x000\t2\tfile4.txt\x002\t2\tfile5.txt",
|
||||
nil,
|
||||
),
|
||||
[]*models.File{
|
||||
showNumstatInFilesView: true,
|
||||
expectedFiles: []*models.File{
|
||||
{
|
||||
Name: "file1.txt",
|
||||
HasStagedChanges: true,
|
||||
@@ -45,6 +51,8 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
HasInlineMergeConflicts: false,
|
||||
DisplayString: "MM file1.txt",
|
||||
ShortStatus: "MM",
|
||||
LinesAdded: 4,
|
||||
LinesDeleted: 1,
|
||||
},
|
||||
{
|
||||
Name: "file3.txt",
|
||||
@@ -57,6 +65,8 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
HasInlineMergeConflicts: false,
|
||||
DisplayString: "A file3.txt",
|
||||
ShortStatus: "A ",
|
||||
LinesAdded: 2,
|
||||
LinesDeleted: 2,
|
||||
},
|
||||
{
|
||||
Name: "file2.txt",
|
||||
@@ -69,6 +79,8 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
HasInlineMergeConflicts: false,
|
||||
DisplayString: "AM file2.txt",
|
||||
ShortStatus: "AM",
|
||||
LinesAdded: 1,
|
||||
LinesDeleted: 0,
|
||||
},
|
||||
{
|
||||
Name: "file4.txt",
|
||||
@@ -81,6 +93,8 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
HasInlineMergeConflicts: false,
|
||||
DisplayString: "?? file4.txt",
|
||||
ShortStatus: "??",
|
||||
LinesAdded: 0,
|
||||
LinesDeleted: 2,
|
||||
},
|
||||
{
|
||||
Name: "file5.txt",
|
||||
@@ -93,15 +107,17 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
HasInlineMergeConflicts: true,
|
||||
DisplayString: "UU file5.txt",
|
||||
ShortStatus: "UU",
|
||||
LinesAdded: 2,
|
||||
LinesDeleted: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"File with new line char",
|
||||
50,
|
||||
oscommands.NewFakeRunner(t).
|
||||
testName: "File with new line char",
|
||||
similarityThreshold: 50,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"}, "MM a\nb.txt", nil),
|
||||
[]*models.File{
|
||||
expectedFiles: []*models.File{
|
||||
{
|
||||
Name: "a\nb.txt",
|
||||
HasStagedChanges: true,
|
||||
@@ -117,14 +133,14 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
"Renamed files",
|
||||
50,
|
||||
oscommands.NewFakeRunner(t).
|
||||
testName: "Renamed files",
|
||||
similarityThreshold: 50,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"},
|
||||
"R after1.txt\x00before1.txt\x00RM after2.txt\x00before2.txt",
|
||||
nil,
|
||||
),
|
||||
[]*models.File{
|
||||
expectedFiles: []*models.File{
|
||||
{
|
||||
Name: "after1.txt",
|
||||
PreviousName: "before1.txt",
|
||||
@@ -154,14 +170,14 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
"File with arrow in name",
|
||||
50,
|
||||
oscommands.NewFakeRunner(t).
|
||||
testName: "File with arrow in name",
|
||||
similarityThreshold: 50,
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"status", "--untracked-files=yes", "--porcelain", "-z", "--find-renames=50%"},
|
||||
`?? a -> b.txt`,
|
||||
nil,
|
||||
),
|
||||
[]*models.File{
|
||||
expectedFiles: []*models.File{
|
||||
{
|
||||
Name: "a -> b.txt",
|
||||
HasStagedChanges: false,
|
||||
@@ -185,8 +201,14 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
appState := &config.AppState{}
|
||||
appState.RenameSimilarityThreshold = s.similarityThreshold
|
||||
|
||||
userConfig := &config.UserConfig{
|
||||
Gui: config.GuiConfig{
|
||||
ShowNumstatInFilesView: s.showNumstatInFilesView,
|
||||
},
|
||||
}
|
||||
|
||||
loader := &FileLoader{
|
||||
GitCommon: buildGitCommon(commonDeps{appState: appState}),
|
||||
GitCommon: buildGitCommon(commonDeps{appState: appState, userConfig: userConfig}),
|
||||
cmd: cmd,
|
||||
config: &FakeFileLoaderConfig{showUntrackedFiles: "yes"},
|
||||
getFileType: func(string) string { return "file" },
|
||||
|
||||
@@ -76,6 +76,14 @@ func (self *GitCommandBuilder) Worktree(path string) *GitCommandBuilder {
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *GitCommandBuilder) WorktreePathIf(condition bool, path string) *GitCommandBuilder {
|
||||
if condition {
|
||||
return self.Worktree(path)
|
||||
}
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
// Note, you may prefer to use the Dir method instead of this one
|
||||
func (self *GitCommandBuilder) GitDir(path string) *GitCommandBuilder {
|
||||
// git dir arg comes before the command
|
||||
|
||||
@@ -145,11 +145,11 @@ func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, startIdx
|
||||
|
||||
baseHashOrRoot := getBaseHashOrRoot(commits, baseIndex)
|
||||
|
||||
changes := lo.Map(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) daemon.ChangeTodoAction {
|
||||
changes := lo.FilterMap(commits[startIdx:endIdx+1], func(commit *models.Commit, _ int) (daemon.ChangeTodoAction, bool) {
|
||||
return daemon.ChangeTodoAction{
|
||||
Hash: commit.Hash,
|
||||
NewAction: action,
|
||||
}
|
||||
}, !commit.IsMerge()
|
||||
})
|
||||
|
||||
self.os.LogCommand(logTodoChanges(changes), false)
|
||||
@@ -284,6 +284,11 @@ func (self *RebaseCommands) GitRebaseEditTodo(todosFileContent []byte) error {
|
||||
return cmdObj.Run()
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) getHashOfLastCommitMade() (string, error) {
|
||||
cmdArgs := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToArgv()
|
||||
return self.cmd.New(cmdArgs).RunWithOutput()
|
||||
}
|
||||
|
||||
// AmendTo amends the given commit with whatever files are staged
|
||||
func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) error {
|
||||
commit := commits[commitIndex]
|
||||
@@ -292,9 +297,7 @@ func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) e
|
||||
return err
|
||||
}
|
||||
|
||||
// Get the hash of the commit we just created
|
||||
cmdArgs := NewGitCmd("rev-parse").Arg("--verify", "HEAD").ToArgv()
|
||||
fixupHash, err := self.cmd.New(cmdArgs).RunWithOutput()
|
||||
fixupHash, err := self.getHashOfLastCommitMade()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -302,15 +305,28 @@ func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) e
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseHashOrRoot: getBaseHashOrRoot(commits, commitIndex+1),
|
||||
overrideEditor: true,
|
||||
instruction: daemon.NewMoveFixupCommitDownInstruction(commit.Hash, fixupHash),
|
||||
instruction: daemon.NewMoveFixupCommitDownInstruction(commit.Hash, fixupHash, true),
|
||||
}).Run()
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) MoveFixupCommitDown(commits []*models.Commit, targetCommitIndex int) error {
|
||||
fixupHash, err := self.getHashOfLastCommitMade()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseHashOrRoot: getBaseHashOrRoot(commits, targetCommitIndex+1),
|
||||
overrideEditor: true,
|
||||
instruction: daemon.NewMoveFixupCommitDownInstruction(commits[targetCommitIndex].Hash, fixupHash, false),
|
||||
}).Run()
|
||||
}
|
||||
|
||||
func todoFromCommit(commit *models.Commit) utils.Todo {
|
||||
if commit.Action == todo.UpdateRef {
|
||||
return utils.Todo{Ref: commit.Name, Action: commit.Action}
|
||||
return utils.Todo{Ref: commit.Name}
|
||||
} else {
|
||||
return utils.Todo{Hash: commit.Hash, Action: commit.Action}
|
||||
return utils.Todo{Hash: commit.Hash}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -319,7 +335,6 @@ func (self *RebaseCommands) EditRebaseTodo(commits []*models.Commit, action todo
|
||||
commitsWithAction := lo.Map(commits, func(commit *models.Commit, _ int) utils.TodoChange {
|
||||
return utils.TodoChange{
|
||||
Hash: commit.Hash,
|
||||
OldAction: commit.Action,
|
||||
NewAction: action,
|
||||
}
|
||||
})
|
||||
@@ -354,7 +369,7 @@ func (self *RebaseCommands) MoveTodosDown(commits []*models.Commit) error {
|
||||
return todoFromCommit(commit)
|
||||
})
|
||||
|
||||
return utils.MoveTodosDown(fileName, todosToMove, self.config.GetCoreCommentChar())
|
||||
return utils.MoveTodosDown(fileName, todosToMove, true, self.config.GetCoreCommentChar())
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) MoveTodosUp(commits []*models.Commit) error {
|
||||
@@ -363,7 +378,7 @@ func (self *RebaseCommands) MoveTodosUp(commits []*models.Commit) error {
|
||||
return todoFromCommit(commit)
|
||||
})
|
||||
|
||||
return utils.MoveTodosUp(fileName, todosToMove, self.config.GetCoreCommentChar())
|
||||
return utils.MoveTodosUp(fileName, todosToMove, true, self.config.GetCoreCommentChar())
|
||||
}
|
||||
|
||||
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one
|
||||
@@ -549,6 +564,13 @@ func (self *RebaseCommands) CherryPickCommitsDuringRebase(commits []*models.Comm
|
||||
return utils.PrependStrToTodoFile(filePath, []byte(todo))
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) DropMergeCommit(commits []*models.Commit, commitIndex int) error {
|
||||
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
||||
baseHashOrRoot: getBaseHashOrRoot(commits, commitIndex+1),
|
||||
instruction: daemon.NewDropMergeCommitInstruction(commits[commitIndex].Hash),
|
||||
}).Run()
|
||||
}
|
||||
|
||||
// we can't start an interactive rebase from the first commit without passing the
|
||||
// '--root' arg
|
||||
func getBaseHashOrRoot(commits []*models.Commit, index int) string {
|
||||
|
||||
@@ -49,9 +49,10 @@ func (self *RemoteCommands) UpdateRemoteUrl(remoteName string, updatedUrl string
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
}
|
||||
|
||||
func (self *RemoteCommands) DeleteRemoteBranch(task gocui.Task, remoteName string, branchName string) error {
|
||||
func (self *RemoteCommands) DeleteRemoteBranch(task gocui.Task, remoteName string, branchNames []string) error {
|
||||
cmdArgs := NewGitCmd("push").
|
||||
Arg(remoteName, "--delete", branchName).
|
||||
Arg(remoteName, "--delete").
|
||||
Arg(branchNames...).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).PromptOnCredentialRequest(task).Run()
|
||||
|
||||
@@ -100,7 +100,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
|
||||
type scenario struct {
|
||||
testName string
|
||||
index int
|
||||
contextSize int
|
||||
contextSize uint64
|
||||
similarityThreshold int
|
||||
ignoreWhitespace bool
|
||||
expected []string
|
||||
|
||||
@@ -88,6 +88,7 @@ type PullOptions struct {
|
||||
BranchName string
|
||||
FastForwardOnly bool
|
||||
WorktreeGitDir string
|
||||
WorktreePath string
|
||||
}
|
||||
|
||||
func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error {
|
||||
@@ -97,6 +98,7 @@ func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error {
|
||||
ArgIf(opts.RemoteName != "", opts.RemoteName).
|
||||
ArgIf(opts.BranchName != "", "refs/heads/"+opts.BranchName).
|
||||
GitDirIf(opts.WorktreeGitDir != "", opts.WorktreeGitDir).
|
||||
WorktreePathIf(opts.WorktreePath != "", opts.WorktreePath).
|
||||
ToArgv()
|
||||
|
||||
// setting GIT_SEQUENCE_EDITOR to ':' as a way of skipping it, in case the user
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
@@ -100,7 +99,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
|
||||
Added: true,
|
||||
},
|
||||
removeFile: func(string) error {
|
||||
return fmt.Errorf("an error occurred when removing file")
|
||||
return errors.New("an error occurred when removing file")
|
||||
},
|
||||
runner: oscommands.NewFakeRunner(t),
|
||||
expectedError: "an error occurred when removing file",
|
||||
@@ -210,7 +209,7 @@ func TestWorkingTreeDiff(t *testing.T) {
|
||||
plain bool
|
||||
cached bool
|
||||
ignoreWhitespace bool
|
||||
contextSize int
|
||||
contextSize uint64
|
||||
similarityThreshold int
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
}
|
||||
@@ -352,7 +351,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
|
||||
reverse bool
|
||||
plain bool
|
||||
ignoreWhitespace bool
|
||||
contextSize int
|
||||
contextSize uint64
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ var azdoServiceDef = ServiceDefinition{
|
||||
regexStrings: []string{
|
||||
`^git@ssh.dev.azure.com.*/(?P<org>.*)/(?P<project>.*)/(?P<repo>.*?)(?:\.git)?$`,
|
||||
`^https://.*@dev.azure.com/(?P<org>.*?)/(?P<project>.*?)/_git/(?P<repo>.*?)(?:\.git)?$`,
|
||||
`^https://.*/(?P<org>.*?)/(?P<project>.*?)/_git/(?P<repo>.*?)(?:\.git)?$`,
|
||||
},
|
||||
repoURLTemplate: "https://{{.webDomain}}/{{.org}}/{{.project}}/_git/{{.repo}}",
|
||||
}
|
||||
|
||||
@@ -210,6 +210,19 @@ func TestGetPullRequestURL(t *testing.T) {
|
||||
assert.Equal(t, "https://dev.azure.com/myorg/myproject/_git/myrepo/pullrequestcreate?sourceRef=feature%2Fnew&targetRef=dev", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Opens a link to new pull request on Azure DevOps Server (HTTP)",
|
||||
from: "feature/new",
|
||||
remoteUrl: "https://mycompany.azuredevops.com/collection/myproject/_git/myrepo",
|
||||
configServiceDomains: map[string]string{
|
||||
// valid configuration for a azure devops server URL
|
||||
"mycompany.azuredevops.com": "azuredevops:mycompany.azuredevops.com",
|
||||
},
|
||||
test: func(url string, err error) {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "https://mycompany.azuredevops.com/collection/myproject/_git/myrepo/pullrequestcreate?sourceRef=feature%2Fnew", url)
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "Opens a link to new pull request on Bitbucket Server (SSH)",
|
||||
from: "feature/new",
|
||||
|
||||
@@ -19,6 +19,8 @@ type File struct {
|
||||
HasInlineMergeConflicts bool
|
||||
DisplayString string
|
||||
ShortStatus string // e.g. 'AD', ' A', 'M ', '??'
|
||||
LinesDeleted int
|
||||
LinesAdded int
|
||||
|
||||
// If true, this must be a worktree folder
|
||||
IsWorktree bool
|
||||
|
||||
@@ -52,7 +52,7 @@ func (self *CmdObjBuilder) NewShell(commandStr string) ICmdObj {
|
||||
}
|
||||
|
||||
func (self *CmdObjBuilder) NewInteractiveShell(commandStr string) ICmdObj {
|
||||
quotedCommand := self.quotedCommandString(commandStr)
|
||||
quotedCommand := self.quotedCommandString(commandStr + self.platform.InteractiveShellExit)
|
||||
cmdArgs := str.ToArgv(fmt.Sprintf("%s %s %s %s", self.platform.InteractiveShell, self.platform.InteractiveShellArg, self.platform.ShellArg, quotedCommand))
|
||||
|
||||
return self.New(cmdArgs)
|
||||
|
||||
@@ -51,13 +51,14 @@ func NewDummyCmdObjBuilder(runner ICmdObjRunner) *CmdObjBuilder {
|
||||
}
|
||||
|
||||
var dummyPlatform = &Platform{
|
||||
OS: "darwin",
|
||||
Shell: "bash",
|
||||
InteractiveShell: "bash",
|
||||
ShellArg: "-c",
|
||||
InteractiveShellArg: "-i",
|
||||
OpenCommand: "open {{filename}}",
|
||||
OpenLinkCommand: "open {{link}}",
|
||||
OS: "darwin",
|
||||
Shell: "bash",
|
||||
InteractiveShell: "bash",
|
||||
ShellArg: "-c",
|
||||
InteractiveShellArg: "-i",
|
||||
InteractiveShellExit: "; exit $?",
|
||||
OpenCommand: "open {{filename}}",
|
||||
OpenLinkCommand: "open {{link}}",
|
||||
}
|
||||
|
||||
func NewDummyOSCommandWithRunner(runner *FakeCmdObjRunner) *OSCommand {
|
||||
|
||||
@@ -35,13 +35,14 @@ type OSCommand struct {
|
||||
|
||||
// Platform stores the os state
|
||||
type Platform struct {
|
||||
OS string
|
||||
Shell string
|
||||
InteractiveShell string
|
||||
ShellArg string
|
||||
InteractiveShellArg string
|
||||
OpenCommand string
|
||||
OpenLinkCommand string
|
||||
OS string
|
||||
Shell string
|
||||
InteractiveShell string
|
||||
ShellArg string
|
||||
InteractiveShellArg string
|
||||
InteractiveShellExit string
|
||||
OpenCommand string
|
||||
OpenLinkCommand string
|
||||
}
|
||||
|
||||
// NewOSCommand os command runner
|
||||
|
||||
@@ -6,17 +6,31 @@ package oscommands
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetPlatform() *Platform {
|
||||
shell := getUserShell()
|
||||
|
||||
interactiveShell := shell
|
||||
interactiveShellArg := "-i"
|
||||
interactiveShellExit := "; exit $?"
|
||||
|
||||
if !(strings.HasSuffix(shell, "bash") || strings.HasSuffix(shell, "zsh")) {
|
||||
interactiveShell = "bash"
|
||||
interactiveShellArg = ""
|
||||
interactiveShellExit = ""
|
||||
}
|
||||
|
||||
return &Platform{
|
||||
OS: runtime.GOOS,
|
||||
Shell: "bash",
|
||||
InteractiveShell: getUserShell(),
|
||||
ShellArg: "-c",
|
||||
InteractiveShellArg: "-i",
|
||||
OpenCommand: "open {{filename}}",
|
||||
OpenLinkCommand: "open {{link}}",
|
||||
OS: runtime.GOOS,
|
||||
Shell: "bash",
|
||||
InteractiveShell: interactiveShell,
|
||||
ShellArg: "-c",
|
||||
InteractiveShellArg: interactiveShellArg,
|
||||
InteractiveShellExit: interactiveShellExit,
|
||||
OpenCommand: "open {{filename}}",
|
||||
OpenLinkCommand: "open {{link}}",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ func TestOSCommandFileType(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
"nonExistant",
|
||||
"nonExistent",
|
||||
func() {},
|
||||
func(output string) {
|
||||
assert.EqualValues(t, "other", output)
|
||||
|
||||
@@ -2,10 +2,11 @@ package oscommands
|
||||
|
||||
func GetPlatform() *Platform {
|
||||
return &Platform{
|
||||
OS: "windows",
|
||||
Shell: "cmd",
|
||||
InteractiveShell: "cmd",
|
||||
ShellArg: "/c",
|
||||
InteractiveShellArg: "",
|
||||
OS: "windows",
|
||||
Shell: "cmd",
|
||||
InteractiveShell: "cmd",
|
||||
ShellArg: "/c",
|
||||
InteractiveShellArg: "",
|
||||
InteractiveShellExit: "",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,3 +154,24 @@ func (self *Patch) LineCount() int {
|
||||
func (self *Patch) HunkCount() int {
|
||||
return len(self.hunks)
|
||||
}
|
||||
|
||||
// Adjust the given line number (one-based) according to the current patch. The
|
||||
// patch is supposed to be a diff of an old file state against the working
|
||||
// directory; the line number is a line number in that old file, and the
|
||||
// function returns the corresponding line number in the working directory file.
|
||||
func (self *Patch) AdjustLineNumber(lineNumber int) int {
|
||||
adjustedLineNumber := lineNumber
|
||||
for _, hunk := range self.hunks {
|
||||
if hunk.oldStart >= lineNumber {
|
||||
break
|
||||
}
|
||||
|
||||
if hunk.oldStart+hunk.oldLength() > lineNumber {
|
||||
return hunk.newStart
|
||||
}
|
||||
|
||||
adjustedLineNumber += hunk.newLength() - hunk.oldLength()
|
||||
}
|
||||
|
||||
return adjustedLineNumber
|
||||
}
|
||||
|
||||
@@ -639,3 +639,59 @@ func TestGetNextStageableLineIndex(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdjustLineNumber(t *testing.T) {
|
||||
type scenario struct {
|
||||
oldLineNumbers []int
|
||||
expectedResults []int
|
||||
}
|
||||
scenarios := []scenario{
|
||||
{
|
||||
oldLineNumbers: []int{1, 2, 3, 4, 5, 6, 7},
|
||||
expectedResults: []int{1, 2, 2, 3, 4, 7, 8},
|
||||
},
|
||||
}
|
||||
|
||||
// The following diff was generated from old.txt:
|
||||
// 1
|
||||
// 2a
|
||||
// 2b
|
||||
// 3
|
||||
// 4
|
||||
// 7
|
||||
// 8
|
||||
// against new.txt:
|
||||
// 1
|
||||
// 2
|
||||
// 3
|
||||
// 4
|
||||
// 5
|
||||
// 6
|
||||
// 7
|
||||
// 8
|
||||
|
||||
// This test setup makes the test easy to understand, because the resulting
|
||||
// adjusted line numbers are the same as the content of the lines in new.txt.
|
||||
|
||||
diff := `--- old.txt 2024-12-16 18:04:29
|
||||
+++ new.txt 2024-12-16 18:04:27
|
||||
@@ -2,2 +2 @@
|
||||
-2a
|
||||
-2b
|
||||
+2
|
||||
@@ -5,0 +5,2 @@
|
||||
+5
|
||||
+6
|
||||
`
|
||||
|
||||
patch := Parse(diff)
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run("TestAdjustLineNumber", func(t *testing.T) {
|
||||
for idx, oldLineNumber := range s.oldLineNumbers {
|
||||
result := patch.AdjustLineNumber(oldLineNumber)
|
||||
assert.Equal(t, s.expectedResults[idx], result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,16 +217,23 @@ func loadUserConfig(configFiles []*ConfigFile, base *UserConfig) (*UserConfig, e
|
||||
// from one container to another, or changing the type of a key (e.g. from bool
|
||||
// to an enum).
|
||||
func migrateUserConfig(path string, content []byte) ([]byte, error) {
|
||||
changedContent, err := yaml_utils.RenameYamlKey(content, []string{"gui", "skipUnstageLineWarning"},
|
||||
"skipDiscardChangeWarning")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
|
||||
changedContent := content
|
||||
|
||||
pathsToReplace := []struct {
|
||||
oldPath []string
|
||||
newName string
|
||||
}{
|
||||
{[]string{"gui", "skipUnstageLineWarning"}, "skipDiscardChangeWarning"},
|
||||
{[]string{"keybinding", "universal", "executeCustomCommand"}, "executeShellCommand"},
|
||||
{[]string{"gui", "windowSize"}, "screenMode"},
|
||||
}
|
||||
|
||||
changedContent, err = yaml_utils.RenameYamlKey(changedContent, []string{"keybinding", "universal", "executeCustomCommand"},
|
||||
"executeShellCommand")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
|
||||
var err error
|
||||
for _, pathToReplace := range pathsToReplace {
|
||||
changedContent, err = yaml_utils.RenameYamlKey(changedContent, 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)
|
||||
}
|
||||
}
|
||||
|
||||
changedContent, err = changeNullKeybindingsToDisabled(changedContent)
|
||||
@@ -457,7 +464,7 @@ type AppState struct {
|
||||
|
||||
HideCommandLog bool
|
||||
IgnoreWhitespaceInDiffView bool
|
||||
DiffContextSize int
|
||||
DiffContextSize uint64
|
||||
RenameSimilarityThreshold int
|
||||
LocalBranchSortOrder string
|
||||
RemoteBranchSortOrder string
|
||||
|
||||
@@ -21,8 +21,8 @@ func isContainer() bool {
|
||||
func GetPlatformDefaultConfig() OSConfig {
|
||||
if isWSL() && !isContainer() {
|
||||
return OSConfig{
|
||||
Open: `powershell.exe start explorer.exe {{filename}} >/dev/null`,
|
||||
OpenLink: `powershell.exe start {{link}} >/dev/null`,
|
||||
Open: `powershell.exe start explorer.exe "$(wslpath -w {{filename}})" >/dev/null`,
|
||||
OpenLink: `powershell.exe start '{{link}}' >/dev/null`,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -66,9 +66,15 @@ func getPreset(osConfig *OSConfig, guessDefaultEditor func() string) *editPreset
|
||||
return !ok
|
||||
},
|
||||
},
|
||||
"lvim": standardTerminalEditorPreset("lvim"),
|
||||
"emacs": standardTerminalEditorPreset("emacs"),
|
||||
"micro": standardTerminalEditorPreset("micro"),
|
||||
"lvim": standardTerminalEditorPreset("lvim"),
|
||||
"emacs": standardTerminalEditorPreset("emacs"),
|
||||
"micro": {
|
||||
editTemplate: "micro {{filename}}",
|
||||
editAtLineTemplate: "micro +{{line}} {{filename}}",
|
||||
editAtLineAndWaitTemplate: "micro +{{line}} {{filename}}",
|
||||
openDirInEditorTemplate: "micro {{dir}}",
|
||||
suspend: returnBool(true),
|
||||
},
|
||||
"nano": standardTerminalEditorPreset("nano"),
|
||||
"kakoune": standardTerminalEditorPreset("kak"),
|
||||
"helix": {
|
||||
|
||||
@@ -52,7 +52,10 @@ type GuiConfig struct {
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-author-color
|
||||
AuthorColors map[string]string `yaml:"authorColors"`
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color
|
||||
// Deprecated: use branchColorPatterns instead
|
||||
BranchColors map[string]string `yaml:"branchColors"`
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#custom-branch-color
|
||||
BranchColorPatterns map[string]string `yaml:"branchColorPatterns"`
|
||||
// The number of lines you scroll by when scrolling the main window
|
||||
ScrollHeight int `yaml:"scrollHeight" jsonschema:"minimum=1"`
|
||||
// If true, allow scrolling past the bottom of the content in the main window
|
||||
@@ -91,6 +94,10 @@ type GuiConfig struct {
|
||||
// - 'left': split the window horizontally (side panel on the left, main view on the right)
|
||||
// - 'top': split the window vertically (side panel on top, main view below)
|
||||
EnlargedSideViewLocation string `yaml:"enlargedSideViewLocation"`
|
||||
// If true, wrap lines in the staging view to the width of the view. This
|
||||
// makes it much easier to work with diffs that have long lines, e.g.
|
||||
// paragraphs of markdown text.
|
||||
WrapLinesInStagingView bool `yaml:"wrapLinesInStagingView"`
|
||||
// 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.
|
||||
@@ -109,6 +116,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, 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
|
||||
ShowRandomTip bool `yaml:"showRandomTip"`
|
||||
// If true, show the command log
|
||||
@@ -142,9 +151,9 @@ type GuiConfig struct {
|
||||
// One of: 'auto' | 'always'
|
||||
// If 'auto', only split the main window when a file has both staged and unstaged changes
|
||||
SplitDiff string `yaml:"splitDiff" jsonschema:"enum=auto,enum=always"`
|
||||
// Default size for focused window. Window size can be changed from within Lazygit with '+' and '_' (but this won't change the default).
|
||||
// Default size for focused window. Can be changed from within Lazygit with '+' and '_' (but this won't change the default).
|
||||
// One of: 'normal' (default) | 'half' | 'full'
|
||||
WindowSize string `yaml:"windowSize" jsonschema:"enum=normal,enum=half,enum=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"`
|
||||
@@ -161,6 +170,12 @@ type GuiConfig struct {
|
||||
// Status panel view.
|
||||
// One of 'dashboard' (default) | 'allBranchesLog'
|
||||
StatusPanelView string `yaml:"statusPanelView" jsonschema:"enum=dashboard,enum=allBranchesLog"`
|
||||
// If true, jump to the Files panel after popping a stash
|
||||
SwitchToFilesAfterStashPop bool `yaml:"switchToFilesAfterStashPop"`
|
||||
// If true, jump to the Files panel after applying a stash
|
||||
SwitchToFilesAfterStashApply bool `yaml:"switchToFilesAfterStashApply"`
|
||||
// If true, when using the panel jump keys (default 1 through 5) and target panel is already active, go to next tab instead
|
||||
SwitchTabsWithPanelJumpKeys bool `yaml:"switchTabsWithPanelJumpKeys"`
|
||||
}
|
||||
|
||||
func (c *GuiConfig) UseFuzzySearch() bool {
|
||||
@@ -232,7 +247,7 @@ type GitConfig struct {
|
||||
// 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: User `allBranchesLogCmds` instead.
|
||||
// Deprecated: Use `allBranchesLogCmds` instead.
|
||||
AllBranchesLogCmd string `yaml:"allBranchesLogCmd"`
|
||||
// Commands used to display git log of all branches in the main window, they will be cycled in order of appearance
|
||||
AllBranchesLogCmds []string `yaml:"allBranchesLogCmds"`
|
||||
@@ -441,6 +456,8 @@ type KeybindingFilesConfig struct {
|
||||
OpenMergeTool string `yaml:"openMergeTool"`
|
||||
OpenStatusFilter string `yaml:"openStatusFilter"`
|
||||
CopyFileInfoToClipboard string `yaml:"copyFileInfoToClipboard"`
|
||||
CollapseAll string `yaml:"collapseAll"`
|
||||
ExpandAll string `yaml:"expandAll"`
|
||||
}
|
||||
|
||||
type KeybindingBranchesConfig struct {
|
||||
@@ -684,6 +701,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
ExpandedSidePanelWeight: 2,
|
||||
MainPanelSplitMode: "flexible",
|
||||
EnlargedSideViewLocation: "left",
|
||||
WrapLinesInStagingView: true,
|
||||
Language: "auto",
|
||||
TimeFormat: "02 Jan 06",
|
||||
ShortTimeFormat: time.Kitchen,
|
||||
@@ -708,6 +726,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
ShowBottomLine: true,
|
||||
ShowPanelJumps: true,
|
||||
ShowFileTree: true,
|
||||
ShowNumstatInFilesView: false,
|
||||
ShowRandomTip: true,
|
||||
ShowIcons: false,
|
||||
NerdFontsVersion: "",
|
||||
@@ -720,7 +739,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
CommandLogSize: 8,
|
||||
SplitDiff: "auto",
|
||||
SkipRewordInEditorWarning: false,
|
||||
WindowSize: "normal",
|
||||
ScreenMode: "normal",
|
||||
Border: "rounded",
|
||||
AnimateExplosion: true,
|
||||
PortraitMode: "auto",
|
||||
@@ -729,7 +748,10 @@ func GetDefaultConfig() *UserConfig {
|
||||
Frames: []string{"|", "/", "-", "\\"},
|
||||
Rate: 50,
|
||||
},
|
||||
StatusPanelView: "dashboard",
|
||||
StatusPanelView: "dashboard",
|
||||
SwitchToFilesAfterStashPop: true,
|
||||
SwitchToFilesAfterStashApply: true,
|
||||
SwitchTabsWithPanelJumpKeys: false,
|
||||
},
|
||||
Git: GitConfig{
|
||||
Paging: PagingConfig{
|
||||
@@ -878,6 +900,8 @@ func GetDefaultConfig() *UserConfig {
|
||||
OpenStatusFilter: "<c-b>",
|
||||
ConfirmDiscard: "x",
|
||||
CopyFileInfoToClipboard: "y",
|
||||
CollapseAll: "-",
|
||||
ExpandAll: "=",
|
||||
},
|
||||
Branches: KeybindingBranchesConfig{
|
||||
CopyPullRequestURL: "<c-y>",
|
||||
|
||||
+11
-15
@@ -3,7 +3,6 @@ package gui
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
@@ -76,21 +75,18 @@ func (self *BackgroundRoutineMgr) startBackgroundRoutines() {
|
||||
func (self *BackgroundRoutineMgr) startBackgroundFetch() {
|
||||
self.gui.waitForIntro.Wait()
|
||||
|
||||
isNew := self.gui.IsNewRepo
|
||||
fetch := func() error {
|
||||
err := self.backgroundFetch()
|
||||
self.gui.c.Render()
|
||||
return err
|
||||
}
|
||||
|
||||
// We want an immediate fetch at startup, and since goEvery starts by
|
||||
// waiting for the interval, we need to trigger one manually first
|
||||
_ = fetch()
|
||||
|
||||
userConfig := self.gui.UserConfig()
|
||||
if !isNew {
|
||||
time.After(time.Duration(userConfig.Refresher.FetchInterval) * time.Second)
|
||||
}
|
||||
err := self.backgroundFetch()
|
||||
if err != nil && strings.Contains(err.Error(), "exit status 128") && isNew {
|
||||
self.gui.c.Alert(self.gui.c.Tr.NoAutomaticGitFetchTitle, self.gui.c.Tr.NoAutomaticGitFetchBody)
|
||||
} else {
|
||||
self.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), self.gui.stopChan, func() error {
|
||||
err := self.backgroundFetch()
|
||||
self.gui.c.Render()
|
||||
return err
|
||||
})
|
||||
}
|
||||
self.goEvery(time.Second*time.Duration(userConfig.Refresher.FetchInterval), self.gui.stopChan, fetch)
|
||||
}
|
||||
|
||||
func (self *BackgroundRoutineMgr) startBackgroundFilesRefresh(refreshInterval int) {
|
||||
|
||||
@@ -30,7 +30,7 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
||||
c.State().GetItemOperation,
|
||||
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
||||
c.Modes().Diffing.Ref,
|
||||
c.Views().Branches.Width(),
|
||||
c.Views().Branches.InnerWidth(),
|
||||
c.Tr,
|
||||
c.UserConfig(),
|
||||
c.Model().Worktrees,
|
||||
@@ -80,6 +80,14 @@ func (self *BranchesContext) GetDiffTerminals() []string {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesContext) RefForAdjustingLineNumberInDiff() string {
|
||||
branch := self.GetSelected()
|
||||
if branch != nil {
|
||||
return branch.ID()
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (self *BranchesContext) ShowBranchHeadsInSubCommits() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -77,6 +77,13 @@ func (self *CommitFilesContext) GetDiffTerminals() []string {
|
||||
return []string{self.GetRef().RefName()}
|
||||
}
|
||||
|
||||
func (self *CommitFilesContext) RefForAdjustingLineNumberInDiff() string {
|
||||
if refs := self.GetRefRange(); refs != nil {
|
||||
return refs.To.RefName()
|
||||
}
|
||||
return self.GetRef().RefName()
|
||||
}
|
||||
|
||||
func (self *CommitFilesContext) GetFromAndToForDiff() (string, string) {
|
||||
if refs := self.GetRefRange(); refs != nil {
|
||||
return refs.From.ParentRefName(), refs.To.RefName()
|
||||
|
||||
@@ -30,6 +30,9 @@ type CommitMessageViewModel struct {
|
||||
// if true, then upon escaping from the commit message panel, we will preserve
|
||||
// the message so that it's still shown next time we open the panel
|
||||
preserveMessage bool
|
||||
// we remember the initial message so that we can tell whether we should preserve
|
||||
// the message; if it's still identical to the initial message, we don't
|
||||
initialMessage string
|
||||
// the full preserved message (combined summary and description)
|
||||
preservedMessage string
|
||||
// invoked when pressing enter in the commit message panel
|
||||
@@ -84,6 +87,10 @@ func (self *CommitMessageContext) SetPreservedMessage(message string) {
|
||||
self.viewModel.preservedMessage = message
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) GetInitialMessage() string {
|
||||
return strings.TrimSpace(self.viewModel.initialMessage)
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) GetHistoryMessage() string {
|
||||
return self.viewModel.historyMessage
|
||||
}
|
||||
@@ -101,11 +108,13 @@ func (self *CommitMessageContext) SetPanelState(
|
||||
summaryTitle string,
|
||||
descriptionTitle string,
|
||||
preserveMessage bool,
|
||||
initialMessage string,
|
||||
onConfirm func(string, string) error,
|
||||
onSwitchToEditor func(string) error,
|
||||
) {
|
||||
self.viewModel.selectedindex = index
|
||||
self.viewModel.preserveMessage = preserveMessage
|
||||
self.viewModel.initialMessage = initialMessage
|
||||
self.viewModel.onConfirm = onConfirm
|
||||
self.viewModel.onSwitchToEditor = onSwitchToEditor
|
||||
self.GetView().Title = summaryTitle
|
||||
|
||||
@@ -131,7 +131,7 @@ func (self *ListContextTrait) IsItemVisible(item types.HasUrn) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// By default, list contexts supporta range select
|
||||
// By default, list contexts supports range select
|
||||
func (self *ListContextTrait) RangeSelectEnabled() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -121,7 +121,10 @@ func (self *ListRenderer) insertNonModelItems(
|
||||
break
|
||||
}
|
||||
if item.Index+offset >= startIdx {
|
||||
padding := strings.Repeat(" ", columnPositions[item.Column])
|
||||
padding := ""
|
||||
if columnPositions != nil {
|
||||
padding = strings.Repeat(" ", columnPositions[item.Column])
|
||||
}
|
||||
lines = slices.Insert(lines, item.Index+offset-startIdx, padding+item.Content)
|
||||
}
|
||||
offset++
|
||||
|
||||
@@ -170,6 +170,14 @@ func (self *LocalCommitsContext) GetDiffTerminals() []string {
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
func (self *LocalCommitsContext) RefForAdjustingLineNumberInDiff() string {
|
||||
commits, _, _ := self.GetSelectedItems()
|
||||
if commits == nil {
|
||||
return ""
|
||||
}
|
||||
return commits[0].Hash
|
||||
}
|
||||
|
||||
func (self *LocalCommitsContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
|
||||
return searchModelCommits(caseSensitive, self.GetCommits(), self.ColumnPositions(), searchStr)
|
||||
}
|
||||
|
||||
@@ -115,5 +115,5 @@ func (self *MergeConflictsContext) SetSelectedLineRange() {
|
||||
func (self *MergeConflictsContext) GetOriginY() int {
|
||||
view := self.GetView()
|
||||
conflictMiddle := self.GetState().GetConflictMiddle()
|
||||
return int(math.Max(0, float64(conflictMiddle-(view.Height()/2))))
|
||||
return int(math.Max(0, float64(conflictMiddle-(view.InnerHeight()/2))))
|
||||
}
|
||||
|
||||
@@ -39,12 +39,13 @@ func NewPatchExplorerContext(
|
||||
mutex: &deadlock.Mutex{},
|
||||
getIncludedLineIndices: getIncludedLineIndices,
|
||||
SimpleContext: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
View: view,
|
||||
WindowName: windowName,
|
||||
Key: key,
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
Focusable: true,
|
||||
HighlightOnFocus: true,
|
||||
View: view,
|
||||
WindowName: windowName,
|
||||
Key: key,
|
||||
Kind: types.MAIN_CONTEXT,
|
||||
Focusable: true,
|
||||
HighlightOnFocus: true,
|
||||
NeedsRerenderOnWidthChange: types.NEEDS_RERENDER_ON_WIDTH_CHANGE_WHEN_WIDTH_CHANGES,
|
||||
})),
|
||||
SearchTrait: NewSearchTrait(c),
|
||||
}
|
||||
@@ -53,11 +54,13 @@ func NewPatchExplorerContext(
|
||||
func(selectedLineIdx int) error {
|
||||
ctx.GetMutex().Lock()
|
||||
defer ctx.GetMutex().Unlock()
|
||||
ctx.NavigateTo(ctx.c.Context().IsCurrent(ctx), selectedLineIdx)
|
||||
ctx.NavigateTo(selectedLineIdx)
|
||||
return nil
|
||||
}),
|
||||
)
|
||||
|
||||
ctx.SetHandleRenderFunc(ctx.OnViewWidthChanged)
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
@@ -79,15 +82,15 @@ func (self *PatchExplorerContext) GetIncludedLineIndices() []int {
|
||||
return self.getIncludedLineIndices()
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) RenderAndFocus(isFocused bool) {
|
||||
self.setContent(isFocused)
|
||||
func (self *PatchExplorerContext) RenderAndFocus() {
|
||||
self.setContent()
|
||||
|
||||
self.FocusSelection()
|
||||
self.c.Render()
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) Render(isFocused bool) {
|
||||
self.setContent(isFocused)
|
||||
func (self *PatchExplorerContext) Render() {
|
||||
self.setContent()
|
||||
|
||||
self.c.Render()
|
||||
}
|
||||
@@ -97,41 +100,40 @@ func (self *PatchExplorerContext) Focus() {
|
||||
self.c.Render()
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) setContent(isFocused bool) {
|
||||
self.GetView().SetContent(self.GetContentToRender(isFocused))
|
||||
func (self *PatchExplorerContext) setContent() {
|
||||
self.GetView().SetContent(self.GetContentToRender())
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) FocusSelection() {
|
||||
view := self.GetView()
|
||||
state := self.GetState()
|
||||
_, viewHeight := view.Size()
|
||||
bufferHeight := viewHeight - 1
|
||||
bufferHeight := view.InnerHeight()
|
||||
_, origin := view.Origin()
|
||||
numLines := view.LinesHeight()
|
||||
numLines := view.ViewLinesHeight()
|
||||
|
||||
newOriginY := state.CalculateOrigin(origin, bufferHeight, numLines)
|
||||
|
||||
view.SetOriginY(newOriginY)
|
||||
|
||||
startIdx, endIdx := state.SelectedRange()
|
||||
startIdx, endIdx := state.SelectedViewRange()
|
||||
// As far as the view is concerned, we are always selecting a range
|
||||
view.SetRangeSelectStart(startIdx)
|
||||
view.SetCursorY(endIdx - newOriginY)
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) GetContentToRender(isFocused bool) string {
|
||||
func (self *PatchExplorerContext) GetContentToRender() string {
|
||||
if self.GetState() == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return self.GetState().RenderForLineIndices(isFocused, self.GetIncludedLineIndices())
|
||||
return self.GetState().RenderForLineIndices(self.GetIncludedLineIndices())
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) NavigateTo(isFocused bool, selectedLineIdx int) {
|
||||
func (self *PatchExplorerContext) NavigateTo(selectedLineIdx int) {
|
||||
self.GetState().SetLineSelectMode()
|
||||
self.GetState().SelectLine(selectedLineIdx)
|
||||
|
||||
self.RenderAndFocus(isFocused)
|
||||
self.RenderAndFocus()
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) GetMutex() *deadlock.Mutex {
|
||||
@@ -141,3 +143,11 @@ func (self *PatchExplorerContext) GetMutex() *deadlock.Mutex {
|
||||
func (self *PatchExplorerContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *PatchExplorerContext) OnViewWidthChanged() {
|
||||
if state := self.GetState(); state != nil {
|
||||
state.OnViewWidthChanged(self.GetView())
|
||||
self.setContent()
|
||||
self.RenderAndFocus()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,6 +86,10 @@ func (self *ReflogCommitsContext) GetDiffTerminals() []string {
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
func (self *ReflogCommitsContext) RefForAdjustingLineNumberInDiff() string {
|
||||
return self.GetSelectedItemId()
|
||||
}
|
||||
|
||||
func (self *ReflogCommitsContext) ShowBranchHeadsInSubCommits() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -78,6 +78,10 @@ func (self *RemoteBranchesContext) GetDiffTerminals() []string {
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesContext) RefForAdjustingLineNumberInDiff() string {
|
||||
return self.GetSelectedItemId()
|
||||
}
|
||||
|
||||
func (self *RemoteBranchesContext) ShowBranchHeadsInSubCommits() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -53,3 +53,7 @@ func (self *RemotesContext) GetDiffTerminals() []string {
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
func (self *RemotesContext) RefForAdjustingLineNumberInDiff() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
type SimpleContext struct {
|
||||
*BaseContext
|
||||
handleRenderFunc func()
|
||||
}
|
||||
|
||||
func NewSimpleContext(baseContext *BaseContext) *SimpleContext {
|
||||
@@ -54,6 +55,13 @@ func (self *SimpleContext) HandleFocusLost(opts types.OnFocusLostOpts) {
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleRender() {
|
||||
if self.handleRenderFunc != nil {
|
||||
self.handleRenderFunc()
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SimpleContext) SetHandleRenderFunc(f func()) {
|
||||
self.handleRenderFunc = f
|
||||
}
|
||||
|
||||
func (self *SimpleContext) HandleRenderToMain() {
|
||||
|
||||
@@ -71,3 +71,7 @@ func (self *StashContext) GetDiffTerminals() []string {
|
||||
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
func (self *StashContext) RefForAdjustingLineNumberInDiff() string {
|
||||
return self.GetSelectedItemId()
|
||||
}
|
||||
|
||||
@@ -217,6 +217,14 @@ func (self *SubCommitsContext) GetDiffTerminals() []string {
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
func (self *SubCommitsContext) RefForAdjustingLineNumberInDiff() string {
|
||||
commits, _, _ := self.GetSelectedItems()
|
||||
if commits == nil {
|
||||
return ""
|
||||
}
|
||||
return commits[0].Hash
|
||||
}
|
||||
|
||||
func (self *SubCommitsContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
|
||||
return searchModelCommits(caseSensitive, self.GetCommits(), self.ColumnPositions(), searchStr)
|
||||
}
|
||||
|
||||
@@ -66,6 +66,10 @@ func (self *TagsContext) GetDiffTerminals() []string {
|
||||
return []string{itemId}
|
||||
}
|
||||
|
||||
func (self *TagsContext) RefForAdjustingLineNumberInDiff() string {
|
||||
return self.GetSelectedItemId()
|
||||
}
|
||||
|
||||
func (self *TagsContext) ShowBranchHeadsInSubCommits() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ func (self *ViewTrait) SetOriginX(value int) {
|
||||
// tells us the start of line indexes shown in the view currently as well as the capacity of lines shown in the viewport.
|
||||
func (self *ViewTrait) ViewPortYBounds() (int, int) {
|
||||
_, start := self.view.Origin()
|
||||
length := self.view.InnerHeight() + 1
|
||||
length := self.view.InnerHeight()
|
||||
return start, length
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ func (self *ViewTrait) ScrollDown(value int) {
|
||||
|
||||
// this returns the amount we'll scroll if we want to scroll by a page.
|
||||
func (self *ViewTrait) PageDelta() int {
|
||||
_, height := self.view.Size()
|
||||
height := self.view.InnerHeight()
|
||||
|
||||
delta := height - 1
|
||||
if delta == 0 {
|
||||
|
||||
@@ -30,7 +30,8 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
|
||||
|
||||
getDisplayStrings := func(_ int, _ int) [][]string {
|
||||
showFileIcons := icons.IsIconEnabled() && c.UserConfig().Gui.ShowFileIcons
|
||||
lines := presentation.RenderFileTree(viewModel, c.Model().Submodules, showFileIcons)
|
||||
showNumstat := c.UserConfig().Gui.ShowNumstatInFilesView
|
||||
lines := presentation.RenderFileTree(viewModel, c.Model().Submodules, showFileIcons, showNumstat)
|
||||
return lo.Map(lines, func(line string, _ int) []string {
|
||||
return []string{line}
|
||||
})
|
||||
|
||||
@@ -107,7 +107,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
Files: helpers.NewFilesHelper(helperCommon),
|
||||
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, refsHelper, commitsHelper, gpgHelper),
|
||||
Tags: helpers.NewTagsHelper(helperCommon, commitsHelper),
|
||||
BranchesHelper: helpers.NewBranchesHelper(helperCommon),
|
||||
BranchesHelper: helpers.NewBranchesHelper(helperCommon, worktreeHelper),
|
||||
GPG: helpers.NewGpgHelper(helperCommon),
|
||||
MergeAndRebase: rebaseHelper,
|
||||
MergeConflicts: mergeConflictsHelper,
|
||||
@@ -299,7 +299,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.CustomPatchBuilderSecondary,
|
||||
verticalScrollControllerFactory.Create(gui.State.Contexts.CustomPatchBuilder),
|
||||
verticalScrollControllerFactory.Create(gui.State.Contexts.CustomPatchBuilderSecondary),
|
||||
)
|
||||
|
||||
controllers.AttachControllers(gui.State.Contexts.MergeConflicts,
|
||||
|
||||
@@ -280,15 +280,7 @@ func (self *BasicCommitsController) createResetMenu(commit *models.Commit) error
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) checkout(commit *models.Commit) error {
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.CheckoutCommit,
|
||||
Prompt: self.c.Tr.SureCheckoutThisCommit,
|
||||
HandleConfirm: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CheckoutCommit)
|
||||
return self.c.Helpers().Refs.CheckoutRef(commit.Hash, types.CheckoutRefOptions{})
|
||||
},
|
||||
})
|
||||
return nil
|
||||
return self.c.Helpers().Refs.CreateCheckoutMenu(commit)
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copyRange(*models.Commit) error {
|
||||
|
||||
@@ -3,7 +3,6 @@ package controllers
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
@@ -92,8 +91,8 @@ func (self *BranchesController) GetKeybindings(opts types.KeybindingsOpts) []*ty
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Remove),
|
||||
Handler: self.withItem(self.delete),
|
||||
GetDisabledReason: self.require(self.singleItemSelected(self.branchIsReal)),
|
||||
Handler: self.withItems(self.delete),
|
||||
GetDisabledReason: self.require(self.itemRangeSelected(self.branchesAreReal)),
|
||||
Description: self.c.Tr.Delete,
|
||||
Tooltip: self.c.Tr.BranchDeleteTooltip,
|
||||
OpensMenu: true,
|
||||
@@ -195,6 +194,10 @@ func (self *BranchesController) GetOnRenderToMain() func() {
|
||||
}
|
||||
|
||||
func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branch) error {
|
||||
upstream := lo.Ternary(selectedBranch.RemoteBranchStoredLocally(),
|
||||
selectedBranch.ShortUpstreamRefName(),
|
||||
self.c.Tr.UpstreamGenericName)
|
||||
|
||||
viewDivergenceItem := &types.MenuItem{
|
||||
LabelColumns: []string{self.c.Tr.ViewDivergenceFromUpstream},
|
||||
OnPress: func() error {
|
||||
@@ -205,7 +208,7 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
|
||||
|
||||
return self.c.Helpers().SubCommits.ViewSubCommits(helpers.ViewSubCommitsOpts{
|
||||
Ref: branch,
|
||||
TitleRef: fmt.Sprintf("%s <-> %s", branch.RefName(), branch.ShortUpstreamRefName()),
|
||||
TitleRef: fmt.Sprintf("%s <-> %s", branch.RefName(), upstream),
|
||||
RefToShowDivergenceFrom: branch.FullUpstreamRefName(),
|
||||
Context: self.context(),
|
||||
ShowBranchHeads: false,
|
||||
@@ -294,9 +297,6 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
|
||||
Key: 's',
|
||||
}
|
||||
|
||||
upstream := lo.Ternary(selectedBranch.RemoteBranchStoredLocally(),
|
||||
fmt.Sprintf("%s/%s", selectedBranch.UpstreamRemote, selectedBranch.Name),
|
||||
self.c.Tr.UpstreamGenericName)
|
||||
upstreamResetOptions := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.ViewUpstreamResetOptions,
|
||||
map[string]string{"upstream": upstream},
|
||||
@@ -333,7 +333,7 @@ func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branc
|
||||
LabelColumns: []string{upstreamRebaseOptions},
|
||||
OpensMenu: true,
|
||||
OnPress: func() error {
|
||||
if err := self.c.Helpers().MergeAndRebase.RebaseOntoRef(selectedBranch.ShortUpstreamRefName()); err != nil {
|
||||
if err := self.c.Helpers().MergeAndRebase.RebaseOntoRef(upstream); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -521,128 +521,84 @@ func (self *BranchesController) createNewBranchWithName(newBranchName string) er
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, KeepBranchSelectionIndex: true})
|
||||
}
|
||||
|
||||
func (self *BranchesController) checkedOutByOtherWorktree(branch *models.Branch) bool {
|
||||
return git_commands.CheckedOutByOtherWorktree(branch, self.c.Model().Worktrees)
|
||||
func (self *BranchesController) localDelete(branches []*models.Branch) error {
|
||||
return self.c.Helpers().BranchesHelper.ConfirmLocalDelete(branches)
|
||||
}
|
||||
|
||||
func (self *BranchesController) promptWorktreeBranchDelete(selectedBranch *models.Branch) error {
|
||||
worktree, ok := self.worktreeForBranch(selectedBranch)
|
||||
if !ok {
|
||||
self.c.Log.Error("promptWorktreeBranchDelete out of sync with list of worktrees")
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: i18n
|
||||
title := utils.ResolvePlaceholderString(self.c.Tr.BranchCheckedOutByWorktree, map[string]string{
|
||||
"worktreeName": worktree.Name,
|
||||
"branchName": selectedBranch.Name,
|
||||
})
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: title,
|
||||
Items: []*types.MenuItem{
|
||||
{
|
||||
Label: self.c.Tr.SwitchToWorktree,
|
||||
OnPress: func() error {
|
||||
return self.c.Helpers().Worktree.Switch(worktree, context.LOCAL_BRANCHES_CONTEXT_KEY)
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.DetachWorktree,
|
||||
Tooltip: self.c.Tr.DetachWorktreeTooltip,
|
||||
OnPress: func() error {
|
||||
return self.c.Helpers().Worktree.Detach(worktree)
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.RemoveWorktree,
|
||||
OnPress: func() error {
|
||||
return self.c.Helpers().Worktree.Remove(worktree, false)
|
||||
},
|
||||
},
|
||||
},
|
||||
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)
|
||||
}
|
||||
|
||||
func (self *BranchesController) localDelete(branch *models.Branch) error {
|
||||
if self.checkedOutByOtherWorktree(branch) {
|
||||
return self.promptWorktreeBranchDelete(branch)
|
||||
}
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(_ gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch)
|
||||
err := self.c.Git().Branch.LocalDelete(branch.Name, false)
|
||||
if err != nil && strings.Contains(err.Error(), "git branch -D ") {
|
||||
return self.forceDelete(branch)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
})
|
||||
func (self *BranchesController) localAndRemoteDelete(branches []*models.Branch) error {
|
||||
return self.c.Helpers().BranchesHelper.ConfirmLocalAndRemoteDelete(branches)
|
||||
}
|
||||
|
||||
func (self *BranchesController) remoteDelete(branch *models.Branch) error {
|
||||
return self.c.Helpers().BranchesHelper.ConfirmDeleteRemote(branch.UpstreamRemote, branch.Name)
|
||||
}
|
||||
|
||||
func (self *BranchesController) forceDelete(branch *models.Branch) error {
|
||||
title := self.c.Tr.ForceDeleteBranchTitle
|
||||
message := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.ForceDeleteBranchMessage,
|
||||
map[string]string{
|
||||
"selectedBranchName": branch.Name,
|
||||
},
|
||||
)
|
||||
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: title,
|
||||
Prompt: message,
|
||||
HandleConfirm: func() error {
|
||||
if err := self.c.Git().Branch.LocalDelete(branch.Name, true); err != nil {
|
||||
return err
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesController) delete(branch *models.Branch) error {
|
||||
func (self *BranchesController) delete(branches []*models.Branch) error {
|
||||
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef()
|
||||
isBranchCheckedOut := lo.SomeBy(branches, func(branch *models.Branch) bool {
|
||||
return checkedOutBranch.Name == branch.Name
|
||||
})
|
||||
hasUpstream := lo.EveryBy(branches, func(branch *models.Branch) bool {
|
||||
return branch.IsTrackingRemote() && !branch.UpstreamGone
|
||||
})
|
||||
|
||||
localDeleteItem := &types.MenuItem{
|
||||
Label: self.c.Tr.DeleteLocalBranch,
|
||||
Label: lo.Ternary(len(branches) > 1, self.c.Tr.DeleteLocalBranches, self.c.Tr.DeleteLocalBranch),
|
||||
Key: 'c',
|
||||
OnPress: func() error {
|
||||
return self.localDelete(branch)
|
||||
return self.localDelete(branches)
|
||||
},
|
||||
}
|
||||
if checkedOutBranch.Name == branch.Name {
|
||||
if isBranchCheckedOut {
|
||||
localDeleteItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.CantDeleteCheckOutBranch}
|
||||
}
|
||||
|
||||
remoteDeleteItem := &types.MenuItem{
|
||||
Label: self.c.Tr.DeleteRemoteBranch,
|
||||
Label: lo.Ternary(len(branches) > 1, self.c.Tr.DeleteRemoteBranches, self.c.Tr.DeleteRemoteBranch),
|
||||
Key: 'r',
|
||||
OnPress: func() error {
|
||||
return self.remoteDelete(branch)
|
||||
return self.remoteDelete(branches)
|
||||
},
|
||||
}
|
||||
if !branch.IsTrackingRemote() || branch.UpstreamGone {
|
||||
remoteDeleteItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.UpstreamNotSetError}
|
||||
if !hasUpstream {
|
||||
remoteDeleteItem.DisabledReason = &types.DisabledReason{
|
||||
Text: lo.Ternary(len(branches) > 1, self.c.Tr.UpstreamsNotSetError, self.c.Tr.UpstreamNotSetError),
|
||||
}
|
||||
}
|
||||
|
||||
menuTitle := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteBranchTitle,
|
||||
map[string]string{
|
||||
"selectedBranchName": branch.Name,
|
||||
deleteBothItem := &types.MenuItem{
|
||||
Label: lo.Ternary(len(branches) > 1, self.c.Tr.DeleteLocalAndRemoteBranches, self.c.Tr.DeleteLocalAndRemoteBranch),
|
||||
Key: 'b',
|
||||
OnPress: func() error {
|
||||
return self.localAndRemoteDelete(branches)
|
||||
},
|
||||
)
|
||||
}
|
||||
if isBranchCheckedOut {
|
||||
deleteBothItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.CantDeleteCheckOutBranch}
|
||||
} else if !hasUpstream {
|
||||
deleteBothItem.DisabledReason = &types.DisabledReason{
|
||||
Text: lo.Ternary(len(branches) > 1, self.c.Tr.UpstreamsNotSetError, self.c.Tr.UpstreamNotSetError),
|
||||
}
|
||||
}
|
||||
|
||||
var menuTitle string
|
||||
if len(branches) == 1 {
|
||||
menuTitle = utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteBranchTitle,
|
||||
map[string]string{
|
||||
"selectedBranchName": branches[0].Name,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
menuTitle = self.c.Tr.DeleteBranchesTitle
|
||||
}
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: menuTitle,
|
||||
Items: []*types.MenuItem{localDeleteItem, remoteDeleteItem},
|
||||
Items: []*types.MenuItem{localDeleteItem, remoteDeleteItem, deleteBothItem},
|
||||
})
|
||||
}
|
||||
|
||||
@@ -674,9 +630,11 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
self.c.LogAction(action)
|
||||
|
||||
worktreeGitDir := ""
|
||||
worktreePath := ""
|
||||
// if it is the current worktree path, no need to specify the path
|
||||
if !worktree.IsCurrent {
|
||||
worktreeGitDir = worktree.GitDir
|
||||
worktreePath = worktree.Path
|
||||
}
|
||||
|
||||
err := self.c.Git().Sync.Pull(
|
||||
@@ -686,6 +644,7 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
BranchName: branch.UpstreamBranch,
|
||||
FastForwardOnly: true,
|
||||
WorktreeGitDir: worktreeGitDir,
|
||||
WorktreePath: worktreePath,
|
||||
},
|
||||
)
|
||||
_ = self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
@@ -793,11 +752,23 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
|
||||
{
|
||||
LabelColumns: fromToLabelColumns(branch.Name, self.c.Tr.SelectBranch),
|
||||
OnPress: func() error {
|
||||
if !branch.IsTrackingRemote() {
|
||||
return errors.New(self.c.Tr.PullRequestNoUpstream)
|
||||
}
|
||||
|
||||
if len(self.c.Model().Remotes) == 1 {
|
||||
toRemote := self.c.Model().Remotes[0].Name
|
||||
self.c.Log.Debugf("PR will target the only existing remote '%s'", toRemote)
|
||||
return self.promptForTargetBranchNameAndCreatePullRequest(branch, toRemote)
|
||||
}
|
||||
|
||||
self.c.Prompt(types.PromptOpts{
|
||||
Title: branch.Name + " →",
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteBranchesSuggestionsFunc("/"),
|
||||
HandleConfirm: func(targetBranchName string) error {
|
||||
return self.createPullRequest(branch.Name, targetBranchName)
|
||||
Title: self.c.Tr.SelectTargetRemote,
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteSuggestionsFunc(),
|
||||
HandleConfirm: func(toRemote string) error {
|
||||
self.c.Log.Debugf("PR will target remote '%s'", toRemote)
|
||||
|
||||
return self.promptForTargetBranchNameAndCreatePullRequest(branch, toRemote)
|
||||
},
|
||||
})
|
||||
|
||||
@@ -827,6 +798,26 @@ func (self *BranchesController) createPullRequestMenu(selectedBranch *models.Bra
|
||||
return self.c.Menu(types.CreateMenuOptions{Title: fmt.Sprint(self.c.Tr.CreatePullRequestOptions), Items: menuItems})
|
||||
}
|
||||
|
||||
func (self *BranchesController) promptForTargetBranchNameAndCreatePullRequest(fromBranch *models.Branch, toRemote string) error {
|
||||
remoteDoesNotExist := lo.NoneBy(self.c.Model().Remotes, func(remote *models.Remote) bool {
|
||||
return remote.Name == toRemote
|
||||
})
|
||||
if remoteDoesNotExist {
|
||||
return fmt.Errorf(self.c.Tr.NoValidRemoteName, toRemote)
|
||||
}
|
||||
|
||||
self.c.Prompt(types.PromptOpts{
|
||||
Title: fmt.Sprintf("%s → %s/", fromBranch.UpstreamBranch, toRemote),
|
||||
FindSuggestionsFunc: self.c.Helpers().Suggestions.GetRemoteBranchesForRemoteSuggestionsFunc(toRemote),
|
||||
HandleConfirm: func(toBranch string) error {
|
||||
self.c.Log.Debugf("PR will target branch '%s' on remote '%s'", toBranch, toRemote)
|
||||
return self.createPullRequest(fromBranch.UpstreamBranch, toBranch)
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesController) createPullRequest(from string, to string) error {
|
||||
url, err := self.c.Helpers().Host.GetPullRequestURL(from, to)
|
||||
if err != nil {
|
||||
@@ -850,6 +841,16 @@ func (self *BranchesController) branchIsReal(branch *models.Branch) *types.Disab
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesController) branchesAreReal(selectedBranches []*models.Branch, startIdx int, endIdx int) *types.DisabledReason {
|
||||
if !lo.EveryBy(selectedBranches, func(branch *models.Branch) bool {
|
||||
return branch.IsRealBranch()
|
||||
}) {
|
||||
return &types.DisabledReason{Text: self.c.Tr.SelectedItemIsNotABranch}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesController) notMergingIntoYourself(branch *models.Branch) *types.DisabledReason {
|
||||
selectedBranchName := branch.Name
|
||||
checkedOutBranch := self.c.Helpers().Refs.GetCheckedOutRef().Name
|
||||
|
||||
@@ -3,7 +3,9 @@ package controllers
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type CommitDescriptionController struct {
|
||||
@@ -59,6 +61,15 @@ 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),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) switchToCommitMessage() error {
|
||||
self.c.Context().Replace(self.c.Contexts().CommitMessage)
|
||||
return nil
|
||||
|
||||
@@ -69,6 +69,12 @@ func (self *CommitMessageController) GetMouseKeybindings(opts types.KeybindingsO
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) GetOnFocus() func(types.OnFocusOpts) {
|
||||
return func(types.OnFocusOpts) {
|
||||
self.c.Views().CommitDescription.Footer = ""
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) GetOnFocusLost() func(types.OnFocusLostOpts) {
|
||||
return func(types.OnFocusLostOpts) {
|
||||
self.context().RenderCommitLength()
|
||||
|
||||
@@ -109,6 +109,20 @@ func (self *CommitFilesController) GetKeybindings(opts types.KeybindingsOpts) []
|
||||
Description: self.c.Tr.ToggleTreeView,
|
||||
Tooltip: self.c.Tr.ToggleTreeViewTooltip,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Files.CollapseAll),
|
||||
Handler: self.collapseAll,
|
||||
Description: self.c.Tr.CollapseAll,
|
||||
Tooltip: self.c.Tr.CollapseAllTooltip,
|
||||
GetDisabledReason: self.require(self.isInTreeMode),
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Files.ExpandAll),
|
||||
Handler: self.expandAll,
|
||||
Description: self.c.Tr.ExpandAll,
|
||||
Tooltip: self.c.Tr.ExpandAllTooltip,
|
||||
GetDisabledReason: self.require(self.isInTreeMode),
|
||||
},
|
||||
}
|
||||
|
||||
return bindings
|
||||
@@ -303,7 +317,8 @@ func (self *CommitFilesController) toggleForPatch(selectedNodes []*filetree.Comm
|
||||
self.c.Git().Patch.PatchBuilder.Reset()
|
||||
}
|
||||
|
||||
return self.c.PostRefreshUpdate(self.context())
|
||||
self.c.PostRefreshUpdate(self.context())
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -387,9 +402,7 @@ func (self *CommitFilesController) enterCommitFile(node *filetree.CommitFileNode
|
||||
func (self *CommitFilesController) handleToggleCommitFileDirCollapsed(node *filetree.CommitFileNode) error {
|
||||
self.context().CommitFileTreeViewModel.ToggleCollapsed(node.GetPath())
|
||||
|
||||
if err := self.c.PostRefreshUpdate(self.context()); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
self.c.PostRefreshUpdate(self.context())
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -398,7 +411,24 @@ func (self *CommitFilesController) handleToggleCommitFileDirCollapsed(node *file
|
||||
func (self *CommitFilesController) toggleTreeView() error {
|
||||
self.context().CommitFileTreeViewModel.ToggleShowTree()
|
||||
|
||||
return self.c.PostRefreshUpdate(self.context())
|
||||
self.c.PostRefreshUpdate(self.context())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) collapseAll() error {
|
||||
self.context().CommitFileTreeViewModel.CollapseAll()
|
||||
|
||||
self.c.PostRefreshUpdate(self.context())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) expandAll() error {
|
||||
self.context().CommitFileTreeViewModel.ExpandAll()
|
||||
|
||||
self.c.PostRefreshUpdate(self.context())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NOTE: these functions are identical to those in files_controller.go (except for types) and
|
||||
@@ -420,3 +450,11 @@ func isDescendentOfSelectedCommitFileNodes(node *filetree.CommitFileNode, select
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) isInTreeMode() *types.DisabledReason {
|
||||
if !self.context().CommitFileTreeViewModel.InTreeMode() {
|
||||
return &types.DisabledReason{Text: self.c.Tr.DisabledInFlatView}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package controllers
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
@@ -68,7 +69,9 @@ func (self *ContextLinesController) Increase() error {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.AppState.DiffContextSize++
|
||||
if self.c.AppState.DiffContextSize < math.MaxUint64 {
|
||||
self.c.AppState.DiffContextSize++
|
||||
}
|
||||
return self.applyChange()
|
||||
}
|
||||
|
||||
@@ -76,14 +79,14 @@ func (self *ContextLinesController) Increase() error {
|
||||
}
|
||||
|
||||
func (self *ContextLinesController) Decrease() error {
|
||||
old_size := self.c.AppState.DiffContextSize
|
||||
|
||||
if self.isShowingDiff() && old_size > 1 {
|
||||
if self.isShowingDiff() {
|
||||
if err := self.checkCanChangeContext(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.AppState.DiffContextSize = old_size - 1
|
||||
if self.c.AppState.DiffContextSize > 0 {
|
||||
self.c.AppState.DiffContextSize--
|
||||
}
|
||||
return self.applyChange()
|
||||
}
|
||||
|
||||
|
||||
@@ -186,6 +186,20 @@ func (self *FilesController) GetKeybindings(opts types.KeybindingsOpts) []*types
|
||||
Description: self.c.Tr.Fetch,
|
||||
Tooltip: self.c.Tr.FetchTooltip,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Files.CollapseAll),
|
||||
Handler: self.collapseAll,
|
||||
Description: self.c.Tr.CollapseAll,
|
||||
Tooltip: self.c.Tr.CollapseAllTooltip,
|
||||
GetDisabledReason: self.require(self.isInTreeMode),
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Files.ExpandAll),
|
||||
Handler: self.expandAll,
|
||||
Description: self.c.Tr.ExpandAll,
|
||||
Tooltip: self.c.Tr.ExpandAllTooltip,
|
||||
GetDisabledReason: self.require(self.isInTreeMode),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,9 +387,7 @@ func (self *FilesController) optimisticChange(nodes []*filetree.FileNode, optimi
|
||||
}
|
||||
|
||||
if rerender {
|
||||
if err := self.c.PostRefreshUpdate(self.c.Contexts().Files); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.PostRefreshUpdate(self.c.Contexts().Files)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -480,6 +492,22 @@ func (self *FilesController) enter() error {
|
||||
return self.EnterFile(types.OnFocusOpts{ClickedWindowName: "", ClickedViewLineIdx: -1})
|
||||
}
|
||||
|
||||
func (self *FilesController) collapseAll() error {
|
||||
self.context().FileTreeViewModel.CollapseAll()
|
||||
|
||||
self.c.PostRefreshUpdate(self.context())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *FilesController) expandAll() error {
|
||||
self.context().FileTreeViewModel.ExpandAll()
|
||||
|
||||
self.c.PostRefreshUpdate(self.context())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *FilesController) EnterFile(opts types.OnFocusOpts) error {
|
||||
node := self.context().GetSelected()
|
||||
if node == nil {
|
||||
@@ -666,7 +694,7 @@ func (self *FilesController) handleAmendCommitPress() error {
|
||||
Title: self.c.Tr.AmendLastCommitTitle,
|
||||
Prompt: self.c.Tr.SureToAmend,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommitableFiles(func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error {
|
||||
if len(self.c.Model().Commits) == 0 {
|
||||
return errors.New(self.c.Tr.NoCommitToAmend)
|
||||
}
|
||||
@@ -697,6 +725,13 @@ func (self *FilesController) handleStatusFilterPressed() error {
|
||||
},
|
||||
Key: 'u',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.FilterTrackedFiles,
|
||||
OnPress: func() error {
|
||||
return self.setStatusFiltering(filetree.DisplayTracked)
|
||||
},
|
||||
Key: 't',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.ResetFilter,
|
||||
OnPress: func() error {
|
||||
@@ -710,7 +745,8 @@ func (self *FilesController) handleStatusFilterPressed() error {
|
||||
|
||||
func (self *FilesController) setStatusFiltering(filter filetree.FileTreeDisplayFilter) error {
|
||||
self.context().FileTreeViewModel.SetStatusFilter(filter)
|
||||
return self.c.PostRefreshUpdate(self.context())
|
||||
self.c.PostRefreshUpdate(self.context())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *FilesController) edit(nodes []*filetree.FileNode) error {
|
||||
@@ -863,7 +899,7 @@ func (self *FilesController) openCopyMenu() error {
|
||||
OnPress: func() error {
|
||||
path := self.context().GetSelectedPath()
|
||||
hasStaged := self.hasPathStagedChanges(node)
|
||||
diff, err := self.c.Git().Diff.GetPathDiff(path, hasStaged)
|
||||
diff, err := self.c.Git().Diff.GetDiff(hasStaged, "--", path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -888,7 +924,7 @@ func (self *FilesController) openCopyMenu() error {
|
||||
Tooltip: self.c.Tr.CopyFileDiffTooltip,
|
||||
OnPress: func() error {
|
||||
hasStaged := self.c.Helpers().WorkingTree.AnyStagedFiles()
|
||||
diff, err := self.c.Git().Diff.GetAllDiff(hasStaged)
|
||||
diff, err := self.c.Git().Diff.GetDiff(hasStaged, "--")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -949,9 +985,7 @@ func (self *FilesController) handleToggleDirCollapsed() error {
|
||||
|
||||
self.context().FileTreeViewModel.ToggleCollapsed(node.GetPath())
|
||||
|
||||
if err := self.c.PostRefreshUpdate(self.c.Contexts().Files); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
self.c.PostRefreshUpdate(self.c.Contexts().Files)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -959,7 +993,8 @@ func (self *FilesController) handleToggleDirCollapsed() error {
|
||||
func (self *FilesController) toggleTreeView() error {
|
||||
self.context().FileTreeViewModel.ToggleShowTree()
|
||||
|
||||
return self.c.PostRefreshUpdate(self.context())
|
||||
self.c.PostRefreshUpdate(self.context())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *FilesController) handleStashSave(stashFunc func(message string) error, action string) error {
|
||||
@@ -1176,3 +1211,11 @@ func (self *FilesController) formattedPaths(nodes []*filetree.FileNode) string {
|
||||
return node.GetPath()
|
||||
}))
|
||||
}
|
||||
|
||||
func (self *FilesController) isInTreeMode() *types.DisabledReason {
|
||||
if !self.context().FileTreeViewModel.InTreeMode() {
|
||||
return &types.DisabledReason{Text: self.c.Tr.DisabledInFlatView}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -69,10 +69,11 @@ func (self *GlobalController) GetKeybindings(opts types.KeybindingsOpts) []*type
|
||||
Modifier: gocui.ModNone,
|
||||
// we have the description on the alt key and not the main key for legacy reasons
|
||||
// (the original main key was 'x' but we've reassigned that to other purposes)
|
||||
Description: self.c.Tr.OpenKeybindingsMenu,
|
||||
Handler: self.createOptionsMenu,
|
||||
ShortDescription: self.c.Tr.Keybindings,
|
||||
DisplayOnScreen: true,
|
||||
Description: self.c.Tr.OpenKeybindingsMenu,
|
||||
Handler: self.createOptionsMenu,
|
||||
ShortDescription: self.c.Tr.Keybindings,
|
||||
DisplayOnScreen: true,
|
||||
GetDisabledReason: self.optionsMenuDisabledReason,
|
||||
},
|
||||
{
|
||||
ViewName: "",
|
||||
@@ -156,6 +157,17 @@ func (self *GlobalController) createOptionsMenu() error {
|
||||
return (&OptionsMenuAction{c: self.c}).Call()
|
||||
}
|
||||
|
||||
func (self *GlobalController) optionsMenuDisabledReason() *types.DisabledReason {
|
||||
ctx := self.c.Context().Current()
|
||||
// Don't show options menu while displaying popup.
|
||||
if ctx.GetKind() == types.PERSISTENT_POPUP || ctx.GetKind() == types.TEMPORARY_POPUP {
|
||||
// The empty error text is intentional. We don't want to show an error
|
||||
// toast for this, but only hide it from the options map.
|
||||
return &types.DisabledReason{Text: ""}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *GlobalController) createFilteringMenu() error {
|
||||
return (&FilteringMenuAction{c: self.c}).Call()
|
||||
}
|
||||
|
||||
@@ -1,44 +1,115 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type BranchesHelper struct {
|
||||
c *HelperCommon
|
||||
c *HelperCommon
|
||||
worktreeHelper *WorktreeHelper
|
||||
}
|
||||
|
||||
func NewBranchesHelper(c *HelperCommon) *BranchesHelper {
|
||||
func NewBranchesHelper(c *HelperCommon, worktreeHelper *WorktreeHelper) *BranchesHelper {
|
||||
return &BranchesHelper{
|
||||
c: c,
|
||||
c: c,
|
||||
worktreeHelper: worktreeHelper,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) ConfirmDeleteRemote(remoteName string, branchName string) error {
|
||||
title := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteBranchTitle,
|
||||
map[string]string{
|
||||
"selectedBranchName": branchName,
|
||||
func (self *BranchesHelper) ConfirmLocalDelete(branches []*models.Branch) error {
|
||||
if len(branches) > 1 {
|
||||
if lo.SomeBy(branches, func(branch *models.Branch) bool { return self.checkedOutByOtherWorktree(branch) }) {
|
||||
return errors.New(self.c.Tr.SomeBranchesCheckedOutByWorktreeError)
|
||||
}
|
||||
} else if self.checkedOutByOtherWorktree(branches[0]) {
|
||||
return self.promptWorktreeBranchDelete(branches[0])
|
||||
}
|
||||
|
||||
allBranchesMerged, err := self.allBranchesMerged(branches)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
doDelete := func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(_ gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch)
|
||||
branchNames := lo.Map(branches, func(branch *models.Branch, _ int) string { return branch.Name })
|
||||
if err := self.c.Git().Branch.LocalDelete(branchNames, true); err != nil {
|
||||
return err
|
||||
}
|
||||
selectionStart, _ := self.c.Contexts().Branches.GetSelectionRange()
|
||||
self.c.Contexts().Branches.SetSelectedLineIdx(selectionStart)
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES}})
|
||||
})
|
||||
}
|
||||
|
||||
if allBranchesMerged {
|
||||
return doDelete()
|
||||
}
|
||||
|
||||
title := self.c.Tr.ForceDeleteBranchTitle
|
||||
var message string
|
||||
if len(branches) == 1 {
|
||||
message = utils.ResolvePlaceholderString(
|
||||
self.c.Tr.ForceDeleteBranchMessage,
|
||||
map[string]string{
|
||||
"selectedBranchName": branches[0].Name,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
message = self.c.Tr.ForceDeleteBranchesMessage
|
||||
}
|
||||
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: title,
|
||||
Prompt: message,
|
||||
HandleConfirm: func() error {
|
||||
return doDelete()
|
||||
},
|
||||
)
|
||||
prompt := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteRemoteBranchPrompt,
|
||||
map[string]string{
|
||||
"selectedBranchName": branchName,
|
||||
"upstream": remoteName,
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) ConfirmDeleteRemote(remoteBranches []*models.RemoteBranch) error {
|
||||
var title string
|
||||
if len(remoteBranches) == 1 {
|
||||
title = utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteBranchTitle,
|
||||
map[string]string{
|
||||
"selectedBranchName": remoteBranches[0].Name,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
title = self.c.Tr.DeleteBranchesTitle
|
||||
}
|
||||
var prompt string
|
||||
if len(remoteBranches) == 1 {
|
||||
prompt = utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteRemoteBranchPrompt,
|
||||
map[string]string{
|
||||
"selectedBranchName": remoteBranches[0].Name,
|
||||
"upstream": remoteBranches[0].RemoteName,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
prompt = self.c.Tr.DeleteRemoteBranchesPrompt
|
||||
}
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: title,
|
||||
Prompt: prompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(task gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteRemoteBranch)
|
||||
if err := self.c.Git().Remote.DeleteRemoteBranch(task, remoteName, branchName); err != nil {
|
||||
if err := self.deleteRemoteBranches(remoteBranches, task); err != nil {
|
||||
return err
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
|
||||
@@ -49,6 +120,146 @@ func (self *BranchesHelper) ConfirmDeleteRemote(remoteName string, branchName st
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) ConfirmLocalAndRemoteDelete(branches []*models.Branch) error {
|
||||
if lo.SomeBy(branches, func(branch *models.Branch) bool { return self.checkedOutByOtherWorktree(branch) }) {
|
||||
return errors.New(self.c.Tr.SomeBranchesCheckedOutByWorktreeError)
|
||||
}
|
||||
|
||||
allBranchesMerged, err := self.allBranchesMerged(branches)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var prompt string
|
||||
if len(branches) == 1 {
|
||||
prompt = utils.ResolvePlaceholderString(
|
||||
self.c.Tr.DeleteLocalAndRemoteBranchPrompt,
|
||||
map[string]string{
|
||||
"localBranchName": branches[0].Name,
|
||||
"remoteBranchName": branches[0].UpstreamBranch,
|
||||
"remoteName": branches[0].UpstreamRemote,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
prompt = self.c.Tr.DeleteLocalAndRemoteBranchesPrompt
|
||||
}
|
||||
|
||||
if !allBranchesMerged {
|
||||
if len(branches) == 1 {
|
||||
prompt += "\n\n" + utils.ResolvePlaceholderString(
|
||||
self.c.Tr.ForceDeleteBranchMessage,
|
||||
map[string]string{
|
||||
"selectedBranchName": branches[0].Name,
|
||||
},
|
||||
)
|
||||
} else {
|
||||
prompt += "\n\n" + self.c.Tr.ForceDeleteBranchesMessage
|
||||
}
|
||||
}
|
||||
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.DeleteLocalAndRemoteBranch,
|
||||
Prompt: prompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DeletingStatus, func(task gocui.Task) error {
|
||||
// Delete the remote branches first so that we keep the local ones
|
||||
// in case of failure
|
||||
remoteBranches := lo.Map(branches, func(branch *models.Branch, _ int) *models.RemoteBranch {
|
||||
return &models.RemoteBranch{Name: branch.UpstreamBranch, RemoteName: branch.UpstreamRemote}
|
||||
})
|
||||
if err := self.deleteRemoteBranches(remoteBranches, task); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteLocalBranch)
|
||||
branchNames := lo.Map(branches, func(branch *models.Branch, _ int) string { return branch.Name })
|
||||
if err := self.c.Git().Branch.LocalDelete(branchNames, true); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
selectionStart, _ := self.c.Contexts().Branches.GetSelectionRange()
|
||||
self.c.Contexts().Branches.SetSelectedLineIdx(selectionStart)
|
||||
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.BRANCHES, types.REMOTES}})
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ShortBranchName(fullBranchName string) string {
|
||||
return strings.TrimPrefix(strings.TrimPrefix(fullBranchName, "refs/heads/"), "refs/remotes/")
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) checkedOutByOtherWorktree(branch *models.Branch) bool {
|
||||
return git_commands.CheckedOutByOtherWorktree(branch, self.c.Model().Worktrees)
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) worktreeForBranch(branch *models.Branch) (*models.Worktree, bool) {
|
||||
return git_commands.WorktreeForBranch(branch, self.c.Model().Worktrees)
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) promptWorktreeBranchDelete(selectedBranch *models.Branch) error {
|
||||
worktree, ok := self.worktreeForBranch(selectedBranch)
|
||||
if !ok {
|
||||
self.c.Log.Error("promptWorktreeBranchDelete out of sync with list of worktrees")
|
||||
return nil
|
||||
}
|
||||
|
||||
title := utils.ResolvePlaceholderString(self.c.Tr.BranchCheckedOutByWorktree, map[string]string{
|
||||
"worktreeName": worktree.Name,
|
||||
"branchName": selectedBranch.Name,
|
||||
})
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: title,
|
||||
Items: []*types.MenuItem{
|
||||
{
|
||||
Label: self.c.Tr.SwitchToWorktree,
|
||||
OnPress: func() error {
|
||||
return self.worktreeHelper.Switch(worktree, context.LOCAL_BRANCHES_CONTEXT_KEY)
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.DetachWorktree,
|
||||
Tooltip: self.c.Tr.DetachWorktreeTooltip,
|
||||
OnPress: func() error {
|
||||
return self.worktreeHelper.Detach(worktree)
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.RemoveWorktree,
|
||||
OnPress: func() error {
|
||||
return self.worktreeHelper.Remove(worktree, false)
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) allBranchesMerged(branches []*models.Branch) (bool, error) {
|
||||
allBranchesMerged := true
|
||||
for _, branch := range branches {
|
||||
isMerged, err := self.c.Git().Branch.IsBranchMerged(branch, self.c.Model().MainBranches)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !isMerged {
|
||||
allBranchesMerged = false
|
||||
break
|
||||
}
|
||||
}
|
||||
return allBranchesMerged, nil
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) deleteRemoteBranches(remoteBranches []*models.RemoteBranch, task gocui.Task) error {
|
||||
remotes := lo.GroupBy(remoteBranches, func(branch *models.RemoteBranch) string { return branch.RemoteName })
|
||||
for remote, branches := range remotes {
|
||||
self.c.LogAction(self.c.Tr.Actions.DeleteRemoteBranch)
|
||||
branchNames := lo.Map(branches, func(branch *models.RemoteBranch, _ int) string { return branch.Name })
|
||||
if err := self.c.Git().Remote.DeleteRemoteBranch(task, remote, branchNames); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/cherrypicking"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
@@ -57,15 +60,22 @@ func (self *CherryPickHelper) CopyRange(commitsList []*models.Commit, context ty
|
||||
}
|
||||
}
|
||||
|
||||
return self.rerender()
|
||||
self.getData().DidPaste = false
|
||||
|
||||
self.rerender()
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandlePasteCommits begins a cherry-pick rebase with the commits the user has copied.
|
||||
// Only to be called from the branch commits controller
|
||||
func (self *CherryPickHelper) Paste() error {
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.CherryPick,
|
||||
Prompt: self.c.Tr.SureCherryPick,
|
||||
Title: self.c.Tr.CherryPick,
|
||||
Prompt: utils.ResolvePlaceholderString(
|
||||
self.c.Tr.SureCherryPick,
|
||||
map[string]string{
|
||||
"numCommits": strconv.Itoa(len(self.getData().CherryPickedCommits)),
|
||||
}),
|
||||
HandleConfirm: func() error {
|
||||
isInRebase, err := self.c.Git().Status.IsInInteractiveRebase()
|
||||
if err != nil {
|
||||
@@ -102,7 +112,8 @@ func (self *CherryPickHelper) Paste() error {
|
||||
return err
|
||||
}
|
||||
if !isInRebase {
|
||||
return self.Reset()
|
||||
self.getData().DidPaste = true
|
||||
self.rerender()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -113,14 +124,15 @@ func (self *CherryPickHelper) Paste() error {
|
||||
}
|
||||
|
||||
func (self *CherryPickHelper) CanPaste() bool {
|
||||
return self.getData().Active()
|
||||
return self.getData().CanPaste()
|
||||
}
|
||||
|
||||
func (self *CherryPickHelper) Reset() error {
|
||||
self.getData().ContextKey = ""
|
||||
self.getData().CherryPickedCommits = nil
|
||||
|
||||
return self.rerender()
|
||||
self.rerender()
|
||||
return nil
|
||||
}
|
||||
|
||||
// you can only copy from one context at a time, because the order and position of commits matter
|
||||
@@ -136,16 +148,12 @@ func (self *CherryPickHelper) resetIfNecessary(context types.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CherryPickHelper) rerender() error {
|
||||
func (self *CherryPickHelper) rerender() {
|
||||
for _, context := range []types.Context{
|
||||
self.c.Contexts().LocalCommits,
|
||||
self.c.Contexts().ReflogCommits,
|
||||
self.c.Contexts().SubCommits,
|
||||
} {
|
||||
if err := self.c.PostRefreshUpdate(context); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.PostRefreshUpdate(context)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@ func (self *CommitsHelper) OpenCommitMessagePanel(opts *OpenCommitMessagePanelOp
|
||||
opts.SummaryTitle,
|
||||
opts.DescriptionTitle,
|
||||
opts.PreserveMessage,
|
||||
opts.InitialMessage,
|
||||
onConfirm,
|
||||
opts.OnSwitchToEditor,
|
||||
)
|
||||
@@ -177,8 +178,9 @@ func (self *CommitsHelper) HandleCommitConfirm() error {
|
||||
func (self *CommitsHelper) CloseCommitMessagePanel() {
|
||||
if self.c.Contexts().CommitMessage.GetPreserveMessage() {
|
||||
message := self.JoinCommitMessageAndUnwrappedDescription()
|
||||
|
||||
self.c.Contexts().CommitMessage.SetPreservedMessage(message)
|
||||
if message != self.c.Contexts().CommitMessage.GetInitialMessage() {
|
||||
self.c.Contexts().CommitMessage.SetPreservedMessage(message)
|
||||
}
|
||||
} else {
|
||||
self.SetMessageAndDescriptionInView("")
|
||||
}
|
||||
|
||||
@@ -3,12 +3,11 @@ package helpers
|
||||
import (
|
||||
goContext "context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/theme"
|
||||
"github.com/mattn/go-runewidth"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
)
|
||||
|
||||
type ConfirmationHelper struct {
|
||||
@@ -57,61 +56,9 @@ func (self *ConfirmationHelper) DeactivateConfirmationPrompt() {
|
||||
self.clearConfirmationViewKeyBindings()
|
||||
}
|
||||
|
||||
// Temporary hack: we're just duplicating the logic in `gocui.lineWrap`
|
||||
func getMessageHeight(wrap bool, message string, width int) int {
|
||||
return len(wrapMessageToWidth(wrap, message, width))
|
||||
}
|
||||
|
||||
func wrapMessageToWidth(wrap bool, message string, width int) []string {
|
||||
lines := strings.Split(message, "\n")
|
||||
if !wrap {
|
||||
return lines
|
||||
}
|
||||
|
||||
wrappedLines := make([]string, 0, len(lines))
|
||||
|
||||
for _, line := range lines {
|
||||
n := 0
|
||||
offset := 0
|
||||
lastWhitespaceIndex := -1
|
||||
for i, currChr := range line {
|
||||
rw := runewidth.RuneWidth(currChr)
|
||||
n += rw
|
||||
|
||||
if n > width {
|
||||
if currChr == ' ' {
|
||||
wrappedLines = append(wrappedLines, line[offset:i])
|
||||
offset = i + 1
|
||||
n = 0
|
||||
} else if currChr == '-' {
|
||||
wrappedLines = append(wrappedLines, line[offset:i])
|
||||
offset = i
|
||||
n = rw
|
||||
} else if lastWhitespaceIndex != -1 && lastWhitespaceIndex+1 != i {
|
||||
if line[lastWhitespaceIndex] == '-' {
|
||||
wrappedLines = append(wrappedLines, line[offset:lastWhitespaceIndex+1])
|
||||
offset = lastWhitespaceIndex + 1
|
||||
n = i - lastWhitespaceIndex
|
||||
} else {
|
||||
wrappedLines = append(wrappedLines, line[offset:lastWhitespaceIndex])
|
||||
offset = lastWhitespaceIndex + 1
|
||||
n = i - lastWhitespaceIndex + 1
|
||||
}
|
||||
} else {
|
||||
wrappedLines = append(wrappedLines, line[offset:i])
|
||||
offset = i
|
||||
n = rw
|
||||
}
|
||||
lastWhitespaceIndex = -1
|
||||
} else if currChr == ' ' || currChr == '-' {
|
||||
lastWhitespaceIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
wrappedLines = append(wrappedLines, line[offset:])
|
||||
}
|
||||
|
||||
return wrappedLines
|
||||
wrappedLines, _, _ := utils.WrapViewLinesToWidth(wrap, message, width)
|
||||
return len(wrappedLines)
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) getPopupPanelDimensionsForContentHeight(panelWidth, contentHeight int, parentPopupContext types.Context) (int, int, int, int) {
|
||||
@@ -221,7 +168,7 @@ func (self *ConfirmationHelper) CreatePopupPanel(ctx goContext.Context, opts typ
|
||||
confirmationView.RenderTextArea()
|
||||
} else {
|
||||
self.c.ResetViewOrigin(confirmationView)
|
||||
self.c.SetViewContent(confirmationView, style.AttrBold.Sprint(underlineLinks(opts.Prompt)))
|
||||
self.c.SetViewContent(confirmationView, style.AttrBold.Sprint(opts.Prompt))
|
||||
}
|
||||
|
||||
self.setKeyBindings(cancel, opts)
|
||||
@@ -233,28 +180,6 @@ func (self *ConfirmationHelper) CreatePopupPanel(ctx goContext.Context, opts typ
|
||||
self.c.Context().Push(self.c.Contexts().Confirmation)
|
||||
}
|
||||
|
||||
func underlineLinks(text string) string {
|
||||
result := ""
|
||||
remaining := text
|
||||
for {
|
||||
linkStart := strings.Index(remaining, "https://")
|
||||
if linkStart == -1 {
|
||||
break
|
||||
}
|
||||
|
||||
linkEnd := strings.IndexAny(remaining[linkStart:], " \n>")
|
||||
if linkEnd == -1 {
|
||||
linkEnd = len(remaining)
|
||||
} else {
|
||||
linkEnd += linkStart
|
||||
}
|
||||
underlinedLink := style.PrintSimpleHyperlink(remaining[linkStart:linkEnd])
|
||||
result += remaining[:linkStart] + underlinedLink
|
||||
remaining = remaining[linkEnd:]
|
||||
}
|
||||
return result + remaining
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) setKeyBindings(cancel goContext.CancelFunc, opts types.CreatePopupPanelOpts) {
|
||||
var onConfirm func() error
|
||||
if opts.HandleConfirmPrompt != nil {
|
||||
@@ -351,7 +276,7 @@ func (self *ConfirmationHelper) layoutMenuPrompt(contentWidth int) int {
|
||||
var promptLines []string
|
||||
prompt := self.c.Contexts().Menu.GetPrompt()
|
||||
if len(prompt) > 0 {
|
||||
promptLines = wrapMessageToWidth(true, prompt, contentWidth)
|
||||
promptLines, _, _ = utils.WrapViewLinesToWidth(true, prompt, contentWidth)
|
||||
promptLines = append(promptLines, "")
|
||||
}
|
||||
self.c.Contexts().Menu.SetPromptLines(promptLines)
|
||||
@@ -379,13 +304,14 @@ func (self *ConfirmationHelper) resizeConfirmationPanel(parentPopupContext types
|
||||
suggestionsViewHeight = 11
|
||||
}
|
||||
panelWidth := self.getPopupPanelWidth()
|
||||
contentWidth := panelWidth - 2 // minus 2 for the frame
|
||||
prompt := self.c.Views().Confirmation.Buffer()
|
||||
wrap := true
|
||||
if self.c.Views().Confirmation.Editable {
|
||||
prompt = self.c.Views().Confirmation.TextArea.GetContent()
|
||||
wrap = false
|
||||
}
|
||||
panelHeight := getMessageHeight(wrap, prompt, panelWidth) + suggestionsViewHeight
|
||||
panelHeight := getMessageHeight(wrap, prompt, contentWidth) + suggestionsViewHeight
|
||||
x0, y0, x1, y1 := self.getPopupPanelDimensionsAux(panelWidth, panelHeight, parentPopupContext)
|
||||
confirmationViewBottom := y1 - suggestionsViewHeight
|
||||
_, _ = self.c.GocuiGui().SetView(self.c.Views().Confirmation.Name(), x0, y0, x1, confirmationViewBottom, 0)
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gookit/color"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/xo/terminfo"
|
||||
)
|
||||
|
||||
func Test_underlineLinks(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
text string
|
||||
expectedResult string
|
||||
}{
|
||||
{
|
||||
name: "empty string",
|
||||
text: "",
|
||||
expectedResult: "",
|
||||
},
|
||||
{
|
||||
name: "no links",
|
||||
text: "abc",
|
||||
expectedResult: "abc",
|
||||
},
|
||||
{
|
||||
name: "entire string is a link",
|
||||
text: "https://example.com",
|
||||
expectedResult: "\x1b]8;;https://example.com\x1b\\https://example.com\x1b]8;;\x1b\\",
|
||||
},
|
||||
{
|
||||
name: "link preceded and followed by text",
|
||||
text: "bla https://example.com xyz",
|
||||
expectedResult: "bla \x1b]8;;https://example.com\x1b\\https://example.com\x1b]8;;\x1b\\ xyz",
|
||||
},
|
||||
{
|
||||
name: "more than one link",
|
||||
text: "bla https://link1 blubb https://link2 xyz",
|
||||
expectedResult: "bla \x1b]8;;https://link1\x1b\\https://link1\x1b]8;;\x1b\\ blubb \x1b]8;;https://link2\x1b\\https://link2\x1b]8;;\x1b\\ xyz",
|
||||
},
|
||||
{
|
||||
name: "link in angle brackets",
|
||||
text: "See <https://example.com> for details",
|
||||
expectedResult: "See <\x1b]8;;https://example.com\x1b\\https://example.com\x1b]8;;\x1b\\> for details",
|
||||
},
|
||||
{
|
||||
name: "link followed by newline",
|
||||
text: "URL: https://example.com\nNext line",
|
||||
expectedResult: "URL: \x1b]8;;https://example.com\x1b\\https://example.com\x1b]8;;\x1b\\\nNext line",
|
||||
},
|
||||
}
|
||||
|
||||
oldColorLevel := color.ForceSetColorLevel(terminfo.ColorLevelMillions)
|
||||
defer color.ForceSetColorLevel(oldColorLevel)
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
result := underlineLinks(s.text)
|
||||
assert.Equal(t, s.expectedResult, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/patch"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/modes/diffing"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
@@ -34,10 +35,6 @@ func (self *DiffHelper) DiffArgs() []string {
|
||||
output = append(output, "-R")
|
||||
}
|
||||
|
||||
if self.c.GetAppState().IgnoreWhitespaceInDiffView {
|
||||
output = append(output, "--ignore-all-space")
|
||||
}
|
||||
|
||||
output = append(output, "--")
|
||||
|
||||
file := self.currentlySelectedFilename()
|
||||
@@ -59,9 +56,6 @@ func (self *DiffHelper) GetUpdateTaskForRenderingCommitsDiff(commit *models.Comm
|
||||
if refRange != nil {
|
||||
from, to := refRange.From, refRange.To
|
||||
args := []string{from.ParentRefName(), to.RefName(), "--stat", "-p"}
|
||||
if self.c.GetAppState().IgnoreWhitespaceInDiffView {
|
||||
args = append(args, "--ignore-all-space")
|
||||
}
|
||||
args = append(args, "--")
|
||||
if path := self.c.Modes().Filtering.GetPath(); path != "" {
|
||||
args = append(args, path)
|
||||
@@ -171,3 +165,45 @@ func (self *DiffHelper) OpenDiffToolForRef(selectedRef types.Ref) error {
|
||||
}))
|
||||
return err
|
||||
}
|
||||
|
||||
// AdjustLineNumber is used to adjust a line number in the diff that's currently
|
||||
// being viewed, so that it corresponds to the line number in the actual working
|
||||
// copy state of the file. It is used when clicking on a delta hyperlink in a
|
||||
// diff, or when pressing `e` in the staging or patch building panels. It works
|
||||
// by getting a diff of what's being viewed in the main view against the working
|
||||
// copy, and then using that diff to adjust the line number.
|
||||
// path is the file path of the file being viewed
|
||||
// linenumber is the line number to adjust (one-based)
|
||||
// viewname is the name of the view that shows the diff. We need to pass it
|
||||
// because the diff adjustment is slightly different depending on which view is
|
||||
// showing the diff.
|
||||
func (self *DiffHelper) AdjustLineNumber(path string, linenumber int, viewname string) int {
|
||||
switch viewname {
|
||||
|
||||
case "main", "patchBuilding":
|
||||
if diffableContext, ok := self.c.Context().CurrentSide().(types.DiffableContext); ok {
|
||||
ref := diffableContext.RefForAdjustingLineNumberInDiff()
|
||||
if len(ref) != 0 {
|
||||
return self.adjustLineNumber(linenumber, ref, "--", path)
|
||||
}
|
||||
}
|
||||
// if the type cast to DiffableContext returns false, we are in the
|
||||
// unstaged changes view of the Files panel; no need to adjust line
|
||||
// numbers in this case
|
||||
|
||||
case "secondary", "stagingSecondary":
|
||||
return self.adjustLineNumber(linenumber, "--", path)
|
||||
}
|
||||
|
||||
return linenumber
|
||||
}
|
||||
|
||||
func (self *DiffHelper) adjustLineNumber(linenumber int, diffArgs ...string) int {
|
||||
args := append([]string{"--unified=0"}, diffArgs...)
|
||||
diff, err := self.c.Git().Diff.GetDiff(false, args...)
|
||||
if err != nil {
|
||||
return linenumber
|
||||
}
|
||||
patch := patch.Parse(diff)
|
||||
return patch.AdjustLineNumber(linenumber)
|
||||
}
|
||||
|
||||
@@ -487,5 +487,6 @@ func (self *MergeAndRebaseHelper) SquashMergeCommitted(refName, checkedOutBranch
|
||||
|
||||
func (self *MergeAndRebaseHelper) ResetMarkedBaseCommit() error {
|
||||
self.c.Modes().MarkedBaseCommit.Reset()
|
||||
return self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
|
||||
self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -52,7 +52,8 @@ func (self *PatchBuildingHelper) Reset() error {
|
||||
}
|
||||
|
||||
// refreshing the current context so that the secondary panel is hidden if necessary.
|
||||
return self.c.PostRefreshUpdate(self.c.Context().Current())
|
||||
self.c.PostRefreshUpdate(self.c.Context().Current())
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *PatchBuildingHelper) RefreshPatchBuildingPanel(opts types.OnFocusOpts) {
|
||||
@@ -90,14 +91,14 @@ func (self *PatchBuildingHelper) RefreshPatchBuildingPanel(opts types.OnFocusOpt
|
||||
|
||||
oldState := context.GetState()
|
||||
|
||||
state := patch_exploring.NewState(diff, selectedLineIdx, oldState, self.c.Log)
|
||||
state := patch_exploring.NewState(diff, selectedLineIdx, context.GetView(), oldState)
|
||||
context.SetState(state)
|
||||
if state == nil {
|
||||
self.Escape()
|
||||
return
|
||||
}
|
||||
|
||||
mainContent := context.GetContentToRender(true)
|
||||
mainContent := context.GetContentToRender()
|
||||
|
||||
self.c.Contexts().CustomPatchBuilder.FocusSelection()
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.STASH) {
|
||||
refresh("stash", func() { _ = self.refreshStashEntries() })
|
||||
refresh("stash", func() { self.refreshStashEntries() })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.TAGS) {
|
||||
@@ -169,7 +169,7 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) error {
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.WORKTREES) && !includeWorktreesWithBranches {
|
||||
refresh("worktrees", func() { _ = self.refreshWorktrees() })
|
||||
refresh("worktrees", func() { self.refreshWorktrees() })
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.STAGING) {
|
||||
@@ -343,7 +343,8 @@ func (self *RefreshHelper) refreshCommitsWithLimit() error {
|
||||
self.c.Model().WorkingTreeStateAtLastCommitRefresh = self.c.Git().Status.WorkingTreeState()
|
||||
self.c.Model().CheckedOutBranch = checkedOutBranchName
|
||||
|
||||
return self.refreshView(self.c.Contexts().LocalCommits)
|
||||
self.refreshView(self.c.Contexts().LocalCommits)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshSubCommitsWithLimit() error {
|
||||
@@ -368,7 +369,8 @@ func (self *RefreshHelper) refreshSubCommitsWithLimit() error {
|
||||
self.c.Model().SubCommits = commits
|
||||
self.RefreshAuthors(commits)
|
||||
|
||||
return self.refreshView(self.c.Contexts().SubCommits)
|
||||
self.refreshView(self.c.Contexts().SubCommits)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) RefreshAuthors(commits []*models.Commit) {
|
||||
@@ -397,7 +399,8 @@ func (self *RefreshHelper) refreshCommitFilesContext() error {
|
||||
self.c.Model().CommitFiles = files
|
||||
self.c.Contexts().CommitFiles.CommitFileTreeViewModel.SetTree()
|
||||
|
||||
return self.refreshView(self.c.Contexts().CommitFiles)
|
||||
self.refreshView(self.c.Contexts().CommitFiles)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshRebaseCommits() error {
|
||||
@@ -411,7 +414,8 @@ func (self *RefreshHelper) refreshRebaseCommits() error {
|
||||
self.c.Model().Commits = updatedCommits
|
||||
self.c.Model().WorkingTreeStateAtLastCommitRefresh = self.c.Git().Status.WorkingTreeState()
|
||||
|
||||
return self.refreshView(self.c.Contexts().LocalCommits)
|
||||
self.refreshView(self.c.Contexts().LocalCommits)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshTags() error {
|
||||
@@ -422,7 +426,8 @@ func (self *RefreshHelper) refreshTags() error {
|
||||
|
||||
self.c.Model().Tags = tags
|
||||
|
||||
return self.refreshView(self.c.Contexts().Tags)
|
||||
self.refreshView(self.c.Contexts().Tags)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshStateSubmoduleConfigs() error {
|
||||
@@ -482,9 +487,7 @@ func (self *RefreshHelper) refreshBranches(refreshWorktrees bool, keepBranchSele
|
||||
|
||||
if refreshWorktrees {
|
||||
self.loadWorktrees()
|
||||
if err := self.refreshView(self.c.Contexts().Worktrees); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
self.refreshView(self.c.Contexts().Worktrees)
|
||||
}
|
||||
|
||||
if !keepBranchSelectionIndex && prevSelectedBranch != nil {
|
||||
@@ -495,9 +498,7 @@ func (self *RefreshHelper) refreshBranches(refreshWorktrees bool, keepBranchSele
|
||||
}
|
||||
}
|
||||
|
||||
if err := self.refreshView(self.c.Contexts().Branches); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
self.refreshView(self.c.Contexts().Branches)
|
||||
|
||||
// Need to re-render the commits view because the visualization of local
|
||||
// branch heads might have changed
|
||||
@@ -525,14 +526,8 @@ func (self *RefreshHelper) refreshFilesAndSubmodules() error {
|
||||
}
|
||||
|
||||
self.c.OnUIThread(func() error {
|
||||
if err := self.refreshView(self.c.Contexts().Submodules); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
|
||||
if err := self.refreshView(self.c.Contexts().Files); err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
|
||||
self.refreshView(self.c.Contexts().Submodules)
|
||||
self.refreshView(self.c.Contexts().Files)
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -653,7 +648,8 @@ func (self *RefreshHelper) refreshReflogCommits() error {
|
||||
model.FilteredReflogCommits = model.ReflogCommits
|
||||
}
|
||||
|
||||
return self.refreshView(self.c.Contexts().ReflogCommits)
|
||||
self.refreshView(self.c.Contexts().ReflogCommits)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshRemotes() error {
|
||||
@@ -677,14 +673,8 @@ func (self *RefreshHelper) refreshRemotes() error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := self.refreshView(self.c.Contexts().Remotes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.refreshView(self.c.Contexts().RemoteBranches); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.refreshView(self.c.Contexts().Remotes)
|
||||
self.refreshView(self.c.Contexts().RemoteBranches)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -698,23 +688,20 @@ func (self *RefreshHelper) loadWorktrees() {
|
||||
self.c.Model().Worktrees = worktrees
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshWorktrees() error {
|
||||
func (self *RefreshHelper) refreshWorktrees() {
|
||||
self.loadWorktrees()
|
||||
|
||||
// need to refresh branches because the branches view shows worktrees against
|
||||
// branches
|
||||
if err := self.refreshView(self.c.Contexts().Branches); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.refreshView(self.c.Contexts().Worktrees)
|
||||
self.refreshView(self.c.Contexts().Branches)
|
||||
self.refreshView(self.c.Contexts().Worktrees)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshStashEntries() error {
|
||||
func (self *RefreshHelper) refreshStashEntries() {
|
||||
self.c.Model().StashEntries = self.c.Git().Loaders.StashLoader.
|
||||
GetStashEntries(self.c.Modes().Filtering.GetPath())
|
||||
|
||||
return self.refreshView(self.c.Contexts().Stash)
|
||||
self.refreshView(self.c.Contexts().Stash)
|
||||
}
|
||||
|
||||
// never call this on its own, it should only be called from within refreshCommits()
|
||||
@@ -754,12 +741,12 @@ func (self *RefreshHelper) refForLog() string {
|
||||
return bisectInfo.GetStartHash()
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshView(context types.Context) error {
|
||||
func (self *RefreshHelper) refreshView(context types.Context) {
|
||||
// Re-applying the filter must be done before re-rendering the view, so that
|
||||
// the filtered list model is up to date for rendering.
|
||||
self.searchHelper.ReApplyFilter(context)
|
||||
|
||||
err := self.c.PostRefreshUpdate(context)
|
||||
self.c.PostRefreshUpdate(context)
|
||||
|
||||
self.c.AfterLayout(func() error {
|
||||
// Re-applying the search must be done after re-rendering the view though,
|
||||
@@ -773,6 +760,4 @@ func (self *RefreshHelper) refreshView(context types.Context) error {
|
||||
self.searchHelper.ReApplySearch(context)
|
||||
return nil
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ type IRefsHelper interface {
|
||||
CheckoutRef(ref string, options types.CheckoutRefOptions) error
|
||||
GetCheckedOutRef() *models.Branch
|
||||
CreateGitResetMenu(ref string) error
|
||||
CreateCheckoutMenu(commit *models.Commit) error
|
||||
ResetToRef(ref string, strength string, envVars []string) error
|
||||
NewBranch(from string, fromDescription string, suggestedBranchname string) error
|
||||
}
|
||||
@@ -74,7 +75,7 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
|
||||
return options.OnRefNotFound(ref)
|
||||
}
|
||||
|
||||
if IsSwitchBranchUncommitedChangesError(err) {
|
||||
if IsSwitchBranchUncommittedChangesError(err) {
|
||||
// offer to autostash changes
|
||||
self.c.OnUIThread(func() error {
|
||||
// (Before showing the prompt, render again to remove the inline status)
|
||||
@@ -271,6 +272,53 @@ func (self *RefsHelper) CreateGitResetMenu(ref string) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *RefsHelper) CreateCheckoutMenu(commit *models.Commit) error {
|
||||
branches := lo.Filter(self.c.Model().Branches, func(branch *models.Branch, _ int) bool {
|
||||
return commit.Hash == branch.CommitHash && branch.Name != self.c.Model().CheckedOutBranch
|
||||
})
|
||||
|
||||
hash := commit.Hash
|
||||
var menuItems []*types.MenuItem
|
||||
|
||||
if len(branches) > 0 {
|
||||
menuItems = append(menuItems, lo.Map(branches, func(branch *models.Branch, index int) *types.MenuItem {
|
||||
var key types.Key
|
||||
if index < 9 {
|
||||
key = rune(index + 1 + '0') // Convert 1-based index to key
|
||||
}
|
||||
return &types.MenuItem{
|
||||
LabelColumns: []string{fmt.Sprintf(self.c.Tr.Actions.CheckoutBranchAtCommit, branch.Name)},
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CheckoutBranch)
|
||||
return self.CheckoutRef(branch.RefName(), types.CheckoutRefOptions{})
|
||||
},
|
||||
Key: key,
|
||||
}
|
||||
})...)
|
||||
} else {
|
||||
menuItems = append(menuItems, &types.MenuItem{
|
||||
LabelColumns: []string{self.c.Tr.Actions.CheckoutBranch},
|
||||
OnPress: func() error { return nil },
|
||||
DisabledReason: &types.DisabledReason{Text: self.c.Tr.NoBranchesFoundAtCommitTooltip},
|
||||
Key: '1',
|
||||
})
|
||||
}
|
||||
|
||||
menuItems = append(menuItems, &types.MenuItem{
|
||||
LabelColumns: []string{fmt.Sprintf(self.c.Tr.Actions.CheckoutCommitAsDetachedHead, utils.ShortHash(hash))},
|
||||
OnPress: func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CheckoutCommit)
|
||||
return self.CheckoutRef(hash, types.CheckoutRefOptions{})
|
||||
},
|
||||
Key: 'd',
|
||||
})
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.Actions.CheckoutBranchOrCommit,
|
||||
Items: menuItems,
|
||||
})
|
||||
}
|
||||
|
||||
func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggestedBranchName string) error {
|
||||
message := utils.ResolvePlaceholderString(
|
||||
self.c.Tr.NewBranchNameBranchOff,
|
||||
@@ -305,7 +353,7 @@ func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggest
|
||||
newBranchFunc = self.c.Git().Branch.NewWithoutTracking
|
||||
}
|
||||
if err := newBranchFunc(newBranchName, from); err != nil {
|
||||
if IsSwitchBranchUncommitedChangesError(err) {
|
||||
if IsSwitchBranchUncommittedChangesError(err) {
|
||||
// offer to autostash changes
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.AutoStashTitle,
|
||||
@@ -365,6 +413,6 @@ func (self *RefsHelper) ParseRemoteBranchName(fullBranchName string) (string, st
|
||||
return remoteName, branchName, true
|
||||
}
|
||||
|
||||
func IsSwitchBranchUncommitedChangesError(err error) bool {
|
||||
func IsSwitchBranchUncommittedChangesError(err error) bool {
|
||||
return strings.Contains(err.Error(), "Please commit your changes or stash them before you switch branch")
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@ func (self *SearchHelper) Cancel() {
|
||||
switch context := state.Context.(type) {
|
||||
case types.IFilterableContext:
|
||||
context.ClearFilter()
|
||||
_ = self.c.PostRefreshUpdate(context)
|
||||
self.c.PostRefreshUpdate(context)
|
||||
case types.ISearchableContext:
|
||||
context.ClearSearchString()
|
||||
context.GetView().ClearSearch()
|
||||
@@ -231,7 +231,7 @@ func (self *SearchHelper) OnPromptContentChanged(searchString string) {
|
||||
context.SetSelection(0)
|
||||
context.GetView().SetOriginY(0)
|
||||
context.SetFilter(searchString, self.c.UserConfig().Gui.UseFuzzySearch())
|
||||
_ = self.c.PostRefreshUpdate(context)
|
||||
self.c.PostRefreshUpdate(context)
|
||||
case types.ISearchableContext:
|
||||
// do nothing
|
||||
default:
|
||||
|
||||
@@ -23,7 +23,7 @@ func NewSnakeHelper(c *HelperCommon) *SnakeHelper {
|
||||
func (self *SnakeHelper) StartGame() {
|
||||
view := self.c.Views().Snake
|
||||
|
||||
game := snake.NewGame(view.Width(), view.Height(), self.renderSnakeGame, self.c.LogAction)
|
||||
game := snake.NewGame(view.InnerWidth(), view.InnerHeight(), self.renderSnakeGame, self.c.LogAction)
|
||||
self.game = game
|
||||
game.Start()
|
||||
}
|
||||
|
||||
@@ -63,18 +63,18 @@ func (self *StagingHelper) RefreshStagingPanel(focusOpts types.OnFocusOpts) {
|
||||
secondaryContext.GetMutex().Lock()
|
||||
|
||||
mainContext.SetState(
|
||||
patch_exploring.NewState(mainDiff, mainSelectedLineIdx, mainContext.GetState(), self.c.Log),
|
||||
patch_exploring.NewState(mainDiff, mainSelectedLineIdx, mainContext.GetView(), mainContext.GetState()),
|
||||
)
|
||||
|
||||
secondaryContext.SetState(
|
||||
patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondaryContext.GetState(), self.c.Log),
|
||||
patch_exploring.NewState(secondaryDiff, secondarySelectedLineIdx, secondaryContext.GetView(), secondaryContext.GetState()),
|
||||
)
|
||||
|
||||
mainState := mainContext.GetState()
|
||||
secondaryState := secondaryContext.GetState()
|
||||
|
||||
mainContent := mainContext.GetContentToRender(!secondaryFocused)
|
||||
secondaryContent := secondaryContext.GetContentToRender(secondaryFocused)
|
||||
mainContent := mainContext.GetContentToRender()
|
||||
secondaryContent := secondaryContext.GetContentToRender()
|
||||
|
||||
mainContext.GetMutex().Unlock()
|
||||
secondaryContext.GetMutex().Unlock()
|
||||
|
||||
@@ -67,10 +67,7 @@ func (self *SubCommitsHelper) ViewSubCommits(opts ViewSubCommitsOpts) error {
|
||||
subCommitsContext.GetView().ClearSearch()
|
||||
subCommitsContext.GetView().TitlePrefix = opts.Context.GetView().TitlePrefix
|
||||
|
||||
err = self.c.PostRefreshUpdate(self.c.Contexts().SubCommits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.PostRefreshUpdate(self.c.Contexts().SubCommits)
|
||||
|
||||
self.c.Context().Push(self.c.Contexts().SubCommits)
|
||||
return nil
|
||||
|
||||
@@ -162,10 +162,26 @@ func (self *SuggestionsHelper) getRemoteBranchNames(separator string) []string {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *SuggestionsHelper) getRemoteBranchNamesForRemote(remoteName string) []string {
|
||||
remote, ok := lo.Find(self.c.Model().Remotes, func(remote *models.Remote) bool {
|
||||
return remote.Name == remoteName
|
||||
})
|
||||
if ok {
|
||||
return lo.Map(remote.Branches, func(branch *models.RemoteBranch, _ int) string {
|
||||
return branch.Name
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SuggestionsHelper) GetRemoteBranchesSuggestionsFunc(separator string) func(string) []*types.Suggestion {
|
||||
return FilterFunc(self.getRemoteBranchNames(separator), self.c.UserConfig().Gui.UseFuzzySearch())
|
||||
}
|
||||
|
||||
func (self *SuggestionsHelper) GetRemoteBranchesForRemoteSuggestionsFunc(remoteName string) func(string) []*types.Suggestion {
|
||||
return FilterFunc(self.getRemoteBranchNamesForRemote(remoteName), self.c.UserConfig().Gui.UseFuzzySearch())
|
||||
}
|
||||
|
||||
func (self *SuggestionsHelper) getTagNames() []string {
|
||||
return lo.Map(self.c.Model().Tags, func(tag *models.Tag, _ int) string {
|
||||
return tag.Name
|
||||
|
||||
@@ -53,7 +53,7 @@ type WindowArrangementArgs struct {
|
||||
// staged and unstaged changes)
|
||||
SplitMainPanel bool
|
||||
// The current screen mode (normal, half, full)
|
||||
ScreenMode types.WindowMaximisation
|
||||
ScreenMode types.ScreenMode
|
||||
// The content shown on the bottom left of the screen when showing a loader
|
||||
// or toast e.g. 'Rebasing /'
|
||||
AppStatus string
|
||||
|
||||
@@ -422,7 +422,7 @@ func renderLayout(windows map[string]boxlayout.Dimensions) string {
|
||||
return dimensionsA.X0 < dimensionsB.X0
|
||||
})
|
||||
|
||||
// Uniquefy windows by dimensions (so perfectly overlapping windows are de-duped). This prevents getting 'fileshes' as a label where the files and branches windows overlap.
|
||||
// Uniquify windows by dimensions (so perfectly overlapping windows are de-duped). This prevents getting 'fileshes' as a label where the files and branches windows overlap.
|
||||
// branches windows overlap.
|
||||
windowNames = lo.UniqBy(windowNames, func(windowName string) boxlayout.Dimensions {
|
||||
return windows[windowName]
|
||||
|
||||
@@ -87,7 +87,7 @@ func (self *WorkingTreeHelper) OpenMergeTool() error {
|
||||
}
|
||||
|
||||
func (self *WorkingTreeHelper) HandleCommitPressWithMessage(initialMessage string) error {
|
||||
return self.WithEnsureCommitableFiles(func() error {
|
||||
return self.WithEnsureCommittableFiles(func() error {
|
||||
self.commitsHelper.OpenCommitMessagePanel(
|
||||
&OpenCommitMessagePanelOpts{
|
||||
CommitIndex: context.NoCommitIndex,
|
||||
@@ -131,7 +131,7 @@ func (self *WorkingTreeHelper) switchFromCommitMessagePanelToEditor(filepath str
|
||||
// HandleCommitEditorPress - handle when the user wants to commit changes via
|
||||
// their editor rather than via the popup panel
|
||||
func (self *WorkingTreeHelper) HandleCommitEditorPress() error {
|
||||
return self.WithEnsureCommitableFiles(func() error {
|
||||
return self.WithEnsureCommittableFiles(func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.Commit)
|
||||
return self.c.RunSubprocessAndRefresh(
|
||||
self.c.Git().Commit.CommitEditorCmdObj(),
|
||||
@@ -172,7 +172,7 @@ func (self *WorkingTreeHelper) HandleCommitPress() error {
|
||||
return self.HandleCommitPressWithMessage(message)
|
||||
}
|
||||
|
||||
func (self *WorkingTreeHelper) WithEnsureCommitableFiles(handler func() error) error {
|
||||
func (self *WorkingTreeHelper) WithEnsureCommittableFiles(handler func() error) error {
|
||||
if err := self.prepareFilesForCommit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -49,7 +49,8 @@ func (self *JumpToSideWindowController) GetKeybindings(opts types.KeybindingsOpt
|
||||
|
||||
func (self *JumpToSideWindowController) goToSideWindow(window string) func() error {
|
||||
return func() error {
|
||||
if self.c.Helpers().Window.CurrentWindow() == window {
|
||||
sideWindowAlreadyActive := self.c.Helpers().Window.CurrentWindow() == window
|
||||
if sideWindowAlreadyActive && self.c.UserConfig().Gui.SwitchTabsWithPanelJumpKeys {
|
||||
return self.nextTabFunc()
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/types/enums"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
@@ -115,7 +116,7 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(editCommitKey),
|
||||
Handler: self.withItems(self.edit),
|
||||
Handler: self.withItemsRange(self.edit),
|
||||
GetDisabledReason: self.require(
|
||||
self.itemRangeSelected(self.midRebaseCommandEnabled),
|
||||
),
|
||||
@@ -496,12 +497,17 @@ func (self *LocalCommitsController) drop(selectedCommits []*models.Commit, start
|
||||
return self.updateTodos(todo.Drop, selectedCommits)
|
||||
}
|
||||
|
||||
isMerge := selectedCommits[0].IsMerge()
|
||||
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.DropCommitTitle,
|
||||
Prompt: self.c.Tr.DropCommitPrompt,
|
||||
Prompt: lo.Ternary(isMerge, self.c.Tr.DropMergeCommitPrompt, self.c.Tr.DropCommitPrompt),
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.DroppingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.DropCommit)
|
||||
if isMerge {
|
||||
return self.dropMergeCommit(startIdx)
|
||||
}
|
||||
return self.interactiveRebase(todo.Drop, startIdx, endIdx)
|
||||
})
|
||||
},
|
||||
@@ -510,11 +516,30 @@ func (self *LocalCommitsController) drop(selectedCommits []*models.Commit, start
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) edit(selectedCommits []*models.Commit) error {
|
||||
func (self *LocalCommitsController) dropMergeCommit(commitIdx int) error {
|
||||
err := self.c.Git().Rebase.DropMergeCommit(self.c.Model().Commits, commitIdx)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) edit(selectedCommits []*models.Commit, startIdx int, endIdx int) error {
|
||||
if self.isRebasing() {
|
||||
return self.updateTodos(todo.Edit, selectedCommits)
|
||||
}
|
||||
|
||||
commits := self.c.Model().Commits
|
||||
if !commits[endIdx].IsMerge() {
|
||||
selectionRangeAndMode := self.getSelectionRangeAndMode()
|
||||
err := self.c.Git().Rebase.InteractiveRebase(commits, startIdx, endIdx, todo.Edit)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
|
||||
err,
|
||||
types.RefreshOptions{
|
||||
Mode: types.BLOCK_UI, Then: func() error {
|
||||
self.restoreSelectionRangeAndMode(selectionRangeAndMode)
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return self.startInteractiveRebaseWithEdit(selectedCommits)
|
||||
}
|
||||
|
||||
@@ -532,10 +557,7 @@ func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
|
||||
) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.EditCommit)
|
||||
selectedIdx, rangeStartIdx, rangeSelectMode := self.context().GetSelectionRangeAndMode()
|
||||
commits := self.c.Model().Commits
|
||||
selectedHash := commits[selectedIdx].Hash
|
||||
rangeStartHash := commits[rangeStartIdx].Hash
|
||||
selectionRangeAndMode := self.getSelectionRangeAndMode()
|
||||
err := self.c.Git().Rebase.EditRebase(commitsToEdit[len(commitsToEdit)-1].Hash)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
|
||||
err,
|
||||
@@ -554,23 +576,41 @@ func (self *LocalCommitsController) startInteractiveRebaseWithEdit(
|
||||
}
|
||||
}
|
||||
|
||||
// We need to select the same commit range again because after starting a rebase,
|
||||
// new lines can be added for update-ref commands in the TODO file, due to
|
||||
// stacked branches. So the selected commits may be in different positions in the list.
|
||||
_, newSelectedIdx, ok1 := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
|
||||
return c.Hash == selectedHash
|
||||
})
|
||||
_, newRangeStartIdx, ok2 := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
|
||||
return c.Hash == rangeStartHash
|
||||
})
|
||||
if ok1 && ok2 {
|
||||
self.context().SetSelectionRangeAndMode(newSelectedIdx, newRangeStartIdx, rangeSelectMode)
|
||||
}
|
||||
self.restoreSelectionRangeAndMode(selectionRangeAndMode)
|
||||
return nil
|
||||
}})
|
||||
})
|
||||
}
|
||||
|
||||
type SelectionRangeAndMode struct {
|
||||
selectedHash string
|
||||
rangeStartHash string
|
||||
mode traits.RangeSelectMode
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) getSelectionRangeAndMode() SelectionRangeAndMode {
|
||||
selectedIdx, rangeStartIdx, rangeSelectMode := self.context().GetSelectionRangeAndMode()
|
||||
commits := self.c.Model().Commits
|
||||
selectedHash := commits[selectedIdx].Hash
|
||||
rangeStartHash := commits[rangeStartIdx].Hash
|
||||
return SelectionRangeAndMode{selectedHash, rangeStartHash, rangeSelectMode}
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) restoreSelectionRangeAndMode(selectionRangeAndMode SelectionRangeAndMode) {
|
||||
// We need to select the same commit range again because after starting a rebase,
|
||||
// new lines can be added for update-ref commands in the TODO file, due to
|
||||
// stacked branches. So the selected commits may be in different positions in the list.
|
||||
_, newSelectedIdx, ok1 := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
|
||||
return c.Hash == selectionRangeAndMode.selectedHash
|
||||
})
|
||||
_, newRangeStartIdx, ok2 := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
|
||||
return c.Hash == selectionRangeAndMode.rangeStartHash
|
||||
})
|
||||
if ok1 && ok2 {
|
||||
self.context().SetSelectionRangeAndMode(newSelectedIdx, newRangeStartIdx, selectionRangeAndMode.mode)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) findCommitForQuickStartInteractiveRebase() (*models.Commit, error) {
|
||||
commit, index, ok := lo.FindIndexOf(self.c.Model().Commits, func(c *models.Commit) bool {
|
||||
return c.IsMerge() || c.Status == models.StatusMerged
|
||||
@@ -696,7 +736,7 @@ func (self *LocalCommitsController) amendTo(commit *models.Commit) error {
|
||||
Title: self.c.Tr.AmendCommitTitle,
|
||||
Prompt: self.c.Tr.AmendCommitPrompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommitableFiles(func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error {
|
||||
if err := self.c.Helpers().AmendHelper.AmendHead(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -712,7 +752,7 @@ func (self *LocalCommitsController) amendTo(commit *models.Commit) error {
|
||||
Title: self.c.Tr.AmendCommitTitle,
|
||||
Prompt: self.c.Tr.AmendCommitPrompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommitableFiles(func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.AmendingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.AmendCommit)
|
||||
err := self.c.Git().Rebase.AmendTo(self.c.Model().Commits, self.context().GetView().SelectedLineIdx())
|
||||
@@ -888,13 +928,17 @@ func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) err
|
||||
Label: self.c.Tr.FixupMenu_Fixup,
|
||||
Key: 'f',
|
||||
OnPress: func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommitableFiles(func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CreateFixupCommit)
|
||||
return self.c.WithWaitingStatusSync(self.c.Tr.CreatingFixupCommitStatus, func() error {
|
||||
if err := self.c.Git().Commit.CreateFixupCommit(commit.Hash); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.moveFixupCommitToOwnerStackedBranch(commit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.context().MoveSelectedLine(1)
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC})
|
||||
})
|
||||
@@ -907,7 +951,7 @@ func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) err
|
||||
Label: self.c.Tr.FixupMenu_AmendWithChanges,
|
||||
Key: 'a',
|
||||
OnPress: func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommitableFiles(func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error {
|
||||
return self.createAmendCommit(commit, true)
|
||||
})
|
||||
},
|
||||
@@ -924,6 +968,50 @@ func (self *LocalCommitsController) createFixupCommit(commit *models.Commit) err
|
||||
})
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) moveFixupCommitToOwnerStackedBranch(targetCommit *models.Commit) error {
|
||||
if self.c.Git().Version.IsOlderThan(2, 38, 0) {
|
||||
// Git 2.38.0 introduced the `rebase.updateRefs` config option. Don't
|
||||
// move the commit down with older versions, as it would break the stack.
|
||||
return nil
|
||||
}
|
||||
|
||||
if self.c.Git().Status.WorkingTreeState() != enums.REBASE_MODE_NONE {
|
||||
// Can't move commits while rebasing
|
||||
return nil
|
||||
}
|
||||
|
||||
if targetCommit.Status == models.StatusMerged {
|
||||
// Target commit is already on main. It's a bit questionable that we
|
||||
// allow creating a fixup commit for it in the first place, but we
|
||||
// always did, so why restrict that now; however, it doesn't make sense
|
||||
// to move the created fixup commit down in that case.
|
||||
return nil
|
||||
}
|
||||
|
||||
if !self.c.Git().Config.GetRebaseUpdateRefs() {
|
||||
// If the user has disabled rebase.updateRefs, we don't move the fixup
|
||||
// because this would break the stack of branches (presumably they like
|
||||
// to manage it themselves manually, or something).
|
||||
return nil
|
||||
}
|
||||
|
||||
headOfOwnerBranchIdx := -1
|
||||
for i := self.context().GetSelectedLineIdx(); i > 0; i-- {
|
||||
if lo.SomeBy(self.c.Model().Branches, func(b *models.Branch) bool {
|
||||
return b.CommitHash == self.c.Model().Commits[i].Hash
|
||||
}) {
|
||||
headOfOwnerBranchIdx = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if headOfOwnerBranchIdx == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.c.Git().Rebase.MoveFixupCommitDown(self.c.Model().Commits, headOfOwnerBranchIdx)
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) createAmendCommit(commit *models.Commit, includeFileChanges bool) error {
|
||||
commitMessage, err := self.c.Git().Commit.GetCommitMessage(commit.Hash)
|
||||
if err != nil {
|
||||
@@ -947,6 +1035,10 @@ func (self *LocalCommitsController) createAmendCommit(commit *models.Commit, inc
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.moveFixupCommitToOwnerStackedBranch(commit); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.context().MoveSelectedLine(1)
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.SYNC})
|
||||
})
|
||||
@@ -1125,10 +1217,9 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
|
||||
return func() error {
|
||||
self.c.GetAppState().GitLogShowGraph = value
|
||||
self.c.SaveAppStateAndLogError()
|
||||
if err := self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits); err != nil {
|
||||
return err
|
||||
}
|
||||
return self.c.PostRefreshUpdate(self.c.Contexts().SubCommits)
|
||||
self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
|
||||
self.c.PostRefreshUpdate(self.c.Contexts().SubCommits)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
@@ -1191,6 +1282,11 @@ func (self *LocalCommitsController) handleOpenLogMenu() error {
|
||||
OnPress: onPress("author-date-order"),
|
||||
Widget: types.MakeMenuRadioButton(currentValue == "author-date-order"),
|
||||
},
|
||||
{
|
||||
Label: "default",
|
||||
OnPress: onPress("default"),
|
||||
Widget: types.MakeMenuRadioButton(currentValue == "default"),
|
||||
},
|
||||
},
|
||||
})
|
||||
},
|
||||
@@ -1234,7 +1330,8 @@ func (self *LocalCommitsController) markAsBaseCommit(commit *models.Commit) erro
|
||||
} else {
|
||||
self.c.Modes().MarkedBaseCommit.SetHash(commit.Hash)
|
||||
}
|
||||
return self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
|
||||
self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) isHeadCommit(idx int) bool {
|
||||
@@ -1271,11 +1368,15 @@ func (self *LocalCommitsController) canFindCommitForSquashFixupsInCurrentBranch(
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) canSquashOrFixup(_selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason {
|
||||
func (self *LocalCommitsController) canSquashOrFixup(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason {
|
||||
if endIdx >= len(self.c.Model().Commits)-1 {
|
||||
return &types.DisabledReason{Text: self.c.Tr.CannotSquashOrFixupFirstCommit}
|
||||
}
|
||||
|
||||
if lo.SomeBy(selectedCommits, func(c *models.Commit) bool { return c.IsMerge() }) {
|
||||
return &types.DisabledReason{Text: self.c.Tr.CannotSquashOrFixupMergeCommit}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1333,6 +1434,10 @@ func (self *LocalCommitsController) midRebaseCommandEnabled(selectedCommits []*m
|
||||
// Ensures that if we are mid-rebase, we're only selecting commits that can be moved
|
||||
func (self *LocalCommitsController) midRebaseMoveCommandEnabled(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason {
|
||||
if !self.isRebasing() {
|
||||
if lo.SomeBy(selectedCommits, func(c *models.Commit) bool { return c.IsMerge() }) {
|
||||
return &types.DisabledReason{Text: self.c.Tr.CannotMoveMergeCommit}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1353,6 +1458,10 @@ func (self *LocalCommitsController) midRebaseMoveCommandEnabled(selectedCommits
|
||||
|
||||
func (self *LocalCommitsController) canDropCommits(selectedCommits []*models.Commit, startIdx int, endIdx int) *types.DisabledReason {
|
||||
if !self.isRebasing() {
|
||||
if len(selectedCommits) > 1 && lo.SomeBy(selectedCommits, func(c *models.Commit) bool { return c.IsMerge() }) {
|
||||
return &types.DisabledReason{Text: self.c.Tr.DroppingMergeRequiresSingleSelection}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -13,11 +13,6 @@ type OptionsMenuAction struct {
|
||||
|
||||
func (self *OptionsMenuAction) Call() error {
|
||||
ctx := self.c.Context().Current()
|
||||
// Don't show menu while displaying popup.
|
||||
if ctx.GetKind() == types.PERSISTENT_POPUP || ctx.GetKind() == types.TEMPORARY_POPUP {
|
||||
return nil
|
||||
}
|
||||
|
||||
local, global, navigation := self.getBindings(ctx)
|
||||
|
||||
menuItems := []*types.MenuItem{}
|
||||
|
||||
@@ -65,7 +65,7 @@ func (self *PatchBuildingController) GetMouseKeybindings(opts types.KeybindingsO
|
||||
func (self *PatchBuildingController) GetOnFocus() func(types.OnFocusOpts) {
|
||||
return func(opts types.OnFocusOpts) {
|
||||
// no need to change wrap on the secondary view because it can't be interacted with
|
||||
self.c.Views().PatchBuilding.Wrap = false
|
||||
self.c.Views().PatchBuilding.Wrap = self.c.UserConfig().Gui.WrapLinesInStagingView
|
||||
|
||||
self.c.Helpers().PatchBuilding.RefreshPatchBuildingPanel(opts)
|
||||
}
|
||||
@@ -73,6 +73,8 @@ func (self *PatchBuildingController) GetOnFocus() func(types.OnFocusOpts) {
|
||||
|
||||
func (self *PatchBuildingController) GetOnFocusLost() func(types.OnFocusLostOpts) {
|
||||
return func(opts types.OnFocusLostOpts) {
|
||||
self.context().SetState(nil)
|
||||
|
||||
self.c.Views().PatchBuilding.Wrap = true
|
||||
|
||||
if self.c.Git().Patch.PatchBuilder.IsEmpty() {
|
||||
@@ -105,6 +107,7 @@ func (self *PatchBuildingController) EditFile() error {
|
||||
}
|
||||
|
||||
lineNumber := self.context().GetState().CurrentLineNumber()
|
||||
lineNumber = self.c.Helpers().Diff.AdjustLineNumber(path, lineNumber, self.context().GetViewName())
|
||||
return self.c.Helpers().Files.EditFileAtLine(path, lineNumber)
|
||||
}
|
||||
|
||||
@@ -134,13 +137,13 @@ func (self *PatchBuildingController) toggleSelection() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentLineIsStaged := lo.Contains(includedLineIndices, state.GetSelectedLineIdx())
|
||||
currentLineIsStaged := lo.Contains(includedLineIndices, state.GetSelectedPatchLineIdx())
|
||||
if currentLineIsStaged {
|
||||
toggleFunc = self.c.Git().Patch.PatchBuilder.RemoveFileLineRange
|
||||
}
|
||||
|
||||
// add range of lines to those set for the file
|
||||
firstLineIdx, lastLineIdx := state.SelectedRange()
|
||||
firstLineIdx, lastLineIdx := state.SelectedPatchRange()
|
||||
|
||||
if err := toggleFunc(filename, firstLineIdx, lastLineIdx); err != nil {
|
||||
// might actually want to return an error here
|
||||
@@ -160,7 +163,8 @@ func (self *PatchBuildingController) Escape() error {
|
||||
|
||||
if state.SelectingRange() || state.SelectingHunk() {
|
||||
state.SetLineSelectMode()
|
||||
return self.c.PostRefreshUpdate(context)
|
||||
self.c.PostRefreshUpdate(context)
|
||||
return nil
|
||||
}
|
||||
|
||||
self.c.Helpers().PatchBuilding.Escape()
|
||||
|
||||
@@ -170,9 +170,9 @@ func (self *PatchExplorerController) GetMouseKeybindings(opts types.KeybindingsO
|
||||
}
|
||||
|
||||
func (self *PatchExplorerController) HandlePrevLine() error {
|
||||
before := self.context.GetState().GetSelectedLineIdx()
|
||||
before := self.context.GetState().GetSelectedViewLineIdx()
|
||||
self.context.GetState().CycleSelection(false)
|
||||
after := self.context.GetState().GetSelectedLineIdx()
|
||||
after := self.context.GetState().GetSelectedViewLineIdx()
|
||||
|
||||
if self.context.GetState().SelectingLine() {
|
||||
checkScrollUp(self.context.GetViewTrait(), self.c.UserConfig(), before, after)
|
||||
@@ -182,9 +182,9 @@ func (self *PatchExplorerController) HandlePrevLine() error {
|
||||
}
|
||||
|
||||
func (self *PatchExplorerController) HandleNextLine() error {
|
||||
before := self.context.GetState().GetSelectedLineIdx()
|
||||
before := self.context.GetState().GetSelectedViewLineIdx()
|
||||
self.context.GetState().CycleSelection(true)
|
||||
after := self.context.GetState().GetSelectedLineIdx()
|
||||
after := self.context.GetState().GetSelectedViewLineIdx()
|
||||
|
||||
if self.context.GetState().SelectingLine() {
|
||||
checkScrollDown(self.context.GetViewTrait(), self.c.UserConfig(), before, after)
|
||||
@@ -302,7 +302,7 @@ func (self *PatchExplorerController) withRenderAndFocus(f func() error) func() e
|
||||
return err
|
||||
}
|
||||
|
||||
self.context.RenderAndFocus(self.isFocused())
|
||||
self.context.RenderAndFocus()
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user