Compare commits
345 Commits
v0.44.0
...
test-githu
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
689e708baa | ||
|
|
4e38a941de | ||
|
|
471f72e607 | ||
|
|
49f2f818c6 | ||
|
|
f5cd02b54f | ||
|
|
fd01ca3acf | ||
|
|
4845ce1e0f | ||
|
|
0cc6e39f0f | ||
|
|
e90aeb62e5 | ||
|
|
17ab91e668 | ||
|
|
964278255b | ||
|
|
56695078c3 | ||
|
|
96934d5a1d | ||
|
|
f05f81d713 | ||
|
|
9d0740427e | ||
|
|
e62aeb99ed | ||
|
|
fcf48c4f08 | ||
|
|
2ceecad381 | ||
|
|
3b85307f67 | ||
|
|
30e9bf8a75 | ||
|
|
62c6ba7d57 | ||
|
|
72b9e8328d | ||
|
|
38ab7ebefb | ||
|
|
4b30bc6dd3 | ||
|
|
626ca18a23 | ||
|
|
87c3e75811 | ||
|
|
1e05055fff | ||
|
|
0ef3832e59 | ||
|
|
b766ff9c83 | ||
|
|
b2fd6128f6 | ||
|
|
a81f9ea97c | ||
|
|
101bbb0ac5 | ||
|
|
3e11e34181 | ||
|
|
daf1fd3419 | ||
|
|
54d0ebeff0 | ||
|
|
af9699e59a | ||
|
|
d92c6d116b | ||
|
|
343a3e466a | ||
|
|
16f5348790 | ||
|
|
5979b40546 | ||
|
|
a5f78d3222 | ||
|
|
f3791e6ab6 | ||
|
|
130801dbf6 | ||
|
|
67bb7f62c5 | ||
|
|
52b1c42d38 | ||
|
|
11616190ee | ||
|
|
e5137b86cf | ||
|
|
ac3824bd7c | ||
|
|
56c9d5abeb | ||
|
|
c9196812a2 | ||
|
|
caca62b89e | ||
|
|
e15e4951ab | ||
|
|
e3944c1707 | ||
|
|
0d155e1478 | ||
|
|
2fa4ee2cac | ||
|
|
a7bfeca9c0 | ||
|
|
ab23539c0c | ||
|
|
01eece3737 | ||
|
|
57220ba478 | ||
|
|
6636d0c15a | ||
|
|
269d89ea51 | ||
|
|
dcf264b182 | ||
|
|
e37d7d5ad2 | ||
|
|
988162848a | ||
|
|
ed9519a241 | ||
|
|
7731311674 | ||
|
|
853a04dfd0 | ||
|
|
c02709698c | ||
|
|
555ab8735a | ||
|
|
20fe43f972 | ||
|
|
0fd7b9baa4 | ||
|
|
19921b7c42 | ||
|
|
c37376af74 | ||
|
|
9dbde94952 | ||
|
|
3915cb6e71 | ||
|
|
ba6cfc1f85 | ||
|
|
46ebfbbe87 | ||
|
|
3012306ac3 | ||
|
|
e987d4b519 | ||
|
|
190954568e | ||
|
|
f117eed614 | ||
|
|
a5698b86fa | ||
|
|
050a91b7d1 | ||
|
|
0b524293e1 | ||
|
|
ff4ae4a544 | ||
|
|
a0aa7a1940 | ||
|
|
6065908b0d | ||
|
|
a32be7e9fa | ||
|
|
fcf30caf40 | ||
|
|
aad2622278 | ||
|
|
2f4cedd025 | ||
|
|
e883f74f3c | ||
|
|
a1838d33e7 | ||
|
|
7e85cdd027 | ||
|
|
31e54eadaa | ||
|
|
02ca07a9be | ||
|
|
5dff9af027 | ||
|
|
437daf2f74 | ||
|
|
c0141685fe | ||
|
|
4856c96521 | ||
|
|
49f8dc2ce4 | ||
|
|
afc3061c51 | ||
|
|
2bed09ef67 | ||
|
|
0a78d0016e | ||
|
|
c0e3922d02 | ||
|
|
638c9c5fe7 | ||
|
|
e48e7a2ebc | ||
|
|
d2723ff2dd | ||
|
|
7db8fb8e9c | ||
|
|
abf914c923 | ||
|
|
698f9287d4 | ||
|
|
632695f71c | ||
|
|
ef0d319686 | ||
|
|
0397ede8a6 | ||
|
|
333802fffc | ||
|
|
3722824298 | ||
|
|
542478546d | ||
|
|
9ea2ff8f41 | ||
|
|
55236802c3 | ||
|
|
4baf008ac7 | ||
|
|
d768327814 | ||
|
|
0864affc8f | ||
|
|
40d6800fd3 | ||
|
|
20d0b4316d | ||
|
|
fe429c6184 | ||
|
|
a77dcbe3a1 | ||
|
|
2a87c048b9 | ||
|
|
c03b892270 | ||
|
|
5e26183ae1 | ||
|
|
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 |
3
.github/release.yml
vendored
3
.github/release.yml
vendored
@@ -21,6 +21,9 @@ changelog:
|
||||
- title: I18n 🌎
|
||||
labels:
|
||||
- i18n
|
||||
- title: Performance Improvements 📊
|
||||
labels:
|
||||
- performance
|
||||
- title: Other Changes
|
||||
labels:
|
||||
- "*"
|
||||
|
||||
35
.github/workflows/cd.yml
vendored
35
.github/workflows/cd.yml
vendored
@@ -1,35 +0,0 @@
|
||||
name: Continuous Delivery
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: Unshallow repo
|
||||
run: git fetch --prune --unshallow
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.x
|
||||
- name: Run goreleaser
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: v1.17.2
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_API_TOKEN}}
|
||||
homebrew:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Bump Homebrew formula
|
||||
uses: dawidd6/action-homebrew-bump-formula@v3
|
||||
with:
|
||||
token: ${{secrets.GITHUB_API_TOKEN}}
|
||||
formula: lazygit
|
||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -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
|
||||
|
||||
|
||||
137
.github/workflows/release.yml
vendored
Normal file
137
.github/workflows/release.yml
vendored
Normal file
@@ -0,0 +1,137 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Runs at 2:00 AM UTC on the first Saturday of every month
|
||||
- cron: '0 2 1-7 * 6'
|
||||
# Allow manual triggering of the workflow
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version_bump:
|
||||
description: 'Version bump type'
|
||||
type: choice
|
||||
required: true
|
||||
default: 'patch'
|
||||
options:
|
||||
- minor
|
||||
- patch
|
||||
ignore_blocks:
|
||||
description: 'Ignore blocking PRs/issues'
|
||||
type: boolean
|
||||
required: true
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
check-and-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get Latest Tag
|
||||
run: |
|
||||
latest_tag=$(git describe --tags $(git rev-list --tags --max-count=1) || echo "v0.0.0")
|
||||
|
||||
if ! [[ $latest_tag =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Error: Tag format is invalid. Expected format: vX.X.X"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Latest tag: $latest_tag"
|
||||
echo "latest_tag=$latest_tag" >> $GITHUB_ENV
|
||||
|
||||
- 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
|
||||
if: ${{ !inputs.ignore_blocks }}
|
||||
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: |
|
||||
echo "Latest tag: ${{ env.latest_tag }}"
|
||||
IFS='.' read -r major minor patch <<< "${{ env.latest_tag }}"
|
||||
|
||||
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
|
||||
if [[ "${{ inputs.version_bump }}" == "patch" ]]; then
|
||||
patch=$((patch + 1))
|
||||
else
|
||||
minor=$((minor + 1))
|
||||
patch=0
|
||||
fi
|
||||
else
|
||||
# Default behavior for scheduled runs
|
||||
minor=$((minor + 1))
|
||||
patch=0
|
||||
fi
|
||||
|
||||
new_tag="$major.$minor.$patch"
|
||||
|
||||
if ! [[ $new_tag =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "Error: New tag's format is invalid. Expected format: vX.X.X"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "New tag: $new_tag"
|
||||
echo "new_tag=$new_tag" >> $GITHUB_ENV
|
||||
|
||||
- name: Create and Push 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_API_TOKEN }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: 1.22.x
|
||||
|
||||
- name: Run goreleaser
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: v1.17.2
|
||||
args: release --clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_API_TOKEN}}
|
||||
|
||||
- name: Bump Homebrew formula
|
||||
uses: dawidd6/action-homebrew-bump-formula@v3
|
||||
with:
|
||||
token: ${{secrets.GITHUB_API_TOKEN}}
|
||||
formula: lazygit
|
||||
tag: ${{env.new_tag}}
|
||||
9
.vscode/launch.json
vendored
9
.vscode/launch.json
vendored
@@ -26,6 +26,15 @@
|
||||
],
|
||||
"console": "integratedTerminal",
|
||||
},
|
||||
{
|
||||
"name": "JSON Schema generator",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${workspaceFolder}/pkg/jsonschema/generator.go",
|
||||
"cwd": "${workspaceFolder}/pkg/jsonschema",
|
||||
"console": "integratedTerminal",
|
||||
},
|
||||
{
|
||||
"name": "Attach to a running Lazygit",
|
||||
"type": "go",
|
||||
|
||||
@@ -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:
|
||||
|
||||
104
VISION.md
Normal file
104
VISION.md
Normal file
@@ -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.
|
||||
114
docs/Config.md
114
docs/Config.md
@@ -47,6 +47,10 @@ gui:
|
||||
# One of: 'margin' (default) | 'jump'
|
||||
scrollOffBehavior: margin
|
||||
|
||||
# The number of spaces per tab; used for everything that's shown in the main view, but probably mostly relevant for diffs.
|
||||
# Note that when using a pager, the pager has its own tab width setting, so you need to pass it separately in the pager command.
|
||||
tabWidth: 4
|
||||
|
||||
# If true, capture mouse events.
|
||||
# When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS.
|
||||
mouseEvents: true
|
||||
@@ -87,6 +91,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
|
||||
|
||||
@@ -161,9 +170,12 @@ gui:
|
||||
showListFooter: true
|
||||
|
||||
# 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.
|
||||
# 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 +223,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 +258,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 +336,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
|
||||
@@ -324,14 +345,6 @@ git:
|
||||
# If true, do not allow force pushes
|
||||
disableForcePushing: false
|
||||
|
||||
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix
|
||||
commitPrefix:
|
||||
# pattern to match on. E.g. for 'feature/AB-123' to match on the AB-123 use "^\\w+\\/(\\w+-\\w+).*"
|
||||
pattern: ""
|
||||
|
||||
# Replace directive. E.g. for 'feature/AB-123' to start the commit message with 'AB-123 ' use "[$1] "
|
||||
replace: ""
|
||||
|
||||
# See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-branch-name-prefix
|
||||
branchPrefix: ""
|
||||
|
||||
@@ -398,6 +411,9 @@ os:
|
||||
# window is closed.
|
||||
editAtLineAndWait: ""
|
||||
|
||||
# Whether lazygit suspends until an edit process returns
|
||||
editInTerminal: false
|
||||
|
||||
# For opening a directory in an editor
|
||||
openDirInEditor: ""
|
||||
|
||||
@@ -554,6 +570,8 @@ keybinding:
|
||||
openMergeTool: M
|
||||
openStatusFilter: <c-b>
|
||||
copyFileInfoToClipboard: "y"
|
||||
collapseAll: '-'
|
||||
expandAll: =
|
||||
branches:
|
||||
createPullRequest: o
|
||||
viewPullRequestOptions: O
|
||||
@@ -642,6 +660,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 +675,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 +833,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
|
||||
|
||||

|
||||
@@ -876,29 +921,50 @@ Where:
|
||||
## Predefined commit message prefix
|
||||
|
||||
In situations where certain naming pattern is used for branches and commits, pattern can be used to populate commit message with prefix that is parsed from the branch name.
|
||||
If you define multiple naming patterns, they will be attempted in order until one matches.
|
||||
|
||||
Example:
|
||||
Example hitting first match:
|
||||
|
||||
- Branch name: feature/AB-123
|
||||
- Commit message: [AB-123] Adding feature
|
||||
- Generated commit message prefix: [AB-123]
|
||||
|
||||
Example hitting second match:
|
||||
|
||||
- Branch name: CD-456_fix_problem
|
||||
- Generated commit message prefix: (CD-456)
|
||||
|
||||
```yaml
|
||||
git:
|
||||
commitPrefix:
|
||||
pattern: "^\\w+\\/(\\w+-\\w+).*"
|
||||
replace: '[$1] '
|
||||
- pattern: "^\\w+\\/(\\w+-\\w+).*"
|
||||
replace: '[$1] '
|
||||
- pattern: "^([^_]+)_.*" # Take all text prior to the first underscore
|
||||
replace: '($1) '
|
||||
```
|
||||
|
||||
If you want repository-specific prefixes, you can map them with `commitPrefixes`. If you have both `commitPrefixes` defined and an entry in `commitPrefixes` for the current repo, the `commitPrefixes` entry is given higher precedence. Repository folder names must be an exact match.
|
||||
If you want repository-specific prefixes, you can map them with `commitPrefixes`. If you have both entries in `commitPrefix` defined and an repository match in `commitPrefixes` for the current repo, the `commitPrefixes` entries will be attempted first. Repository folder names must be an exact match.
|
||||
|
||||
```yaml
|
||||
git:
|
||||
commitPrefixes:
|
||||
my_project: # This is repository folder name
|
||||
pattern: "^\\w+\\/(\\w+-\\w+).*"
|
||||
replace: '[$1] '
|
||||
- pattern: "^\\w+\\/(\\w+-\\w+).*"
|
||||
replace: '[$1] '
|
||||
commitPrefix:
|
||||
- pattern: "^(\\w+)-.*" # A more general match for any leading word
|
||||
replace : '[$1] '
|
||||
- pattern: ".*" # The final fallthrough regex that copies over the whole branch name
|
||||
replace : '[$0] '
|
||||
```
|
||||
|
||||
> [!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 |
|
||||
@@ -297,6 +297,7 @@ Your commands can contain placeholder strings using Go's [template syntax](https
|
||||
|
||||
```
|
||||
SelectedCommit
|
||||
SelectedCommitRange
|
||||
SelectedFile
|
||||
SelectedPath
|
||||
SelectedLocalBranch
|
||||
@@ -314,6 +315,11 @@ CheckedOutBranch
|
||||
|
||||
To see what fields are available on e.g. the `SelectedFile`, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/gui/services/custom_commands/models.go) (all the modelling lives in the same file).
|
||||
|
||||
We don't support accessing all elements of a range selection yet. We might add this in the future, but as a special case you can access the range of selected commits by using `SelectedCommitRange`, which has two properties `.To` and `.From` which are the hashes of the bottom and top selected commits, respectively. This is useful for passing them to a git command that operates on a range of commits. For example, to create patches for all selected commits, you might use
|
||||
```yml
|
||||
command: "git format-patch {{.SelectedCommitRange.From}}^..{{.SelectedCommitRange.To}}"
|
||||
```
|
||||
|
||||
## Keybinding collisions
|
||||
|
||||
If your custom keybinding collides with an inbuilt keybinding that is defined for the same context, only the custom keybinding will be executed. This also applies to the global context. However, one caveat is that if you have a custom keybinding defined on the global context for some key, and there is an in-built keybinding defined for the same key and for a specific context (say the 'files' context), then the in-built keybinding will take precedence. See how to change in-built keybindings [here](https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#keybindings)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -20,11 +20,15 @@
|
||||
| `<pgup>` | Pgup |
|
||||
| `<pgdown>` | Pgdn |
|
||||
| `<up>` | ArrowUp |
|
||||
| `<s-up>` | ShiftArrowUp |
|
||||
| `<down>` | ArrowDown |
|
||||
| `<s-down>` | ShiftArrowDown |
|
||||
| `<left>` | ArrowLeft |
|
||||
| `<right>` | ArrowRight |
|
||||
| `<tab>` | Tab |
|
||||
| `<backtab>` | Backtab |
|
||||
| `<enter>` | Enter |
|
||||
| `<a-enter>` | AltEnter |
|
||||
| `<esc>` | Esc |
|
||||
| `<backspace>` | Backspace |
|
||||
| `<c-space>` | CtrlSpace |
|
||||
|
||||
@@ -56,6 +56,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | Copy path to clipboard | |
|
||||
| `` y `` | Copy to clipboard | |
|
||||
| `` c `` | Checkout | Checkout file. This replaces the file in your working tree with the version from the selected commit. |
|
||||
| `` d `` | Remove | Discard this commit's changes to this file. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes this file. |
|
||||
| `` o `` | Open file | Open file in default application. |
|
||||
@@ -65,6 +66,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 +150,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 +353,8 @@ 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. |
|
||||
| `` <c-o> `` | Copy tag to clipboard | |
|
||||
| `` <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. |
|
||||
|
||||
@@ -134,6 +134,7 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | ファイル名をクリップボードにコピー | |
|
||||
| `` y `` | Copy to clipboard | |
|
||||
| `` c `` | チェックアウト | Checkout file. This replaces the file in your working tree with the version from the selected commit. |
|
||||
| `` d `` | Remove | Discard this commit's changes to this file. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes this file. |
|
||||
| `` o `` | ファイルを開く | Open file in default application. |
|
||||
@@ -143,6 +144,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 +183,8 @@ 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. |
|
||||
| `` <c-o> `` | Copy tag to clipboard | |
|
||||
| `` <space> `` | チェックアウト | Checkout the selected tag as a detached HEAD. |
|
||||
| `` n `` | タグを作成 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
|
||||
| `` d `` | Delete | View delete options for local/remote tag. |
|
||||
| `` P `` | タグをpush | Push the selected tag to a remote. You'll be prompted to select a remote. |
|
||||
@@ -218,6 +222,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 |
|
||||
| `` / `` | 検索を開始 | |
|
||||
|
||||
## ブランチ
|
||||
|
||||
@@ -189,7 +189,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` <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 this branch from its upstream | Fast-forward selected branch from its upstream. |
|
||||
@@ -247,7 +247,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` 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. |
|
||||
| `` d `` | 삭제 | Delete the remote branch from the remote. |
|
||||
| `` u `` | Set as upstream | Set the selected remote branch as the upstream of the checked-out branch. |
|
||||
| `` s `` | Sort order | |
|
||||
| `` g `` | View reset options | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
@@ -263,7 +263,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` <c-o> `` | 커밋 해시를 클립보드에 복사 | |
|
||||
| `` <c-r> `` | Reset cherry-picked (copied) commits selection | |
|
||||
| `` b `` | Bisect 옵션 보기 | |
|
||||
| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
|
||||
| `` 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. |
|
||||
| `` R `` | 에디터에서 커밋메시지 수정 | |
|
||||
@@ -299,6 +299,7 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 파일명을 클립보드에 복사 | |
|
||||
| `` y `` | 클립보드에 복사 | |
|
||||
| `` c `` | 체크아웃 | Checkout file |
|
||||
| `` d `` | Remove | Discard this commit's changes to this file |
|
||||
| `` o `` | 파일 닫기 | Open file in default application. |
|
||||
@@ -308,6 +309,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,11 +324,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. |
|
||||
| `` <c-o> `` | Copy tag to clipboard | |
|
||||
| `` <space> `` | 체크아웃 | Checkout the selected tag as a detached HEAD. |
|
||||
| `` n `` | 태그를 생성 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
|
||||
| `` d `` | Delete | View delete options for local/remote tag. |
|
||||
| `` d `` | 삭제 | View delete options for local/remote tag. |
|
||||
| `` P `` | 태그를 push | Push the selected tag to a remote. You'll be prompted to select a remote. |
|
||||
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` g `` | 초기화 | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` <c-t> `` | Open external diff tool (git difftool) | |
|
||||
| `` <enter> `` | 커밋 보기 | |
|
||||
| `` w `` | View worktree options | |
|
||||
@@ -338,7 +342,7 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| `` <c-o> `` | 파일명을 클립보드에 복사 | |
|
||||
| `` <space> `` | Staged 전환 | Toggle staged for selected file. |
|
||||
| `` <c-b> `` | 파일을 필터하기 (Staged/unstaged) | |
|
||||
| `` y `` | Copy to clipboard | |
|
||||
| `` y `` | 클립보드에 복사 | |
|
||||
| `` c `` | 커밋 변경내용 | Commit staged changes. |
|
||||
| `` w `` | Commit changes without pre-commit hook | |
|
||||
| `` A `` | 마지맛 커밋 수정 | |
|
||||
@@ -354,11 +358,13 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| `` <enter> `` | Stage individual hunks/lines for file, or collapse/expand for directory | If the selected item is a file, focus the staging view so you can stage individual hunks/lines. If the selected item is a directory, collapse/expand it. |
|
||||
| `` d `` | View 'discard changes' options | View options for discarding changes to the selected file. |
|
||||
| `` g `` | View upstream reset options | |
|
||||
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). |
|
||||
| `` 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 | 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
|
||||
@@ -127,6 +129,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | Kopieer de bestandsnaam naar het klembord | |
|
||||
| `` y `` | Copy to clipboard | |
|
||||
| `` c `` | Uitchecken | Bestand uitchecken |
|
||||
| `` d `` | Remove | Uitsluit deze commit zijn veranderingen aan dit bestand |
|
||||
| `` o `` | Open bestand | Open file in default application. |
|
||||
@@ -136,6 +139,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 +353,8 @@ 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. |
|
||||
| `` <c-o> `` | Copy tag to clipboard | |
|
||||
| `` <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
|
||||
@@ -236,6 +238,7 @@ Jeśli chcesz zamiast tego rozpocząć interaktywny rebase od wybranego commita,
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | Kopiuj ścieżkę do schowka | |
|
||||
| `` y `` | Kopiuj do schowka | |
|
||||
| `` c `` | Przełącz | Przełącz plik. Zastępuje plik w twoim drzewie roboczym wersją z wybranego commita. |
|
||||
| `` d `` | Usuń | Odrzuć zmiany w tym pliku z tego commita. Uruchamia interaktywny rebase w tle, więc możesz otrzymać konflikt scalania, jeśli późniejszy commit również zmienia ten plik. |
|
||||
| `` o `` | Otwórz plik | Otwórz plik w domyślnej aplikacji. |
|
||||
@@ -245,6 +248,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
|
||||
@@ -329,6 +334,7 @@ Jeśli chcesz zamiast tego rozpocząć interaktywny rebase od wybranego commita,
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | Copy tag to clipboard | |
|
||||
| `` <space> `` | Przełącz | Przełącz wybrany tag jako odłączoną głowę (detached HEAD). |
|
||||
| `` n `` | Nowy tag | Utwórz nowy tag z bieżącego commita. Zostaniesz poproszony o wprowadzenie nazwy tagu i opcjonalnego opisu. |
|
||||
| `` d `` | Usuń | Wyświetl opcje usuwania lokalnego/odległego tagu. |
|
||||
|
||||
379
docs/keybindings/Keybindings_pt.md
Normal file
379
docs/keybindings/Keybindings_pt.md
Normal file
@@ -0,0 +1,379 @@
|
||||
_This file is auto-generated. To update, make the changes in the pkg/i18n directory and then run `go generate ./...` from the project root._
|
||||
|
||||
# Lazygit Keybindings
|
||||
|
||||
_Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
|
||||
## Combinações globais de teclas
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-r> `` | Mudar para um repositório recente | |
|
||||
| `` <pgup> (fn+up/shift+k) `` | Scroll up main window | |
|
||||
| `` <pgdown> (fn+down/shift+j) `` | Scroll down main window | |
|
||||
| `` @ `` | View command log options | View options for the command log e.g. show/hide the command log and focus the command log. |
|
||||
| `` P `` | Empurre (Push) | Faça push do branch atual para o seu branch upstream. Se nenhum upstream estiver configurado, você será solicitado a configurar um branch a montante. |
|
||||
| `` p `` | Puxar (Pull) | Puxe alterações do controle remoto para o ramo atual. Se nenhum upstream estiver configurado, será solicitado configurar um ramo a montante. |
|
||||
| `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename. |
|
||||
| `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename. |
|
||||
| `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view. |
|
||||
| `` { `` | Decrease diff context size | Decrease the amount of the context shown around changes in the diff view. |
|
||||
| `` : `` | Execute shell command | Bring up a prompt where you can enter a shell command to execute. |
|
||||
| `` <c-p> `` | View custom patch options | |
|
||||
| `` m `` | Ver opções de mesclar/rebase | Ver opções para abortar/continuar/pular o merge/rebase atual. |
|
||||
| `` R `` | Atualizar | Atualize o estado do git (ou seja, execute `git status`, `git branch`, etc em segundo plano para atualizar o conteúdo de painéis). Isso não executa `git fetch`. |
|
||||
| `` + `` | Next screen mode (normal/half/fullscreen) | |
|
||||
| `` _ `` | Prev screen mode | |
|
||||
| `` ? `` | Open keybindings menu | |
|
||||
| `` <c-s> `` | View filter options | View options for filtering the commit log, so that only commits matching the filter are shown. |
|
||||
| `` W `` | View diffing options | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
|
||||
| `` <c-e> `` | View diffing options | View options relating to diffing two refs e.g. diffing against selected ref, entering ref to diff against, and reversing the diff direction. |
|
||||
| `` q `` | Sair | |
|
||||
| `` <esc> `` | Cancel | |
|
||||
| `` <c-w> `` | Toggle whitespace | Toggle whether or not whitespace changes are shown in the diff view. |
|
||||
| `` z `` | Desfazer | O reflog será usado para determinar qual comando git para executar para desfazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração. |
|
||||
| `` <c-z> `` | Refazer | O reflog será usado para determinar qual comando git para executar para refazer o último comando git. Isto não inclui mudanças na árvore de trabalho; apenas compromissos são tidos em consideração. |
|
||||
|
||||
## List panel navigation
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` , `` | Previous page | |
|
||||
| `` . `` | Next page | |
|
||||
| `` < `` | Scroll to top | |
|
||||
| `` > `` | Scroll to bottom | |
|
||||
| `` v `` | Toggle range select | |
|
||||
| `` <s-down> `` | Range select down | |
|
||||
| `` <s-up> `` | Range select up | |
|
||||
| `` / `` | Search the current view by text | |
|
||||
| `` H `` | Scroll left | |
|
||||
| `` L `` | Scroll right | |
|
||||
| `` ] `` | Next tab | |
|
||||
| `` [ `` | Previous tab | |
|
||||
|
||||
## Arquivos
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | Copy path to clipboard | |
|
||||
| `` <space> `` | Etapa | Alternar para staging para o arquivo selecionado. |
|
||||
| `` <c-b> `` | Filtrar arquivos por status | |
|
||||
| `` y `` | Copy to clipboard | |
|
||||
| `` c `` | Commit | Submeter mudanças em staging |
|
||||
| `` w `` | Commit changes without pre-commit hook | |
|
||||
| `` A `` | Alterar último commit | |
|
||||
| `` C `` | Enviar alteração usando um editor Git | |
|
||||
| `` <c-f> `` | Encontrar commit da base para consertar | Encontre o commit em que as suas mudanças atuais estão se baseando, para alterar/consertar o commit. Isso poupa-te você de ter que olhar pelos commits da sua branch um por um para ver qual commit deve ser alterado/consertado
|
||||
Veja a documentação:
|
||||
<https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
|
||||
| `` e `` | Editar | Abrir arquivo no editor externo. |
|
||||
| `` o `` | Abrir arquivo | Abrir arquivo no aplicativo padrão. |
|
||||
| `` i `` | Ignore or exclude file | |
|
||||
| `` r `` | Atualizar arquivos | |
|
||||
| `` s `` | Stash | Stash all changes. For other variations of stashing, use the view stash options keybinding. |
|
||||
| `` S `` | View stash options | View stash options (e.g. stash all, stash staged, stash unstaged). |
|
||||
| `` a `` | Stage completo | Alternar para todos os arquivos na árvore de trabalho |
|
||||
| `` <enter> `` | Stage lines / Colapso diretório | Se o item selecionado for um arquivo, o foco na exibição de preparo para o estágio de cenas/linhas individuais. Se o item selecionado for um diretório, recolher/expandi-lo. |
|
||||
| `` d `` | Discard | View options for discarding changes to the selected file. |
|
||||
| `` g `` | View upstream reset options | |
|
||||
| `` D `` | Reset | View reset options for working tree (e.g. nuking the working tree). |
|
||||
| `` ` `` | Alternar exibição de árvore de arquivo | Alternar a visualização de arquivo entre layout plano e layout de árvore. Layout plano mostra todos os caminhos de arquivo em uma única lista, layout de árvore agrupa arquivos por diretório. |
|
||||
| `` <c-t> `` | Abrir ferramenta de diff externa (git difftool) | |
|
||||
| `` M `` | Abrir ferramenta de merge externa | Execute `git mergetool`. |
|
||||
| `` f `` | Buscar | Buscar alterações do controle remoto. |
|
||||
| `` - `` | 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 | |
|
||||
|
||||
## Branches locais
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | Copy branch name to clipboard | |
|
||||
| `` i `` | Show git-flow options | |
|
||||
| `` <space> `` | Verificar | Checar item selecionado |
|
||||
| `` n `` | Nova branch | |
|
||||
| `` o `` | Create pull request | |
|
||||
| `` O `` | View create pull request options | |
|
||||
| `` <c-y> `` | Copiar URL do pull request para área de transferência | |
|
||||
| `` c `` | Checar por nome | Checar por nome. Na caixa de entrada você pode inserir '-' para trocar para a última branch |
|
||||
| `` F `` | Forçar checagem | Forçar checagem da branch selecionada. Isso irá descartar todas as mudanças no seu diretório de trabalho antes cheque a branch selecionada |
|
||||
| `` d `` | Delete | View delete options for local/remote branch. |
|
||||
| `` r `` | Refazer | Refazer a branch checada na branch selecionada |
|
||||
| `` M `` | Mesclar | Ver opções para mesclar o item selecionado no branch atual (mesclar regularmente, mesclar squash) |
|
||||
| `` f `` | Avanço rápido | Encaminhamento rápido de branch selecionada a partir do upstream. |
|
||||
| `` T `` | New tag | |
|
||||
| `` s `` | Sort order | |
|
||||
| `` g `` | Reset | |
|
||||
| `` R `` | Rename branch | |
|
||||
| `` 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> `` | Abrir ferramenta de diff externa (git difftool) | |
|
||||
| `` <enter> `` | View commits | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Branches remotos
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | Copy branch name to clipboard | |
|
||||
| `` <space> `` | Verificar | Checar a nova branch baseada na brach remota selecionada, ou a branch remota como HEAD, desanexado |
|
||||
| `` n `` | Nova branch | |
|
||||
| `` M `` | Mesclar | Ver opções para mesclar o item selecionado no branch atual (mesclar regularmente, mesclar squash) |
|
||||
| `` r `` | Refazer | Refazer a branch checada na branch selecionada |
|
||||
| `` d `` | Delete | Delete the remote branch from the remote. |
|
||||
| `` u `` | Set as upstream | Set the selected remote branch as the upstream of the checked-out branch. |
|
||||
| `` s `` | Sort order | |
|
||||
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` <c-t> `` | Abrir ferramenta de diff externa (git difftool) | |
|
||||
| `` <enter> `` | View commits | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Commit files
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | Copy path to clipboard | |
|
||||
| `` y `` | Copy to clipboard | |
|
||||
| `` c `` | Verificar | Checkout file. This replaces the file in your working tree with the version from the selected commit. |
|
||||
| `` d `` | Remove | Discard this commit's changes to this file. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes this file. |
|
||||
| `` o `` | Abrir arquivo | Abrir arquivo no aplicativo padrão. |
|
||||
| `` e `` | Editar | Abrir arquivo no editor externo. |
|
||||
| `` <c-t> `` | Abrir ferramenta de diff externa (git difftool) | |
|
||||
| `` <space> `` | Toggle file included in patch | Toggle whether the file is included in the custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
|
||||
| `` a `` | Toggle all files | Add/remove all commit's files to custom patch. See https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches. |
|
||||
| `` <enter> `` | Enter file / Toggle directory collapsed | If a file is selected, enter the file so that you can add/remove individual lines to the custom patch. If a directory is selected, toggle the directory. |
|
||||
| `` ` `` | Alternar exibição de árvore de arquivo | Alternar a visualização de arquivo entre layout plano e layout de árvore. Layout plano mostra todos os caminhos de arquivo em uma única lista, layout de árvore agrupa arquivos por diretório. |
|
||||
| `` - `` | 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 | |
|
||||
|
||||
## Commits
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | Copy commit hash to clipboard | |
|
||||
| `` <c-r> `` | Reset copied (cherry-picked) commits selection | |
|
||||
| `` b `` | View bisect options | |
|
||||
| `` s `` | Squash | Squash o commit selecionado no commit abaixo dele. A mensagem do commit selecionado será anexada ao commit abaixo dele. |
|
||||
| `` f `` | Fixup | Faça o commit selecionado no commit abaixo dele. Semelhante para o squash, mas a mensagem do commit selecionado será descartada. |
|
||||
| `` r `` | Reword | Repetir a mensagem de submissão selecionada. |
|
||||
| `` R `` | Republicar com o editor | |
|
||||
| `` d `` | Descartar | Solte o commit selecionado. Isso irá remover o commit do branch através de uma rebase. Se o commit faz com que as alterações em commits posteriores dependem, você pode precisar resolver conflitos de merge. |
|
||||
| `` e `` | Editar (iniciar rebase interativa) | Editar o commit selecionado. Use isto para iniciar uma rebase interativa a partir do commit selecionado. Quando já estiver no meio da reconstrução, isto irá marcar o commit selecionado para edição, o que significa que ao continuar com a reformulação. a rebase irá pausar no commit selecionado para permitir que você faça alterações. |
|
||||
| `` 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 `` | Escolher | Marque o commit selecionado para ser escolhido (quando meados da base). Isso significa que o commit será mantido ao continuar o rebase. |
|
||||
| `` F `` | Create fixup commit | Create 'fixup!' commit for the selected commit. Later on, you can press `S` on this same commit to apply all above fixup commits. |
|
||||
| `` S `` | Apply fixup commits | Squash all 'fixup!' commits, either above the selected commit, or all in current branch (autosquash). |
|
||||
| `` <c-j> `` | Mover commit um para baixo | |
|
||||
| `` <c-k> `` | Mover o commit um para cima | |
|
||||
| `` V `` | Colar (cherry-pick) | |
|
||||
| `` B `` | Mark as base commit for rebase | Select a base commit for the next rebase. When you rebase onto a branch, only commits above the base commit will be brought across. This uses the `git rebase --onto` command. |
|
||||
| `` A `` | Modificar | Alterar o commit com mudanças em sted. Se o commit selecionado for o commit HEAD, ele executará o `git commit --amend`. Caso contrário, o compromisso será alterado por meio de uma base de apoio. |
|
||||
| `` a `` | Alterar atributo de commit | Definir/Redefinir autor de submissão ou co-autor definido. |
|
||||
| `` t `` | Reverter | Crie um commit reverter para o commit selecionado, que aplica as alterações do commit selecionado em reverso. |
|
||||
| `` T `` | Tag commit | Create a new tag pointing at the selected commit. You'll be prompted to enter a tag name and optional description. |
|
||||
| `` <c-l> `` | View log options | View options for commit log e.g. changing sort order, hiding the git graph, showing the whole git graph. |
|
||||
| `` <space> `` | Verificar | 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). |
|
||||
| `` o `` | Open commit in browser | |
|
||||
| `` n `` | Create new branch off of commit | |
|
||||
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. |
|
||||
| `` <c-t> `` | Abrir ferramenta de diff externa (git difftool) | |
|
||||
| `` <enter> `` | View files | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Search the current view by text | |
|
||||
|
||||
## Confirmation panel
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <enter> `` | Confirmar | |
|
||||
| `` <esc> `` | Fechar/Cancelar | |
|
||||
|
||||
## Etiquetas
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | Copy tag to clipboard | |
|
||||
| `` <space> `` | Verificar | Checar a tag selecionada como um HEAD, desanexado |
|
||||
| `` 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. |
|
||||
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` <c-t> `` | Abrir ferramenta de diff externa (git difftool) | |
|
||||
| `` <enter> `` | View commits | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Menu
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <enter> `` | Executar | |
|
||||
| `` <esc> `` | Fechar | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Painel Principal (Normal)
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` mouse wheel down (fn+up) `` | Scroll down | |
|
||||
| `` mouse wheel up (fn+down) `` | Scroll up | |
|
||||
|
||||
## Painel Principal (preparação)
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <left> `` | Go to previous hunk | |
|
||||
| `` <right> `` | Go to next hunk | |
|
||||
| `` v `` | Toggle range select | |
|
||||
| `` a `` | Selecione o local | Ativa/desativa modo seleção de hunk |
|
||||
| `` <c-o> `` | Copy selected text to clipboard | |
|
||||
| `` <space> `` | Etapa | Ativar/desativar seleção em staged/unstaged |
|
||||
| `` d `` | Descartar | Quando a mudança não desejada for selecionada, descarte a mudança usando `git reset`. Quando a mudança em fase é selecionada, despare a mudança. |
|
||||
| `` o `` | Abrir arquivo | Abrir arquivo no aplicativo padrão. |
|
||||
| `` e `` | Editar arquivo | Abrir arquivo no editor externo. |
|
||||
| `` <esc> `` | Retornar ao painel de arquivos | |
|
||||
| `` <tab> `` | Mudar de visão | Alternar para outra visão (staged/não processadas alterações). |
|
||||
| `` E `` | Editar hunk | Editar o local selecionado no editor externo. |
|
||||
| `` c `` | Commit | Submeter mudanças em staging |
|
||||
| `` w `` | Commit changes without pre-commit hook | |
|
||||
| `` C `` | Enviar alteração usando um editor Git | |
|
||||
| `` <c-f> `` | Encontrar commit da base para consertar | Encontre o commit em que as suas mudanças atuais estão se baseando, para alterar/consertar o commit. Isso poupa-te você de ter que olhar pelos commits da sua branch um por um para ver qual commit deve ser alterado/consertado
|
||||
Veja a documentação:
|
||||
<https://github.com/jesseduffield/lazygit/tree/master/docs/Fixup_Commits.md> |
|
||||
| `` / `` | Search the current view by text | |
|
||||
|
||||
## Painel principal (mesclagem)
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | Escolha o local | |
|
||||
| `` b `` | Pegar todos os pedaços | |
|
||||
| `` <up> `` | Previous hunk | |
|
||||
| `` <down> `` | Next hunk | |
|
||||
| `` <left> `` | Previous conflict | |
|
||||
| `` <right> `` | Next conflict | |
|
||||
| `` z `` | Desfazer | Desfazer resolução de conflitos de última mesclagem. |
|
||||
| `` e `` | Editar arquivo | Abrir arquivo no editor externo. |
|
||||
| `` o `` | Abrir arquivo | Abrir arquivo no aplicativo padrão. |
|
||||
| `` M `` | Abrir ferramenta de merge externa | Execute `git mergetool`. |
|
||||
| `` <esc> `` | Retornar ao painel de arquivos | |
|
||||
|
||||
## Painel principal (patch build)
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <left> `` | Go to previous hunk | |
|
||||
| `` <right> `` | Go to next hunk | |
|
||||
| `` v `` | Toggle range select | |
|
||||
| `` a `` | Selecione o local | Ativa/desativa modo seleção de hunk |
|
||||
| `` <c-o> `` | Copy selected text to clipboard | |
|
||||
| `` o `` | Abrir arquivo | Abrir arquivo no aplicativo padrão. |
|
||||
| `` e `` | Editar arquivo | Abrir arquivo no editor externo. |
|
||||
| `` <space> `` | Alternar linhas no caminho | |
|
||||
| `` <esc> `` | Exit custom patch builder | |
|
||||
| `` / `` | Search the current view by text | |
|
||||
|
||||
## Reflog
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | Copy commit hash to clipboard | |
|
||||
| `` <space> `` | Verificar | 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). |
|
||||
| `` o `` | Open commit in browser | |
|
||||
| `` n `` | Create new branch off of commit | |
|
||||
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. |
|
||||
| `` <c-r> `` | Reset copied (cherry-picked) commits selection | |
|
||||
| `` <c-t> `` | Abrir ferramenta de diff externa (git difftool) | |
|
||||
| `` <enter> `` | View commits | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Remotes
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <enter> `` | View branches | |
|
||||
| `` n `` | New remote | |
|
||||
| `` d `` | Remove | Remove the selected remote. Any local branches tracking a remote branch from the remote will be unaffected. |
|
||||
| `` e `` | Editar | Edit the selected remote's name or URL. |
|
||||
| `` f `` | Buscar | Fetch updates from the remote repository. This retrieves new commits and branches without merging them into your local branches. |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Stash
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <space> `` | Aplicar | Aplique o stash no seu diretório de trabalho. |
|
||||
| `` g `` | Pop | Aplique a entrada de stash no seu diretório de trabalho e remova a entrada de stash. |
|
||||
| `` d `` | Descartar | Remova a entrada do stash da lista de armazenamento. |
|
||||
| `` n `` | Nova branch | Criar um novo ramo a partir da entrada de lixo selecionada. Isso funciona verificando o commit do qual a entrada de lixo foi criada, criar um novo branch a partir desse commit e, em seguida, aplicar a entrada de lixo ao novo branch como um commit adicional. |
|
||||
| `` r `` | Renomear o stasj | |
|
||||
| `` <enter> `` | View files | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Status
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` o `` | Abrir o ficheiro de config | Abrir arquivo no aplicativo padrão. |
|
||||
| `` e `` | Editar arquivo de configuração | Abrir arquivo no editor externo. |
|
||||
| `` u `` | Verificar atualização | |
|
||||
| `` <enter> `` | Mudar para um repositório recente | |
|
||||
| `` a `` | Mostrar todos os logs da branch | |
|
||||
|
||||
## Sub-commits
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | Copy commit hash to clipboard | |
|
||||
| `` <space> `` | Verificar | 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). |
|
||||
| `` o `` | Open commit in browser | |
|
||||
| `` n `` | Create new branch off of commit | |
|
||||
| `` g `` | Reset | View reset options (soft/mixed/hard) for resetting onto selected item. |
|
||||
| `` C `` | Copiar (cherry-pick) | Marcar commit como copiado. Então, dentro da visualização local de commits, você pode pressionar `V` para colar (cherry-pick) o(s) commit(s) copiado(s) em seu branch de check-out. A qualquer momento você pode pressionar `<esc>` para cancelar a seleção. |
|
||||
| `` <c-r> `` | Reset copied (cherry-picked) commits selection | |
|
||||
| `` <c-t> `` | Abrir ferramenta de diff externa (git difftool) | |
|
||||
| `` <enter> `` | View files | |
|
||||
| `` w `` | View worktree options | |
|
||||
| `` / `` | Search the current view by text | |
|
||||
|
||||
## Submodules
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | Copy submodule name to clipboard | |
|
||||
| `` <enter> `` | Enter | Enter submodule. After entering the submodule, you can press `<esc>` to escape back to the parent repo. |
|
||||
| `` d `` | Remove | Remove the selected submodule and its corresponding directory. |
|
||||
| `` u `` | Update | Update selected submodule. |
|
||||
| `` n `` | New submodule | |
|
||||
| `` e `` | Update submodule URL | |
|
||||
| `` i `` | Initialize | Initialize the selected submodule to prepare for fetching. You probably want to follow this up by invoking the 'update' action to fetch the submodule. |
|
||||
| `` b `` | View bulk submodule options | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Sumário do commit
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <enter> `` | Confirmar | |
|
||||
| `` <esc> `` | Fechar | |
|
||||
|
||||
## Worktrees
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` n `` | New worktree | |
|
||||
| `` <space> `` | Switch | Switch to the selected worktree. |
|
||||
| `` o `` | Abrir no 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 | |
|
||||
@@ -261,6 +261,7 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | Скопировать название файла в буфер обмена | |
|
||||
| `` y `` | Copy to clipboard | |
|
||||
| `` c `` | Переключить | Переключить файл |
|
||||
| `` d `` | Remove | Отменить изменения коммита в этом файле |
|
||||
| `` o `` | Открыть файл | Open file in default application. |
|
||||
@@ -270,6 +271,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 +289,8 @@ 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. |
|
||||
| `` <c-o> `` | Copy tag to clipboard | |
|
||||
| `` <space> `` | Переключить | Checkout the selected tag as a detached HEAD. |
|
||||
| `` n `` | Создать тег | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
|
||||
| `` d `` | Delete | View delete options for local/remote tag. |
|
||||
| `` P `` | Отправить тег | Push the selected tag to a remote. You'll be prompted to select a remote. |
|
||||
@@ -353,6 +357,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,119 @@ _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 |
|
||||
|-----|--------|-------------|
|
||||
| `` <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. |
|
||||
| `` / `` | 开始搜索 | |
|
||||
|
||||
## 提交讯息
|
||||
## 提交信息
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <enter> `` | 确认 | |
|
||||
| `` <esc> `` | 关闭 | |
|
||||
|
||||
## 提交文件
|
||||
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 将文件名复制到剪贴板 | |
|
||||
| `` y `` | 复制到剪贴板 | |
|
||||
| `` c `` | 检出 | 检出文件 |
|
||||
| `` d `` | 删除 | 放弃对此文件的提交变更 |
|
||||
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
|
||||
| `` e `` | 编辑 | 使用外部编辑器打开文件 |
|
||||
| `` <c-t> `` | 使用外部差异比较工具(git difftool) | |
|
||||
| `` <space> `` | 补丁中包含的切换文件 | 切换文件是否包含在自定义补丁中。请参阅 https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches。 |
|
||||
| `` a `` | 操作所有文件 | 添加或删除所有提交中的文件到自定义的补丁中。请参阅 https://github.com/jesseduffield/lazygit#rebase-magic-custom-patches。 |
|
||||
| `` <enter> `` | 输入文件以将所选行添加到补丁中(或切换目录折叠) | 如果已选择一个文件,则Enter进入该文件,以便您可以向自定义补丁添加/删除单独的行。如果选择了目录,则切换目录。 |
|
||||
| `` ` `` | 切换文件树视图 | 在平铺部署与树布局之间切换文件视图。平铺布局在一个列表中展示所有文件路径,树布局则根据目录分组展示。 |
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` / `` | 开始搜索 | |
|
||||
|
||||
## 文件
|
||||
|
||||
| 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 +239,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 +251,16 @@ 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) | |
|
||||
| `` <c-o> `` | Copy tag to clipboard | |
|
||||
| `` <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 +272,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 +285,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 +311,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 +330,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 `` | 檢視工作目錄選項 | |
|
||||
| `` / `` | 搜尋 | |
|
||||
@@ -210,15 +210,18 @@ If you would instead like to start an interactive rebase from the selected commi
|
||||
| Key | Action | Info |
|
||||
|-----|--------|-------------|
|
||||
| `` <c-o> `` | 複製檔案名稱到剪貼簿 | |
|
||||
| `` y `` | 複製到剪貼簿 | |
|
||||
| `` 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 +249,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 +260,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 +285,13 @@ 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. |
|
||||
| `` <c-o> `` | Copy tag to clipboard | |
|
||||
| `` <space> `` | 檢出 | Checkout the selected tag as a detached HEAD. |
|
||||
| `` n `` | 建立標籤 | Create new tag from current commit. You'll be prompted to enter a tag name and optional description. |
|
||||
| `` d `` | Delete | View delete options for local/remote tag. |
|
||||
| `` 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 +303,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 +352,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 +365,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 `` | 檢視工作目錄選項 | |
|
||||
| `` / `` | 搜尋 | |
|
||||
|
||||
24
go.mod
24
go.mod
@@ -8,16 +8,15 @@ 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.1
|
||||
github.com/go-errors/errors v1.5.1
|
||||
github.com/gookit/color v1.4.2
|
||||
github.com/iancoleman/orderedmap v0.3.0
|
||||
github.com/imdario/mergo v0.3.11
|
||||
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.20250220081214-b376cb0857ac
|
||||
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
|
||||
@@ -30,7 +29,7 @@ require (
|
||||
github.com/sahilm/fuzzy v0.1.0
|
||||
github.com/samber/lo v1.31.0
|
||||
github.com/sanity-io/litter v1.5.2
|
||||
github.com/sasha-s/go-deadlock v0.3.1
|
||||
github.com/sasha-s/go-deadlock v0.3.5
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/spf13/afero v1.9.5
|
||||
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad
|
||||
@@ -38,7 +37,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.11.0
|
||||
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
@@ -54,7 +53,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
|
||||
@@ -67,16 +65,16 @@ require (
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/onsi/ginkgo v1.10.3 // indirect
|
||||
github.com/onsi/gomega v1.7.1 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
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.30.0 // indirect
|
||||
golang.org/x/term v0.29.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
)
|
||||
|
||||
81
go.sum
81
go.sum
@@ -85,11 +85,11 @@ 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/go.mod h1:bj8ori1BG3OYMjmb3IklZVWfZUJ1UBQt9JXrOCOhGWw=
|
||||
github.com/gdamore/tcell/v2 v2.8.1 h1:KPNxyqclpWpWQlPLx6Xui1pMk8S+7+R37h3g07997NU=
|
||||
github.com/gdamore/tcell/v2 v2.8.1/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 +145,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=
|
||||
@@ -171,8 +171,6 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/iancoleman/orderedmap v0.3.0 h1:5cbR2grmZR/DiVt+VJopEhtVs9YGInGIxAoMJn+Ichc=
|
||||
github.com/iancoleman/orderedmap v0.3.0/go.mod h1:XuLcCUkdL5owUCQeF2Ue9uuw1EptkJDkXXS7VoV7XGE=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/imdario/mergo v0.3.9/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
|
||||
@@ -188,10 +186,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.20250220081214-b376cb0857ac h1:vUNTiVEB9Bz16pTJ5kNgb/1HhnWdSA1P0GfFLUJeITI=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20250220081214-b376cb0857ac/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 +233,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=
|
||||
@@ -251,8 +248,8 @@ github.com/onsi/ginkgo v1.10.3 h1:OoxbjfXVZyod1fmWYhI7SEyaD8B00ynP3T+D5GiyHOY=
|
||||
github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.7.1 h1:K0jcRCwNQM3vFGh1ppMtDh/+7ApJrjldlX8fA0jDTLQ=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
|
||||
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
|
||||
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw=
|
||||
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -272,8 +269,8 @@ github.com/samber/lo v1.31.0 h1:Sfa+/064Tdo4SvlohQUQzBhgSer9v/coGvKQI/XLWAM=
|
||||
github.com/samber/lo v1.31.0/go.mod h1:HLeWcJRRyLKp3+/XBJvOrerCQn9mhdKMHyd7IRlgeQ8=
|
||||
github.com/sanity-io/litter v1.5.2 h1:AnC8s9BMORWH5a4atZ4D6FPVvKGzHcnc5/IVTa87myw=
|
||||
github.com/sanity-io/litter v1.5.2/go.mod h1:5Z71SvaYy5kcGtyglXOC9rrUi3c1E8CamFWjQsazTh0=
|
||||
github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0=
|
||||
github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM=
|
||||
github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU=
|
||||
github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U=
|
||||
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
|
||||
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
@@ -327,8 +324,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 +367,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 +405,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 +432,12 @@ 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/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.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,24 @@ 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/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.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/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
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 +513,13 @@ 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/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
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 +572,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=
|
||||
|
||||
@@ -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())
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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,27 @@ 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)
|
||||
})...).
|
||||
Arg("--").
|
||||
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"
|
||||
@@ -31,13 +32,17 @@ func NewFileLoader(gitCommon *GitCommon, cmd oscommands.ICmdObjBuilder, config F
|
||||
|
||||
type GetStatusFileOptions struct {
|
||||
NoRenames bool
|
||||
// If true, we'll show untracked files even if the user has set the config to hide them.
|
||||
// This is useful for users with bare repos for dotfiles who default to hiding untracked files,
|
||||
// but want to occasionally see them to `git add` a new file.
|
||||
ForceShowUntracked bool
|
||||
}
|
||||
|
||||
func (self *FileLoader) GetStatusFiles(opts GetStatusFileOptions) []*models.File {
|
||||
// check if config wants us ignoring untracked files
|
||||
untrackedFilesSetting := self.config.GetShowUntrackedFiles()
|
||||
|
||||
if untrackedFilesSetting == "" {
|
||||
if opts.ForceShowUntracked || untrackedFilesSetting == "" {
|
||||
untrackedFilesSetting = "all"
|
||||
}
|
||||
untrackedFilesArg := fmt.Sprintf("--untracked-files=%s", untrackedFilesSetting)
|
||||
@@ -48,6 +53,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 +73,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 +105,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 +157,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 {
|
||||
|
||||
@@ -149,7 +149,7 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) {
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"rebase", "--interactive", "--autostash", "--keep-empty", "--no-autosquash", "--rebase-merges", "abcdef"}, "", nil).
|
||||
ExpectGitArgs([]string{"cat-file", "-e", "HEAD^:test999.txt"}, "", nil).
|
||||
ExpectGitArgs([]string{"checkout", "HEAD^", "--", "test999.txt"}, "", nil).
|
||||
ExpectGitArgs([]string{"-c", disableHooksFlag, "checkout", "HEAD^", "--", "test999.txt"}, "", nil).
|
||||
ExpectGitArgs([]string{"commit", "--amend", "--no-edit", "--allow-empty"}, "", nil).
|
||||
ExpectGitArgs([]string{"rebase", "--continue"}, "", nil),
|
||||
test: func(err error) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -81,9 +81,11 @@ func (self *StashCommands) Hash(index int) (string, error) {
|
||||
}
|
||||
|
||||
func (self *StashCommands) ShowStashEntryCmdObj(index int) oscommands.ICmdObj {
|
||||
// "-u" is the same as "--include-untracked", but the latter fails in older git versions for some reason
|
||||
cmdArgs := NewGitCmd("stash").Arg("show").
|
||||
Arg("-p").
|
||||
Arg("--stat").
|
||||
Arg("-u").
|
||||
Arg(fmt.Sprintf("--color=%s", self.UserConfig().Git.Paging.ColorArg)).
|
||||
Arg(fmt.Sprintf("--unified=%d", self.AppState.DiffContextSize)).
|
||||
ArgIf(self.AppState.IgnoreWhitespaceInDiffView, "--ignore-all-space").
|
||||
|
||||
@@ -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
|
||||
@@ -113,7 +113,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
|
||||
contextSize: 3,
|
||||
similarityThreshold: 50,
|
||||
ignoreWhitespace: false,
|
||||
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "--color=always", "--unified=3", "--find-renames=50%", "stash@{5}"},
|
||||
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=3", "--find-renames=50%", "stash@{5}"},
|
||||
},
|
||||
{
|
||||
testName: "Show diff with custom context size",
|
||||
@@ -121,7 +121,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
|
||||
contextSize: 77,
|
||||
similarityThreshold: 50,
|
||||
ignoreWhitespace: false,
|
||||
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "--color=always", "--unified=77", "--find-renames=50%", "stash@{5}"},
|
||||
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=77", "--find-renames=50%", "stash@{5}"},
|
||||
},
|
||||
{
|
||||
testName: "Show diff with custom similarity threshold",
|
||||
@@ -129,7 +129,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
|
||||
contextSize: 3,
|
||||
similarityThreshold: 33,
|
||||
ignoreWhitespace: false,
|
||||
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "--color=always", "--unified=3", "--find-renames=33%", "stash@{5}"},
|
||||
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=3", "--find-renames=33%", "stash@{5}"},
|
||||
},
|
||||
{
|
||||
testName: "Default case",
|
||||
@@ -137,7 +137,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
|
||||
contextSize: 3,
|
||||
similarityThreshold: 50,
|
||||
ignoreWhitespace: true,
|
||||
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "--color=always", "--unified=3", "--ignore-all-space", "--find-renames=50%", "stash@{5}"},
|
||||
expected: []string{"git", "-C", "/path/to/worktree", "stash", "show", "-p", "--stat", "-u", "--color=always", "--unified=3", "--ignore-all-space", "--find-renames=50%", "stash@{5}"},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
@@ -20,6 +22,7 @@ func NewSyncCommands(gitCommon *GitCommon) *SyncCommands {
|
||||
type PushOpts struct {
|
||||
Force bool
|
||||
ForceWithLease bool
|
||||
CurrentBranch string
|
||||
UpstreamRemote string
|
||||
UpstreamBranch string
|
||||
SetUpstream bool
|
||||
@@ -35,7 +38,7 @@ func (self *SyncCommands) PushCmdObj(task gocui.Task, opts PushOpts) (oscommands
|
||||
ArgIf(opts.ForceWithLease, "--force-with-lease").
|
||||
ArgIf(opts.SetUpstream, "--set-upstream").
|
||||
ArgIf(opts.UpstreamRemote != "", opts.UpstreamRemote).
|
||||
ArgIf(opts.UpstreamBranch != "", "HEAD:"+opts.UpstreamBranch).
|
||||
ArgIf(opts.UpstreamBranch != "", fmt.Sprintf("refs/heads/%s:%s", opts.CurrentBranch, opts.UpstreamBranch)).
|
||||
ToArgv()
|
||||
|
||||
cmdObj := self.cmd.New(cmdArgs).PromptOnCredentialRequest(task)
|
||||
@@ -88,6 +91,7 @@ type PullOptions struct {
|
||||
BranchName string
|
||||
FastForwardOnly bool
|
||||
WorktreeGitDir string
|
||||
WorktreePath string
|
||||
}
|
||||
|
||||
func (self *SyncCommands) Pull(task gocui.Task, opts PullOptions) error {
|
||||
@@ -97,6 +101,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
|
||||
|
||||
@@ -44,11 +44,12 @@ func TestSyncPush(t *testing.T) {
|
||||
testName: "Push with force disabled, upstream supplied",
|
||||
opts: PushOpts{
|
||||
ForceWithLease: false,
|
||||
CurrentBranch: "master",
|
||||
UpstreamRemote: "origin",
|
||||
UpstreamBranch: "master",
|
||||
},
|
||||
test: func(cmdObj oscommands.ICmdObj, err error) {
|
||||
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "origin", "HEAD:master"})
|
||||
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "origin", "refs/heads/master:master"})
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
@@ -56,12 +57,13 @@ func TestSyncPush(t *testing.T) {
|
||||
testName: "Push with force disabled, setting upstream",
|
||||
opts: PushOpts{
|
||||
ForceWithLease: false,
|
||||
CurrentBranch: "master-local",
|
||||
UpstreamRemote: "origin",
|
||||
UpstreamBranch: "master",
|
||||
SetUpstream: true,
|
||||
},
|
||||
test: func(cmdObj oscommands.ICmdObj, err error) {
|
||||
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--set-upstream", "origin", "HEAD:master"})
|
||||
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--set-upstream", "origin", "refs/heads/master-local:master"})
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
@@ -69,12 +71,13 @@ func TestSyncPush(t *testing.T) {
|
||||
testName: "Push with force-with-lease enabled, setting upstream",
|
||||
opts: PushOpts{
|
||||
ForceWithLease: true,
|
||||
CurrentBranch: "master",
|
||||
UpstreamRemote: "origin",
|
||||
UpstreamBranch: "master",
|
||||
SetUpstream: true,
|
||||
},
|
||||
test: func(cmdObj oscommands.ICmdObj, err error) {
|
||||
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease", "--set-upstream", "origin", "HEAD:master"})
|
||||
assert.Equal(t, cmdObj.Args(), []string{"git", "push", "--force-with-lease", "--set-upstream", "origin", "refs/heads/master:master"})
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
},
|
||||
|
||||
@@ -109,6 +109,10 @@ func (self *WorkingTreeCommands) BeforeAndAfterFileForRename(file *models.File)
|
||||
return beforeFile, afterFile, nil
|
||||
}
|
||||
|
||||
func newCheckoutCommand() *GitCommandBuilder {
|
||||
return NewGitCmd("checkout").Config(fmt.Sprintf("core.hooksPath=%s", os.DevNull))
|
||||
}
|
||||
|
||||
// DiscardAllFileChanges directly
|
||||
func (self *WorkingTreeCommands) DiscardAllFileChanges(file *models.File) error {
|
||||
if file.IsRename() {
|
||||
@@ -130,7 +134,7 @@ func (self *WorkingTreeCommands) DiscardAllFileChanges(file *models.File) error
|
||||
|
||||
if file.ShortStatus == "AA" {
|
||||
if err := self.cmd.New(
|
||||
NewGitCmd("checkout").Arg("--ours", "--", file.Name).ToArgv(),
|
||||
newCheckoutCommand().Arg("--ours", "--", file.Name).ToArgv(),
|
||||
).Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -189,7 +193,7 @@ func (self *WorkingTreeCommands) DiscardUnstagedDirChanges(node IFileNode) error
|
||||
return err
|
||||
}
|
||||
|
||||
cmdArgs := NewGitCmd("checkout").Arg("--", node.GetPath()).ToArgv()
|
||||
cmdArgs := newCheckoutCommand().Arg("--", node.GetPath()).ToArgv()
|
||||
if err := self.cmd.New(cmdArgs).Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -222,7 +226,7 @@ func (self *WorkingTreeCommands) RemoveUntrackedDirFiles(node IFileNode) error {
|
||||
}
|
||||
|
||||
func (self *WorkingTreeCommands) DiscardUnstagedFileChanges(file *models.File) error {
|
||||
cmdArgs := NewGitCmd("checkout").Arg("--", file.Name).ToArgv()
|
||||
cmdArgs := newCheckoutCommand().Arg("--", file.Name).ToArgv()
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
}
|
||||
|
||||
@@ -315,7 +319,7 @@ func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reve
|
||||
|
||||
// CheckoutFile checks out the file for the given commit
|
||||
func (self *WorkingTreeCommands) CheckoutFile(commitHash, fileName string) error {
|
||||
cmdArgs := NewGitCmd("checkout").Arg(commitHash, "--", fileName).
|
||||
cmdArgs := newCheckoutCommand().Arg(commitHash, "--", fileName).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
@@ -323,7 +327,7 @@ func (self *WorkingTreeCommands) CheckoutFile(commitHash, fileName string) error
|
||||
|
||||
// DiscardAnyUnstagedFileChanges discards any unstaged file changes via `git checkout -- .`
|
||||
func (self *WorkingTreeCommands) DiscardAnyUnstagedFileChanges() error {
|
||||
cmdArgs := NewGitCmd("checkout").Arg("--", ".").
|
||||
cmdArgs := newCheckoutCommand().Arg("--", ".").
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
|
||||
@@ -2,6 +2,7 @@ package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/go-errors/errors"
|
||||
@@ -11,6 +12,8 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var disableHooksFlag = fmt.Sprintf("core.hooksPath=%s", os.DevNull)
|
||||
|
||||
func TestWorkingTreeStageFile(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"add", "--", "test.txt"}, "", nil)
|
||||
@@ -100,7 +103,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",
|
||||
@@ -114,7 +117,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
|
||||
},
|
||||
removeFile: func(string) error { return nil },
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"checkout", "--", "test"}, "", errors.New("error")),
|
||||
ExpectGitArgs([]string{"-c", disableHooksFlag, "checkout", "--", "test"}, "", errors.New("error")),
|
||||
expectedError: "error",
|
||||
},
|
||||
{
|
||||
@@ -126,7 +129,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
|
||||
},
|
||||
removeFile: func(string) error { return nil },
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"checkout", "--", "test"}, "", nil),
|
||||
ExpectGitArgs([]string{"-c", disableHooksFlag, "checkout", "--", "test"}, "", nil),
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
@@ -139,7 +142,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
|
||||
removeFile: func(string) error { return nil },
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"reset", "--", "test"}, "", nil).
|
||||
ExpectGitArgs([]string{"checkout", "--", "test"}, "", nil),
|
||||
ExpectGitArgs([]string{"-c", disableHooksFlag, "checkout", "--", "test"}, "", nil),
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
@@ -152,7 +155,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
|
||||
removeFile: func(string) error { return nil },
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"reset", "--", "test"}, "", nil).
|
||||
ExpectGitArgs([]string{"checkout", "--", "test"}, "", nil),
|
||||
ExpectGitArgs([]string{"-c", disableHooksFlag, "checkout", "--", "test"}, "", nil),
|
||||
expectedError: "",
|
||||
},
|
||||
{
|
||||
@@ -210,7 +213,7 @@ func TestWorkingTreeDiff(t *testing.T) {
|
||||
plain bool
|
||||
cached bool
|
||||
ignoreWhitespace bool
|
||||
contextSize int
|
||||
contextSize uint64
|
||||
similarityThreshold int
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
}
|
||||
@@ -352,7 +355,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
|
||||
reverse bool
|
||||
plain bool
|
||||
ignoreWhitespace bool
|
||||
contextSize int
|
||||
contextSize uint64
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
}
|
||||
|
||||
@@ -429,7 +432,7 @@ func TestWorkingTreeCheckoutFile(t *testing.T) {
|
||||
commitHash: "11af912",
|
||||
fileName: "test999.txt",
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"checkout", "11af912", "--", "test999.txt"}, "", nil),
|
||||
ExpectGitArgs([]string{"-c", disableHooksFlag, "checkout", "11af912", "--", "test999.txt"}, "", nil),
|
||||
test: func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
@@ -439,7 +442,7 @@ func TestWorkingTreeCheckoutFile(t *testing.T) {
|
||||
commitHash: "11af912",
|
||||
fileName: "test999.txt",
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"checkout", "11af912", "--", "test999.txt"}, "", errors.New("error")),
|
||||
ExpectGitArgs([]string{"-c", disableHooksFlag, "checkout", "11af912", "--", "test999.txt"}, "", errors.New("error")),
|
||||
test: func(err error) {
|
||||
assert.Error(t, err)
|
||||
},
|
||||
@@ -469,7 +472,7 @@ func TestWorkingTreeDiscardUnstagedFileChanges(t *testing.T) {
|
||||
testName: "valid case",
|
||||
file: &models.File{Name: "test.txt"},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"checkout", "--", "test.txt"}, "", nil),
|
||||
ExpectGitArgs([]string{"-c", disableHooksFlag, "checkout", "--", "test.txt"}, "", nil),
|
||||
test: func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
@@ -496,7 +499,7 @@ func TestWorkingTreeDiscardAnyUnstagedFileChanges(t *testing.T) {
|
||||
{
|
||||
testName: "valid case",
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"checkout", "--", "."}, "", nil),
|
||||
ExpectGitArgs([]string{"-c", disableHooksFlag, "checkout", "--", "."}, "", nil),
|
||||
test: func(err error) {
|
||||
assert.NoError(t, err)
|
||||
},
|
||||
|
||||
@@ -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,43 @@ 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")
|
||||
changedContent, err := computeMigratedConfig(path, content)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
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)
|
||||
// Write config back if changed
|
||||
if string(changedContent) != string(content) {
|
||||
fmt.Println("Provided user config is deprecated but auto-fixable. Attempting to write fixed version back to file...")
|
||||
if err := os.WriteFile(path, changedContent, 0o644); err != nil {
|
||||
return nil, fmt.Errorf("While attempting to write back fixed user config to %s, an error occurred: %s", path, err)
|
||||
}
|
||||
fmt.Printf("Success. New config written to %s\n", path)
|
||||
return changedContent, nil
|
||||
}
|
||||
|
||||
return content, nil
|
||||
}
|
||||
|
||||
// A pure function helper for testing purposes
|
||||
func computeMigratedConfig(path string, content []byte) ([]byte, error) {
|
||||
changedContent := content
|
||||
|
||||
pathsToReplace := []struct {
|
||||
oldPath []string
|
||||
newName string
|
||||
}{
|
||||
{[]string{"gui", "skipUnstageLineWarning"}, "skipDiscardChangeWarning"},
|
||||
{[]string{"keybinding", "universal", "executeCustomCommand"}, "executeShellCommand"},
|
||||
{[]string{"gui", "windowSize"}, "screenMode"},
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -234,17 +261,18 @@ func migrateUserConfig(path string, content []byte) ([]byte, error) {
|
||||
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
|
||||
}
|
||||
|
||||
// Add more migrations here...
|
||||
|
||||
// Write config back if changed
|
||||
if string(changedContent) != string(content) {
|
||||
if err := os.WriteFile(path, changedContent, 0o644); err != nil {
|
||||
return nil, fmt.Errorf("Couldn't write migrated config back to `%s`: %s", path, err)
|
||||
}
|
||||
return changedContent, nil
|
||||
changedContent, err = changeElementToSequence(changedContent, []string{"git", "commitPrefix"})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
|
||||
}
|
||||
|
||||
return content, nil
|
||||
changedContent, err = changeCommitPrefixesMap(changedContent)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't migrate config file at `%s`: %s", path, err)
|
||||
}
|
||||
// Add more migrations here...
|
||||
|
||||
return changedContent, nil
|
||||
}
|
||||
|
||||
func changeNullKeybindingsToDisabled(changedContent []byte) ([]byte, error) {
|
||||
@@ -258,6 +286,46 @@ func changeNullKeybindingsToDisabled(changedContent []byte) ([]byte, error) {
|
||||
})
|
||||
}
|
||||
|
||||
func changeElementToSequence(changedContent []byte, path []string) ([]byte, error) {
|
||||
return yaml_utils.TransformNode(changedContent, path, func(node *yaml.Node) (bool, error) {
|
||||
if node.Kind == yaml.MappingNode {
|
||||
nodeContentCopy := node.Content
|
||||
node.Kind = yaml.SequenceNode
|
||||
node.Value = ""
|
||||
node.Tag = "!!seq"
|
||||
node.Content = []*yaml.Node{{
|
||||
Kind: yaml.MappingNode,
|
||||
Content: nodeContentCopy,
|
||||
}}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
}
|
||||
|
||||
func changeCommitPrefixesMap(changedContent []byte) ([]byte, error) {
|
||||
return yaml_utils.TransformNode(changedContent, []string{"git", "commitPrefixes"}, func(prefixesNode *yaml.Node) (bool, error) {
|
||||
changedAnyNodes := false
|
||||
if prefixesNode.Kind == yaml.MappingNode {
|
||||
for _, contentNode := range prefixesNode.Content {
|
||||
if contentNode.Kind == yaml.MappingNode {
|
||||
nodeContentCopy := contentNode.Content
|
||||
contentNode.Kind = yaml.SequenceNode
|
||||
contentNode.Value = ""
|
||||
contentNode.Tag = "!!seq"
|
||||
contentNode.Content = []*yaml.Node{{
|
||||
Kind: yaml.MappingNode,
|
||||
Content: nodeContentCopy,
|
||||
}}
|
||||
changedAnyNodes = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return changedAnyNodes, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (c *AppConfig) GetDebug() bool {
|
||||
return c.debug
|
||||
}
|
||||
@@ -457,7 +525,7 @@ type AppState struct {
|
||||
|
||||
HideCommandLog bool
|
||||
IgnoreWhitespaceInDiffView bool
|
||||
DiffContextSize int
|
||||
DiffContextSize uint64
|
||||
RenameSimilarityThreshold int
|
||||
LocalBranchSortOrder string
|
||||
RemoteBranchSortOrder string
|
||||
|
||||
89
pkg/config/app_config_test.go
Normal file
89
pkg/config/app_config_test.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestCommitPrefixMigrations(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "Empty String",
|
||||
input: "",
|
||||
expected: "",
|
||||
}, {
|
||||
name: "Single CommitPrefix Rename",
|
||||
input: `git:
|
||||
commitPrefix:
|
||||
pattern: "^\\w+-\\w+.*"
|
||||
replace: '[JIRA $0] '
|
||||
`,
|
||||
expected: `git:
|
||||
commitPrefix:
|
||||
- pattern: "^\\w+-\\w+.*"
|
||||
replace: '[JIRA $0] '
|
||||
`,
|
||||
}, {
|
||||
name: "Complicated CommitPrefixes Rename",
|
||||
input: `git:
|
||||
commitPrefixes:
|
||||
foo:
|
||||
pattern: "^\\w+-\\w+.*"
|
||||
replace: '[OTHER $0] '
|
||||
CrazyName!@#$^*&)_-)[[}{f{[]:
|
||||
pattern: "^foo.bar*"
|
||||
replace: '[FUN $0] '
|
||||
`,
|
||||
expected: `git:
|
||||
commitPrefixes:
|
||||
foo:
|
||||
- pattern: "^\\w+-\\w+.*"
|
||||
replace: '[OTHER $0] '
|
||||
CrazyName!@#$^*&)_-)[[}{f{[]:
|
||||
- pattern: "^foo.bar*"
|
||||
replace: '[FUN $0] '
|
||||
`,
|
||||
}, {
|
||||
name: "Incomplete Configuration",
|
||||
input: "git:",
|
||||
expected: "git:",
|
||||
}, {
|
||||
// This test intentionally uses non-standard indentation to test that the migration
|
||||
// does not change the input.
|
||||
name: "No changes made when already migrated",
|
||||
input: `
|
||||
git:
|
||||
commitPrefix:
|
||||
- pattern: "Hello World"
|
||||
replace: "Goodbye"
|
||||
commitPrefixes:
|
||||
foo:
|
||||
- pattern: "^\\w+-\\w+.*"
|
||||
replace: '[JIRA $0] '`,
|
||||
expected: `
|
||||
git:
|
||||
commitPrefix:
|
||||
- pattern: "Hello World"
|
||||
replace: "Goodbye"
|
||||
commitPrefixes:
|
||||
foo:
|
||||
- pattern: "^\\w+-\\w+.*"
|
||||
replace: '[JIRA $0] '`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
actual, err := computeMigratedConfig("path doesn't matter", []byte(s.input))
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
assert.Equal(t, s.expected, string(actual))
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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": {
|
||||
|
||||
93
pkg/config/keynames.go
Normal file
93
pkg/config/keynames.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// NOTE: if you make changes to this table, be sure to update
|
||||
// docs/keybindings/Custom_Keybindings.md as well
|
||||
|
||||
var LabelByKey = map[gocui.Key]string{
|
||||
gocui.KeyF1: "<f1>",
|
||||
gocui.KeyF2: "<f2>",
|
||||
gocui.KeyF3: "<f3>",
|
||||
gocui.KeyF4: "<f4>",
|
||||
gocui.KeyF5: "<f5>",
|
||||
gocui.KeyF6: "<f6>",
|
||||
gocui.KeyF7: "<f7>",
|
||||
gocui.KeyF8: "<f8>",
|
||||
gocui.KeyF9: "<f9>",
|
||||
gocui.KeyF10: "<f10>",
|
||||
gocui.KeyF11: "<f11>",
|
||||
gocui.KeyF12: "<f12>",
|
||||
gocui.KeyInsert: "<insert>",
|
||||
gocui.KeyDelete: "<delete>",
|
||||
gocui.KeyHome: "<home>",
|
||||
gocui.KeyEnd: "<end>",
|
||||
gocui.KeyPgup: "<pgup>",
|
||||
gocui.KeyPgdn: "<pgdown>",
|
||||
gocui.KeyArrowUp: "<up>",
|
||||
gocui.KeyShiftArrowUp: "<s-up>",
|
||||
gocui.KeyArrowDown: "<down>",
|
||||
gocui.KeyShiftArrowDown: "<s-down>",
|
||||
gocui.KeyArrowLeft: "<left>",
|
||||
gocui.KeyArrowRight: "<right>",
|
||||
gocui.KeyTab: "<tab>", // <c-i>
|
||||
gocui.KeyBacktab: "<backtab>",
|
||||
gocui.KeyEnter: "<enter>", // <c-m>
|
||||
gocui.KeyAltEnter: "<a-enter>",
|
||||
gocui.KeyEsc: "<esc>", // <c-[>, <c-3>
|
||||
gocui.KeyBackspace: "<backspace>", // <c-h>
|
||||
gocui.KeyCtrlSpace: "<c-space>", // <c-~>, <c-2>
|
||||
gocui.KeyCtrlSlash: "<c-/>", // <c-_>
|
||||
gocui.KeySpace: "<space>",
|
||||
gocui.KeyCtrlA: "<c-a>",
|
||||
gocui.KeyCtrlB: "<c-b>",
|
||||
gocui.KeyCtrlC: "<c-c>",
|
||||
gocui.KeyCtrlD: "<c-d>",
|
||||
gocui.KeyCtrlE: "<c-e>",
|
||||
gocui.KeyCtrlF: "<c-f>",
|
||||
gocui.KeyCtrlG: "<c-g>",
|
||||
gocui.KeyCtrlJ: "<c-j>",
|
||||
gocui.KeyCtrlK: "<c-k>",
|
||||
gocui.KeyCtrlL: "<c-l>",
|
||||
gocui.KeyCtrlN: "<c-n>",
|
||||
gocui.KeyCtrlO: "<c-o>",
|
||||
gocui.KeyCtrlP: "<c-p>",
|
||||
gocui.KeyCtrlQ: "<c-q>",
|
||||
gocui.KeyCtrlR: "<c-r>",
|
||||
gocui.KeyCtrlS: "<c-s>",
|
||||
gocui.KeyCtrlT: "<c-t>",
|
||||
gocui.KeyCtrlU: "<c-u>",
|
||||
gocui.KeyCtrlV: "<c-v>",
|
||||
gocui.KeyCtrlW: "<c-w>",
|
||||
gocui.KeyCtrlX: "<c-x>",
|
||||
gocui.KeyCtrlY: "<c-y>",
|
||||
gocui.KeyCtrlZ: "<c-z>",
|
||||
gocui.KeyCtrl4: "<c-4>", // <c-\>
|
||||
gocui.KeyCtrl5: "<c-5>", // <c-]>
|
||||
gocui.KeyCtrl6: "<c-6>",
|
||||
gocui.KeyCtrl8: "<c-8>",
|
||||
gocui.MouseWheelUp: "mouse wheel up",
|
||||
gocui.MouseWheelDown: "mouse wheel down",
|
||||
}
|
||||
|
||||
var KeyByLabel = lo.Invert(LabelByKey)
|
||||
|
||||
func isValidKeybindingKey(key string) bool {
|
||||
runeCount := utf8.RuneCountInString(key)
|
||||
if key == "<disabled>" {
|
||||
return true
|
||||
}
|
||||
|
||||
if runeCount > 1 {
|
||||
_, ok := KeyByLabel[strings.ToLower(key)]
|
||||
return ok
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@@ -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
|
||||
@@ -61,6 +64,9 @@ type GuiConfig struct {
|
||||
ScrollOffMargin int `yaml:"scrollOffMargin"`
|
||||
// One of: 'margin' (default) | 'jump'
|
||||
ScrollOffBehavior string `yaml:"scrollOffBehavior"`
|
||||
// The number of spaces per tab; used for everything that's shown in the main view, but probably mostly relevant for diffs.
|
||||
// Note that when using a pager, the pager has its own tab width setting, so you need to pass it separately in the pager command.
|
||||
TabWidth int `yaml:"tabWidth" jsonschema:"minimum=1"`
|
||||
// If true, capture mouse events.
|
||||
// When mouse events are captured, it's a little harder to select text: e.g. requiring you to hold the option key when on macOS.
|
||||
MouseEvents bool `yaml:"mouseEvents"`
|
||||
@@ -91,6 +97,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.
|
||||
@@ -107,8 +117,10 @@ type GuiConfig struct {
|
||||
// If true, show the '5 of 20' footer at the bottom of list views
|
||||
ShowListFooter bool `yaml:"showListFooter"`
|
||||
// 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.
|
||||
// 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 +154,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 +173,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 +250,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"`
|
||||
@@ -241,9 +259,9 @@ type GitConfig struct {
|
||||
// If true, do not allow force pushes
|
||||
DisableForcePushing bool `yaml:"disableForcePushing"`
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix
|
||||
CommitPrefix *CommitPrefixConfig `yaml:"commitPrefix"`
|
||||
CommitPrefix []CommitPrefixConfig `yaml:"commitPrefix"`
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix
|
||||
CommitPrefixes map[string]CommitPrefixConfig `yaml:"commitPrefixes"`
|
||||
CommitPrefixes map[string][]CommitPrefixConfig `yaml:"commitPrefixes"`
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-branch-name-prefix
|
||||
BranchPrefix string `yaml:"branchPrefix"`
|
||||
// If true, parse emoji strings in commit messages e.g. render :rocket: as 🚀
|
||||
@@ -441,6 +459,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 {
|
||||
@@ -537,8 +557,8 @@ type OSConfig struct {
|
||||
EditAtLineAndWait string `yaml:"editAtLineAndWait,omitempty"`
|
||||
|
||||
// Whether lazygit suspends until an edit process returns
|
||||
// Pointer to bool so that we can distinguish unset (nil) from false.
|
||||
// We're naming this `editInTerminal` for backwards compatibility
|
||||
// [dev] Pointer to bool so that we can distinguish unset (nil) from false.
|
||||
// [dev] We're naming this `editInTerminal` for backwards compatibility
|
||||
SuspendOnEdit *bool `yaml:"editInTerminal,omitempty"`
|
||||
|
||||
// For opening a directory in an editor
|
||||
@@ -594,8 +614,8 @@ type CustomCommandAfterHook struct {
|
||||
type CustomCommand struct {
|
||||
// The key to trigger the command. Use a single letter or one of the values from https://github.com/jesseduffield/lazygit/blob/master/docs/keybindings/Custom_Keybindings.md
|
||||
Key string `yaml:"key"`
|
||||
// The context in which to listen for the key
|
||||
Context string `yaml:"context" jsonschema:"enum=status,enum=files,enum=worktrees,enum=localBranches,enum=remotes,enum=remoteBranches,enum=tags,enum=commits,enum=reflogCommits,enum=subCommits,enum=commitFiles,enum=stash,enum=global"`
|
||||
// The context in which to listen for the key. Valid values are: status, files, worktrees, localBranches, remotes, remoteBranches, tags, commits, reflogCommits, subCommits, commitFiles, stash, and global. Multiple contexts separated by comma are allowed; most useful for "commits, subCommits" or "files, commitFiles".
|
||||
Context string `yaml:"context" jsonschema:"example=status,example=files,example=worktrees,example=localBranches,example=remotes,example=remoteBranches,example=tags,example=commits,example=reflogCommits,example=subCommits,example=commitFiles,example=stash,example=global"`
|
||||
// The command to run (using Go template syntax for placeholder values)
|
||||
Command string `yaml:"command" jsonschema:"example=git fetch {{.Form.Remote}} {{.Form.Branch}} && git checkout FETCH_HEAD"`
|
||||
// If true, run the command in a subprocess (e.g. if the command requires user input)
|
||||
@@ -676,6 +696,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
ScrollPastBottom: true,
|
||||
ScrollOffMargin: 2,
|
||||
ScrollOffBehavior: "margin",
|
||||
TabWidth: 4,
|
||||
MouseEvents: true,
|
||||
SkipDiscardChangeWarning: false,
|
||||
SkipStashWarning: false,
|
||||
@@ -684,6 +705,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
ExpandedSidePanelWeight: 2,
|
||||
MainPanelSplitMode: "flexible",
|
||||
EnlargedSideViewLocation: "left",
|
||||
WrapLinesInStagingView: true,
|
||||
Language: "auto",
|
||||
TimeFormat: "02 Jan 06",
|
||||
ShortTimeFormat: time.Kitchen,
|
||||
@@ -708,6 +730,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
ShowBottomLine: true,
|
||||
ShowPanelJumps: true,
|
||||
ShowFileTree: true,
|
||||
ShowNumstatInFilesView: false,
|
||||
ShowRandomTip: true,
|
||||
ShowIcons: false,
|
||||
NerdFontsVersion: "",
|
||||
@@ -720,7 +743,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
CommandLogSize: 8,
|
||||
SplitDiff: "auto",
|
||||
SkipRewordInEditorWarning: false,
|
||||
WindowSize: "normal",
|
||||
ScreenMode: "normal",
|
||||
Border: "rounded",
|
||||
AnimateExplosion: true,
|
||||
PortraitMode: "auto",
|
||||
@@ -729,7 +752,10 @@ func GetDefaultConfig() *UserConfig {
|
||||
Frames: []string{"|", "/", "-", "\\"},
|
||||
Rate: 50,
|
||||
},
|
||||
StatusPanelView: "dashboard",
|
||||
StatusPanelView: "dashboard",
|
||||
SwitchToFilesAfterStashPop: true,
|
||||
SwitchToFilesAfterStashApply: true,
|
||||
SwitchTabsWithPanelJumpKeys: false,
|
||||
},
|
||||
Git: GitConfig{
|
||||
Paging: PagingConfig{
|
||||
@@ -762,7 +788,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
BranchLogCmd: "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --",
|
||||
AllBranchesLogCmd: "git log --graph --all --color=always --abbrev-commit --decorate --date=relative --pretty=medium",
|
||||
DisableForcePushing: false,
|
||||
CommitPrefixes: map[string]CommitPrefixConfig(nil),
|
||||
CommitPrefixes: map[string][]CommitPrefixConfig(nil),
|
||||
BranchPrefix: "",
|
||||
ParseEmoji: false,
|
||||
TruncateCopiedCommitHashesTo: 12,
|
||||
@@ -878,6 +904,8 @@ func GetDefaultConfig() *UserConfig {
|
||||
OpenStatusFilter: "<c-b>",
|
||||
ConfirmDiscard: "x",
|
||||
CopyFileInfoToClipboard: "y",
|
||||
CollapseAll: "-",
|
||||
ExpandAll: "=",
|
||||
},
|
||||
Branches: KeybindingBranchesConfig{
|
||||
CopyPullRequestURL: "<c-y>",
|
||||
|
||||
@@ -2,8 +2,12 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/constants"
|
||||
)
|
||||
|
||||
func (config *UserConfig) Validate() error {
|
||||
@@ -15,6 +19,12 @@ func (config *UserConfig) Validate() error {
|
||||
[]string{"none", "onlyArrow", "arrowAndNumber"}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateKeybindings(config.Keybinding); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateCustomCommands(config.CustomCommands); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -25,3 +35,67 @@ func validateEnum(name string, value string, allowedValues []string) error {
|
||||
allowedValuesStr := strings.Join(allowedValues, ", ")
|
||||
return fmt.Errorf("Unexpected value '%s' for '%s'. Allowed values: %s", value, name, allowedValuesStr)
|
||||
}
|
||||
|
||||
func validateKeybindingsRecurse(path string, node any) error {
|
||||
value := reflect.ValueOf(node)
|
||||
if value.Kind() == reflect.Struct {
|
||||
for _, field := range reflect.VisibleFields(reflect.TypeOf(node)) {
|
||||
var newPath string
|
||||
if len(path) == 0 {
|
||||
newPath = field.Name
|
||||
} else {
|
||||
newPath = fmt.Sprintf("%s.%s", path, field.Name)
|
||||
}
|
||||
if err := validateKeybindingsRecurse(newPath,
|
||||
value.FieldByName(field.Name).Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if value.Kind() == reflect.Slice {
|
||||
for i := 0; i < value.Len(); i++ {
|
||||
if err := validateKeybindingsRecurse(
|
||||
fmt.Sprintf("%s[%d]", path, i), value.Index(i).Interface()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if value.Kind() == reflect.String {
|
||||
key := node.(string)
|
||||
if !isValidKeybindingKey(key) {
|
||||
return fmt.Errorf("Unrecognized key '%s' for keybinding '%s'. For permitted values see %s",
|
||||
key, path, constants.Links.Docs.CustomKeybindings)
|
||||
}
|
||||
} else {
|
||||
log.Fatalf("Unexpected type for property '%s': %s", path, value.Kind())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateKeybindings(keybindingConfig KeybindingConfig) error {
|
||||
if err := validateKeybindingsRecurse("", keybindingConfig); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(keybindingConfig.Universal.JumpToBlock) != 5 {
|
||||
return fmt.Errorf("keybinding.universal.jumpToBlock must have 5 elements; found %d.",
|
||||
len(keybindingConfig.Universal.JumpToBlock))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateCustomCommandKey(key string) error {
|
||||
if !isValidKeybindingKey(key) {
|
||||
return fmt.Errorf("Unrecognized key '%s' for custom command. For permitted values see %s",
|
||||
key, constants.Links.Docs.CustomKeybindings)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateCustomCommands(customCommands []CustomCommand) error {
|
||||
for _, customCommand := range customCommands {
|
||||
if err := validateCustomCommandKey(customCommand.Key); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -29,6 +30,50 @@ func TestUserConfigValidate_enums(t *testing.T) {
|
||||
{value: "invalid_value", valid: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Keybindings",
|
||||
setup: func(config *UserConfig, value string) {
|
||||
config.Keybinding.Universal.Quit = value
|
||||
},
|
||||
testCases: []testCase{
|
||||
{value: "", valid: true},
|
||||
{value: "<disabled>", valid: true},
|
||||
{value: "q", valid: true},
|
||||
{value: "<c-c>", valid: true},
|
||||
{value: "invalid_value", valid: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "JumpToBlock keybinding",
|
||||
setup: func(config *UserConfig, value string) {
|
||||
config.Keybinding.Universal.JumpToBlock = strings.Split(value, ",")
|
||||
},
|
||||
testCases: []testCase{
|
||||
{value: "", valid: false},
|
||||
{value: "1,2,3", valid: false},
|
||||
{value: "1,2,3,4,5", valid: true},
|
||||
{value: "1,2,3,4,invalid", valid: false},
|
||||
{value: "1,2,3,4,5,6", valid: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Custom command keybinding",
|
||||
setup: func(config *UserConfig, value string) {
|
||||
config.CustomCommands = []CustomCommand{
|
||||
{
|
||||
Key: value,
|
||||
Command: "echo 'hello'",
|
||||
},
|
||||
}
|
||||
},
|
||||
testCases: []testCase{
|
||||
{value: "", valid: true},
|
||||
{value: "<disabled>", valid: true},
|
||||
{value: "q", valid: true},
|
||||
{value: "<c-c>", valid: true},
|
||||
{value: "invalid_value", valid: false},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
|
||||
@@ -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 {
|
||||
return self.gui.helpers.AppStatus.WithWaitingStatusImpl(self.gui.Tr.FetchingStatus, func(gocui.Task) error {
|
||||
return self.backgroundFetch()
|
||||
}, nil)
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -8,8 +10,11 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/spf13/afero"
|
||||
)
|
||||
|
||||
const PreservedCommitMessageFileName = "LAZYGIT_PENDING_COMMIT"
|
||||
|
||||
type CommitMessageContext struct {
|
||||
c *ContextCommon
|
||||
types.Context
|
||||
@@ -30,8 +35,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
|
||||
// the full preserved message (combined summary and description)
|
||||
preservedMessage string
|
||||
// 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
|
||||
// invoked when pressing enter in the commit message panel
|
||||
onConfirm func(string, string) error
|
||||
// invoked when pressing the switch-to-editor key binding
|
||||
@@ -72,16 +78,55 @@ func (self *CommitMessageContext) GetSelectedIndex() int {
|
||||
return self.viewModel.selectedindex
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) GetPreservedMessagePath() string {
|
||||
return filepath.Join(self.c.Git().RepoPaths.WorktreeGitDirPath(), PreservedCommitMessageFileName)
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) GetPreserveMessage() bool {
|
||||
return self.viewModel.preserveMessage
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) GetPreservedMessage() string {
|
||||
return self.viewModel.preservedMessage
|
||||
func (self *CommitMessageContext) getPreservedMessage() (string, error) {
|
||||
buf, err := afero.ReadFile(self.c.Fs, self.GetPreservedMessagePath())
|
||||
if os.IsNotExist(err) {
|
||||
return "", nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) SetPreservedMessage(message string) {
|
||||
self.viewModel.preservedMessage = message
|
||||
func (self *CommitMessageContext) GetPreservedMessageAndLogError() string {
|
||||
msg, err := self.getPreservedMessage()
|
||||
if err != nil {
|
||||
self.c.Log.Errorf("error when retrieving persisted commit message: %v", err)
|
||||
}
|
||||
return msg
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) setPreservedMessage(message string) error {
|
||||
preservedFilePath := self.GetPreservedMessagePath()
|
||||
|
||||
if len(message) == 0 {
|
||||
err := self.c.Fs.Remove(preservedFilePath)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return afero.WriteFile(self.c.Fs, preservedFilePath, []byte(message), 0o644)
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) SetPreservedMessageAndLogError(message string) {
|
||||
if err := self.setPreservedMessage(message); err != nil {
|
||||
self.c.Log.Errorf("error when persisting commit message: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) GetInitialMessage() string {
|
||||
return strings.TrimSpace(self.viewModel.initialMessage)
|
||||
}
|
||||
|
||||
func (self *CommitMessageContext) GetHistoryMessage() string {
|
||||
@@ -101,11 +146,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,
|
||||
|
||||
@@ -3,6 +3,7 @@ package controllers
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
@@ -121,52 +122,93 @@ func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
|
||||
return bindings
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) getCommitMessageBody(hash string) string {
|
||||
commitMessageBody, err := self.c.Git().Commit.GetCommitMessage(hash)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
_, body := self.c.Helpers().Commits.SplitCommitMessageAndDescription(commitMessageBody)
|
||||
return body
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copyCommitAttribute(commit *models.Commit) error {
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.Actions.CopyCommitAttributeToClipboard,
|
||||
Items: []*types.MenuItem{
|
||||
{
|
||||
Label: self.c.Tr.CommitHash,
|
||||
OnPress: func() error {
|
||||
return self.copyCommitHashToClipboard(commit)
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.CommitSubject,
|
||||
OnPress: func() error {
|
||||
return self.copyCommitSubjectToClipboard(commit)
|
||||
},
|
||||
Key: 's',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.CommitMessage,
|
||||
OnPress: func() error {
|
||||
return self.copyCommitMessageToClipboard(commit)
|
||||
},
|
||||
Key: 'm',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.CommitURL,
|
||||
OnPress: func() error {
|
||||
return self.copyCommitURLToClipboard(commit)
|
||||
},
|
||||
Key: 'u',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.CommitDiff,
|
||||
OnPress: func() error {
|
||||
return self.copyCommitDiffToClipboard(commit)
|
||||
},
|
||||
Key: 'd',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.CommitAuthor,
|
||||
OnPress: func() error {
|
||||
return self.copyAuthorToClipboard(commit)
|
||||
},
|
||||
Key: 'a',
|
||||
commitMessageBody := self.getCommitMessageBody(commit.Hash)
|
||||
var commitMessageBodyDisabled *types.DisabledReason
|
||||
if commitMessageBody == "" {
|
||||
commitMessageBodyDisabled = &types.DisabledReason{
|
||||
Text: self.c.Tr.CommitHasNoMessageBody,
|
||||
}
|
||||
}
|
||||
|
||||
items := []*types.MenuItem{
|
||||
{
|
||||
Label: self.c.Tr.CommitHash,
|
||||
OnPress: func() error {
|
||||
return self.copyCommitHashToClipboard(commit)
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.CommitSubject,
|
||||
OnPress: func() error {
|
||||
return self.copyCommitSubjectToClipboard(commit)
|
||||
},
|
||||
Key: 's',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.CommitMessage,
|
||||
OnPress: func() error {
|
||||
return self.copyCommitMessageToClipboard(commit)
|
||||
},
|
||||
Key: 'm',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.CommitMessageBody,
|
||||
DisabledReason: commitMessageBodyDisabled,
|
||||
OnPress: func() error {
|
||||
return self.copyCommitMessageBodyToClipboard(commitMessageBody)
|
||||
},
|
||||
Key: 'b',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.CommitURL,
|
||||
OnPress: func() error {
|
||||
return self.copyCommitURLToClipboard(commit)
|
||||
},
|
||||
Key: 'u',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.CommitDiff,
|
||||
OnPress: func() error {
|
||||
return self.copyCommitDiffToClipboard(commit)
|
||||
},
|
||||
Key: 'd',
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.CommitAuthor,
|
||||
OnPress: func() error {
|
||||
return self.copyAuthorToClipboard(commit)
|
||||
},
|
||||
Key: 'a',
|
||||
},
|
||||
}
|
||||
|
||||
commitTagsItem := types.MenuItem{
|
||||
Label: self.c.Tr.CommitTags,
|
||||
OnPress: func() error {
|
||||
return self.copyCommitTagsToClipboard(commit)
|
||||
},
|
||||
Key: 't',
|
||||
}
|
||||
|
||||
if len(commit.Tags) == 0 {
|
||||
commitTagsItem.DisabledReason = &types.DisabledReason{Text: self.c.Tr.CommitHasNoTags}
|
||||
}
|
||||
|
||||
items = append(items, &commitTagsItem)
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.Actions.CopyCommitAttributeToClipboard,
|
||||
Items: items,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -242,6 +284,16 @@ func (self *BasicCommitsController) copyCommitMessageToClipboard(commit *models.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copyCommitMessageBodyToClipboard(commitMessageBody string) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyCommitMessageBodyToClipboard)
|
||||
if err := self.c.OS().CopyToClipboard(commitMessageBody); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Toast(self.c.Tr.CommitMessageBodyCopiedToClipboard)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copyCommitSubjectToClipboard(commit *models.Commit) error {
|
||||
message, err := self.c.Git().Commit.GetCommitSubject(commit.Hash)
|
||||
if err != nil {
|
||||
@@ -257,6 +309,18 @@ func (self *BasicCommitsController) copyCommitSubjectToClipboard(commit *models.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) copyCommitTagsToClipboard(commit *models.Commit) error {
|
||||
message := strings.Join(commit.Tags, "\n")
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.CopyCommitTagsToClipboard)
|
||||
if err := self.c.OS().CopyToClipboard(message); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Toast(self.c.Tr.CommitTagsCopiedToClipboard)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) openInBrowser(commit *models.Commit) error {
|
||||
url, err := self.c.Helpers().Host.GetCommitURL(commit.Hash)
|
||||
if err != nil {
|
||||
@@ -280,15 +344,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 {
|
||||
@@ -26,7 +28,7 @@ func (self *CommitDescriptionController) GetKeybindings(opts types.KeybindingsOp
|
||||
bindings := []*types.Binding{
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
|
||||
Handler: self.switchToCommitMessage,
|
||||
Handler: self.handleTogglePanel,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
@@ -59,11 +61,46 @@ 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
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) handleTogglePanel() error {
|
||||
// The default keybinding for this action is "<tab>", which means that we
|
||||
// also get here when pasting multi-line text that contains tabs. In that
|
||||
// case we don't want to toggle the panel, but insert the tab as a character
|
||||
// (somehow, see below).
|
||||
//
|
||||
// Only do this if the TogglePanel command is actually mapped to "<tab>"
|
||||
// (the default). If it's not, we can only hope that it's mapped to some
|
||||
// ctrl key or fn key, which is unlikely to occur in pasted text. And if
|
||||
// they mapped some *other* command to "<tab>", then we're totally out of
|
||||
// luck.
|
||||
if self.c.GocuiGui().IsPasting && self.c.UserConfig().Keybinding.Universal.TogglePanel == "<tab>" {
|
||||
// Handling tabs in pasted commit messages is not optimal, but hopefully
|
||||
// good enough for now. We simply insert 4 spaces without worrying about
|
||||
// column alignment. This works well enough for leading indentation,
|
||||
// which is common in pasted code snippets.
|
||||
view := self.Context().GetView()
|
||||
for range 4 {
|
||||
view.Editor.Edit(view, gocui.KeySpace, ' ', 0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.switchToCommitMessage()
|
||||
}
|
||||
|
||||
func (self *CommitDescriptionController) close() error {
|
||||
self.c.Helpers().Commits.CloseCommitMessagePanel()
|
||||
return nil
|
||||
|
||||
@@ -48,7 +48,7 @@ func (self *CommitMessageController) GetKeybindings(opts types.KeybindingsOpts)
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
|
||||
Handler: self.switchToCommitDescription,
|
||||
Handler: self.handleTogglePanel,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.CommitMessage.CommitMenu),
|
||||
@@ -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()
|
||||
@@ -99,6 +105,32 @@ func (self *CommitMessageController) switchToCommitDescription() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) handleTogglePanel() error {
|
||||
// The default keybinding for this action is "<tab>", which means that we
|
||||
// also get here when pasting multi-line text that contains tabs. In that
|
||||
// case we don't want to toggle the panel, but insert the tab as a character
|
||||
// (somehow, see below).
|
||||
//
|
||||
// Only do this if the TogglePanel command is actually mapped to "<tab>"
|
||||
// (the default). If it's not, we can only hope that it's mapped to some
|
||||
// ctrl key or fn key, which is unlikely to occur in pasted text. And if
|
||||
// they mapped some *other* command to "<tab>", then we're totally out of
|
||||
// luck.
|
||||
if self.c.GocuiGui().IsPasting && self.c.UserConfig().Keybinding.Universal.TogglePanel == "<tab>" {
|
||||
// It is unlikely that a pasted commit message contains a tab in the
|
||||
// subject line, so it shouldn't matter too much how we handle it.
|
||||
// Simply insert 4 spaces instead; all that matters is that we don't
|
||||
// switch to the description panel.
|
||||
view := self.context().GetView()
|
||||
for range 4 {
|
||||
view.Editor.Edit(view, gocui.KeySpace, ' ', 0)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.switchToCommitDescription()
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) handleCommitIndexChange(value int) error {
|
||||
currentIndex := self.context().GetSelectedIndex()
|
||||
newIndex := currentIndex + value
|
||||
@@ -134,6 +166,20 @@ func (self *CommitMessageController) setCommitMessageAtIndex(index int) (bool, e
|
||||
}
|
||||
|
||||
func (self *CommitMessageController) confirm() error {
|
||||
// The default keybinding for this action is "<enter>", which means that we
|
||||
// also get here when pasting multi-line text that contains newlines. In
|
||||
// that case we don't want to confirm the commit, but switch to the
|
||||
// description panel instead so that the rest of the pasted text goes there.
|
||||
//
|
||||
// Only do this if the SubmitEditorText command is actually mapped to
|
||||
// "<enter>" (the default). If it's not, we can only hope that it's mapped
|
||||
// to some ctrl key or fn key, which is unlikely to occur in pasted text.
|
||||
// And if they mapped some *other* command to "<enter>", then we're totally
|
||||
// out of luck.
|
||||
if self.c.GocuiGui().IsPasting && self.c.UserConfig().Keybinding.Universal.SubmitEditorText == "<enter>" {
|
||||
return self.switchToCommitDescription()
|
||||
}
|
||||
|
||||
return self.c.Helpers().Commits.HandleCommitConfirm()
|
||||
}
|
||||
|
||||
|
||||
@@ -41,6 +41,12 @@ func NewCommitFilesController(
|
||||
|
||||
func (self *CommitFilesController) GetKeybindings(opts types.KeybindingsOpts) []*types.Binding {
|
||||
bindings := []*types.Binding{
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Files.CopyFileInfoToClipboard),
|
||||
Handler: self.openCopyMenu,
|
||||
Description: self.c.Tr.CopyToClipboardMenu,
|
||||
OpensMenu: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.CommitFiles.CheckoutCommitFile),
|
||||
Handler: self.withItem(self.checkout),
|
||||
@@ -109,6 +115,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
|
||||
@@ -167,6 +187,77 @@ func (self *CommitFilesController) onClickMain(opts gocui.ViewMouseBindingOpts)
|
||||
return self.enterCommitFile(node, types.OnFocusOpts{ClickedWindowName: "main", ClickedViewLineIdx: opts.Y})
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) copyDiffToClipboard(path string, toastMessage string) error {
|
||||
from, to := self.context().GetFromAndToForDiff()
|
||||
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(from)
|
||||
|
||||
cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(from, to, reverse, path, true)
|
||||
diff, err := cmdObj.RunWithOutput()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := self.c.OS().CopyToClipboard(diff); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Toast(toastMessage)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) openCopyMenu() error {
|
||||
node := self.context().GetSelected()
|
||||
|
||||
copyNameItem := &types.MenuItem{
|
||||
Label: self.c.Tr.CopyFileName,
|
||||
OnPress: func() error {
|
||||
if err := self.c.OS().CopyToClipboard(node.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Toast(self.c.Tr.FileNameCopiedToast)
|
||||
return nil
|
||||
},
|
||||
DisabledReason: self.require(self.singleItemSelected())(),
|
||||
Key: 'n',
|
||||
}
|
||||
copyPathItem := &types.MenuItem{
|
||||
Label: self.c.Tr.CopyFilePath,
|
||||
OnPress: func() error {
|
||||
if err := self.c.OS().CopyToClipboard(node.Path); err != nil {
|
||||
return err
|
||||
}
|
||||
self.c.Toast(self.c.Tr.FilePathCopiedToast)
|
||||
return nil
|
||||
},
|
||||
DisabledReason: self.require(self.singleItemSelected())(),
|
||||
Key: 'p',
|
||||
}
|
||||
copyFileDiffItem := &types.MenuItem{
|
||||
Label: self.c.Tr.CopySelectedDiff,
|
||||
OnPress: func() error {
|
||||
return self.copyDiffToClipboard(node.GetPath(), self.c.Tr.FileDiffCopiedToast)
|
||||
},
|
||||
DisabledReason: self.require(self.singleItemSelected())(),
|
||||
Key: 's',
|
||||
}
|
||||
copyAllDiff := &types.MenuItem{
|
||||
Label: self.c.Tr.CopyAllFilesDiff,
|
||||
OnPress: func() error {
|
||||
return self.copyDiffToClipboard(".", self.c.Tr.AllFilesDiffCopiedToast)
|
||||
},
|
||||
DisabledReason: self.require(self.itemsSelected())(),
|
||||
Key: 'a',
|
||||
}
|
||||
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.CopyToClipboardMenu,
|
||||
Items: []*types.MenuItem{
|
||||
copyNameItem,
|
||||
copyPathItem,
|
||||
copyFileDiffItem,
|
||||
copyAllDiff,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *CommitFilesController) checkout(node *filetree.CommitFileNode) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.CheckoutFile)
|
||||
if err := self.c.Git().WorkingTree.CheckoutFile(self.context().GetRef().RefName(), node.GetPath()); err != nil {
|
||||
@@ -303,7 +394,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 +479,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 +488,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 +527,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()
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
@@ -186,6 +187,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 +388,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 +493,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 {
|
||||
@@ -662,24 +691,70 @@ func (self *FilesController) refresh() error {
|
||||
}
|
||||
|
||||
func (self *FilesController) handleAmendCommitPress() error {
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.AmendLastCommitTitle,
|
||||
Prompt: self.c.Tr.SureToAmend,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommitableFiles(func() error {
|
||||
if len(self.c.Model().Commits) == 0 {
|
||||
return errors.New(self.c.Tr.NoCommitToAmend)
|
||||
}
|
||||
doAmend := func() error {
|
||||
return self.c.Helpers().WorkingTree.WithEnsureCommittableFiles(func() error {
|
||||
if len(self.c.Model().Commits) == 0 {
|
||||
return errors.New(self.c.Tr.NoCommitToAmend)
|
||||
}
|
||||
|
||||
return self.c.Helpers().AmendHelper.AmendHead()
|
||||
})
|
||||
},
|
||||
})
|
||||
return self.c.Helpers().AmendHelper.AmendHead()
|
||||
})
|
||||
}
|
||||
|
||||
if self.isResolvingConflicts() {
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.AmendCommitTitle,
|
||||
Prompt: self.c.Tr.AmendCommitWithConflictsMenuPrompt,
|
||||
HideCancel: true, // We want the cancel item first, so we add one manually
|
||||
Items: []*types.MenuItem{
|
||||
{
|
||||
Label: self.c.Tr.Cancel,
|
||||
OnPress: func() error {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.AmendCommitWithConflictsContinue,
|
||||
OnPress: func() error {
|
||||
return self.c.Helpers().MergeAndRebase.ContinueRebase()
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.AmendCommitWithConflictsAmend,
|
||||
OnPress: func() error {
|
||||
return doAmend()
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
} else {
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.AmendLastCommitTitle,
|
||||
Prompt: self.c.Tr.SureToAmend,
|
||||
HandleConfirm: func() error {
|
||||
return doAmend()
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *FilesController) isResolvingConflicts() bool {
|
||||
commits := self.c.Model().Commits
|
||||
for _, c := range commits {
|
||||
if c.Status != models.StatusRebasing {
|
||||
break
|
||||
}
|
||||
if c.Action == models.ActionConflict {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (self *FilesController) handleStatusFilterPressed() error {
|
||||
currentFilter := self.context().GetFilter()
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.FilteringMenuTitle,
|
||||
Items: []*types.MenuItem{
|
||||
@@ -688,29 +763,79 @@ func (self *FilesController) handleStatusFilterPressed() error {
|
||||
OnPress: func() error {
|
||||
return self.setStatusFiltering(filetree.DisplayStaged)
|
||||
},
|
||||
Key: 's',
|
||||
Key: 's',
|
||||
Widget: types.MakeMenuRadioButton(currentFilter == filetree.DisplayStaged),
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.FilterUnstagedFiles,
|
||||
OnPress: func() error {
|
||||
return self.setStatusFiltering(filetree.DisplayUnstaged)
|
||||
},
|
||||
Key: 'u',
|
||||
Key: 'u',
|
||||
Widget: types.MakeMenuRadioButton(currentFilter == filetree.DisplayUnstaged),
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.ResetFilter,
|
||||
Label: self.c.Tr.FilterTrackedFiles,
|
||||
OnPress: func() error {
|
||||
return self.setStatusFiltering(filetree.DisplayTracked)
|
||||
},
|
||||
Key: 't',
|
||||
Widget: types.MakeMenuRadioButton(currentFilter == filetree.DisplayTracked),
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.FilterUntrackedFiles,
|
||||
OnPress: func() error {
|
||||
return self.setStatusFiltering(filetree.DisplayUntracked)
|
||||
},
|
||||
Key: 'T',
|
||||
Widget: types.MakeMenuRadioButton(currentFilter == filetree.DisplayUntracked),
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.NoFilter,
|
||||
OnPress: func() error {
|
||||
return self.setStatusFiltering(filetree.DisplayAll)
|
||||
},
|
||||
Key: 'r',
|
||||
Key: 'r',
|
||||
Widget: types.MakeMenuRadioButton(currentFilter == filetree.DisplayAll),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *FilesController) filteringLabel(filter filetree.FileTreeDisplayFilter) string {
|
||||
switch filter {
|
||||
case filetree.DisplayAll:
|
||||
return ""
|
||||
case filetree.DisplayStaged:
|
||||
return self.c.Tr.FilterLabelStagedFiles
|
||||
case filetree.DisplayUnstaged:
|
||||
return self.c.Tr.FilterLabelUnstagedFiles
|
||||
case filetree.DisplayTracked:
|
||||
return self.c.Tr.FilterLabelTrackedFiles
|
||||
case filetree.DisplayUntracked:
|
||||
return self.c.Tr.FilterLabelUntrackedFiles
|
||||
case filetree.DisplayConflicted:
|
||||
return self.c.Tr.FilterLabelConflictingFiles
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("Unexpected files display filter: %d", filter))
|
||||
}
|
||||
|
||||
func (self *FilesController) setStatusFiltering(filter filetree.FileTreeDisplayFilter) error {
|
||||
previousFilter := self.context().GetFilter()
|
||||
|
||||
self.context().FileTreeViewModel.SetStatusFilter(filter)
|
||||
return self.c.PostRefreshUpdate(self.context())
|
||||
self.c.Contexts().Files.GetView().Subtitle = self.filteringLabel(filter)
|
||||
|
||||
// Whenever we switch between untracked and other filters, we need to refresh the files view
|
||||
// because the untracked files filter applies when running `git status`.
|
||||
if previousFilter != filter && (previousFilter == filetree.DisplayUntracked || filter == filetree.DisplayUntracked) {
|
||||
return self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.FILES}, Mode: types.ASYNC})
|
||||
} else {
|
||||
self.c.PostRefreshUpdate(self.context())
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *FilesController) edit(nodes []*filetree.FileNode) error {
|
||||
@@ -863,7 +988,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 +1013,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 +1074,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 +1082,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 +1300,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
|
||||
}
|
||||
|
||||
@@ -36,25 +36,25 @@ func (self *GlobalController) GetKeybindings(opts types.KeybindingsOpts) []*type
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.CreateRebaseOptionsMenu),
|
||||
Handler: self.c.Helpers().MergeAndRebase.CreateRebaseOptionsMenu,
|
||||
Handler: opts.Guards.NoPopupPanel(self.c.Helpers().MergeAndRebase.CreateRebaseOptionsMenu),
|
||||
Description: self.c.Tr.ViewMergeRebaseOptions,
|
||||
Tooltip: self.c.Tr.ViewMergeRebaseOptionsTooltip,
|
||||
OpensMenu: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Refresh),
|
||||
Handler: self.refresh,
|
||||
Handler: opts.Guards.NoPopupPanel(self.refresh),
|
||||
Description: self.c.Tr.Refresh,
|
||||
Tooltip: self.c.Tr.RefreshTooltip,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.NextScreenMode),
|
||||
Handler: self.nextScreenMode,
|
||||
Handler: opts.Guards.NoPopupPanel(self.nextScreenMode),
|
||||
Description: self.c.Tr.NextScreenMode,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.PrevScreenMode),
|
||||
Handler: self.prevScreenMode,
|
||||
Handler: opts.Guards.NoPopupPanel(self.prevScreenMode),
|
||||
Description: self.c.Tr.PrevScreenMode,
|
||||
},
|
||||
{
|
||||
@@ -69,29 +69,30 @@ 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: "",
|
||||
Key: opts.GetKey(opts.Config.Universal.FilteringMenu),
|
||||
Handler: self.createFilteringMenu,
|
||||
Handler: opts.Guards.NoPopupPanel(self.createFilteringMenu),
|
||||
Description: self.c.Tr.OpenFilteringMenu,
|
||||
Tooltip: self.c.Tr.OpenFilteringMenuTooltip,
|
||||
OpensMenu: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.DiffingMenu),
|
||||
Handler: self.createDiffingMenu,
|
||||
Handler: opts.Guards.NoPopupPanel(self.createDiffingMenu),
|
||||
Description: self.c.Tr.ViewDiffingOptions,
|
||||
Tooltip: self.c.Tr.ViewDiffingOptionsTooltip,
|
||||
OpensMenu: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.DiffingMenuAlt),
|
||||
Handler: self.createDiffingMenu,
|
||||
Handler: opts.Guards.NoPopupPanel(self.createDiffingMenu),
|
||||
Description: self.c.Tr.ViewDiffingOptions,
|
||||
Tooltip: self.c.Tr.ViewDiffingOptionsTooltip,
|
||||
OpensMenu: true,
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -60,9 +60,13 @@ func (self appStatusHelperTask) Continue() {
|
||||
// withWaitingStatus wraps a function and shows a waiting status while the function is still executing
|
||||
func (self *AppStatusHelper) WithWaitingStatus(message string, f func(gocui.Task) error) {
|
||||
self.c.OnWorker(func(task gocui.Task) error {
|
||||
return self.statusMgr().WithWaitingStatus(message, self.renderAppStatus, func(waitingStatusHandle *status.WaitingStatusHandle) error {
|
||||
return f(appStatusHelperTask{task, waitingStatusHandle})
|
||||
})
|
||||
return self.WithWaitingStatusImpl(message, f, task)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *AppStatusHelper) WithWaitingStatusImpl(message string, f func(gocui.Task) error, task gocui.Task) error {
|
||||
return self.statusMgr().WithWaitingStatus(message, self.renderAppStatus, func(waitingStatusHandle *status.WaitingStatusHandle) error {
|
||||
return f(appStatusHelperTask{task, waitingStatusHandle})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -113,7 +113,7 @@ func (self *CommitsHelper) UpdateCommitPanelView(message string) {
|
||||
}
|
||||
|
||||
if self.c.Contexts().CommitMessage.GetPreserveMessage() {
|
||||
preservedMessage := self.c.Contexts().CommitMessage.GetPreservedMessage()
|
||||
preservedMessage := self.c.Contexts().CommitMessage.GetPreservedMessageAndLogError()
|
||||
self.SetMessageAndDescriptionInView(preservedMessage)
|
||||
return
|
||||
}
|
||||
@@ -143,6 +143,7 @@ func (self *CommitsHelper) OpenCommitMessagePanel(opts *OpenCommitMessagePanelOp
|
||||
opts.SummaryTitle,
|
||||
opts.DescriptionTitle,
|
||||
opts.PreserveMessage,
|
||||
opts.InitialMessage,
|
||||
onConfirm,
|
||||
opts.OnSwitchToEditor,
|
||||
)
|
||||
@@ -155,7 +156,7 @@ func (self *CommitsHelper) OpenCommitMessagePanel(opts *OpenCommitMessagePanelOp
|
||||
func (self *CommitsHelper) OnCommitSuccess() {
|
||||
// if we have a preserved message we want to clear it on success
|
||||
if self.c.Contexts().CommitMessage.GetPreserveMessage() {
|
||||
self.c.Contexts().CommitMessage.SetPreservedMessage("")
|
||||
self.c.Contexts().CommitMessage.SetPreservedMessageAndLogError("")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.SetPreservedMessageAndLogError(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
|
||||
func getMessageHeight(wrap bool, editable bool, message string, width int, tabWidth int) int {
|
||||
wrappedLines, _, _ := utils.WrapViewLinesToWidth(wrap, editable, message, width, tabWidth)
|
||||
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 {
|
||||
@@ -340,7 +265,7 @@ func (self *ConfirmationHelper) resizeMenu(parentPopupContext types.Context) {
|
||||
if selectedItem != nil {
|
||||
tooltip = self.TooltipForMenuItem(selectedItem)
|
||||
}
|
||||
tooltipHeight := getMessageHeight(true, tooltip, contentWidth) + 2 // plus 2 for the frame
|
||||
tooltipHeight := getMessageHeight(true, false, tooltip, contentWidth, self.c.Views().Menu.TabWidth) + 2 // plus 2 for the frame
|
||||
_, _ = self.c.GocuiGui().SetView(self.c.Views().Tooltip.Name(), x0, tooltipTop, x1, tooltipTop+tooltipHeight-1, 0)
|
||||
}
|
||||
|
||||
@@ -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, false, prompt, contentWidth, self.c.Views().Menu.TabWidth)
|
||||
promptLines = append(promptLines, "")
|
||||
}
|
||||
self.c.Contexts().Menu.SetPromptLines(promptLines)
|
||||
@@ -379,16 +304,19 @@ func (self *ConfirmationHelper) resizeConfirmationPanel(parentPopupContext types
|
||||
suggestionsViewHeight = 11
|
||||
}
|
||||
panelWidth := self.getPopupPanelWidth()
|
||||
prompt := self.c.Views().Confirmation.Buffer()
|
||||
contentWidth := panelWidth - 2 // minus 2 for the frame
|
||||
confirmationView := self.c.Views().Confirmation
|
||||
prompt := confirmationView.Buffer()
|
||||
wrap := true
|
||||
if self.c.Views().Confirmation.Editable {
|
||||
prompt = self.c.Views().Confirmation.TextArea.GetContent()
|
||||
editable := confirmationView.Editable
|
||||
if editable {
|
||||
prompt = confirmationView.TextArea.GetContent()
|
||||
wrap = false
|
||||
}
|
||||
panelHeight := getMessageHeight(wrap, prompt, panelWidth) + suggestionsViewHeight
|
||||
panelHeight := getMessageHeight(wrap, editable, prompt, contentWidth, confirmationView.TabWidth) + 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)
|
||||
_, _ = self.c.GocuiGui().SetView(confirmationView.Name(), x0, y0, x1, confirmationViewBottom, 0)
|
||||
|
||||
suggestionsViewTop := confirmationViewBottom + 1
|
||||
_, _ = self.c.GocuiGui().SetView(self.c.Views().Suggestions.Name(), x0, suggestionsViewTop, x1, suggestionsViewTop+suggestionsViewHeight, 0)
|
||||
@@ -398,7 +326,7 @@ func (self *ConfirmationHelper) ResizeCommitMessagePanels(parentPopupContext typ
|
||||
panelWidth := self.getPopupPanelWidth()
|
||||
content := self.c.Views().CommitDescription.TextArea.GetContent()
|
||||
summaryViewHeight := 3
|
||||
panelHeight := getMessageHeight(false, content, panelWidth)
|
||||
panelHeight := getMessageHeight(false, true, content, panelWidth, self.c.Views().CommitDescription.TabWidth)
|
||||
minHeight := 7
|
||||
if panelHeight < minHeight {
|
||||
panelHeight = minHeight
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -77,6 +77,10 @@ func (self *MergeAndRebaseHelper) CreateRebaseOptionsMenu() error {
|
||||
return self.c.Menu(types.CreateMenuOptions{Title: title, Items: menuItems})
|
||||
}
|
||||
|
||||
func (self *MergeAndRebaseHelper) ContinueRebase() error {
|
||||
return self.genericMergeCommand(REBASE_OPTION_CONTINUE)
|
||||
}
|
||||
|
||||
func (self *MergeAndRebaseHelper) genericMergeCommand(command string) error {
|
||||
status := self.c.Git().Status.WorkingTreeState()
|
||||
|
||||
@@ -487,5 +491,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,12 +487,12 @@ 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 {
|
||||
self.searchHelper.ReApplyFilter(self.c.Contexts().Branches)
|
||||
|
||||
_, idx, found := lo.FindIndexOf(self.c.Contexts().Branches.GetItems(),
|
||||
func(b *models.Branch) bool { return b.Name == prevSelectedBranch.Name })
|
||||
if found {
|
||||
@@ -495,9 +500,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 +528,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
|
||||
})
|
||||
|
||||
@@ -575,7 +572,9 @@ func (self *RefreshHelper) refreshStateFiles() error {
|
||||
}
|
||||
|
||||
files := self.c.Git().Loaders.FileLoader.
|
||||
GetStatusFiles(git_commands.GetStatusFileOptions{})
|
||||
GetStatusFiles(git_commands.GetStatusFileOptions{
|
||||
ForceShowUntracked: self.c.Contexts().Files.ForceShowUntracked(),
|
||||
})
|
||||
|
||||
conflictFileCount := 0
|
||||
for _, file := range files {
|
||||
@@ -591,16 +590,14 @@ func (self *RefreshHelper) refreshStateFiles() error {
|
||||
fileTreeViewModel.RWMutex.Lock()
|
||||
|
||||
// only taking over the filter if it hasn't already been set by the user.
|
||||
// Though this does make it impossible for the user to actually say they want to display all if
|
||||
// conflicts are currently being shown. Hmm. Worth it I reckon. If we need to add some
|
||||
// extra state here to see if the user's set the filter themselves we can do that, but
|
||||
// I'd prefer to maintain as little state as possible.
|
||||
if conflictFileCount > 0 {
|
||||
if conflictFileCount > 0 && prevConflictFileCount == 0 {
|
||||
if fileTreeViewModel.GetFilter() == filetree.DisplayAll {
|
||||
fileTreeViewModel.SetStatusFilter(filetree.DisplayConflicted)
|
||||
self.c.Contexts().Files.GetView().Subtitle = self.c.Tr.FilterLabelConflictingFiles
|
||||
}
|
||||
} else if fileTreeViewModel.GetFilter() == filetree.DisplayConflicted {
|
||||
} else if conflictFileCount == 0 && fileTreeViewModel.GetFilter() == filetree.DisplayConflicted {
|
||||
fileTreeViewModel.SetStatusFilter(filetree.DisplayAll)
|
||||
self.c.Contexts().Files.GetView().Subtitle = ""
|
||||
}
|
||||
|
||||
self.c.Model().Files = files
|
||||
@@ -653,7 +650,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 +675,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 +690,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 +743,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 +762,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,54 @@ 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
|
||||
|
||||
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',
|
||||
},
|
||||
}
|
||||
|
||||
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',
|
||||
})
|
||||
}
|
||||
|
||||
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 +354,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 +414,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")
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user