mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-03 12:36:57 -04:00
Compare commits
62
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7357c1daea | ||
|
|
75488f1041 | ||
|
|
3db8175bb5 | ||
|
|
5f8bca6508 | ||
|
|
3b677cbcfe | ||
|
|
658dbd2bdf | ||
|
|
a9a13619fd | ||
|
|
5d452122b3 | ||
|
|
f70b0a214b | ||
|
|
b81b6e3c2e | ||
|
|
4bed4b8965 | ||
|
|
4fab224ccd | ||
|
|
95005999e6 | ||
|
|
f84cff3ba5 | ||
|
|
2ae87d3d0f | ||
|
|
a2b2fa7a5e | ||
|
|
1d0db51caf | ||
|
|
980a8ca53e | ||
|
|
aa7d26be59 | ||
|
|
4b69d93660 | ||
|
|
82e283429a | ||
|
|
6edf29b0dc | ||
|
|
c9a883b273 | ||
|
|
7eb561921b | ||
|
|
48f2e1dfc1 | ||
|
|
2f575a0b37 | ||
|
|
641740fdec | ||
|
|
bdc780a890 | ||
|
|
443452ad6a | ||
|
|
d97531aeef | ||
|
|
819212b758 | ||
|
|
48a8a71d31 | ||
|
|
be703f3d27 | ||
|
|
06a9ff12ab | ||
|
|
4c9cc1ab7f | ||
|
|
02997d30d9 | ||
|
|
ce202a5612 | ||
|
|
f317a97ac1 | ||
|
|
6f6545e44f | ||
|
|
99e26be3d9 | ||
|
|
de98258e39 | ||
|
|
3bf833aea7 | ||
|
|
39cd4746d5 | ||
|
|
1ede4e6eb9 | ||
|
|
4c731abedd | ||
|
|
c10dfcefcf | ||
|
|
50b5c17d93 | ||
|
|
8d36c8b3c1 | ||
|
|
9f44caa568 | ||
|
|
39477172b8 | ||
|
|
7545fc48e5 | ||
|
|
3d78263e0c | ||
|
|
11e877cbad | ||
|
|
268d1934f8 | ||
|
|
4bcf3e181c | ||
|
|
df1e5512da | ||
|
|
2ba3c1dc3f | ||
|
|
b41bb8f845 | ||
|
|
0339b414d9 | ||
|
|
cb719c11c5 | ||
|
|
19605ad476 | ||
|
|
e0c1d1c752 |
@@ -1,7 +1,7 @@
|
||||
# adapted from https://github.com/devcontainers/images/blob/main/src/go/.devcontainer/Dockerfile
|
||||
|
||||
# [Choice] Go version (use -bullseye variants on local arm64/Apple Silicon): 1, 1.19, 1.18, 1-bullseye, 1.19-bullseye, 1.18-bullseye, 1-buster, 1.19-buster, 1.18-buster
|
||||
ARG VARIANT=1-bullseye
|
||||
ARG VARIANT=1-trixie
|
||||
FROM golang:${VARIANT}
|
||||
|
||||
RUN go install mvdan.cc/gofumpt@latest
|
||||
|
||||
+17
-17
@@ -28,9 +28,9 @@ jobs:
|
||||
GOFLAGS: -mod=vendor
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.25.x
|
||||
- name: Test code
|
||||
@@ -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@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: coverage-unit-${{ matrix.os }}-${{ github.run_id }}
|
||||
path: /tmp/code_coverage
|
||||
@@ -59,7 +59,7 @@ jobs:
|
||||
GOFLAGS: -mod=vendor
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Restore Git cache
|
||||
if: matrix.git-version != 'latest'
|
||||
id: cache-git-restore
|
||||
@@ -85,7 +85,7 @@ jobs:
|
||||
path: ~/git-${{matrix.git-version}}
|
||||
key: ${{runner.os}}-git-${{matrix.git-version}}
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.25.x
|
||||
- name: Print git version
|
||||
@@ -98,7 +98,7 @@ jobs:
|
||||
mkdir -p /tmp/code_coverage
|
||||
./scripts/run_integration_tests.sh
|
||||
- name: Upload code coverage artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: coverage-integration-${{ matrix.git-version }}-${{ github.run_id }}
|
||||
path: /tmp/code_coverage
|
||||
@@ -109,9 +109,9 @@ jobs:
|
||||
GOARCH: amd64
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.25.x
|
||||
- name: Build linux binary
|
||||
@@ -136,9 +136,9 @@ jobs:
|
||||
GOARCH: amd64
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.25.x
|
||||
- name: Check Vendor Directory
|
||||
@@ -162,13 +162,13 @@ jobs:
|
||||
GOFLAGS: -mod=vendor
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.25.x
|
||||
- name: Lint
|
||||
uses: golangci/golangci-lint-action@v8
|
||||
uses: golangci/golangci-lint-action@v9
|
||||
with:
|
||||
# If you change this, make sure to also update scripts/golangci-lint-shim.sh
|
||||
version: v2.4.0
|
||||
@@ -182,15 +182,15 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.25.x
|
||||
|
||||
- name: Download all coverage artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: /tmp/code_coverage
|
||||
|
||||
@@ -219,7 +219,7 @@ jobs:
|
||||
run: echo "PR_FETCH_DEPTH=$(( ${{ github.event.pull_request.commits }} ))" >> "${GITHUB_ENV}"
|
||||
|
||||
- name: "Checkout PR branch and all PR commits"
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
|
||||
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.event.issue.pull_request == null && startsWith(github.event.comment.body, '/close') }}
|
||||
steps:
|
||||
- uses: actions/github-script@v7
|
||||
- uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const trustedUsers = ['ChrisMcD1', 'jesseduffield', 'stefanhaller']
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
- name: Annotate locations with typos
|
||||
uses: codespell-project/codespell-problem-matcher@v1
|
||||
- name: Codespell
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
schedule:
|
||||
# Runs at 8:00 AM UTC on every Saturday
|
||||
# We'll check below if it's the first Saturday of the month, and fail if not
|
||||
- cron: '0 8 * * 6'
|
||||
# schedule:
|
||||
# # Runs at 8:00 AM UTC on every Saturday
|
||||
# # We'll check below if it's the first Saturday of the month, and fail if not
|
||||
# - cron: '0 8 * * 6'
|
||||
|
||||
# Allow manual triggering of the workflow
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
@@ -45,7 +46,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
repository: jesseduffield/lazygit
|
||||
token: ${{ secrets.LAZYGIT_RELEASE_PAT }}
|
||||
@@ -146,7 +147,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.LAZYGIT_RELEASE_PAT }}
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version: 1.25.x
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ jobs:
|
||||
if: ${{ github.repository == 'jesseduffield/lazygit' }}
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Generate Sponsors 💖
|
||||
uses: JamesIves/github-sponsors-readme-action@v1.2.2
|
||||
@@ -19,7 +19,7 @@ jobs:
|
||||
file: "README.md"
|
||||
|
||||
- name: Create Pull Request 🚀
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
with:
|
||||
commit-message: "README.md: Update Sponsors"
|
||||
title: "README.md: Update Sponsors"
|
||||
|
||||
@@ -481,6 +481,10 @@ git:
|
||||
# to 40 to disable truncation.
|
||||
truncateCopiedCommitHashesTo: 12
|
||||
|
||||
# If true and if `gh` is installed and on version >=2, we will use `gh` to
|
||||
# display pull requests against branches.
|
||||
enableGithubCli: true
|
||||
|
||||
# Periodic update checks
|
||||
update:
|
||||
# One of: 'prompt' (default) | 'background' | 'never'
|
||||
@@ -713,6 +717,7 @@ keybinding:
|
||||
renameCommitWithEditor: R
|
||||
viewResetOptions: g
|
||||
markCommitAsFixup: f
|
||||
setFixupMessage: c
|
||||
createFixupCommit: F
|
||||
squashAboveCommits: S
|
||||
moveDownCommit: <c-j>
|
||||
|
||||
@@ -71,7 +71,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` / `` | Search the current view by text | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Commit summary
|
||||
|
||||
@@ -89,6 +89,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` b `` | View bisect options | |
|
||||
| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
|
||||
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
|
||||
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
|
||||
| `` r `` | Reword | Reword the selected commit's message. |
|
||||
| `` R `` | Reword with editor | |
|
||||
| `` d `` | Drop | 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. |
|
||||
@@ -159,7 +160,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` / `` | Search the current view by text | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Input prompt
|
||||
|
||||
@@ -236,6 +237,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` o `` | Open file | Open file in default application. |
|
||||
| `` e `` | Edit file | Open file in external editor. |
|
||||
| `` <space> `` | Toggle lines in patch | |
|
||||
| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. |
|
||||
| `` <esc> `` | Exit custom patch builder | |
|
||||
| `` / `` | Search the current view by text | |
|
||||
|
||||
|
||||
@@ -69,6 +69,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
|
||||
| `` b `` | bisectオプションを表示 | |
|
||||
| `` s `` | スカッシュ | 選択したコミットをその下のコミットにスカッシュします。スカッシュとは複数のコミットを1つにまとめる操作です。選択したコミットのメッセージが下のコミットに追加されます。 |
|
||||
| `` f `` | フィックスアップ | 選択したコミットをその下のコミットにマージします。フィックスアップはスカッシュと似ていますが、選択したコミットのメッセージは破棄され、下のコミットのメッセージのみが保持されます。 |
|
||||
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
|
||||
| `` r `` | メッセージ変更 | 選択したコミットのメッセージを変更します。 |
|
||||
| `` R `` | エディタでメッセージ変更 | |
|
||||
| `` d `` | 削除 | 選択したコミットを削除します。これはリベースを通じてブランチからコミットを削除します。コミットが後続のコミットが依存する変更を行っている場合、マージコンフリクトを解決する必要があるかもしれません。 |
|
||||
@@ -114,11 +115,11 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
|
||||
| `` <space> `` | パッチに含めるファイルを切り替え | ファイルがカスタムパッチに含まれるかどうかを切り替えます。https://github.com/jesseduffield/lazygit#rebase-magic-custom-patchesを参照してください。 |
|
||||
| `` a `` | すべてのファイルを切り替え | コミットのすべてのファイルをカスタムパッチに追加/削除します。https://github.com/jesseduffield/lazygit#rebase-magic-custom-patchesを参照してください。 |
|
||||
| `` <enter> `` | ファイルに入る / ディレクトリの折りたたみを切り替える | ファイルが選択されている場合、そのファイルに入ってカスタムパッチに個々の行を追加/削除できます。ディレクトリが選択されている場合、ディレクトリを切り替えます。 |
|
||||
| `` ` `` | ファイルツリービューを切り替え | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
|
||||
| `` ` `` | ファイルツリービューを切り替え | ファイル表示をフラット表示とツリー表示で切り替えます。フラット表示はすべてのファイルパスを一覧で表示し、ツリー表示はディレクトリごとにファイルをグループ化します。<br><br>デフォルトは設定ファイル内の 'gui.showFileTree' キーで変更できます。 |
|
||||
| `` - `` | すべてのファイルを折りたたむ | ファイルツリー内のすべてのディレクトリを折りたたみます |
|
||||
| `` = `` | すべてのファイルを展開 | ファイルツリー内のすべてのディレクトリを展開します |
|
||||
| `` 0 `` | メインビューにフォーカス | |
|
||||
| `` / `` | 現在のビューをテキストで検索 | |
|
||||
| `` / `` | 現在のビューをテキストでフィルタリング | |
|
||||
|
||||
## コミット概要
|
||||
|
||||
@@ -234,14 +235,14 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
|
||||
| `` d `` | 破棄 | 選択したファイルの変更を破棄するオプションを表示します。 |
|
||||
| `` g `` | アップストリームへのリセットオプションを表示 | |
|
||||
| `` D `` | リセット | 作業ツリーのリセットオプション(例:作業ツリーの完全破棄)を表示します。 |
|
||||
| `` ` `` | ファイルツリービューを切り替え | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
|
||||
| `` ` `` | ファイルツリービューを切り替え | ファイル表示をフラット表示とツリー表示で切り替えます。フラット表示はすべてのファイルパスを一覧で表示し、ツリー表示はディレクトリごとにファイルをグループ化します。<br><br>デフォルトは設定ファイル内の 'gui.showFileTree' キーで変更できます。 |
|
||||
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
|
||||
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
|
||||
| `` f `` | フェッチ | リモートから変更をフェッチします。 |
|
||||
| `` - `` | すべてのファイルを折りたたむ | ファイルツリー内のすべてのディレクトリを折りたたみます |
|
||||
| `` = `` | すべてのファイルを展開 | ファイルツリー内のすべてのディレクトリを展開します |
|
||||
| `` 0 `` | メインビューにフォーカス | |
|
||||
| `` / `` | 現在のビューをテキストで検索 | |
|
||||
| `` / `` | 現在のビューをテキストでフィルタリング | |
|
||||
|
||||
## メインパネル(ステージング)
|
||||
|
||||
@@ -277,6 +278,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
|
||||
| `` o `` | ファイルを開く | デフォルトのアプリケーションでファイルを開きます。 |
|
||||
| `` e `` | ファイルを編集 | 外部エディタでファイルを開きます。 |
|
||||
| `` <space> `` | パッチ内の行を切り替え | |
|
||||
| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. |
|
||||
| `` <esc> `` | カスタムパッチビルダーを終了 | |
|
||||
| `` / `` | 現在のビューをテキストで検索 | |
|
||||
|
||||
|
||||
@@ -178,6 +178,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` o `` | 파일 닫기 | Open file in default application. |
|
||||
| `` e `` | 파일 편집 | Open file in external editor. |
|
||||
| `` <space> `` | Line(s)을 패치에 추가/삭제 | |
|
||||
| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. |
|
||||
| `` <esc> `` | Exit custom patch builder | |
|
||||
| `` / `` | 검색 시작 | |
|
||||
|
||||
@@ -298,6 +299,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` b `` | Bisect 옵션 보기 | |
|
||||
| `` 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. |
|
||||
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
|
||||
| `` r `` | 커밋메시지 변경 | Reword the selected commit's message. |
|
||||
| `` R `` | 에디터에서 커밋메시지 수정 | |
|
||||
| `` d `` | 커밋 삭제 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
|
||||
@@ -347,7 +349,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` / `` | 검색 시작 | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## 커밋메시지
|
||||
|
||||
@@ -403,7 +405,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` / `` | 검색 시작 | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## 확인 패널
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` / `` | Start met zoeken | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Bevestigingspaneel
|
||||
|
||||
@@ -149,7 +149,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` / `` | Start met zoeken | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Commits
|
||||
|
||||
@@ -160,6 +160,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` b `` | View bisect options | |
|
||||
| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
|
||||
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
|
||||
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
|
||||
| `` r `` | Hernoem commit | Reword the selected commit's message. |
|
||||
| `` R `` | Hernoem commit met editor | |
|
||||
| `` d `` | Verwijder commit | 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. |
|
||||
@@ -244,6 +245,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` o `` | Open bestand | Open file in default application. |
|
||||
| `` e `` | Verander bestand | Open file in external editor. |
|
||||
| `` <space> `` | Voeg toe/verwijder lijn(en) in patch | |
|
||||
| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. |
|
||||
| `` <esc> `` | Sluit lijn-bij-lijn modus | |
|
||||
| `` / `` | Start met zoeken | |
|
||||
|
||||
|
||||
@@ -62,6 +62,7 @@ _Legenda: `<c-b>` oznacza ctrl+b, `<a-b>` oznacza alt+b, `B` oznacza shift+b_
|
||||
| `` b `` | Zobacz opcje bisect | |
|
||||
| `` s `` | Scal | Scal wybrany commit z commitami poniżej. Wiadomość wybranego commita zostanie dołączona do commita poniżej. |
|
||||
| `` f `` | Poprawka | Włącz wybrany commit do commita poniżej. Podobnie do fixup, ale wiadomość wybranego commita zostanie odrzucona. |
|
||||
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
|
||||
| `` r `` | Przeformułuj | Przeformułuj wiadomość wybranego commita. |
|
||||
| `` R `` | Przeformułuj za pomocą edytora | |
|
||||
| `` d `` | Usuń | Usuń wybrany commit. To usunie commit z gałęzi za pomocą rebazowania. Jeśli commit wprowadza zmiany, od których zależą późniejsze commity, być może będziesz musiał rozwiązać konflikty scalania. |
|
||||
@@ -123,6 +124,7 @@ _Legenda: `<c-b>` oznacza ctrl+b, `<a-b>` oznacza alt+b, `B` oznacza shift+b_
|
||||
| `` o `` | Otwórz plik | Otwórz plik w domyślnej aplikacji. |
|
||||
| `` e `` | Edytuj plik | Otwórz plik w zewnętrznym edytorze. |
|
||||
| `` <space> `` | Przełącz linie w łatce | |
|
||||
| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. |
|
||||
| `` <esc> `` | Wyjdź z budowniczego niestandardowej łatki | |
|
||||
| `` / `` | Szukaj w bieżącym widoku po tekście | |
|
||||
|
||||
@@ -258,7 +260,7 @@ _Legenda: `<c-b>` oznacza ctrl+b, `<a-b>` oznacza alt+b, `B` oznacza shift+b_
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` / `` | Szukaj w bieżącym widoku po tekście | |
|
||||
| `` / `` | Filtruj bieżący widok po tekście | |
|
||||
|
||||
## Pliki commita
|
||||
|
||||
@@ -278,7 +280,7 @@ _Legenda: `<c-b>` oznacza ctrl+b, `<a-b>` oznacza alt+b, `B` oznacza shift+b_
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` / `` | Szukaj w bieżącym widoku po tekście | |
|
||||
| `` / `` | Filtruj bieżący widok po tekście | |
|
||||
|
||||
## Podsumowanie commita
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` - `` | Recolher todos os arquivos | Recolher todos os diretórios na árvore de arquivos |
|
||||
| `` = `` | Expandir todos os arquivos | Expandir todos os diretórios na árvore do arquivo |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` / `` | Search the current view by text | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Branches locais
|
||||
|
||||
@@ -153,7 +153,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` - `` | Recolher todos os arquivos | Recolher todos os diretórios na árvore de arquivos |
|
||||
| `` = `` | Expandir todos os arquivos | Expandir todos os diretórios na árvore do arquivo |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` / `` | Search the current view by text | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Commits
|
||||
|
||||
@@ -164,6 +164,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` 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. |
|
||||
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
|
||||
| `` 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. |
|
||||
@@ -294,6 +295,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` o `` | Abrir arquivo | Abrir arquivo no aplicativo padrão. |
|
||||
| `` e `` | Editar arquivo | Abrir arquivo no editor externo. |
|
||||
| `` <space> `` | Alternar linhas no caminho | |
|
||||
| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. |
|
||||
| `` <esc> `` | Sair do construtor de patch personalizado | |
|
||||
| `` / `` | Search the current view by text | |
|
||||
|
||||
|
||||
@@ -138,6 +138,7 @@ _Связки клавиш_
|
||||
| `` o `` | Открыть файл | Open file in default application. |
|
||||
| `` e `` | Редактировать файл | Open file in external editor. |
|
||||
| `` <space> `` | Добавить/удалить строку(и) для патча | |
|
||||
| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. |
|
||||
| `` <esc> `` | Выйти из сборщика пользовательских патчей | |
|
||||
| `` / `` | Найти | |
|
||||
|
||||
@@ -170,6 +171,7 @@ _Связки клавиш_
|
||||
| `` 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. |
|
||||
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
|
||||
| `` r `` | Перефразировать коммит | Reword the selected commit's message. |
|
||||
| `` R `` | Переписать коммит с помощью редактора | |
|
||||
| `` d `` | Удалить коммит | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
|
||||
@@ -306,7 +308,7 @@ _Связки клавиш_
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` / `` | Найти | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Статус
|
||||
|
||||
@@ -397,7 +399,7 @@ _Связки клавиш_
|
||||
| `` - `` | Collapse all files | Collapse all directories in the files tree |
|
||||
| `` = `` | Expand all files | Expand all directories in the file tree |
|
||||
| `` 0 `` | Focus main view | |
|
||||
| `` / `` | Найти | |
|
||||
| `` / `` | Filter the current view by text | |
|
||||
|
||||
## Хранилище
|
||||
|
||||
|
||||
@@ -126,6 +126,7 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
|
||||
| `` b `` | 查看二分查找选项 | |
|
||||
| `` s `` | 压缩(Squash) | 将已选提交压缩到该提交之下。这些选定的提交的消息会附加到该提交的消息之下。 |
|
||||
| `` f `` | 修正 (fixup) | 将选定的提交合并到其下面的提交中。与压缩类似,但所选提交的消息将被丢弃。 |
|
||||
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
|
||||
| `` r `` | 改写提交 | 重写所选提交的消息。 |
|
||||
| `` R `` | 使用编辑器重命名提交 | |
|
||||
| `` d `` | 删除提交 | 删除选中的提交。这将通过变基从分支中删除该提交,如果该提交修改的内容依赖于后续的提交,则需要解决合并冲突。 |
|
||||
@@ -182,7 +183,7 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
|
||||
| `` - `` | 折叠全部文件 | 折叠文件树中的全部目录 |
|
||||
| `` = `` | 展开全部文件 | 展开文件树中的全部目录 |
|
||||
| `` 0 `` | 聚焦主视图 | |
|
||||
| `` / `` | 开始搜索 | |
|
||||
| `` / `` | 通过文本过滤当前视图 | |
|
||||
|
||||
## 文件
|
||||
|
||||
@@ -215,7 +216,7 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
|
||||
| `` - `` | 折叠全部文件 | 折叠文件树中的全部目录 |
|
||||
| `` = `` | 展开全部文件 | 展开文件树中的全部目录 |
|
||||
| `` 0 `` | 聚焦主视图 | |
|
||||
| `` / `` | 开始搜索 | |
|
||||
| `` / `` | 通过文本过滤当前视图 | |
|
||||
|
||||
## 本地分支
|
||||
|
||||
@@ -234,7 +235,7 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
|
||||
| `` F `` | 强制检出 | 强制检出所选分支。这将在检出所选分支之前放弃工作目录中的所有本地更改。 |
|
||||
| `` d `` | 删除 | 查看本地/远程分支的删除选项 |
|
||||
| `` r `` | 变基 | 将检出的分支变基到所选的分支上。 |
|
||||
| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. |
|
||||
| `` M `` | 合并到当前检出的分支 | 查看将选中项合并到当前分支的选项(正常合并,压缩合并) |
|
||||
| `` f `` | 从上游快进此分支 | 将当前分支直接移动到远程追踪分支的最新提交 |
|
||||
| `` T `` | 创建标签 | |
|
||||
| `` s `` | 排序 | |
|
||||
@@ -259,6 +260,7 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
|
||||
| `` o `` | 打开文件 | 使用默认程序打开该文件 |
|
||||
| `` e `` | 编辑文件 | 使用外部编辑器打开文件 |
|
||||
| `` <space> `` | 添加/移除 行到补丁 | |
|
||||
| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. |
|
||||
| `` <esc> `` | 退出逐行模式 | |
|
||||
| `` / `` | 开始搜索 | |
|
||||
|
||||
@@ -401,7 +403,7 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
|
||||
| `` <c-o> `` | 复制分支名称到剪贴板 | |
|
||||
| `` <space> `` | 检出 | 基于当前选中的远程分支检出一个新的本地分支,或者将远程分支作分离的HEAD。 |
|
||||
| `` n `` | 新分支 | |
|
||||
| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. |
|
||||
| `` M `` | 合并到当前检出的分支 | 查看将选中项合并到当前分支的选项(正常合并,压缩合并) |
|
||||
| `` r `` | 变基 | 将检出的分支变基到所选的分支上。 |
|
||||
| `` d `` | 删除 | 从远程删除远程分支。 |
|
||||
| `` u `` | 设置为上游 | 设置为检出分支的上游 |
|
||||
|
||||
@@ -72,6 +72,7 @@ _說明:`<c-b>` 表示 Ctrl+B、`<a-b>` 表示 Alt+B,`B`表示 Shift+B
|
||||
| `` o `` | 開啟檔案 | 使用預設軟體開啟 |
|
||||
| `` e `` | 編輯檔案 | 使用外部編輯器開啟 |
|
||||
| `` <space> `` | 向 (或從) 補丁中添加/刪除行 | |
|
||||
| `` d `` | Remove lines from commit | Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines. |
|
||||
| `` <esc> `` | 退出自訂補丁建立器 | |
|
||||
| `` / `` | 搜尋 | |
|
||||
|
||||
@@ -184,6 +185,7 @@ _說明:`<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. |
|
||||
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target 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. |
|
||||
|
||||
@@ -481,6 +481,10 @@ git:
|
||||
# to 40 to disable truncation.
|
||||
truncateCopiedCommitHashesTo: 12
|
||||
|
||||
# If true and if if `gh` is installed and on version >=2, we will use `gh` to
|
||||
# display pull requests against branches.
|
||||
enableGithubCli: true
|
||||
|
||||
# Periodic update checks
|
||||
update:
|
||||
# One of: 'prompt' (default) | 'background' | 'never'
|
||||
@@ -713,6 +717,7 @@ keybinding:
|
||||
renameCommitWithEditor: R
|
||||
viewResetOptions: g
|
||||
markCommitAsFixup: f
|
||||
setFixupMessage: c
|
||||
createFixupCommit: F
|
||||
squashAboveCommits: S
|
||||
moveDownCommit: <c-j>
|
||||
|
||||
@@ -89,6 +89,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` b `` | View bisect options | |
|
||||
| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
|
||||
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
|
||||
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
|
||||
| `` r `` | Reword | Reword the selected commit's message. |
|
||||
| `` R `` | Reword with editor | |
|
||||
| `` d `` | Drop | 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. |
|
||||
|
||||
@@ -69,6 +69,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
|
||||
| `` b `` | bisectオプションを表示 | |
|
||||
| `` s `` | スカッシュ | 選択したコミットをその下のコミットにスカッシュします。スカッシュとは複数のコミットを1つにまとめる操作です。選択したコミットのメッセージが下のコミットに追加されます。 |
|
||||
| `` f `` | フィックスアップ | 選択したコミットをその下のコミットにマージします。フィックスアップはスカッシュと似ていますが、選択したコミットのメッセージは破棄され、下のコミットのメッセージのみが保持されます。 |
|
||||
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
|
||||
| `` r `` | メッセージ変更 | 選択したコミットのメッセージを変更します。 |
|
||||
| `` R `` | エディタでメッセージ変更 | |
|
||||
| `` d `` | 削除 | 選択したコミットを削除します。これはリベースを通じてブランチからコミットを削除します。コミットが後続のコミットが依存する変更を行っている場合、マージコンフリクトを解決する必要があるかもしれません。 |
|
||||
@@ -114,7 +115,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
|
||||
| `` <space> `` | パッチに含めるファイルを切り替え | ファイルがカスタムパッチに含まれるかどうかを切り替えます。https://github.com/jesseduffield/lazygit#rebase-magic-custom-patchesを参照してください。 |
|
||||
| `` a `` | すべてのファイルを切り替え | コミットのすべてのファイルをカスタムパッチに追加/削除します。https://github.com/jesseduffield/lazygit#rebase-magic-custom-patchesを参照してください。 |
|
||||
| `` <enter> `` | ファイルに入る / ディレクトリの折りたたみを切り替える | ファイルが選択されている場合、そのファイルに入ってカスタムパッチに個々の行を追加/削除できます。ディレクトリが選択されている場合、ディレクトリを切り替えます。 |
|
||||
| `` ` `` | ファイルツリービューを切り替え | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
|
||||
| `` ` `` | ファイルツリービューを切り替え | ファイル表示をフラット表示とツリー表示で切り替えます。フラット表示はすべてのファイルパスを一覧で表示し、ツリー表示はディレクトリごとにファイルをグループ化します。<br><br>デフォルトは設定ファイル内の 'gui.showFileTree' キーで変更できます。 |
|
||||
| `` - `` | すべてのファイルを折りたたむ | ファイルツリー内のすべてのディレクトリを折りたたみます |
|
||||
| `` = `` | すべてのファイルを展開 | ファイルツリー内のすべてのディレクトリを展開します |
|
||||
| `` 0 `` | メインビューにフォーカス | |
|
||||
@@ -234,7 +235,7 @@ _凡例:`<c-b>` はctrl+b、`<a-b>` はalt+b、`B` はshift+bを意味
|
||||
| `` d `` | 破棄 | 選択したファイルの変更を破棄するオプションを表示します。 |
|
||||
| `` g `` | アップストリームへのリセットオプションを表示 | |
|
||||
| `` D `` | リセット | 作業ツリーのリセットオプション(例:作業ツリーの完全破棄)を表示します。 |
|
||||
| `` ` `` | ファイルツリービューを切り替え | Toggle file view between flat and tree layout. Flat layout shows all file paths in a single list, tree layout groups files by directory.<br><br>The default can be changed in the config file with the key 'gui.showFileTree'. |
|
||||
| `` ` `` | ファイルツリービューを切り替え | ファイル表示をフラット表示とツリー表示で切り替えます。フラット表示はすべてのファイルパスを一覧で表示し、ツリー表示はディレクトリごとにファイルをグループ化します。<br><br>デフォルトは設定ファイル内の 'gui.showFileTree' キーで変更できます。 |
|
||||
| `` <c-t> `` | 外部差分ツールを開く(git difftool) | |
|
||||
| `` M `` | View merge conflict options | View options for resolving merge conflicts. |
|
||||
| `` f `` | フェッチ | リモートから変更をフェッチします。 |
|
||||
|
||||
@@ -298,6 +298,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` b `` | Bisect 옵션 보기 | |
|
||||
| `` 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. |
|
||||
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
|
||||
| `` r `` | 커밋메시지 변경 | Reword the selected commit's message. |
|
||||
| `` R `` | 에디터에서 커밋메시지 수정 | |
|
||||
| `` d `` | 커밋 삭제 | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
|
||||
|
||||
@@ -160,6 +160,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` b `` | View bisect options | |
|
||||
| `` s `` | Squash | Squash the selected commit into the commit below it. The selected commit's message will be appended to the commit below it. |
|
||||
| `` f `` | Fixup | Meld the selected commit into the commit below it. Similar to squash, but the selected commit's message will be discarded. |
|
||||
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
|
||||
| `` r `` | Hernoem commit | Reword the selected commit's message. |
|
||||
| `` R `` | Hernoem commit met editor | |
|
||||
| `` d `` | Verwijder commit | 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. |
|
||||
|
||||
@@ -62,6 +62,7 @@ _Legenda: `<c-b>` oznacza ctrl+b, `<a-b>` oznacza alt+b, `B` oznacza shift+b_
|
||||
| `` b `` | Zobacz opcje bisect | |
|
||||
| `` s `` | Scal | Scal wybrany commit z commitami poniżej. Wiadomość wybranego commita zostanie dołączona do commita poniżej. |
|
||||
| `` f `` | Poprawka | Włącz wybrany commit do commita poniżej. Podobnie do fixup, ale wiadomość wybranego commita zostanie odrzucona. |
|
||||
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
|
||||
| `` r `` | Przeformułuj | Przeformułuj wiadomość wybranego commita. |
|
||||
| `` R `` | Przeformułuj za pomocą edytora | |
|
||||
| `` d `` | Usuń | Usuń wybrany commit. To usunie commit z gałęzi za pomocą rebazowania. Jeśli commit wprowadza zmiany, od których zależą późniejsze commity, być może będziesz musiał rozwiązać konflikty scalania. |
|
||||
|
||||
@@ -164,6 +164,7 @@ _Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b_
|
||||
| `` 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. |
|
||||
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
|
||||
| `` 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. |
|
||||
|
||||
@@ -170,6 +170,7 @@ _Связки клавиш_
|
||||
| `` 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. |
|
||||
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
|
||||
| `` r `` | Перефразировать коммит | Reword the selected commit's message. |
|
||||
| `` R `` | Переписать коммит с помощью редактора | |
|
||||
| `` d `` | Удалить коммит | Drop the selected commit. This will remove the commit from the branch via a rebase. If the commit makes changes that later commits depend on, you may need to resolve merge conflicts. |
|
||||
|
||||
@@ -126,6 +126,7 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
|
||||
| `` b `` | 查看二分查找选项 | |
|
||||
| `` s `` | 压缩(Squash) | 将已选提交压缩到该提交之下。这些选定的提交的消息会附加到该提交的消息之下。 |
|
||||
| `` f `` | 修正 (fixup) | 将选定的提交合并到其下面的提交中。与压缩类似,但所选提交的消息将被丢弃。 |
|
||||
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message. |
|
||||
| `` r `` | 改写提交 | 重写所选提交的消息。 |
|
||||
| `` R `` | 使用编辑器重命名提交 | |
|
||||
| `` d `` | 删除提交 | 删除选中的提交。这将通过变基从分支中删除该提交,如果该提交修改的内容依赖于后续的提交,则需要解决合并冲突。 |
|
||||
@@ -234,7 +235,7 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
|
||||
| `` F `` | 强制检出 | 强制检出所选分支。这将在检出所选分支之前放弃工作目录中的所有本地更改。 |
|
||||
| `` d `` | 删除 | 查看本地/远程分支的删除选项 |
|
||||
| `` r `` | 变基 | 将检出的分支变基到所选的分支上。 |
|
||||
| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. |
|
||||
| `` M `` | 合并到当前检出的分支 | 查看将选中项合并到当前分支的选项(正常合并,压缩合并) |
|
||||
| `` f `` | 从上游快进此分支 | 将当前分支直接移动到远程追踪分支的最新提交 |
|
||||
| `` T `` | 创建标签 | |
|
||||
| `` s `` | 排序 | |
|
||||
@@ -401,7 +402,7 @@ _图例:`<c-b>` 意味着ctrl+b, `<a-b>意味着Alt+b, `B` 意味着shift+b_
|
||||
| `` <c-o> `` | 复制分支名称到剪贴板 | |
|
||||
| `` <space> `` | 检出 | 基于当前选中的远程分支检出一个新的本地分支,或者将远程分支作分离的HEAD。 |
|
||||
| `` n `` | 新分支 | |
|
||||
| `` M `` | 合并到当前检出的分支 | Merge selected branch into currently checked out branch. |
|
||||
| `` M `` | 合并到当前检出的分支 | 查看将选中项合并到当前分支的选项(正常合并,压缩合并) |
|
||||
| `` r `` | 变基 | 将检出的分支变基到所选的分支上。 |
|
||||
| `` d `` | 删除 | 从远程删除远程分支。 |
|
||||
| `` u `` | 设置为上游 | 设置为检出分支的上游 |
|
||||
|
||||
@@ -184,6 +184,7 @@ _說明:`<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. |
|
||||
| `` c `` | Set fixup message | Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target 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. |
|
||||
|
||||
@@ -10,15 +10,16 @@ require (
|
||||
github.com/adrg/xdg v0.4.0
|
||||
github.com/atotto/clipboard v0.1.4
|
||||
github.com/aybabtme/humanlog v0.4.1
|
||||
github.com/cli/go-gh/v2 v2.9.0
|
||||
github.com/cloudfoundry/jibber_jabber v0.0.0-20151120183258-bcc4c8345a21
|
||||
github.com/creack/pty v1.1.11
|
||||
github.com/gdamore/tcell/v2 v2.13.7
|
||||
github.com/gdamore/tcell/v2 v2.13.8
|
||||
github.com/go-errors/errors v1.5.1
|
||||
github.com/gookit/color v1.4.2
|
||||
github.com/integrii/flaggy v1.4.0
|
||||
github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c
|
||||
github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20260111170441-330357056207
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20260128194906-9d8c3cdfac18
|
||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
|
||||
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0
|
||||
github.com/karimkhaleel/jsonschema v0.0.0-20231001195015-d933f0d94ea3
|
||||
@@ -39,7 +40,7 @@ require (
|
||||
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778
|
||||
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56
|
||||
golang.org/x/sync v0.19.0
|
||||
golang.org/x/sys v0.39.0
|
||||
golang.org/x/sys v0.40.0
|
||||
gopkg.in/ozeidan/fuzzy-patricia.v3 v3.0.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
@@ -49,6 +50,7 @@ require (
|
||||
github.com/ProtonMail/go-crypto v1.1.6 // indirect
|
||||
github.com/bahlo/generic-list-go v0.2.0 // indirect
|
||||
github.com/buger/jsonparser v1.1.1 // indirect
|
||||
github.com/cli/safeexec v1.0.0 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
@@ -66,8 +68,8 @@ require (
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-colorable v0.1.11 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/onsi/ginkgo v1.10.3 // indirect
|
||||
github.com/petermattis/goid v0.0.0-20250813065127-a731cc31b4fe // indirect
|
||||
github.com/pjbgf/sha1cd v0.3.2 // indirect
|
||||
@@ -78,8 +80,8 @@ require (
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
golang.org/x/crypto v0.45.0 // indirect
|
||||
golang.org/x/net v0.47.0 // indirect
|
||||
golang.org/x/term v0.38.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
golang.org/x/term v0.39.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
gopkg.in/fsnotify.v1 v1.4.7 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
|
||||
@@ -64,6 +64,10 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
|
||||
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
|
||||
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
|
||||
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
|
||||
github.com/cli/go-gh/v2 v2.9.0 h1:D3lTjEneMYl54M+WjZ+kRPrR5CEJ5BHS05isBPOV3LI=
|
||||
github.com/cli/go-gh/v2 v2.9.0/go.mod h1:MeRoKzXff3ygHu7zP+NVTT+imcHW6p3tpuxHAzRM2xE=
|
||||
github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
|
||||
github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||
@@ -95,8 +99,8 @@ 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/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.13.7 h1:yfHdeC7ODIYCc6dgRos8L1VujQtXHmUpU6UZotzD6os=
|
||||
github.com/gdamore/tcell/v2 v2.13.7/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo=
|
||||
github.com/gdamore/tcell/v2 v2.13.8 h1:Mys/Kl5wfC/GcC5Cx4C2BIQH9dbnhnkPgS9/wF3RlfU=
|
||||
github.com/gdamore/tcell/v2 v2.13.8/go.mod h1:+Wfe208WDdB7INEtCsNrAN6O2m+wsTPk1RAovjaILlo=
|
||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||
github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk=
|
||||
@@ -189,8 +193,8 @@ github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c h1:tC2Paiis
|
||||
github.com/jesseduffield/generics v0.0.0-20250517122708-b0b4a53a6f5c/go.mod h1:F2fEBk0ddf6ixrBrJjY7phfQ3hL9rXG0uSjvwYe50bE=
|
||||
github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd h1:ViKj6qth8FgcIWizn9KiACWwPemWSymx62OPN0tHT+Q=
|
||||
github.com/jesseduffield/go-git/v5 v5.14.1-0.20250407170251-e1a013310ccd/go.mod h1:lRhCiBr6XjQrvcQVa+UYsy/99d3wMXn/a0nSQlhnhlA=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20260111170441-330357056207 h1:YYXHnZVN9nt2UsBO6dYPVPRDVJuEVcGIzhM0SH9x4/Q=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20260111170441-330357056207/go.mod h1:lQCd2TvvNXVKFBowy4A7xxZbUp+1KEiGs4j0Q5Zt9gQ=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20260128194906-9d8c3cdfac18 h1:+Q17GqvNaGzuvIR1JCdIS0khVjMzdwrhXBBDxnsdN8Y=
|
||||
github.com/jesseduffield/gocui v0.3.1-0.20260128194906-9d8c3cdfac18/go.mod h1:lQCd2TvvNXVKFBowy4A7xxZbUp+1KEiGs4j0Q5Zt9gQ=
|
||||
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/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
@@ -223,13 +227,14 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-colorable v0.1.0/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
|
||||
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
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-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mgutz/str v1.2.0 h1:4IzWSdIz9qPQWLfKZ0rJcV0jcUDpxvP4JVZ4GXQyvSw=
|
||||
github.com/mgutz/str v1.2.0/go.mod h1:w1v0ofgLaJdoD0HpQ3fycxKD1WtxpjSo151pK/31q6w=
|
||||
github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc=
|
||||
@@ -451,20 +456,20 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
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.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q=
|
||||
golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg=
|
||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
||||
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=
|
||||
@@ -475,8 +480,8 @@ 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.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||
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=
|
||||
|
||||
@@ -199,6 +199,7 @@ func (self *ChangeTodoActionsInstruction) run(common *common.Common) error {
|
||||
return utils.TodoChange{
|
||||
Hash: c.Hash,
|
||||
NewAction: c.NewAction,
|
||||
Flag: c.Flag,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
type ChangeTodoAction struct {
|
||||
Hash string
|
||||
NewAction todo.TodoCommand
|
||||
Flag string
|
||||
}
|
||||
|
||||
func handleInteractiveRebase(common *common.Common, f func(path string) error) error {
|
||||
|
||||
+47
-41
@@ -18,27 +18,29 @@ import (
|
||||
|
||||
// GitCommand is our main git interface
|
||||
type GitCommand struct {
|
||||
Blame *git_commands.BlameCommands
|
||||
Branch *git_commands.BranchCommands
|
||||
Commit *git_commands.CommitCommands
|
||||
Config *git_commands.ConfigCommands
|
||||
Custom *git_commands.CustomCommands
|
||||
Diff *git_commands.DiffCommands
|
||||
File *git_commands.FileCommands
|
||||
Flow *git_commands.FlowCommands
|
||||
Patch *git_commands.PatchCommands
|
||||
Rebase *git_commands.RebaseCommands
|
||||
Remote *git_commands.RemoteCommands
|
||||
Stash *git_commands.StashCommands
|
||||
Status *git_commands.StatusCommands
|
||||
Submodule *git_commands.SubmoduleCommands
|
||||
Sync *git_commands.SyncCommands
|
||||
Tag *git_commands.TagCommands
|
||||
WorkingTree *git_commands.WorkingTreeCommands
|
||||
Bisect *git_commands.BisectCommands
|
||||
Worktree *git_commands.WorktreeCommands
|
||||
Version *git_commands.GitVersion
|
||||
RepoPaths *git_commands.RepoPaths
|
||||
Blame *git_commands.BlameCommands
|
||||
Branch *git_commands.BranchCommands
|
||||
Commit *git_commands.CommitCommands
|
||||
Config *git_commands.ConfigCommands
|
||||
Custom *git_commands.CustomCommands
|
||||
Diff *git_commands.DiffCommands
|
||||
File *git_commands.FileCommands
|
||||
Flow *git_commands.FlowCommands
|
||||
Patch *git_commands.PatchCommands
|
||||
Rebase *git_commands.RebaseCommands
|
||||
Remote *git_commands.RemoteCommands
|
||||
Stash *git_commands.StashCommands
|
||||
Status *git_commands.StatusCommands
|
||||
Submodule *git_commands.SubmoduleCommands
|
||||
Sync *git_commands.SyncCommands
|
||||
Tag *git_commands.TagCommands
|
||||
WorkingTree *git_commands.WorkingTreeCommands
|
||||
Bisect *git_commands.BisectCommands
|
||||
Worktree *git_commands.WorktreeCommands
|
||||
Version *git_commands.GitVersion
|
||||
RepoPaths *git_commands.RepoPaths
|
||||
GitHub *git_commands.GitHubCommands
|
||||
HostingService *git_commands.HostingService
|
||||
|
||||
Loaders Loaders
|
||||
}
|
||||
@@ -137,6 +139,8 @@ func NewGitCommandAux(
|
||||
bisectCommands := git_commands.NewBisectCommands(gitCommon)
|
||||
worktreeCommands := git_commands.NewWorktreeCommands(gitCommon)
|
||||
blameCommands := git_commands.NewBlameCommands(gitCommon)
|
||||
gitHubCommands := git_commands.NewGitHubCommand(gitCommon)
|
||||
hostingServiceCommands := git_commands.NewHostingServiceCommand(gitCommon)
|
||||
|
||||
branchLoader := git_commands.NewBranchLoader(cmn, gitCommon, cmd, branchCommands.CurrentBranchInfo, configCommands)
|
||||
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
|
||||
@@ -148,26 +152,28 @@ func NewGitCommandAux(
|
||||
tagLoader := git_commands.NewTagLoader(cmn, cmd)
|
||||
|
||||
return &GitCommand{
|
||||
Blame: blameCommands,
|
||||
Branch: branchCommands,
|
||||
Commit: commitCommands,
|
||||
Config: configCommands,
|
||||
Custom: customCommands,
|
||||
Diff: diffCommands,
|
||||
File: fileCommands,
|
||||
Flow: flowCommands,
|
||||
Patch: patchCommands,
|
||||
Rebase: rebaseCommands,
|
||||
Remote: remoteCommands,
|
||||
Stash: stashCommands,
|
||||
Status: statusCommands,
|
||||
Submodule: submoduleCommands,
|
||||
Sync: syncCommands,
|
||||
Tag: tagCommands,
|
||||
Bisect: bisectCommands,
|
||||
WorkingTree: workingTreeCommands,
|
||||
Worktree: worktreeCommands,
|
||||
Version: version,
|
||||
Blame: blameCommands,
|
||||
Branch: branchCommands,
|
||||
Commit: commitCommands,
|
||||
Config: configCommands,
|
||||
Custom: customCommands,
|
||||
Diff: diffCommands,
|
||||
File: fileCommands,
|
||||
Flow: flowCommands,
|
||||
Patch: patchCommands,
|
||||
Rebase: rebaseCommands,
|
||||
Remote: remoteCommands,
|
||||
Stash: stashCommands,
|
||||
Status: statusCommands,
|
||||
Submodule: submoduleCommands,
|
||||
Sync: syncCommands,
|
||||
Tag: tagCommands,
|
||||
Bisect: bisectCommands,
|
||||
WorkingTree: workingTreeCommands,
|
||||
Worktree: worktreeCommands,
|
||||
Version: version,
|
||||
GitHub: gitHubCommands,
|
||||
HostingService: hostingServiceCommands,
|
||||
Loaders: Loaders{
|
||||
BranchLoader: branchLoader,
|
||||
CommitFileLoader: commitFileLoader,
|
||||
|
||||
@@ -154,7 +154,7 @@ func (self *BisectCommands) IsDone() (bool, []string, error) {
|
||||
candidates := []string{}
|
||||
|
||||
cmdArgs := NewGitCmd("rev-list").Arg(newHash).ToArgv()
|
||||
err := self.cmd.New(cmdArgs).RunAndProcessLines(func(line string) (bool, error) {
|
||||
err := self.cmd.New(cmdArgs).DontLog().RunAndProcessLines(func(line string) (bool, error) {
|
||||
hash := strings.TrimSpace(line)
|
||||
|
||||
if status, ok := info.statusMap[hash]; ok {
|
||||
|
||||
@@ -325,6 +325,7 @@ func (self *CommitLoader) getHydratedTodoCommits(hashPool *utils.StringPool, tod
|
||||
hydratedCommits = append(hydratedCommits, rebasingCommit)
|
||||
} else if commit := findFullCommit(rebasingCommit.Hash()); commit != nil {
|
||||
commit.Action = rebasingCommit.Action
|
||||
commit.ActionFlag = rebasingCommit.ActionFlag
|
||||
commit.Status = rebasingCommit.Status
|
||||
hydratedCommits = append(hydratedCommits, commit)
|
||||
}
|
||||
@@ -371,10 +372,11 @@ func (self *CommitLoader) getRebasingCommits(hashPool *utils.StringPool, addConf
|
||||
continue
|
||||
}
|
||||
commits = utils.Prepend(commits, models.NewCommit(hashPool, models.NewCommitOpts{
|
||||
Hash: t.Commit,
|
||||
Name: t.Msg,
|
||||
Status: models.StatusRebasing,
|
||||
Action: t.Command,
|
||||
Hash: t.Commit,
|
||||
Name: t.Msg,
|
||||
Status: models.StatusRebasing,
|
||||
Action: t.Command,
|
||||
ActionFlag: t.Flag,
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cli/go-gh/v2/pkg/auth"
|
||||
gogit "github.com/jesseduffield/go-git/v5"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/hosting_service"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type GitHubCommands struct {
|
||||
*GitCommon
|
||||
}
|
||||
|
||||
func NewGitHubCommand(gitCommon *GitCommon) *GitHubCommands {
|
||||
return &GitHubCommands{
|
||||
GitCommon: gitCommon,
|
||||
}
|
||||
}
|
||||
|
||||
// https://github.com/cli/cli/issues/2300
|
||||
func (self *GitHubCommands) BaseRepo() error {
|
||||
cmdArgs := NewGitCmd("config").
|
||||
Arg("--local", "--get-regexp", ".gh-resolved").
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).DontLog().Run()
|
||||
}
|
||||
|
||||
// Ex: git config --local --add "remote.origin.gh-resolved" "jesseduffield/lazygit"
|
||||
func (self *GitHubCommands) SetBaseRepo(repository string) (string, error) {
|
||||
cmdArgs := NewGitCmd("config").
|
||||
Arg("--local", "--add", "remote.origin.gh-resolved", repository).
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).DontLog().RunWithOutput()
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Data RepositoryQuery `json:"data"`
|
||||
}
|
||||
|
||||
type RepositoryQuery struct {
|
||||
Repository map[string]PullRequest `json:"repository"`
|
||||
}
|
||||
|
||||
type PullRequest struct {
|
||||
Edges []PullRequestEdge `json:"edges"`
|
||||
}
|
||||
|
||||
type PullRequestEdge struct {
|
||||
Node PullRequestNode `json:"node"`
|
||||
}
|
||||
|
||||
type PullRequestNode struct {
|
||||
Title string `json:"title"`
|
||||
HeadRefName string `json:"headRefName"`
|
||||
Number int `json:"number"`
|
||||
Url string `json:"url"`
|
||||
HeadRepositoryOwner GithubRepositoryOwner `json:"headRepositoryOwner"`
|
||||
State string `json:"state"`
|
||||
IsDraft bool `json:"isDraft"`
|
||||
}
|
||||
|
||||
type GithubRepositoryOwner struct {
|
||||
Login string `json:"login"`
|
||||
}
|
||||
|
||||
func fetchPullRequestsQuery(branches []string, owner string, repo string) string {
|
||||
queries := make([]string, 0, len(branches))
|
||||
for i, branch := range branches {
|
||||
// We're making a sub-query per branch, and arbitrarily labelling each subquery
|
||||
// as a1, a2, etc.
|
||||
fieldName := fmt.Sprintf("a%d", i+1)
|
||||
// We fetch a few PRs per branch name because multiple forks may have PRs
|
||||
// with the same head ref name. The mapping logic filters by owner later.
|
||||
queries = append(queries, fmt.Sprintf(`%s: pullRequests(first: 5, headRefName: "%s", orderBy: {field: CREATED_AT, direction: DESC}) {
|
||||
edges {
|
||||
node {
|
||||
title
|
||||
headRefName
|
||||
state
|
||||
number
|
||||
url
|
||||
isDraft
|
||||
headRepositoryOwner {
|
||||
login
|
||||
}
|
||||
}
|
||||
}
|
||||
}`, fieldName, branch))
|
||||
}
|
||||
|
||||
queryString := fmt.Sprintf(`{
|
||||
repository(owner: "%s", name: "%s") {
|
||||
%s
|
||||
}
|
||||
}`, owner, repo, strings.Join(queries, "\n"))
|
||||
|
||||
return queryString
|
||||
}
|
||||
|
||||
// FetchRecentPRs fetches recent pull requests using GraphQL.
|
||||
func (self *GitHubCommands) FetchRecentPRs(branches []string) ([]*models.GithubPullRequest, error) {
|
||||
repoOwner, repoName, err := self.GetBaseRepoOwnerAndName()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
|
||||
var g errgroup.Group
|
||||
results := make(chan []*models.GithubPullRequest)
|
||||
|
||||
// We want at most 5 concurrent requests, but no less than 10 branches per request
|
||||
concurrency := 5
|
||||
minBranchesPerRequest := 10
|
||||
branchesPerRequest := max(len(branches)/concurrency, minBranchesPerRequest)
|
||||
for i := 0; i < len(branches); i += branchesPerRequest {
|
||||
end := i + branchesPerRequest
|
||||
if end > len(branches) {
|
||||
end = len(branches)
|
||||
}
|
||||
branchChunk := branches[i:end]
|
||||
|
||||
// Launch a goroutine for each chunk of branches
|
||||
g.Go(func() error {
|
||||
prs, err := self.FetchRecentPRsAux(repoOwner, repoName, branchChunk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
results <- prs
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Close the results channel when all goroutines are done
|
||||
go func() {
|
||||
_ = g.Wait()
|
||||
close(results)
|
||||
}()
|
||||
|
||||
// Collect results from all goroutines
|
||||
var allPRs []*models.GithubPullRequest
|
||||
for prs := range results {
|
||||
allPRs = append(allPRs, prs...)
|
||||
}
|
||||
|
||||
if err := g.Wait(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
self.Log.Warnf("Fetched PRs in %s", time.Since(t))
|
||||
|
||||
return allPRs, nil
|
||||
}
|
||||
|
||||
func (self *GitHubCommands) FetchRecentPRsAux(repoOwner string, repoName string, branches []string) ([]*models.GithubPullRequest, error) {
|
||||
queryString := fetchPullRequestsQuery(branches, repoOwner, repoName)
|
||||
escapedQueryString := strconv.Quote(queryString)
|
||||
|
||||
body := fmt.Sprintf(`{"query": %s}`, escapedQueryString)
|
||||
req, err := http.NewRequest("POST", "https://api.github.com/graphql", bytes.NewBuffer([]byte(body)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defaultHost, _ := auth.DefaultHost()
|
||||
token, _ := auth.TokenForHost(defaultHost)
|
||||
if token == "" {
|
||||
return nil, fmt.Errorf("No token found for GitHub")
|
||||
}
|
||||
req.Header.Set("Authorization", "token "+token)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
bodyStr := new(bytes.Buffer)
|
||||
_, _ = bodyStr.ReadFrom(resp.Body)
|
||||
return nil, fmt.Errorf("GraphQL query failed with status: %s. Body: %s", resp.Status, bodyStr.String())
|
||||
}
|
||||
|
||||
bodyBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result Response
|
||||
err = json.Unmarshal(bodyBytes, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prs := []*models.GithubPullRequest{}
|
||||
for _, repoQuery := range result.Data.Repository {
|
||||
for _, edge := range repoQuery.Edges {
|
||||
node := edge.Node
|
||||
pr := &models.GithubPullRequest{
|
||||
HeadRefName: node.HeadRefName,
|
||||
Number: node.Number,
|
||||
Title: node.Title,
|
||||
State: lo.Ternary(node.IsDraft && node.State != "CLOSED", "DRAFT", node.State),
|
||||
Url: node.Url,
|
||||
HeadRepositoryOwner: models.GithubRepositoryOwner{
|
||||
Login: node.HeadRepositoryOwner.Login,
|
||||
},
|
||||
}
|
||||
prs = append(prs, pr)
|
||||
}
|
||||
}
|
||||
|
||||
return prs, nil
|
||||
}
|
||||
|
||||
// returns a map from branch name to pull request
|
||||
func GenerateGithubPullRequestMap(
|
||||
prs []*models.GithubPullRequest,
|
||||
branches []*models.Branch,
|
||||
remotes []*models.Remote,
|
||||
) map[string]*models.GithubPullRequest {
|
||||
res := map[string]*models.GithubPullRequest{}
|
||||
|
||||
if len(prs) == 0 {
|
||||
return res
|
||||
}
|
||||
|
||||
remotesToOwnersMap := getRemotesToOwnersMap(remotes)
|
||||
|
||||
// A PR can be identified by two things: the owner e.g. 'jesseduffield' and the
|
||||
// branch name e.g. 'feature/my-feature'. The owner might be different
|
||||
// to the owner of the repo if the PR is from a fork of that repo.
|
||||
type prKey struct {
|
||||
owner string
|
||||
branchName string
|
||||
}
|
||||
|
||||
prByKey := map[prKey]models.GithubPullRequest{}
|
||||
|
||||
for _, pr := range prs {
|
||||
key := prKey{owner: pr.UserName(), branchName: pr.BranchName()}
|
||||
// PRs are returned newest-first from the API, so the first one we
|
||||
// see for each key is the most recent and therefore the most relevant.
|
||||
if _, exists := prByKey[key]; !exists {
|
||||
prByKey[key] = *pr
|
||||
}
|
||||
}
|
||||
|
||||
for _, branch := range branches {
|
||||
if !branch.IsTrackingRemote() {
|
||||
continue
|
||||
}
|
||||
|
||||
owner, foundRemoteOwner := remotesToOwnersMap[branch.UpstreamRemote]
|
||||
if !foundRemoteOwner {
|
||||
// UpstreamRemote may be a full URL rather than a remote name;
|
||||
// try parsing the owner directly from it.
|
||||
repoInfo, err := hosting_service.GetRepoInfoFromURL(branch.UpstreamRemote)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
owner = repoInfo.Owner
|
||||
}
|
||||
|
||||
pr, hasPr := prByKey[prKey{owner: owner, branchName: branch.UpstreamBranch}]
|
||||
|
||||
if !hasPr {
|
||||
continue
|
||||
}
|
||||
|
||||
res[branch.Name] = &pr
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
func getRemotesToOwnersMap(remotes []*models.Remote) map[string]string {
|
||||
res := map[string]string{}
|
||||
for _, remote := range remotes {
|
||||
if len(remote.Urls) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
repoInfo, err := hosting_service.GetRepoInfoFromURL(remote.Urls[0])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
res[remote.Name] = repoInfo.Owner
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// return <installed>, <valid version>
|
||||
func (self *GitHubCommands) DetermineGitHubCliState() (bool, bool) {
|
||||
output, err := self.cmd.New([]string{"gh", "--version"}).DontLog().RunWithOutput()
|
||||
if err != nil {
|
||||
// assuming a failure here means that it's not installed
|
||||
return false, false
|
||||
}
|
||||
|
||||
if !isGhVersionValid(output) {
|
||||
return true, false
|
||||
}
|
||||
|
||||
return true, true
|
||||
}
|
||||
|
||||
func isGhVersionValid(versionStr string) bool {
|
||||
// output should be something like:
|
||||
// gh version 2.0.0 (2021-08-23)
|
||||
// https://github.com/cli/cli/releases/tag/v2.0.0
|
||||
re := regexp.MustCompile(`[^\d]+([\d\.]+)`)
|
||||
matches := re.FindStringSubmatch(versionStr)
|
||||
|
||||
if len(matches) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
ghVersion := matches[1]
|
||||
majorVersion, err := strconv.Atoi(ghVersion[0:1])
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if majorVersion < 2 {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *GitHubCommands) InGithubRepo() bool {
|
||||
remotes, err := self.repo.Remotes()
|
||||
if err != nil {
|
||||
self.Log.Error(err)
|
||||
return false
|
||||
}
|
||||
|
||||
if len(remotes) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
remote := GetMainRemote(remotes)
|
||||
|
||||
if len(remote.Config().URLs) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
url := remote.Config().URLs[0]
|
||||
return strings.Contains(url, "github.com")
|
||||
}
|
||||
|
||||
func GetMainRemote(remotes []*gogit.Remote) *gogit.Remote {
|
||||
for _, remote := range remotes {
|
||||
if remote.Config().Name == "origin" {
|
||||
return remote
|
||||
}
|
||||
}
|
||||
|
||||
// need to sort remotes by name so that this is deterministic
|
||||
return lo.MinBy(remotes, func(a, b *gogit.Remote) bool {
|
||||
return a.Config().Name < b.Config().Name
|
||||
})
|
||||
}
|
||||
|
||||
func GetSuggestedRemoteName(remotes []*models.Remote) string {
|
||||
if len(remotes) == 0 {
|
||||
return "origin"
|
||||
}
|
||||
|
||||
for _, remote := range remotes {
|
||||
if remote.Name == "origin" {
|
||||
return remote.Name
|
||||
}
|
||||
}
|
||||
|
||||
return remotes[0].Name
|
||||
}
|
||||
|
||||
func (self *GitHubCommands) GetBaseRepoOwnerAndName() (string, string, error) {
|
||||
remotes, err := self.repo.Remotes()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
if len(remotes) == 0 {
|
||||
return "", "", fmt.Errorf("No remotes found")
|
||||
}
|
||||
|
||||
originRemote, ok := lo.Find(remotes, func(remote *gogit.Remote) bool {
|
||||
return remote.Config().Name == "origin"
|
||||
})
|
||||
|
||||
if !ok {
|
||||
return "", "", fmt.Errorf("Origin remote not found")
|
||||
}
|
||||
|
||||
if len(originRemote.Config().URLs) == 0 {
|
||||
return "", "", fmt.Errorf("No URLs found for remote")
|
||||
}
|
||||
|
||||
url := originRemote.Config().URLs[0]
|
||||
|
||||
repoInfo, err := hosting_service.GetRepoInfoFromURL(url)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
return repoInfo.Owner, repoInfo.Repository, nil
|
||||
}
|
||||
@@ -0,0 +1,392 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/hosting_service"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsValidGhVersion(t *testing.T) {
|
||||
type scenario struct {
|
||||
versionStr string
|
||||
expectedResult bool
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
`gh version 1.0.0 (2020-08-23)
|
||||
https://github.com/cli/cli/releases/tag/v1.0.0`,
|
||||
false,
|
||||
},
|
||||
{
|
||||
`gh version 2.0.0 (2021-08-23)
|
||||
https://github.com/cli/cli/releases/tag/v2.0.0`,
|
||||
true,
|
||||
},
|
||||
{
|
||||
`gh version 1.1.0 (2021-10-14)
|
||||
https://github.com/cli/cli/releases/tag/v1.1.0
|
||||
|
||||
A new release of gh is available: 1.1.0 → v2.2.0
|
||||
To upgrade, run: brew update && brew upgrade gh
|
||||
https://github.com/cli/cli/releases/tag/v2.2.0`,
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.versionStr, func(t *testing.T) {
|
||||
result := isGhVersionValid(s.versionStr)
|
||||
assert.Equal(t, result, s.expectedResult)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSuggestedRemoteName(t *testing.T) {
|
||||
cases := []struct {
|
||||
remotes []*models.Remote
|
||||
expected string
|
||||
}{
|
||||
{mkRemoteList(), "origin"},
|
||||
{mkRemoteList("upstream", "origin", "foo"), "origin"},
|
||||
{mkRemoteList("upstream", "foo", "bar"), "upstream"},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
result := GetSuggestedRemoteName(c.remotes)
|
||||
assert.EqualValues(t, c.expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetRepoInfoFromURL(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
url string
|
||||
expected hosting_service.RepoInformation
|
||||
}{
|
||||
{
|
||||
name: "SSH URL",
|
||||
url: "git@github.com:jesseduffield/lazygit.git",
|
||||
expected: hosting_service.RepoInformation{
|
||||
Owner: "jesseduffield",
|
||||
Repository: "lazygit",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "HTTPS URL",
|
||||
url: "https://github.com/jesseduffield/lazygit.git",
|
||||
expected: hosting_service.RepoInformation{
|
||||
Owner: "jesseduffield",
|
||||
Repository: "lazygit",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "HTTPS URL without .git",
|
||||
url: "https://github.com/jesseduffield/lazygit",
|
||||
expected: hosting_service.RepoInformation{
|
||||
Owner: "jesseduffield",
|
||||
Repository: "lazygit",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "SSH URL with org nesting",
|
||||
url: "git@github.com:my-org/sub-group/lazygit.git",
|
||||
expected: hosting_service.RepoInformation{
|
||||
Owner: "my-org/sub-group",
|
||||
Repository: "lazygit",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
result, err := hosting_service.GetRepoInfoFromURL(c.url)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, c.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateGithubPullRequestMap(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
prs []*models.GithubPullRequest
|
||||
branches []*models.Branch
|
||||
remotes []*models.Remote
|
||||
expected map[string]*models.GithubPullRequest
|
||||
}{
|
||||
{
|
||||
name: "empty inputs",
|
||||
prs: []*models.GithubPullRequest{},
|
||||
branches: []*models.Branch{},
|
||||
remotes: []*models.Remote{},
|
||||
expected: map[string]*models.GithubPullRequest{},
|
||||
},
|
||||
{
|
||||
name: "matches PR to branch tracking origin",
|
||||
prs: []*models.GithubPullRequest{
|
||||
{
|
||||
HeadRefName: "feature-branch",
|
||||
Number: 42,
|
||||
Title: "Add feature",
|
||||
State: "OPEN",
|
||||
Url: "https://github.com/jesseduffield/lazygit/pull/42",
|
||||
HeadRepositoryOwner: models.GithubRepositoryOwner{Login: "jesseduffield"},
|
||||
},
|
||||
},
|
||||
branches: []*models.Branch{
|
||||
{
|
||||
Name: "feature-branch",
|
||||
UpstreamRemote: "origin",
|
||||
UpstreamBranch: "feature-branch",
|
||||
},
|
||||
},
|
||||
remotes: []*models.Remote{
|
||||
{
|
||||
Name: "origin",
|
||||
Urls: []string{"git@github.com:jesseduffield/lazygit.git"},
|
||||
},
|
||||
},
|
||||
expected: map[string]*models.GithubPullRequest{
|
||||
"feature-branch": {
|
||||
HeadRefName: "feature-branch",
|
||||
Number: 42,
|
||||
Title: "Add feature",
|
||||
State: "OPEN",
|
||||
Url: "https://github.com/jesseduffield/lazygit/pull/42",
|
||||
HeadRepositoryOwner: models.GithubRepositoryOwner{Login: "jesseduffield"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "does not match branch without upstream",
|
||||
prs: []*models.GithubPullRequest{
|
||||
{
|
||||
HeadRefName: "feature-branch",
|
||||
Number: 42,
|
||||
Title: "Add feature",
|
||||
State: "OPEN",
|
||||
HeadRepositoryOwner: models.GithubRepositoryOwner{Login: "jesseduffield"},
|
||||
},
|
||||
},
|
||||
branches: []*models.Branch{
|
||||
{
|
||||
Name: "feature-branch",
|
||||
// no upstream set
|
||||
},
|
||||
},
|
||||
remotes: []*models.Remote{
|
||||
{
|
||||
Name: "origin",
|
||||
Urls: []string{"git@github.com:jesseduffield/lazygit.git"},
|
||||
},
|
||||
},
|
||||
expected: map[string]*models.GithubPullRequest{},
|
||||
},
|
||||
{
|
||||
name: "matches fork PR to branch tracking fork remote",
|
||||
prs: []*models.GithubPullRequest{
|
||||
{
|
||||
HeadRefName: "fix-bug",
|
||||
Number: 99,
|
||||
Title: "Fix bug",
|
||||
State: "OPEN",
|
||||
HeadRepositoryOwner: models.GithubRepositoryOwner{Login: "contributor"},
|
||||
},
|
||||
},
|
||||
branches: []*models.Branch{
|
||||
{
|
||||
Name: "fix-bug",
|
||||
UpstreamRemote: "contributor",
|
||||
UpstreamBranch: "fix-bug",
|
||||
},
|
||||
},
|
||||
remotes: []*models.Remote{
|
||||
{
|
||||
Name: "origin",
|
||||
Urls: []string{"git@github.com:jesseduffield/lazygit.git"},
|
||||
},
|
||||
{
|
||||
Name: "contributor",
|
||||
Urls: []string{"git@github.com:contributor/lazygit.git"},
|
||||
},
|
||||
},
|
||||
expected: map[string]*models.GithubPullRequest{
|
||||
"fix-bug": {
|
||||
HeadRefName: "fix-bug",
|
||||
Number: 99,
|
||||
Title: "Fix bug",
|
||||
State: "OPEN",
|
||||
HeadRepositoryOwner: models.GithubRepositoryOwner{Login: "contributor"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "does not match when owner differs",
|
||||
prs: []*models.GithubPullRequest{
|
||||
{
|
||||
HeadRefName: "feature-branch",
|
||||
Number: 42,
|
||||
Title: "Add feature",
|
||||
State: "OPEN",
|
||||
HeadRepositoryOwner: models.GithubRepositoryOwner{Login: "someone-else"},
|
||||
},
|
||||
},
|
||||
branches: []*models.Branch{
|
||||
{
|
||||
Name: "feature-branch",
|
||||
UpstreamRemote: "origin",
|
||||
UpstreamBranch: "feature-branch",
|
||||
},
|
||||
},
|
||||
remotes: []*models.Remote{
|
||||
{
|
||||
Name: "origin",
|
||||
Urls: []string{"git@github.com:jesseduffield/lazygit.git"},
|
||||
},
|
||||
},
|
||||
expected: map[string]*models.GithubPullRequest{},
|
||||
},
|
||||
{
|
||||
name: "matches when UpstreamRemote is a full URL",
|
||||
prs: []*models.GithubPullRequest{
|
||||
{
|
||||
HeadRefName: "my-branch",
|
||||
Number: 55,
|
||||
Title: "Full URL upstream",
|
||||
State: "OPEN",
|
||||
HeadRepositoryOwner: models.GithubRepositoryOwner{Login: "contributor"},
|
||||
},
|
||||
},
|
||||
branches: []*models.Branch{
|
||||
{
|
||||
Name: "my-branch",
|
||||
UpstreamRemote: "git@github.com:contributor/lazygit.git",
|
||||
UpstreamBranch: "my-branch",
|
||||
},
|
||||
},
|
||||
remotes: []*models.Remote{
|
||||
{
|
||||
Name: "origin",
|
||||
Urls: []string{"git@github.com:jesseduffield/lazygit.git"},
|
||||
},
|
||||
},
|
||||
expected: map[string]*models.GithubPullRequest{
|
||||
"my-branch": {
|
||||
HeadRefName: "my-branch",
|
||||
Number: 55,
|
||||
Title: "Full URL upstream",
|
||||
State: "OPEN",
|
||||
HeadRepositoryOwner: models.GithubRepositoryOwner{Login: "contributor"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "uses first PR when branch name is reused (API returns newest first)",
|
||||
prs: []*models.GithubPullRequest{
|
||||
// API returns newest first (CREATED_AT DESC)
|
||||
{
|
||||
HeadRefName: "update-sponsors",
|
||||
Number: 50,
|
||||
Title: "Newest PR",
|
||||
State: "CLOSED",
|
||||
Url: "https://github.com/jesseduffield/lazygit/pull/50",
|
||||
HeadRepositoryOwner: models.GithubRepositoryOwner{Login: "jesseduffield"},
|
||||
},
|
||||
{
|
||||
HeadRefName: "update-sponsors",
|
||||
Number: 30,
|
||||
Title: "Middle PR",
|
||||
State: "OPEN",
|
||||
Url: "https://github.com/jesseduffield/lazygit/pull/30",
|
||||
HeadRepositoryOwner: models.GithubRepositoryOwner{Login: "jesseduffield"},
|
||||
},
|
||||
{
|
||||
HeadRefName: "update-sponsors",
|
||||
Number: 10,
|
||||
Title: "Oldest PR",
|
||||
State: "CLOSED",
|
||||
Url: "https://github.com/jesseduffield/lazygit/pull/10",
|
||||
HeadRepositoryOwner: models.GithubRepositoryOwner{Login: "jesseduffield"},
|
||||
},
|
||||
},
|
||||
branches: []*models.Branch{
|
||||
{
|
||||
Name: "update-sponsors",
|
||||
UpstreamRemote: "origin",
|
||||
UpstreamBranch: "update-sponsors",
|
||||
},
|
||||
},
|
||||
remotes: []*models.Remote{
|
||||
{
|
||||
Name: "origin",
|
||||
Urls: []string{"git@github.com:jesseduffield/lazygit.git"},
|
||||
},
|
||||
},
|
||||
expected: map[string]*models.GithubPullRequest{
|
||||
"update-sponsors": {
|
||||
HeadRefName: "update-sponsors",
|
||||
Number: 50,
|
||||
Title: "Newest PR",
|
||||
State: "CLOSED",
|
||||
Url: "https://github.com/jesseduffield/lazygit/pull/50",
|
||||
HeadRepositoryOwner: models.GithubRepositoryOwner{Login: "jesseduffield"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "matches with HTTPS remote URL",
|
||||
prs: []*models.GithubPullRequest{
|
||||
{
|
||||
HeadRefName: "my-pr",
|
||||
Number: 10,
|
||||
Title: "My PR",
|
||||
State: "MERGED",
|
||||
HeadRepositoryOwner: models.GithubRepositoryOwner{Login: "jesseduffield"},
|
||||
},
|
||||
},
|
||||
branches: []*models.Branch{
|
||||
{
|
||||
Name: "my-pr",
|
||||
UpstreamRemote: "origin",
|
||||
UpstreamBranch: "my-pr",
|
||||
},
|
||||
},
|
||||
remotes: []*models.Remote{
|
||||
{
|
||||
Name: "origin",
|
||||
Urls: []string{"https://github.com/jesseduffield/lazygit.git"},
|
||||
},
|
||||
},
|
||||
expected: map[string]*models.GithubPullRequest{
|
||||
"my-pr": {
|
||||
HeadRefName: "my-pr",
|
||||
Number: 10,
|
||||
Title: "My PR",
|
||||
State: "MERGED",
|
||||
HeadRepositoryOwner: models.GithubRepositoryOwner{Login: "jesseduffield"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
t.Run(c.name, func(t *testing.T) {
|
||||
result := GenerateGithubPullRequestMap(c.prs, c.branches, c.remotes)
|
||||
assert.Equal(t, c.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mkRemoteList(names ...string) []*models.Remote {
|
||||
return lo.Map(names, func(name string, _ int) *models.Remote {
|
||||
return &models.Remote{Name: name}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package git_commands
|
||||
|
||||
import "github.com/jesseduffield/lazygit/pkg/commands/hosting_service"
|
||||
|
||||
// a hosting service is something like github, gitlab, bitbucket etc
|
||||
type HostingService struct {
|
||||
*GitCommon
|
||||
}
|
||||
|
||||
func NewHostingServiceCommand(gitCommon *GitCommon) *HostingService {
|
||||
return &HostingService{
|
||||
GitCommon: gitCommon,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *HostingService) GetPullRequestURL(from string, to string) (string, error) {
|
||||
return self.getHostingServiceMgr(self.config.GetRemoteURL()).GetPullRequestURL(from, to)
|
||||
}
|
||||
|
||||
func (self *HostingService) GetCommitURL(commitSha string) (string, error) {
|
||||
return self.getHostingServiceMgr(self.config.GetRemoteURL()).GetCommitURL(commitSha)
|
||||
}
|
||||
|
||||
func (self *HostingService) GetRepoNameFromRemoteURL(remoteURL string) (string, error) {
|
||||
return self.getHostingServiceMgr(remoteURL).GetRepoName()
|
||||
}
|
||||
|
||||
// getting this on every request rather than storing it in state in case our remoteURL changes
|
||||
// from one invocation to the next. Note however that we're currently caching config
|
||||
// results so we might want to invalidate the cache here if it becomes a problem.
|
||||
func (self *HostingService) getHostingServiceMgr(remoteURL string) *hosting_service.HostingServiceMgr {
|
||||
configServices := self.UserConfig().Services
|
||||
return hosting_service.NewHostingServiceMgr(self.Log, self.Tr, remoteURL, configServices)
|
||||
}
|
||||
@@ -139,7 +139,7 @@ func (self *RebaseCommands) MoveCommitsUp(commits []*models.Commit, startIdx int
|
||||
}).Run()
|
||||
}
|
||||
|
||||
func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, startIdx int, endIdx int, action todo.TodoCommand) error {
|
||||
func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, startIdx int, endIdx int, action todo.TodoCommand, flag string) error {
|
||||
baseIndex := endIdx + 1
|
||||
if action == todo.Squash || action == todo.Fixup {
|
||||
baseIndex++
|
||||
@@ -151,6 +151,7 @@ func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, startIdx
|
||||
return daemon.ChangeTodoAction{
|
||||
Hash: commit.Hash(),
|
||||
NewAction: action,
|
||||
Flag: flag,
|
||||
}, !commit.IsMerge()
|
||||
})
|
||||
|
||||
@@ -334,11 +335,12 @@ func todoFromCommit(commit *models.Commit) utils.Todo {
|
||||
}
|
||||
|
||||
// Sets the action for the given commits in the git-rebase-todo file
|
||||
func (self *RebaseCommands) EditRebaseTodo(commits []*models.Commit, action todo.TodoCommand) error {
|
||||
func (self *RebaseCommands) EditRebaseTodo(commits []*models.Commit, action todo.TodoCommand, flag string) error {
|
||||
commitsWithAction := lo.Map(commits, func(commit *models.Commit, _ int) utils.TodoChange {
|
||||
return utils.TodoChange{
|
||||
Hash: commit.Hash(),
|
||||
NewAction: action,
|
||||
Flag: flag,
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -233,10 +233,10 @@ func (self *WorkingTreeCommands) DiscardUnstagedFileChanges(file *models.File) e
|
||||
return self.cmd.New(cmdArgs).Run()
|
||||
}
|
||||
|
||||
// Escapes special characters in a filename for gitignore and exclude files
|
||||
// Escapes special characters in a filename for gitignore and exclude files, and prepends `/`
|
||||
func escapeFilename(filename string) string {
|
||||
re := regexp.MustCompile(`^[!#]|[\[\]*]`)
|
||||
return re.ReplaceAllString(filename, `\${0}`)
|
||||
return "/" + re.ReplaceAllString(filename, `\${0}`)
|
||||
}
|
||||
|
||||
// Ignore adds a file to the gitignore for the repo
|
||||
@@ -253,11 +253,14 @@ func (self *WorkingTreeCommands) Exclude(filename string) error {
|
||||
// WorktreeFileDiff returns the diff of a file
|
||||
func (self *WorkingTreeCommands) WorktreeFileDiff(file *models.File, plain bool, cached bool) string {
|
||||
// for now we assume an error means the file was deleted
|
||||
s, _ := self.WorktreeFileDiffCmdObj(file, plain, cached).RunWithOutput()
|
||||
s, _ := self.WorktreeFileDiffCmdObj(file, plain, cached, nil).RunWithOutput()
|
||||
return s
|
||||
}
|
||||
|
||||
func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool) *oscommands.CmdObj {
|
||||
// WorktreeFileDiffCmdObj returns a command object for diffing a file or directory
|
||||
// in the working tree. When pathOverrides is non-empty, those paths are used instead of
|
||||
// the node's path (used to diff only filtered/visible files within a directory).
|
||||
func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain bool, cached bool, pathOverrides []string) *oscommands.CmdObj {
|
||||
colorArg := self.pagerConfig.GetColorArg()
|
||||
if plain {
|
||||
colorArg = "never"
|
||||
@@ -270,6 +273,11 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain
|
||||
useExtDiff := extDiffCmd != "" && !plain
|
||||
useExtDiffGitConfig := self.pagerConfig.GetUseExternalDiffGitConfig() && !plain
|
||||
|
||||
paths := pathOverrides
|
||||
if len(paths) == 0 {
|
||||
paths = []string{node.GetPath()}
|
||||
}
|
||||
|
||||
cmdArgs := NewGitCmd("diff").
|
||||
ConfigIf(useExtDiff, "diff.external="+extDiffCmd).
|
||||
ArgIfElse(useExtDiff || useExtDiffGitConfig, "--ext-diff", "--no-ext-diff").
|
||||
@@ -282,7 +290,7 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain
|
||||
ArgIf(noIndex, "--no-index").
|
||||
Arg("--").
|
||||
ArgIf(noIndex, "/dev/null").
|
||||
Arg(node.GetPath()).
|
||||
Arg(paths...).
|
||||
ArgIf(prevPath != "", prevPath).
|
||||
Dir(self.repoPaths.worktreePath).
|
||||
ToArgv()
|
||||
@@ -293,10 +301,10 @@ func (self *WorkingTreeCommands) WorktreeFileDiffCmdObj(node models.IFile, plain
|
||||
// ShowFileDiff get the diff of specified from and to. Typically this will be used for a single commit so it'll be 123abc^..123abc
|
||||
// but when we're in diff mode it could be any 'from' to any 'to'. The reverse flag is also here thanks to diff mode.
|
||||
func (self *WorkingTreeCommands) ShowFileDiff(from string, to string, reverse bool, fileName string, plain bool) (string, error) {
|
||||
return self.ShowFileDiffCmdObj(from, to, reverse, fileName, plain).RunWithOutput()
|
||||
return self.ShowFileDiffCmdObj(from, to, reverse, []string{fileName}, plain).RunWithOutput()
|
||||
}
|
||||
|
||||
func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileName string, plain bool) *oscommands.CmdObj {
|
||||
func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reverse bool, fileNames []string, plain bool) *oscommands.CmdObj {
|
||||
contextSize := self.UserConfig().Git.DiffContextSize
|
||||
|
||||
colorArg := self.pagerConfig.GetColorArg()
|
||||
@@ -321,7 +329,7 @@ func (self *WorkingTreeCommands) ShowFileDiffCmdObj(from string, to string, reve
|
||||
ArgIf(reverse, "-R").
|
||||
ArgIf(!plain && self.UserConfig().Git.IgnoreWhitespaceInDiffView, "--ignore-all-space").
|
||||
Arg("--").
|
||||
Arg(fileName).
|
||||
Arg(fileNames...).
|
||||
Dir(self.repoPaths.worktreePath).
|
||||
ToArgv()
|
||||
|
||||
|
||||
@@ -6,7 +6,11 @@ var defaultUrlRegexStrings = []string{
|
||||
`^(?:https?|ssh)://[^/]+/(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
|
||||
`^(.*?@)?.*:/*(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
|
||||
}
|
||||
var defaultRepoURLTemplate = "https://{{.webDomain}}/{{.owner}}/{{.repo}}"
|
||||
|
||||
var (
|
||||
defaultRepoURLTemplate = "https://{{.webDomain}}/{{.owner}}/{{.repo}}"
|
||||
defaultRepoNameTemplate = "{{.owner}}/{{.repo}}"
|
||||
)
|
||||
|
||||
// we've got less type safety using go templates but this lends itself better to
|
||||
// users adding custom service definitions in their config
|
||||
@@ -17,6 +21,7 @@ var githubServiceDef = ServiceDefinition{
|
||||
commitURL: "/commit/{{.CommitHash}}",
|
||||
regexStrings: defaultUrlRegexStrings,
|
||||
repoURLTemplate: defaultRepoURLTemplate,
|
||||
repoNameTemplate: defaultRepoNameTemplate,
|
||||
}
|
||||
|
||||
var bitbucketServiceDef = ServiceDefinition{
|
||||
@@ -28,7 +33,8 @@ var bitbucketServiceDef = ServiceDefinition{
|
||||
`^(?:https?|ssh)://.*/(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
|
||||
`^.*@.*:/*(?P<owner>.*)/(?P<repo>.*?)(?:\.git)?$`,
|
||||
},
|
||||
repoURLTemplate: defaultRepoURLTemplate,
|
||||
repoURLTemplate: defaultRepoURLTemplate,
|
||||
repoNameTemplate: defaultRepoNameTemplate,
|
||||
}
|
||||
|
||||
var gitLabServiceDef = ServiceDefinition{
|
||||
@@ -38,6 +44,7 @@ var gitLabServiceDef = ServiceDefinition{
|
||||
commitURL: "/-/commit/{{.CommitHash}}",
|
||||
regexStrings: defaultUrlRegexStrings,
|
||||
repoURLTemplate: defaultRepoURLTemplate,
|
||||
repoNameTemplate: defaultRepoNameTemplate,
|
||||
}
|
||||
|
||||
var azdoServiceDef = ServiceDefinition{
|
||||
@@ -52,6 +59,8 @@ var azdoServiceDef = ServiceDefinition{
|
||||
`^https://.*/(?P<org>.*?)/(?P<project>.*?)/_git/(?P<repo>.*?)(?:\.git)?$`,
|
||||
},
|
||||
repoURLTemplate: "https://{{.webDomain}}/{{.org}}/{{.project}}/_git/{{.repo}}",
|
||||
// TODO: verify this is actually correct
|
||||
repoNameTemplate: "{{.org}}/{{.project}}/{{.repo}}",
|
||||
}
|
||||
|
||||
var bitbucketServerServiceDef = ServiceDefinition{
|
||||
@@ -64,6 +73,8 @@ var bitbucketServerServiceDef = ServiceDefinition{
|
||||
`^https://.*/scm/(?P<project>.*)/(?P<repo>.*?)(?:\.git)?$`,
|
||||
},
|
||||
repoURLTemplate: "https://{{.webDomain}}/projects/{{.project}}/repos/{{.repo}}",
|
||||
// TODO: verify this is actually correct
|
||||
repoNameTemplate: "{{.project}}/{{.repo}}",
|
||||
}
|
||||
|
||||
var giteaServiceDef = ServiceDefinition{
|
||||
|
||||
@@ -61,6 +61,18 @@ func (self *HostingServiceMgr) GetCommitURL(commitHash string) (string, error) {
|
||||
return pullRequestURL, nil
|
||||
}
|
||||
|
||||
// e.g. 'jesseduffield/lazygit'
|
||||
func (self *HostingServiceMgr) GetRepoName() (string, error) {
|
||||
gitService, err := self.getService()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
repoName := gitService.repoName
|
||||
|
||||
return repoName, nil
|
||||
}
|
||||
|
||||
func (self *HostingServiceMgr) getService() (*Service, error) {
|
||||
serviceDomain, err := self.getServiceDomain(self.remoteURL)
|
||||
if err != nil {
|
||||
@@ -72,8 +84,14 @@ func (self *HostingServiceMgr) getService() (*Service, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
repoName, err := serviceDomain.serviceDefinition.getRepoNameFromRemoteURL(self.remoteURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Service{
|
||||
repoURL: repoURL,
|
||||
repoName: repoName,
|
||||
ServiceDefinition: serviceDomain.serviceDefinition,
|
||||
}, nil
|
||||
}
|
||||
@@ -144,24 +162,68 @@ type ServiceDefinition struct {
|
||||
regexStrings []string
|
||||
|
||||
// can expect 'webdomain' to be passed in. Otherwise, you get to pick what we match in the regex
|
||||
repoURLTemplate string
|
||||
repoURLTemplate string
|
||||
repoNameTemplate string
|
||||
}
|
||||
|
||||
func (self ServiceDefinition) getRepoURLFromRemoteURL(url string, webDomain string) (string, error) {
|
||||
matches, err := self.parseRemoteUrl(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
matches["webDomain"] = webDomain
|
||||
return utils.ResolvePlaceholderString(self.repoURLTemplate, matches), nil
|
||||
}
|
||||
|
||||
func (self ServiceDefinition) getRepoNameFromRemoteURL(url string) (string, error) {
|
||||
matches, err := self.parseRemoteUrl(url)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return utils.ResolvePlaceholderString(self.repoNameTemplate, matches), nil
|
||||
}
|
||||
|
||||
func (self ServiceDefinition) parseRemoteUrl(url string) (map[string]string, error) {
|
||||
for _, regexStr := range self.regexStrings {
|
||||
re := regexp.MustCompile(regexStr)
|
||||
input := utils.FindNamedMatches(re, url)
|
||||
if input != nil {
|
||||
input["webDomain"] = webDomain
|
||||
return utils.ResolvePlaceholderString(self.repoURLTemplate, input), nil
|
||||
matches := utils.FindNamedMatches(re, url)
|
||||
if matches != nil {
|
||||
return matches, nil
|
||||
}
|
||||
}
|
||||
|
||||
return "", errors.New("Failed to parse repo information from url")
|
||||
return nil, errors.New("Failed to parse repo information from url")
|
||||
}
|
||||
|
||||
// RepoInformation holds the owner and repository name parsed from a remote URL.
|
||||
type RepoInformation struct {
|
||||
Owner string
|
||||
Repository string
|
||||
}
|
||||
|
||||
// GetRepoInfoFromURL parses a remote URL (SSH or HTTPS) and extracts the
|
||||
// owner and repository name using the default URL regex patterns.
|
||||
func GetRepoInfoFromURL(url string) (RepoInformation, error) {
|
||||
for _, regexStr := range defaultUrlRegexStrings {
|
||||
re := regexp.MustCompile(regexStr)
|
||||
matches := utils.FindNamedMatches(re, url)
|
||||
if matches != nil {
|
||||
return RepoInformation{
|
||||
Owner: matches["owner"],
|
||||
Repository: matches["repo"],
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
return RepoInformation{}, errors.New("Failed to parse repo information from url")
|
||||
}
|
||||
|
||||
type Service struct {
|
||||
repoURL string
|
||||
// e.g. 'jesseduffield/lazygit'
|
||||
repoName string
|
||||
ServiceDefinition
|
||||
}
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ type Commit struct {
|
||||
|
||||
Status CommitStatus
|
||||
Action todo.TodoCommand
|
||||
ActionFlag string // e.g. "-C" for fixup -C
|
||||
Divergence Divergence // set to DivergenceNone unless we are showing the divergence view
|
||||
}
|
||||
|
||||
@@ -68,6 +69,7 @@ type NewCommitOpts struct {
|
||||
Name string
|
||||
Status CommitStatus
|
||||
Action todo.TodoCommand
|
||||
ActionFlag string
|
||||
Tags []string
|
||||
ExtraInfo string
|
||||
AuthorName string
|
||||
@@ -83,6 +85,7 @@ func NewCommit(hashPool *utils.StringPool, opts NewCommitOpts) *Commit {
|
||||
Name: opts.Name,
|
||||
Status: opts.Status,
|
||||
Action: opts.Action,
|
||||
ActionFlag: opts.ActionFlag,
|
||||
Tags: opts.Tags,
|
||||
ExtraInfo: opts.ExtraInfo,
|
||||
AuthorName: opts.AuthorName,
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package models
|
||||
|
||||
// TODO: see if I need to store the head repo name in case it differs from the base repo
|
||||
type GithubPullRequest struct {
|
||||
HeadRefName string `json:"headRefName"`
|
||||
Number int `json:"number"`
|
||||
Title string `json:"title"`
|
||||
State string `json:"state"` // "MERGED", "OPEN", "CLOSED", "DRAFT"
|
||||
Url string `json:"url"`
|
||||
HeadRepositoryOwner GithubRepositoryOwner `json:"headRepositoryOwner"`
|
||||
}
|
||||
|
||||
func (pr *GithubPullRequest) UserName() string {
|
||||
// e.g. 'jesseduffield'
|
||||
return pr.HeadRepositoryOwner.Login
|
||||
}
|
||||
|
||||
func (pr *GithubPullRequest) BranchName() string {
|
||||
// e.g. 'feature/my-feature'
|
||||
return pr.HeadRefName
|
||||
}
|
||||
|
||||
type GithubRepositoryOwner struct {
|
||||
Login string `json:"login"`
|
||||
}
|
||||
@@ -704,10 +704,27 @@ type AppState struct {
|
||||
ShellCommandsHistory []string `yaml:"customcommandshistory"`
|
||||
|
||||
HideCommandLog bool
|
||||
|
||||
// Cache of GitHub pull requests per repo path, so that PR info can be
|
||||
// shown instantly on startup before the async refresh completes.
|
||||
GithubPullRequests map[string][]CachedPullRequest `yaml:"githubPullRequests"`
|
||||
}
|
||||
|
||||
// CachedPullRequest stores the essential fields of a GitHub pull request
|
||||
// for persisting in the app state cache.
|
||||
type CachedPullRequest struct {
|
||||
HeadRefName string `yaml:"headRefName"`
|
||||
Number int `yaml:"number"`
|
||||
Title string `yaml:"title"`
|
||||
State string `yaml:"state"`
|
||||
Url string `yaml:"url"`
|
||||
HeadRepositoryOwner string `yaml:"headRepositoryOwner"`
|
||||
}
|
||||
|
||||
func getDefaultAppState() *AppState {
|
||||
return &AppState{}
|
||||
return &AppState{
|
||||
GithubPullRequests: make(map[string][]CachedPullRequest),
|
||||
}
|
||||
}
|
||||
|
||||
func LogPath() (string, error) {
|
||||
|
||||
@@ -60,6 +60,10 @@ func getPreset(shell string, osConfig *OSConfig, guessDefaultEditor func() strin
|
||||
nvimRemoteEditTemplate = `begin; if test -z "$NVIM"; nvim -- {{filename}}; else; nvim --server "$NVIM" --remote-send "q"; nvim --server "$NVIM" --remote-tab {{filename}}; end; end`
|
||||
nvimRemoteEditAtLineTemplate = `begin; if test -z "$NVIM"; nvim +{{line}} -- {{filename}}; else; nvim --server "$NVIM" --remote-send "q"; nvim --server "$NVIM" --remote-tab {{filename}}; nvim --server "$NVIM" --remote-send ":{{line}}<CR>"; end; end`
|
||||
nvimRemoteOpenDirInEditorTemplate = `begin; if test -z "$NVIM"; nvim -- {{dir}}; else; nvim --server "$NVIM" --remote-send "q"; nvim --server "$NVIM" --remote-tab {{dir}}; end; end`
|
||||
} else if strings.HasSuffix(shell, "nu") || strings.HasSuffix(shell, "nushell") || os.Getenv("NU_VERSION") != "" {
|
||||
nvimRemoteEditTemplate = `if ($env | get -i NVIM | is-empty) { nvim -- {{filename}} } else { nvim --server $env.NVIM --remote-send "q"; nvim --server $env.NVIM --remote-tab {{filename}} }`
|
||||
nvimRemoteEditAtLineTemplate = `if ($env | get -i NVIM | is-empty) { nvim +{{line}} -- {{filename}} } else { nvim --server $env.NVIM --remote-send "q"; nvim --server $env.NVIM --remote-tab {{filename}}; nvim --server $env.NVIM --remote-send ":{{line}}<CR>" }`
|
||||
nvimRemoteOpenDirInEditorTemplate = `if ($env | get -i NVIM | is-empty) { nvim -- {{dir}} } else { nvim --server $env.NVIM --remote-send "q"; nvim --server $env.NVIM --remote-tab {{dir}} }`
|
||||
} else {
|
||||
nvimRemoteEditTemplate = `[ -z "$NVIM" ] && (nvim -- {{filename}}) || (nvim --server "$NVIM" --remote-send "q" && nvim --server "$NVIM" --remote-tab {{filename}})`
|
||||
nvimRemoteEditAtLineTemplate = `[ -z "$NVIM" ] && (nvim +{{line}} -- {{filename}}) || (nvim --server "$NVIM" --remote-send "q" && nvim --server "$NVIM" --remote-tab {{filename}} && nvim --server "$NVIM" --remote-send ":{{line}}<CR>")`
|
||||
|
||||
@@ -321,6 +321,8 @@ type GitConfig struct {
|
||||
RemoteBranchSortOrder string `yaml:"remoteBranchSortOrder" jsonschema:"enum=date,enum=alphabetical"`
|
||||
// When copying commit hashes to the clipboard, truncate them to this length. Set to 40 to disable truncation.
|
||||
TruncateCopiedCommitHashesTo int `yaml:"truncateCopiedCommitHashesTo"`
|
||||
// If true and if `gh` is installed and on version >=2, we will use `gh` to display pull requests against branches.
|
||||
EnableGithubCli bool `yaml:"enableGithubCli"`
|
||||
}
|
||||
|
||||
type PagerType string
|
||||
@@ -551,6 +553,7 @@ type KeybindingCommitsConfig struct {
|
||||
RenameCommitWithEditor string `yaml:"renameCommitWithEditor"`
|
||||
ViewResetOptions string `yaml:"viewResetOptions"`
|
||||
MarkCommitAsFixup string `yaml:"markCommitAsFixup"`
|
||||
SetFixupMessage string `yaml:"setFixupMessage"`
|
||||
CreateFixupCommit string `yaml:"createFixupCommit"`
|
||||
SquashAboveCommits string `yaml:"squashAboveCommits"`
|
||||
MoveDownCommit string `yaml:"moveDownCommit"`
|
||||
@@ -856,6 +859,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
BranchPrefix: "",
|
||||
ParseEmoji: false,
|
||||
TruncateCopiedCommitHashesTo: 12,
|
||||
EnableGithubCli: true,
|
||||
},
|
||||
Refresher: RefresherConfig{
|
||||
RefreshInterval: 10,
|
||||
@@ -1008,6 +1012,7 @@ func GetDefaultConfig() *UserConfig {
|
||||
RenameCommitWithEditor: "R",
|
||||
ViewResetOptions: "g",
|
||||
MarkCommitAsFixup: "f",
|
||||
SetFixupMessage: "c",
|
||||
CreateFixupCommit: "F",
|
||||
SquashAboveCommits: "S",
|
||||
MoveDownCommit: "<c-j>",
|
||||
|
||||
@@ -151,7 +151,7 @@ func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan stru
|
||||
func (self *BackgroundRoutineMgr) backgroundFetch() (err error) {
|
||||
err = self.gui.git.Sync.FetchBackground()
|
||||
|
||||
self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.SYNC})
|
||||
self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS, types.PULL_REQUESTS}, Mode: types.SYNC})
|
||||
|
||||
if err == nil {
|
||||
err = self.gui.helpers.BranchesHelper.AutoForwardBranches()
|
||||
|
||||
@@ -28,6 +28,8 @@ func NewBranchesContext(c *ContextCommon) *BranchesContext {
|
||||
return presentation.GetBranchListDisplayStrings(
|
||||
viewModel.GetItems(),
|
||||
c.State().GetItemOperation,
|
||||
c.Model().PullRequests,
|
||||
c.Model().Remotes,
|
||||
c.State().GetRepoState().GetScreenMode() != types.SCREEN_NORMAL,
|
||||
c.Modes().Diffing.Ref,
|
||||
c.Views().Branches.InnerWidth()+c.Views().Branches.OriginX(),
|
||||
|
||||
@@ -3,7 +3,6 @@ package context
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
@@ -17,13 +16,12 @@ type CommitFilesContext struct {
|
||||
*filetree.CommitFileTreeViewModel
|
||||
*ListContextTrait
|
||||
*DynamicTitleBuilder
|
||||
*SearchTrait
|
||||
}
|
||||
|
||||
var (
|
||||
_ types.IListContext = (*CommitFilesContext)(nil)
|
||||
_ types.DiffableContext = (*CommitFilesContext)(nil)
|
||||
_ types.ISearchableContext = (*CommitFilesContext)(nil)
|
||||
_ types.IFilterableContext = (*CommitFilesContext)(nil)
|
||||
)
|
||||
|
||||
func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
|
||||
@@ -48,7 +46,6 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
|
||||
ctx := &CommitFilesContext{
|
||||
CommitFileTreeViewModel: viewModel,
|
||||
DynamicTitleBuilder: NewDynamicTitleBuilder(c.Tr.CommitFilesDynamicTitle),
|
||||
SearchTrait: NewSearchTrait(c),
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(
|
||||
NewBaseContext(NewBaseContextOpts{
|
||||
@@ -68,9 +65,6 @@ func NewCommitFilesContext(c *ContextCommon) *CommitFilesContext {
|
||||
},
|
||||
}
|
||||
|
||||
ctx.GetView().SetRenderSearchStatus(ctx.SearchTrait.RenderSearchStatus)
|
||||
ctx.GetView().SetOnSelectItem(ctx.OnSearchSelect)
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
@@ -93,10 +87,6 @@ func (self *CommitFilesContext) GetFromAndToForDiff() (string, string) {
|
||||
return ref.ParentRefName(), ref.RefName()
|
||||
}
|
||||
|
||||
func (self *CommitFilesContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *CommitFilesContext) ReInit(ref models.Ref, refRange *types.RefRange) {
|
||||
self.SetRef(ref)
|
||||
self.SetRefRange(refRange)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation"
|
||||
@@ -13,12 +12,11 @@ import (
|
||||
type WorkingTreeContext struct {
|
||||
*filetree.FileTreeViewModel
|
||||
*ListContextTrait
|
||||
*SearchTrait
|
||||
}
|
||||
|
||||
var (
|
||||
_ types.IListContext = (*WorkingTreeContext)(nil)
|
||||
_ types.ISearchableContext = (*WorkingTreeContext)(nil)
|
||||
_ types.IFilterableContext = (*WorkingTreeContext)(nil)
|
||||
)
|
||||
|
||||
func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
|
||||
@@ -38,7 +36,6 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
|
||||
}
|
||||
|
||||
ctx := &WorkingTreeContext{
|
||||
SearchTrait: NewSearchTrait(c),
|
||||
FileTreeViewModel: viewModel,
|
||||
ListContextTrait: &ListContextTrait{
|
||||
Context: NewSimpleContext(NewBaseContext(NewBaseContextOpts{
|
||||
@@ -56,12 +53,5 @@ func NewWorkingTreeContext(c *ContextCommon) *WorkingTreeContext {
|
||||
},
|
||||
}
|
||||
|
||||
ctx.GetView().SetRenderSearchStatus(ctx.SearchTrait.RenderSearchStatus)
|
||||
ctx.GetView().SetOnSelectItem(ctx.OnSearchSelect)
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (self *WorkingTreeContext) ModelSearchResults(searchStr string, caseSensitive bool) []gocui.SearchPosition {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -68,6 +68,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
||||
mergeConflictsHelper,
|
||||
worktreeHelper,
|
||||
searchHelper,
|
||||
suggestionsHelper,
|
||||
)
|
||||
diffHelper := helpers.NewDiffHelper(helperCommon)
|
||||
cherryPickHelper := helpers.NewCherryPickHelper(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
@@ -130,14 +129,6 @@ func (self *BasicCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
|
||||
GetDisabledReason: self.require(self.canSelectCommitsOfCurrentBranch),
|
||||
Description: self.c.Tr.SelectCommitsOfCurrentBranch,
|
||||
},
|
||||
// Putting this at the bottom of the list so that it has the lowest priority,
|
||||
// meaning that if the user has configured another keybinding to the same key
|
||||
// then that will take precedence.
|
||||
{
|
||||
// Hardcoding this key because it's not configurable
|
||||
Key: opts.GetKey("c"),
|
||||
Handler: self.handleOldCherryPickKey,
|
||||
},
|
||||
}
|
||||
|
||||
return bindings
|
||||
@@ -382,16 +373,6 @@ func (self *BasicCommitsController) canCopyCommits(selectedCommits []*models.Com
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) handleOldCherryPickKey() error {
|
||||
msg := utils.ResolvePlaceholderString(self.c.Tr.OldCherryPickKeyWarning,
|
||||
map[string]string{
|
||||
"copy": keybindings.Label(self.c.UserConfig().Keybinding.Commits.CherryPickCopy),
|
||||
"paste": keybindings.Label(self.c.UserConfig().Keybinding.Commits.PasteCommits),
|
||||
})
|
||||
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
func (self *BasicCommitsController) openDiffTool(commit *models.Commit) error {
|
||||
to := commit.RefName()
|
||||
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(commit.ParentRefName())
|
||||
|
||||
@@ -3,12 +3,16 @@ package controllers
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/gookit/color"
|
||||
"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/controllers/helpers"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/presentation/icons"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/style"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
@@ -192,7 +196,27 @@ func (self *BranchesController) GetOnRenderToMain() func() {
|
||||
} else {
|
||||
cmdObj := self.c.Git().Branch.GetGraphCmdObj(branch.FullRefName())
|
||||
|
||||
task = types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
ptyTask := types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
task = ptyTask
|
||||
|
||||
// Shouldn't we hold on to the map for longer instead of generating it every time?
|
||||
// It is also generated every time we render the branches list.
|
||||
prs := git_commands.GenerateGithubPullRequestMap(
|
||||
self.c.Model().PullRequests,
|
||||
self.c.Model().Branches,
|
||||
self.c.Model().Remotes,
|
||||
)
|
||||
|
||||
if pr, ok := prs[branch.Name]; ok {
|
||||
icon := lo.Ternary(icons.IsIconEnabled(), icons.IconForRemoteUrl(pr.Url)+" ", "")
|
||||
ptyTask.Prefix = style.PrintHyperlink(fmt.Sprintf("%s%s %s %s\n",
|
||||
icon,
|
||||
coloredStateText(pr.State),
|
||||
pr.Title,
|
||||
style.FgCyan.Sprintf("#%d", pr.Number)),
|
||||
pr.Url)
|
||||
ptyTask.Prefix += strings.Repeat("─", self.c.Contexts().Normal.GetView().InnerWidth()) + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
@@ -206,6 +230,77 @@ func (self *BranchesController) GetOnRenderToMain() func() {
|
||||
}
|
||||
}
|
||||
|
||||
func stateText(state string) string {
|
||||
if icons.IsIconEnabled() {
|
||||
switch state {
|
||||
case "OPEN":
|
||||
return " Open"
|
||||
case "CLOSED":
|
||||
return " Closed"
|
||||
case "MERGED":
|
||||
return " Merged"
|
||||
case "DRAFT":
|
||||
return " Draft"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
switch state {
|
||||
case "OPEN":
|
||||
return "Open"
|
||||
case "CLOSED":
|
||||
return "Closed"
|
||||
case "MERGED":
|
||||
return "Merged"
|
||||
case "DRAFT":
|
||||
return "Draft"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func coloredStateText(state string) string {
|
||||
if icons.IsIconEnabled() {
|
||||
return fmt.Sprintf("%s%s%s",
|
||||
withPrFgColor(state, ""),
|
||||
withPrBgColor(state, style.FgWhite.Sprint(stateText(state))),
|
||||
withPrFgColor(state, ""))
|
||||
}
|
||||
|
||||
return withPrFgColor(state, stateText(state))
|
||||
}
|
||||
|
||||
func withPrFgColor(state string, text string) string {
|
||||
switch state {
|
||||
case "OPEN":
|
||||
return style.FgGreen.Sprint(text)
|
||||
case "CLOSED":
|
||||
return style.FgRed.Sprint(text)
|
||||
case "MERGED":
|
||||
return style.FgMagenta.Sprint(text)
|
||||
case "DRAFT":
|
||||
return color.RGB(0x66, 0x66, 0x66, false).Sprint(text)
|
||||
default:
|
||||
return style.FgDefault.Sprint(text)
|
||||
}
|
||||
}
|
||||
|
||||
func withPrBgColor(state string, text string) string {
|
||||
switch state {
|
||||
case "OPEN":
|
||||
return style.BgGreen.Sprint(text)
|
||||
case "CLOSED":
|
||||
return style.BgRed.Sprint(text)
|
||||
case "MERGED":
|
||||
return style.BgMagenta.Sprint(text)
|
||||
case "DRAFT":
|
||||
return color.RGB(0x66, 0x66, 0x66, true).Sprint(text)
|
||||
default:
|
||||
return style.BgDefault.Sprint(text)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *BranchesController) viewUpstreamOptions(selectedBranch *models.Branch) error {
|
||||
upstream := lo.Ternary(selectedBranch.RemoteBranchStoredLocally(),
|
||||
selectedBranch.ShortUpstreamRefName(),
|
||||
|
||||
@@ -152,7 +152,8 @@ func (self *CommitFilesController) GetOnRenderToMain() func() {
|
||||
from, to := self.context().GetFromAndToForDiff()
|
||||
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(from)
|
||||
|
||||
cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(from, to, reverse, node.GetPath(), false)
|
||||
paths := self.pathsForDiff(node)
|
||||
cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(from, to, reverse, paths, false)
|
||||
task := types.NewRunPtyTask(cmdObj.GetCmd())
|
||||
|
||||
self.c.RenderToMainViews(types.RefreshMainOpts{
|
||||
@@ -171,7 +172,7 @@ func (self *CommitFilesController) copyDiffToClipboard(path string, toastMessage
|
||||
from, to := self.context().GetFromAndToForDiff()
|
||||
from, reverse := self.c.Modes().Diffing.GetFromAndReverseArgsForDiff(from)
|
||||
|
||||
cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(from, to, reverse, path, true)
|
||||
cmdObj := self.c.Git().WorkingTree.ShowFileDiffCmdObj(from, to, reverse, []string{path}, true)
|
||||
diff, err := cmdObj.RunWithOutput()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -550,6 +551,21 @@ func (self *CommitFilesController) GetOnClickFocusedMainView() func(mainViewName
|
||||
}
|
||||
}
|
||||
|
||||
// pathsForDiff returns the file paths to use for a diff command. When a text
|
||||
// filter is active and the node is a directory, only the visible (filtered)
|
||||
// file paths are returned so the diff reflects what the user sees.
|
||||
func (self *CommitFilesController) pathsForDiff(node *filetree.CommitFileNode) []string {
|
||||
if !node.IsFile() && self.context().IsFiltering() {
|
||||
var paths []string
|
||||
node.ForEachFile(func(file *models.CommitFile) error {
|
||||
paths = append(paths, file.Path)
|
||||
return nil
|
||||
})
|
||||
return paths
|
||||
}
|
||||
return []string{node.GetPath()}
|
||||
}
|
||||
|
||||
// NOTE: these functions are identical to those in files_controller.go (except for types) and
|
||||
// could also be cleaned up with some generics
|
||||
func normalisedSelectedCommitFileNodes(selectedNodes []*filetree.CommitFileNode) []*filetree.CommitFileNode {
|
||||
|
||||
@@ -294,7 +294,8 @@ func (self *FilesController) GetOnRenderToMain() func() {
|
||||
split := self.c.UserConfig().Gui.SplitDiff == "always" || (node.GetHasUnstagedChanges() && node.GetHasStagedChanges())
|
||||
mainShowsStaged := !split && node.GetHasStagedChanges()
|
||||
|
||||
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, mainShowsStaged)
|
||||
pathOverrides := self.pathOverridesForDiff(node)
|
||||
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, mainShowsStaged, pathOverrides)
|
||||
title := self.c.Tr.UnstagedChanges
|
||||
if mainShowsStaged {
|
||||
title = self.c.Tr.StagedChanges
|
||||
@@ -309,7 +310,7 @@ func (self *FilesController) GetOnRenderToMain() func() {
|
||||
}
|
||||
|
||||
if split {
|
||||
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, true)
|
||||
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(node, false, true, pathOverrides)
|
||||
|
||||
title := self.c.Tr.StagedChanges
|
||||
if mainShowsStaged {
|
||||
@@ -434,7 +435,19 @@ func (self *FilesController) pressWithLock(selectedNodes []*filetree.FileNode) e
|
||||
}
|
||||
}
|
||||
|
||||
// When filtering, expand directory nodes to individual visible file paths
|
||||
// so that only filtered files are staged/unstaged.
|
||||
toPaths := func(nodes []*filetree.FileNode) []string {
|
||||
if self.context().IsFiltering() {
|
||||
var paths []string
|
||||
for _, node := range nodes {
|
||||
node.ForEachFile(func(file *models.File) error {
|
||||
paths = append(paths, file.Path)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return paths
|
||||
}
|
||||
return lo.Map(nodes, func(node *filetree.FileNode, _ int) string {
|
||||
return node.GetPath()
|
||||
})
|
||||
@@ -449,7 +462,7 @@ func (self *FilesController) pressWithLock(selectedNodes []*filetree.FileNode) e
|
||||
if len(unstagedSelectedNodes) > 0 {
|
||||
var extraArgs []string
|
||||
|
||||
if self.context().GetFilter() == filetree.DisplayTracked {
|
||||
if self.context().GetStatusFilter() == filetree.DisplayTracked {
|
||||
extraArgs = []string{"-u"}
|
||||
}
|
||||
|
||||
@@ -469,22 +482,29 @@ func (self *FilesController) pressWithLock(selectedNodes []*filetree.FileNode) e
|
||||
return err
|
||||
}
|
||||
|
||||
// need to partition the paths into tracked and untracked (where we assume directories are tracked). Then we'll run the commands separately.
|
||||
trackedNodes, untrackedNodes := utils.Partition(selectedNodes, func(node *filetree.FileNode) bool {
|
||||
// We treat all directories as tracked. I'm not actually sure why we do this but
|
||||
// it's been the existing behaviour for a while and nobody has complained
|
||||
return !node.IsFile() || node.GetIsTracked()
|
||||
})
|
||||
|
||||
if len(untrackedNodes) > 0 {
|
||||
if err := self.c.Git().WorkingTree.UnstageUntrackedFiles(toPaths(untrackedNodes)); err != nil {
|
||||
if self.context().IsFiltering() {
|
||||
// When filtering, only unstage visible files
|
||||
if err := self.unstageFilteredFiles(selectedNodes); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// need to partition the paths into tracked and untracked (where we assume directories are tracked). Then we'll run the commands separately.
|
||||
trackedNodes, untrackedNodes := utils.Partition(selectedNodes, func(node *filetree.FileNode) bool {
|
||||
// We treat all directories as tracked. I'm not actually sure why we do this but
|
||||
// it's been the existing behaviour for a while and nobody has complained
|
||||
return !node.IsFile() || node.GetIsTracked()
|
||||
})
|
||||
|
||||
if len(trackedNodes) > 0 {
|
||||
if err := self.c.Git().WorkingTree.UnstageTrackedFiles(toPaths(trackedNodes)); err != nil {
|
||||
return err
|
||||
if len(untrackedNodes) > 0 {
|
||||
if err := self.c.Git().WorkingTree.UnstageUntrackedFiles(toPaths(untrackedNodes)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if len(trackedNodes) > 0 {
|
||||
if err := self.c.Git().WorkingTree.UnstageTrackedFiles(toPaths(trackedNodes)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -503,6 +523,48 @@ func (self *FilesController) press(nodes []*filetree.FileNode) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// pathOverridesForDiff returns file paths to override the node's path in diff
|
||||
// commands when a text filter is active and the node is a directory. This
|
||||
// ensures the diff only shows filtered/visible files.
|
||||
func (self *FilesController) pathOverridesForDiff(node *filetree.FileNode) []string {
|
||||
if !node.IsFile() && self.context().IsFiltering() {
|
||||
var paths []string
|
||||
node.ForEachFile(func(file *models.File) error {
|
||||
paths = append(paths, file.Path)
|
||||
return nil
|
||||
})
|
||||
return paths
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// unstageFilteredFiles unstages only the visible (filtered) files from the
|
||||
// given nodes, correctly partitioning by tracked/untracked.
|
||||
func (self *FilesController) unstageFilteredFiles(nodes []*filetree.FileNode) error {
|
||||
var trackedPaths, untrackedPaths []string
|
||||
for _, node := range nodes {
|
||||
node.ForEachFile(func(file *models.File) error {
|
||||
if file.Tracked || file.HasStagedChanges {
|
||||
trackedPaths = append(trackedPaths, file.Path)
|
||||
} else {
|
||||
untrackedPaths = append(untrackedPaths, file.Path)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if len(untrackedPaths) > 0 {
|
||||
if err := self.c.Git().WorkingTree.UnstageUntrackedFiles(untrackedPaths); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if len(trackedPaths) > 0 {
|
||||
if err := self.c.Git().WorkingTree.UnstageTrackedFiles(trackedPaths); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *FilesController) Context() types.Context {
|
||||
return self.context()
|
||||
}
|
||||
@@ -648,9 +710,21 @@ func (self *FilesController) toggleStagedAllWithLock() error {
|
||||
return err
|
||||
}
|
||||
|
||||
onlyTrackedFiles := self.context().GetFilter() == filetree.DisplayTracked
|
||||
if err := self.c.Git().WorkingTree.StageAll(onlyTrackedFiles); err != nil {
|
||||
return err
|
||||
if self.context().IsFiltering() {
|
||||
// When filtering, only stage visible files
|
||||
var paths []string
|
||||
root.ForEachFile(func(file *models.File) error {
|
||||
paths = append(paths, file.Path)
|
||||
return nil
|
||||
})
|
||||
if err := self.c.Git().WorkingTree.StageFiles(paths, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
onlyTrackedFiles := self.context().GetStatusFilter() == filetree.DisplayTracked
|
||||
if err := self.c.Git().WorkingTree.StageAll(onlyTrackedFiles); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.c.LogAction(self.c.Tr.Actions.UnstageAllFiles)
|
||||
@@ -659,8 +733,15 @@ func (self *FilesController) toggleStagedAllWithLock() error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := self.c.Git().WorkingTree.UnstageAll(); err != nil {
|
||||
return err
|
||||
if self.context().IsFiltering() {
|
||||
// When filtering, only unstage visible files
|
||||
if err := self.unstageFilteredFiles([]*filetree.FileNode{root}); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := self.c.Git().WorkingTree.UnstageAll(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -836,7 +917,7 @@ func (self *FilesController) isResolvingConflicts() bool {
|
||||
}
|
||||
|
||||
func (self *FilesController) handleStatusFilterPressed() error {
|
||||
currentFilter := self.context().GetFilter()
|
||||
currentFilter := self.context().GetStatusFilter()
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.FilteringMenuTitle,
|
||||
Items: []*types.MenuItem{
|
||||
@@ -904,7 +985,7 @@ func (self *FilesController) filteringLabel(filter filetree.FileTreeDisplayFilte
|
||||
}
|
||||
|
||||
func (self *FilesController) setStatusFiltering(filter filetree.FileTreeDisplayFilter) error {
|
||||
previousFilter := self.context().GetFilter()
|
||||
previousFilter := self.context().GetStatusFilter()
|
||||
|
||||
self.context().FileTreeViewModel.SetStatusFilter(filter)
|
||||
self.c.Contexts().Files.GetView().Subtitle = self.filteringLabel(filter)
|
||||
|
||||
@@ -125,10 +125,10 @@ func (self *ConfirmationHelper) getPopupPanelDimensionsAux(panelWidth int, panel
|
||||
height/2 + panelHeight/2
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) getPopupPanelWidth() int {
|
||||
func (self *ConfirmationHelper) getPopupPanelWidth(maxWidth int) int {
|
||||
width, _ := self.c.GocuiGui().Size()
|
||||
// we want a minimum width up to a point, then we do it based on ratio.
|
||||
panelWidth := 4 * width / 7
|
||||
// we want a minimum width up to a point, then we do it based on ratio, but only up to the given max width
|
||||
panelWidth := min(4*width/7, maxWidth)
|
||||
minWidth := 80
|
||||
if panelWidth < minWidth {
|
||||
panelWidth = min(width-2, minWidth)
|
||||
@@ -328,7 +328,7 @@ func (self *ConfirmationHelper) resizeMenu(parentPopupContext types.Context) {
|
||||
// resize the window
|
||||
itemCount := self.c.Contexts().Menu.UnfilteredLen()
|
||||
offset := 3
|
||||
panelWidth := self.getPopupPanelWidth()
|
||||
panelWidth := self.getPopupPanelWidth(90)
|
||||
contentWidth := panelWidth - 2 // minus 2 for the frame
|
||||
promptLinesCount := self.layoutMenuPrompt(contentWidth)
|
||||
x0, y0, x1, y1 := self.getPopupPanelDimensionsForContentHeight(panelWidth, itemCount+offset+promptLinesCount, parentPopupContext)
|
||||
@@ -375,7 +375,7 @@ func (self *ConfirmationHelper) layoutMenuPrompt(contentWidth int) int {
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) resizeConfirmationPanel(parentPopupContext types.Context) {
|
||||
panelWidth := self.getPopupPanelWidth()
|
||||
panelWidth := self.getPopupPanelWidth(80)
|
||||
contentWidth := panelWidth - 2 // minus 2 for the frame
|
||||
confirmationView := self.c.Views().Confirmation
|
||||
prompt := confirmationView.Buffer()
|
||||
@@ -389,7 +389,7 @@ func (self *ConfirmationHelper) resizePromptPanel(parentPopupContext types.Conte
|
||||
if self.c.Views().Suggestions.Visible {
|
||||
suggestionsViewHeight = 11
|
||||
}
|
||||
panelWidth := self.getPopupPanelWidth()
|
||||
panelWidth := self.getPopupPanelWidth(80)
|
||||
contentWidth := panelWidth - 2 // minus 2 for the frame
|
||||
promptView := self.c.Views().Prompt
|
||||
prompt := promptView.TextArea.GetContent()
|
||||
@@ -403,7 +403,13 @@ func (self *ConfirmationHelper) resizePromptPanel(parentPopupContext types.Conte
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) ResizeCommitMessagePanels(parentPopupContext types.Context) {
|
||||
panelWidth := self.getPopupPanelWidth()
|
||||
maxWidth := 100
|
||||
if self.c.UserConfig().Git.Commit.AutoWrapCommitMessage {
|
||||
// Adding some extra space to make it very obvious that we're wrapping the commit message
|
||||
// for real, not just soft-wrapping for display at the window width.
|
||||
maxWidth = self.c.UserConfig().Git.Commit.AutoWrapWidth + 25
|
||||
}
|
||||
panelWidth := self.getPopupPanelWidth(maxWidth)
|
||||
content := self.c.Views().CommitDescription.TextArea.GetContent()
|
||||
summaryViewHeight := 3
|
||||
panelHeight := getMessageHeight(false, true, content, panelWidth, self.c.Views().CommitDescription.TabWidth)
|
||||
|
||||
@@ -110,20 +110,24 @@ func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error {
|
||||
return errors.New(self.c.Tr.BaseCommitIsAlreadyOnMainBranch)
|
||||
}
|
||||
|
||||
if len(hashGroups[NOT_MERGED]) > 1 {
|
||||
// If there are multiple commits that could be the base commit, list
|
||||
foundCommits := getCommitsForHashes(commits, hashGroups[NOT_MERGED])
|
||||
// If there are multiple commits that could be the base commit, remove all
|
||||
// those that are fixups for the last one.
|
||||
foundCommits = removeFixupCommits(foundCommits)
|
||||
|
||||
if len(foundCommits) > 1 {
|
||||
// If there are still multiple commits that could be the base commit, list
|
||||
// them in the error message. But only the candidates from the current
|
||||
// branch, not including any that are already merged.
|
||||
subjects := self.getHashesAndSubjects(commits, hashGroups[NOT_MERGED])
|
||||
subjects := getHashesAndSubjects(foundCommits)
|
||||
message := lo.Ternary(hasStagedChanges,
|
||||
self.c.Tr.MultipleBaseCommitsFoundStaged,
|
||||
self.c.Tr.MultipleBaseCommitsFoundUnstaged)
|
||||
return fmt.Errorf("%s\n\n%s", message, subjects)
|
||||
}
|
||||
|
||||
// At this point we know that the NOT_MERGED bucket has exactly one commit,
|
||||
// and that's the one we want to select.
|
||||
_, index, _ := self.findCommit(commits, hashGroups[NOT_MERGED][0])
|
||||
// Now we know that foundCommits has exactly one commit, so find its index
|
||||
_, index, _ := self.findCommit(commits, foundCommits[0].Hash())
|
||||
|
||||
return self.c.ConfirmIf(warnAboutAddedLines, types.ConfirmOpts{
|
||||
Title: self.c.Tr.FindBaseCommitForFixup,
|
||||
@@ -144,23 +148,52 @@ func (self *FixupHelper) HandleFindBaseCommitForFixupPress() error {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *FixupHelper) getHashesAndSubjects(commits []*models.Commit, hashes []string) string {
|
||||
func getCommitsForHashes(commits []*models.Commit, hashes []string) []*models.Commit {
|
||||
// This is called only for the NOT_MERGED commits, and we know that all of them are contained in
|
||||
// the commits slice.
|
||||
commitsSet := set.NewFromSlice(hashes)
|
||||
subjects := make([]string, 0, len(hashes))
|
||||
result := make([]*models.Commit, 0, len(hashes))
|
||||
for _, c := range commits {
|
||||
if commitsSet.Includes(c.Hash()) {
|
||||
subjects = append(subjects, fmt.Sprintf("%s %s", c.ShortRefName(), c.Name))
|
||||
result = append(result, c)
|
||||
commitsSet.Remove(c.Hash())
|
||||
if commitsSet.Len() == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func getHashesAndSubjects(commits []*models.Commit) string {
|
||||
subjects := lo.Map(commits, func(c *models.Commit, _ int) string {
|
||||
return fmt.Sprintf("%s %s", c.ShortRefName(), c.Name)
|
||||
})
|
||||
return strings.Join(subjects, "\n")
|
||||
}
|
||||
|
||||
func removeFixupCommits(commits []*models.Commit) []*models.Commit {
|
||||
if len(commits) <= 1 {
|
||||
return commits
|
||||
}
|
||||
|
||||
// If the last found commit is itself a fixup, don't eliminate anything.
|
||||
baseSubject, lastIsFixup := IsFixupCommit(commits[len(commits)-1].Name)
|
||||
if lastIsFixup {
|
||||
return commits
|
||||
}
|
||||
|
||||
// need to go backwards because we are mutating the slice as we go
|
||||
for i := len(commits) - 2; i >= 0; i-- {
|
||||
subject, isFixup := IsFixupCommit(commits[i].Name)
|
||||
if isFixup && subject == baseSubject {
|
||||
commits = utils.Remove(commits, i)
|
||||
}
|
||||
}
|
||||
|
||||
return commits
|
||||
}
|
||||
|
||||
func (self *FixupHelper) getDiff() (string, bool, error) {
|
||||
args := []string{"-U0", "--ignore-submodules=all", "HEAD", "--"}
|
||||
|
||||
@@ -350,3 +383,30 @@ func (self *FixupHelper) findCommit(commits []*models.Commit, hash string) (*mod
|
||||
return commit.Hash() == hash
|
||||
})
|
||||
}
|
||||
|
||||
// Check whether the given subject line is the subject of a fixup commit, and
|
||||
// returns (trimmedSubject, true) if so (where trimmedSubject is the subject
|
||||
// with all fixup prefixes removed), or (subject, false) if not.
|
||||
func IsFixupCommit(subject string) (string, bool) {
|
||||
prefixes := []string{"fixup! ", "squash! ", "amend! "}
|
||||
trimPrefix := func(s string) (string, bool) {
|
||||
for _, prefix := range prefixes {
|
||||
if trimmedSubject, ok := strings.CutPrefix(s, prefix); ok {
|
||||
return trimmedSubject, true
|
||||
}
|
||||
}
|
||||
return s, false
|
||||
}
|
||||
|
||||
if subject, wasTrimmed := trimPrefix(subject); wasTrimmed {
|
||||
for {
|
||||
// handle repeated prefixes like "fixup! amend! fixup! Subject"
|
||||
if subject, wasTrimmed = trimPrefix(subject); !wasTrimmed {
|
||||
break
|
||||
}
|
||||
}
|
||||
return subject, true
|
||||
}
|
||||
|
||||
return subject, false
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ package helpers
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -155,3 +158,184 @@ index 9ce8efb33..0632e41b0 100644
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFixupHelper_IsFixupCommit(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
subject string
|
||||
expectedTrimmedSubject string
|
||||
expectedIsFixup bool
|
||||
}{
|
||||
{
|
||||
subject: "Bla",
|
||||
expectedTrimmedSubject: "Bla",
|
||||
expectedIsFixup: false,
|
||||
},
|
||||
{
|
||||
subject: "fixup Bla",
|
||||
expectedTrimmedSubject: "fixup Bla",
|
||||
expectedIsFixup: false,
|
||||
},
|
||||
{
|
||||
subject: "fixup! Bla",
|
||||
expectedTrimmedSubject: "Bla",
|
||||
expectedIsFixup: true,
|
||||
},
|
||||
{
|
||||
subject: "fixup! fixup! Bla",
|
||||
expectedTrimmedSubject: "Bla",
|
||||
expectedIsFixup: true,
|
||||
},
|
||||
{
|
||||
subject: "amend! squash! Bla",
|
||||
expectedTrimmedSubject: "Bla",
|
||||
expectedIsFixup: true,
|
||||
},
|
||||
{
|
||||
subject: "fixup!",
|
||||
expectedTrimmedSubject: "fixup!",
|
||||
expectedIsFixup: false,
|
||||
},
|
||||
}
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.subject, func(t *testing.T) {
|
||||
trimmedSubject, isFixupCommit := IsFixupCommit(s.subject)
|
||||
assert.Equal(t, s.expectedTrimmedSubject, trimmedSubject)
|
||||
assert.Equal(t, s.expectedIsFixup, isFixupCommit)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFixupHelper_removeFixupCommits(t *testing.T) {
|
||||
hashPool := &utils.StringPool{}
|
||||
|
||||
type commitDesc struct {
|
||||
Hash string
|
||||
Name string
|
||||
}
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
commits []commitDesc
|
||||
expectedResult []commitDesc
|
||||
}{
|
||||
{
|
||||
name: "empty list",
|
||||
commits: []commitDesc{},
|
||||
expectedResult: []commitDesc{},
|
||||
},
|
||||
{
|
||||
name: "single commit",
|
||||
commits: []commitDesc{
|
||||
{"abc123", "Some feature"},
|
||||
},
|
||||
expectedResult: []commitDesc{
|
||||
{"abc123", "Some feature"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "two unrelated commits",
|
||||
commits: []commitDesc{
|
||||
{"abc123", "First feature"},
|
||||
{"def456", "Second feature"},
|
||||
},
|
||||
expectedResult: []commitDesc{
|
||||
{"abc123", "First feature"},
|
||||
{"def456", "Second feature"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fixup commit for last commit",
|
||||
commits: []commitDesc{
|
||||
{"abc123", "fixup! Some feature"},
|
||||
{"def456", "Some feature"},
|
||||
},
|
||||
expectedResult: []commitDesc{
|
||||
{"def456", "Some feature"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "amend and squash commits for last commit",
|
||||
commits: []commitDesc{
|
||||
{"abc123", "squash! Some feature"},
|
||||
{"def456", "amend! Some feature"},
|
||||
{"ghi789", "Some feature"},
|
||||
},
|
||||
expectedResult: []commitDesc{
|
||||
{"ghi789", "Some feature"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fixup commit for different commit",
|
||||
commits: []commitDesc{
|
||||
{"abc123", "fixup! Other feature"},
|
||||
{"def456", "Some feature"},
|
||||
},
|
||||
expectedResult: []commitDesc{
|
||||
{"abc123", "fixup! Other feature"},
|
||||
{"def456", "Some feature"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "last commit is a fixup itself",
|
||||
commits: []commitDesc{
|
||||
{"abc123", "fixup! Some feature"},
|
||||
{"def456", "fixup! Some feature"},
|
||||
},
|
||||
expectedResult: []commitDesc{
|
||||
{"abc123", "fixup! Some feature"},
|
||||
{"def456", "fixup! Some feature"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "nested fixup commit",
|
||||
commits: []commitDesc{
|
||||
{"abc123", "fixup! fixup! Some feature"},
|
||||
{"def456", "amend! squash! fixup! Some feature"},
|
||||
{"ghi789", "Some feature"},
|
||||
},
|
||||
expectedResult: []commitDesc{
|
||||
{"ghi789", "Some feature"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "fixup commits mixed with unrelated commits",
|
||||
commits: []commitDesc{
|
||||
{Hash: "abc123", Name: "fixup! Base commit"},
|
||||
{Hash: "def456", Name: "Unrelated commit"},
|
||||
{Hash: "ghi789", Name: "fixup! Base commit"},
|
||||
{Hash: "jkl012", Name: "Base commit"},
|
||||
},
|
||||
expectedResult: []commitDesc{
|
||||
{Hash: "def456", Name: "Unrelated commit"},
|
||||
{Hash: "jkl012", Name: "Base commit"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "only fixup commits for last commit removed, others preserved",
|
||||
commits: []commitDesc{
|
||||
{Hash: "abc123", Name: "fixup! First feature"},
|
||||
{Hash: "def456", Name: "fixup! Second feature"},
|
||||
{Hash: "ghi789", Name: "Second feature"},
|
||||
{Hash: "jkl012", Name: "First feature"},
|
||||
},
|
||||
expectedResult: []commitDesc{
|
||||
{Hash: "def456", Name: "fixup! Second feature"},
|
||||
{Hash: "ghi789", Name: "Second feature"},
|
||||
{Hash: "jkl012", Name: "First feature"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
makeCommitFromDesc := func(desc commitDesc, _ int) *models.Commit {
|
||||
return models.NewCommit(hashPool, models.NewCommitOpts{Hash: desc.Hash, Name: desc.Name})
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
commits := lo.Map(s.commits, makeCommitFromDesc)
|
||||
result := removeFixupCommits(commits)
|
||||
expectedCommits := lo.Map(s.expectedResult, makeCommitFromDesc)
|
||||
assert.Equal(t, expectedCommits, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ type Helpers struct {
|
||||
MergeAndRebase *MergeAndRebaseHelper
|
||||
MergeConflicts *MergeConflictsHelper
|
||||
CherryPick *CherryPickHelper
|
||||
Host *HostHelper
|
||||
PatchBuilding *PatchBuildingHelper
|
||||
Staging *StagingHelper
|
||||
GPG *GpgHelper
|
||||
@@ -53,6 +52,7 @@ type Helpers struct {
|
||||
Search *SearchHelper
|
||||
Worktree *WorktreeHelper
|
||||
SubCommits *SubCommitsHelper
|
||||
Host *HostHelper
|
||||
}
|
||||
|
||||
func NewStubHelpers() *Helpers {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/filetree"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/mergeconflicts"
|
||||
@@ -27,6 +29,7 @@ type RefreshHelper struct {
|
||||
mergeConflictsHelper *MergeConflictsHelper
|
||||
worktreeHelper *WorktreeHelper
|
||||
searchHelper *SearchHelper
|
||||
suggestionsHelper *SuggestionsHelper
|
||||
}
|
||||
|
||||
func NewRefreshHelper(
|
||||
@@ -38,6 +41,7 @@ func NewRefreshHelper(
|
||||
mergeConflictsHelper *MergeConflictsHelper,
|
||||
worktreeHelper *WorktreeHelper,
|
||||
searchHelper *SearchHelper,
|
||||
suggestionsHelper *SuggestionsHelper,
|
||||
) *RefreshHelper {
|
||||
return &RefreshHelper{
|
||||
c: c,
|
||||
@@ -48,6 +52,7 @@ func NewRefreshHelper(
|
||||
mergeConflictsHelper: mergeConflictsHelper,
|
||||
worktreeHelper: worktreeHelper,
|
||||
searchHelper: searchHelper,
|
||||
suggestionsHelper: suggestionsHelper,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +96,7 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) {
|
||||
types.STATUS,
|
||||
types.BISECT_INFO,
|
||||
types.STAGING,
|
||||
types.PULL_REQUESTS,
|
||||
})
|
||||
} else {
|
||||
scopeSet = set.NewFromSlice(options.Scope)
|
||||
@@ -117,6 +123,10 @@ func (self *RefreshHelper) Refresh(options types.RefreshOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
if scopeSet.Includes(types.PULL_REQUESTS) {
|
||||
refresh("pull requests", func() { _ = self.refreshGithubPullRequests() })
|
||||
}
|
||||
|
||||
includeWorktreesWithBranches := false
|
||||
if scopeSet.Includes(types.COMMITS) || scopeSet.Includes(types.BRANCHES) || scopeSet.Includes(types.REFLOG) || scopeSet.Includes(types.BISECT_INFO) {
|
||||
// whenever we change commits, we should update branches because the upstream/downstream
|
||||
@@ -584,11 +594,11 @@ func (self *RefreshHelper) refreshStateFiles() error {
|
||||
|
||||
// only taking over the filter if it hasn't already been set by the user.
|
||||
if conflictFileCount > 0 && prevConflictFileCount == 0 {
|
||||
if fileTreeViewModel.GetFilter() == filetree.DisplayAll {
|
||||
if fileTreeViewModel.GetStatusFilter() == filetree.DisplayAll {
|
||||
fileTreeViewModel.SetStatusFilter(filetree.DisplayConflicted)
|
||||
self.c.Contexts().Files.GetView().Subtitle = self.c.Tr.FilterLabelConflictingFiles
|
||||
}
|
||||
} else if conflictFileCount == 0 && fileTreeViewModel.GetFilter() == filetree.DisplayConflicted {
|
||||
} else if conflictFileCount == 0 && fileTreeViewModel.GetStatusFilter() == filetree.DisplayConflicted {
|
||||
fileTreeViewModel.SetStatusFilter(filetree.DisplayAll)
|
||||
self.c.Contexts().Files.GetView().Subtitle = ""
|
||||
}
|
||||
@@ -757,3 +767,188 @@ func (self *RefreshHelper) refreshView(context types.Context) {
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) refreshGithubPullRequests() error {
|
||||
self.c.Mutexes().RefreshingPullRequestsMutex.Lock()
|
||||
defer self.c.Mutexes().RefreshingPullRequestsMutex.Unlock()
|
||||
|
||||
if !self.c.UserConfig().Git.EnableGithubCli {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !self.c.Git().GitHub.InGithubRepo() {
|
||||
self.c.Model().PullRequests = []*models.GithubPullRequest{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load cached PRs for instant display while we fetch fresh data
|
||||
self.loadCachedPullRequests()
|
||||
|
||||
switch self.c.State().GetGitHubCliState() {
|
||||
case types.UNKNOWN:
|
||||
state := self.determineGithubCliState()
|
||||
self.c.State().SetGitHubCliState(state)
|
||||
if state != types.VALID {
|
||||
if state == types.INVALID_VERSION {
|
||||
// todo: i18n
|
||||
self.c.LogAction("gh version is too old (must be version 2 or greater), so pull requests will not be shown against branches.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
case types.VALID:
|
||||
// continue on
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := self.c.Git().GitHub.BaseRepo(); err != nil {
|
||||
ok, err := self.promptForBaseGithubRepo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if err := self.setGithubPullRequests(); err != nil {
|
||||
self.c.LogAction(fmt.Sprintf("Error fetching pull requests from GitHub: %s", err.Error()))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) promptForBaseGithubRepo() (bool, error) {
|
||||
err := self.refreshRemotes()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
remotes := self.c.Model().Remotes
|
||||
if len(remotes) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// If there's only one remote, or if 'origin' exists, use it automatically
|
||||
remote, hasOrigin := lo.Find(remotes, func(r *models.Remote) bool {
|
||||
return r.Name == "origin"
|
||||
})
|
||||
if !hasOrigin {
|
||||
if len(remotes) == 1 {
|
||||
remote = remotes[0]
|
||||
} else {
|
||||
// Multiple remotes with no 'origin': prompt the user
|
||||
self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.SelectRemoteRepository,
|
||||
InitialContent: "",
|
||||
FindSuggestionsFunc: self.suggestionsHelper.GetRemoteRepoSuggestionsFunc(),
|
||||
HandleConfirm: func(repository string) error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.LcSelectingRemote, func(gocui.Task) error {
|
||||
// `repository` is something like 'jesseduffield/lazygit'
|
||||
_, err := self.c.Git().GitHub.SetBaseRepo(repository)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return self.refreshGithubPullRequests()
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
if len(remote.Urls) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
repoName, err := self.c.Git().HostingService.GetRepoNameFromRemoteURL(remote.Urls[0])
|
||||
if err != nil {
|
||||
self.c.Log.Error(err)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
_, err = self.c.Git().GitHub.SetBaseRepo(repoName)
|
||||
if err != nil {
|
||||
self.c.Log.Error(err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) determineGithubCliState() types.GitHubCliState {
|
||||
installed, validVersion := self.c.Git().GitHub.DetermineGitHubCliState()
|
||||
if validVersion {
|
||||
return types.VALID
|
||||
} else if installed {
|
||||
return types.INVALID_VERSION
|
||||
}
|
||||
return types.NOT_INSTALLED
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) setGithubPullRequests() error {
|
||||
branches := lo.Filter(self.c.Model().Branches, func(branch *models.Branch, _ int) bool {
|
||||
return branch.IsTrackingRemote()
|
||||
})
|
||||
branchNames := lo.Map(branches, func(branch *models.Branch, _ int) string {
|
||||
return branch.UpstreamBranch
|
||||
})
|
||||
|
||||
prs, err := self.c.Git().GitHub.FetchRecentPRs(branchNames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Model().PullRequests = prs
|
||||
self.savePullRequestsToCache(prs)
|
||||
|
||||
self.c.PostRefreshUpdate(self.c.Contexts().Branches)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) loadCachedPullRequests() {
|
||||
repoPath := self.c.Git().RepoPaths.RepoPath()
|
||||
cachedPRs := self.c.GetAppState().GithubPullRequests[repoPath]
|
||||
if len(cachedPRs) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
prs := lo.Map(cachedPRs, func(cached config.CachedPullRequest, _ int) *models.GithubPullRequest {
|
||||
return &models.GithubPullRequest{
|
||||
HeadRefName: cached.HeadRefName,
|
||||
Number: cached.Number,
|
||||
Title: cached.Title,
|
||||
State: cached.State,
|
||||
Url: cached.Url,
|
||||
HeadRepositoryOwner: models.GithubRepositoryOwner{
|
||||
Login: cached.HeadRepositoryOwner,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
self.c.Model().PullRequests = prs
|
||||
self.c.PostRefreshUpdate(self.c.Contexts().Branches)
|
||||
}
|
||||
|
||||
func (self *RefreshHelper) savePullRequestsToCache(prs []*models.GithubPullRequest) {
|
||||
repoPath := self.c.Git().RepoPaths.RepoPath()
|
||||
cached := lo.Map(prs, func(pr *models.GithubPullRequest, _ int) config.CachedPullRequest {
|
||||
return config.CachedPullRequest{
|
||||
HeadRefName: pr.HeadRefName,
|
||||
Number: pr.Number,
|
||||
Title: pr.Title,
|
||||
State: pr.State,
|
||||
Url: pr.Url,
|
||||
HeadRepositoryOwner: pr.HeadRepositoryOwner.Login,
|
||||
}
|
||||
})
|
||||
|
||||
appState := self.c.GetAppState()
|
||||
if appState.GithubPullRequests == nil {
|
||||
appState.GithubPullRequests = make(map[string][]config.CachedPullRequest)
|
||||
}
|
||||
appState.GithubPullRequests[repoPath] = cached
|
||||
self.c.SaveAppStateAndLogError()
|
||||
}
|
||||
|
||||
@@ -54,7 +54,24 @@ func (self *RefsHelper) CheckoutRef(ref string, options types.CheckoutRefOptions
|
||||
// loading a heap of commits is slow so we limit them whenever doing a reset
|
||||
self.c.Contexts().LocalCommits.SetLimitCommits(true)
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true})
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.BLOCK_UI, KeepBranchSelectionIndex: true, Scope: []types.RefreshableView{
|
||||
types.COMMITS,
|
||||
types.BRANCHES,
|
||||
types.FILES,
|
||||
types.STASH,
|
||||
types.REFLOG,
|
||||
types.TAGS,
|
||||
types.REMOTES,
|
||||
types.WORKTREES,
|
||||
types.STATUS,
|
||||
types.BISECT_INFO,
|
||||
types.STAGING,
|
||||
}, Then: func() {
|
||||
// Fetch PRs in the background after checkout; the new branch may
|
||||
// have a PR that we haven't fetched yet. We do this in Then so
|
||||
// that branches are loaded first (PRs are matched against branches).
|
||||
self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC, Scope: []types.RefreshableView{types.PULL_REQUESTS}})
|
||||
}})
|
||||
}
|
||||
|
||||
localBranch, found := lo.Find(self.c.Model().Branches, func(branch *models.Branch) bool {
|
||||
|
||||
@@ -33,6 +33,8 @@ func NewSearchHelper(
|
||||
func (self *SearchHelper) OpenFilterPrompt(context types.IFilterableContext) error {
|
||||
state := self.searchState()
|
||||
|
||||
state.PrevSearchIndex = -1
|
||||
|
||||
state.Context = context
|
||||
|
||||
self.searchPrefixView().SetContent(context.FilterPrefix(self.c.Tr))
|
||||
@@ -238,7 +240,7 @@ func (self *SearchHelper) ReApplyFilter(context types.Context) {
|
||||
filterableContext, ok := context.(types.IFilterableContext)
|
||||
if ok {
|
||||
state := self.searchState()
|
||||
if context == state.Context {
|
||||
if context == state.Context && self.c.Context().Current().GetKey() == self.c.Contexts().Search.GetKey() {
|
||||
filterableContext.SetSelection(0)
|
||||
filterableContext.GetView().SetOriginY(0)
|
||||
}
|
||||
|
||||
@@ -64,6 +64,30 @@ func (self *SuggestionsHelper) getBranchNames() []string {
|
||||
})
|
||||
}
|
||||
|
||||
func (self *SuggestionsHelper) GetRemoteRepoSuggestionsFunc() func(string) []*types.Suggestion {
|
||||
repoNames := self.getRemoteRepoNames()
|
||||
|
||||
return FilterFunc(repoNames, self.c.UserConfig().Gui.UseFuzzySearch())
|
||||
}
|
||||
|
||||
func (self *SuggestionsHelper) getRemoteRepoNames() []string {
|
||||
remotes := self.c.Model().Remotes
|
||||
result := make([]string, 0, len(remotes))
|
||||
for _, remote := range remotes {
|
||||
if len(remote.Urls) == 0 {
|
||||
continue
|
||||
}
|
||||
repoName, err := self.c.Git().HostingService.GetRepoNameFromRemoteURL(remote.Urls[0])
|
||||
if err != nil {
|
||||
self.c.Log.Error(err)
|
||||
continue
|
||||
}
|
||||
result = append(result, repoName)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (self *SuggestionsHelper) GetBranchNameSuggestionsFunc() func(string) []*types.Suggestion {
|
||||
branchNames := self.getBranchNames()
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/git_commands"
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
@@ -60,19 +61,5 @@ func (self *UpstreamHelper) PromptForUpstreamWithoutInitialContent(_ *models.Bra
|
||||
}
|
||||
|
||||
func (self *UpstreamHelper) GetSuggestedRemote() string {
|
||||
return getSuggestedRemote(self.c.Model().Remotes)
|
||||
}
|
||||
|
||||
func getSuggestedRemote(remotes []*models.Remote) string {
|
||||
if len(remotes) == 0 {
|
||||
return "origin"
|
||||
}
|
||||
|
||||
for _, remote := range remotes {
|
||||
if remote.Name == "origin" {
|
||||
return remote.Name
|
||||
}
|
||||
}
|
||||
|
||||
return remotes[0].Name
|
||||
return git_commands.GetSuggestedRemoteName(self.c.Model().Remotes)
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
package helpers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGetSuggestedRemote(t *testing.T) {
|
||||
cases := []struct {
|
||||
remotes []*models.Remote
|
||||
expected string
|
||||
}{
|
||||
{mkRemoteList(), "origin"},
|
||||
{mkRemoteList("upstream", "origin", "foo"), "origin"},
|
||||
{mkRemoteList("upstream", "foo", "bar"), "upstream"},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
result := getSuggestedRemote(c.remotes)
|
||||
assert.EqualValues(t, c.expected, result)
|
||||
}
|
||||
}
|
||||
|
||||
func mkRemoteList(names ...string) []*models.Remote {
|
||||
return lo.Map(names, func(name string, _ int) *models.Remote {
|
||||
return &models.Remote{Name: name}
|
||||
})
|
||||
}
|
||||
@@ -104,6 +104,7 @@ func (self *ListController) handleLineChangeAux(f func(int), change int) error {
|
||||
// doing this check so that if we're holding the up key at the start of the list
|
||||
// we're not constantly re-rendering the main view.
|
||||
cursorMoved := before != after
|
||||
originYBefore := self.context.GetView().OriginY()
|
||||
if cursorMoved {
|
||||
switch change {
|
||||
case -1:
|
||||
@@ -116,6 +117,15 @@ func (self *ListController) handleLineChangeAux(f func(int), change int) error {
|
||||
}
|
||||
|
||||
if cursorMoved || rangeBefore != rangeAfter {
|
||||
if originYBefore != self.context.GetView().OriginY() {
|
||||
// Since we already scrolled the view above, the normal mechanism that
|
||||
// ListContextTrait.FocusLine uses for deciding whether rerendering is needed won't
|
||||
// work. It is based on checking whether the origin was changed by the call to
|
||||
// FocusPoint in that function, but since we scrolled the view directly above, the
|
||||
// origin has already been updated. So we must tell it explicitly to rerender.
|
||||
self.context.SetNeedRerenderVisibleLines()
|
||||
}
|
||||
|
||||
self.context.HandleFocus(types.OnFocusOpts{ScrollSelectionIntoView: true})
|
||||
} else {
|
||||
// If the selection did not change (because, for example, we are at the top of the list and
|
||||
|
||||
@@ -82,6 +82,15 @@ func (self *LocalCommitsController) GetKeybindings(opts types.KeybindingsOpts) [
|
||||
Tooltip: self.c.Tr.FixupTooltip,
|
||||
DisplayOnScreen: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Commits.SetFixupMessage),
|
||||
Handler: self.withItem(self.setFixupMessage),
|
||||
GetDisabledReason: self.require(
|
||||
self.singleItemSelected(self.canSetFixupMessage),
|
||||
),
|
||||
Description: self.c.Tr.SetFixupMessage,
|
||||
Tooltip: self.c.Tr.SetFixupMessageTooltip,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Commits.RenameCommit),
|
||||
Handler: self.withItem(self.reword),
|
||||
@@ -321,20 +330,71 @@ func (self *LocalCommitsController) fixup(selectedCommits []*models.Commit, star
|
||||
return self.updateTodos(todo.Fixup, selectedCommits)
|
||||
}
|
||||
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.Fixup,
|
||||
Prompt: self.c.Tr.SureFixupThisCommit,
|
||||
HandleConfirm: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.FixupCommit)
|
||||
return self.interactiveRebase(todo.Fixup, startIdx, endIdx)
|
||||
})
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.Fixup,
|
||||
Items: []*types.MenuItem{
|
||||
{
|
||||
Label: self.c.Tr.Fixup,
|
||||
Key: 'f',
|
||||
OnPress: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.FixupCommit)
|
||||
return self.interactiveRebase(todo.Fixup, startIdx, endIdx)
|
||||
})
|
||||
},
|
||||
Tooltip: self.c.Tr.FixupTooltip,
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.FixupKeepMessage,
|
||||
Key: 'c',
|
||||
OnPress: func() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.FixingStatus, func(gocui.Task) error {
|
||||
self.c.LogAction(self.c.Tr.Actions.FixupCommitKeepMessage)
|
||||
return self.interactiveRebaseWithFlag(todo.Fixup, startIdx, endIdx, "-C")
|
||||
})
|
||||
},
|
||||
Tooltip: self.c.Tr.FixupKeepMessageTooltip,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) canSetFixupMessage(commit *models.Commit) *types.DisabledReason {
|
||||
if !self.isRebasing() {
|
||||
return &types.DisabledReason{Text: self.c.Tr.NotMidRebase}
|
||||
}
|
||||
|
||||
if commit.Action != todo.Fixup {
|
||||
return &types.DisabledReason{Text: self.c.Tr.MustSelectFixupCommit}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) setFixupMessage(commit *models.Commit) error {
|
||||
return self.c.Menu(types.CreateMenuOptions{
|
||||
Title: self.c.Tr.SetFixupMessage,
|
||||
Items: []*types.MenuItem{
|
||||
{
|
||||
Label: self.c.Tr.FixupDiscardMessage,
|
||||
Key: 'f',
|
||||
OnPress: func() error {
|
||||
return self.updateTodosWithFlag(todo.Fixup, []*models.Commit{commit}, "")
|
||||
},
|
||||
Tooltip: self.c.Tr.FixupDiscardMessageTooltip,
|
||||
},
|
||||
{
|
||||
Label: self.c.Tr.FixupKeepMessage,
|
||||
Key: 'c',
|
||||
OnPress: func() error {
|
||||
return self.updateTodosWithFlag(todo.Fixup, []*models.Commit{commit}, "-C")
|
||||
},
|
||||
Tooltip: self.c.Tr.FixupKeepMessageTooltip,
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) reword(commit *models.Commit) error {
|
||||
commitIdx := self.context().GetSelectedLineIdx()
|
||||
if self.c.Git().Config.NeedsGpgSubprocessForCommit() && !self.isHeadCommit(commitIdx) {
|
||||
@@ -505,7 +565,7 @@ func (self *LocalCommitsController) edit(selectedCommits []*models.Commit, start
|
||||
commits := self.c.Model().Commits
|
||||
if !commits[endIdx].IsMerge() {
|
||||
selectionRangeAndMode := self.getSelectionRangeAndMode()
|
||||
err := self.c.Git().Rebase.InteractiveRebase(commits, startIdx, endIdx, todo.Edit)
|
||||
err := self.c.Git().Rebase.InteractiveRebase(commits, startIdx, endIdx, todo.Edit, "")
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
|
||||
err,
|
||||
types.RefreshOptions{
|
||||
@@ -611,13 +671,17 @@ func (self *LocalCommitsController) pick(selectedCommits []*models.Commit) error
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) interactiveRebase(action todo.TodoCommand, startIdx int, endIdx int) error {
|
||||
return self.interactiveRebaseWithFlag(action, startIdx, endIdx, "")
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) interactiveRebaseWithFlag(action todo.TodoCommand, startIdx int, endIdx int, flag string) error {
|
||||
// When performing an action that will remove the selected commits, we need to select the
|
||||
// next commit down (which will end up at the start index after the action is performed)
|
||||
if action == todo.Drop || action == todo.Fixup || action == todo.Squash {
|
||||
self.context().SetSelection(startIdx)
|
||||
}
|
||||
|
||||
err := self.c.Git().Rebase.InteractiveRebase(self.c.Model().Commits, startIdx, endIdx, action)
|
||||
err := self.c.Git().Rebase.InteractiveRebase(self.c.Model().Commits, startIdx, endIdx, action, flag)
|
||||
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
}
|
||||
@@ -626,7 +690,11 @@ func (self *LocalCommitsController) interactiveRebase(action todo.TodoCommand, s
|
||||
// commit meaning you are trying to edit the todo file rather than actually
|
||||
// begin a rebase. It then updates the todo file with that action
|
||||
func (self *LocalCommitsController) updateTodos(action todo.TodoCommand, selectedCommits []*models.Commit) error {
|
||||
if err := self.c.Git().Rebase.EditRebaseTodo(selectedCommits, action); err != nil {
|
||||
return self.updateTodosWithFlag(action, selectedCommits, "")
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) updateTodosWithFlag(action todo.TodoCommand, selectedCommits []*models.Commit, flag string) error {
|
||||
if err := self.c.Git().Rebase.EditRebaseTodo(selectedCommits, action, flag); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -670,6 +738,7 @@ func (self *LocalCommitsController) moveDown(selectedCommits []*models.Commit, s
|
||||
return err
|
||||
}
|
||||
self.context().MoveSelection(1)
|
||||
self.context().HandleFocus(types.OnFocusOpts{ScrollSelectionIntoView: true})
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS},
|
||||
@@ -682,6 +751,7 @@ func (self *LocalCommitsController) moveDown(selectedCommits []*models.Commit, s
|
||||
err := self.c.Git().Rebase.MoveCommitsDown(self.c.Model().Commits, startIdx, endIdx)
|
||||
if err == nil {
|
||||
self.context().MoveSelection(1)
|
||||
self.context().HandleFocus(types.OnFocusOpts{ScrollSelectionIntoView: true})
|
||||
}
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
|
||||
err, types.RefreshOptions{Mode: types.SYNC})
|
||||
@@ -694,6 +764,7 @@ func (self *LocalCommitsController) moveUp(selectedCommits []*models.Commit, sta
|
||||
return err
|
||||
}
|
||||
self.context().MoveSelection(-1)
|
||||
self.context().HandleFocus(types.OnFocusOpts{ScrollSelectionIntoView: true})
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
Mode: types.SYNC, Scope: []types.RefreshableView{types.REBASE_COMMITS},
|
||||
@@ -706,6 +777,7 @@ func (self *LocalCommitsController) moveUp(selectedCommits []*models.Commit, sta
|
||||
err := self.c.Git().Rebase.MoveCommitsUp(self.c.Model().Commits, startIdx, endIdx)
|
||||
if err == nil {
|
||||
self.context().MoveSelection(-1)
|
||||
self.context().HandleFocus(types.OnFocusOpts{ScrollSelectionIntoView: true})
|
||||
}
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebaseWithRefreshOptions(
|
||||
err, types.RefreshOptions{Mode: types.SYNC})
|
||||
@@ -1096,7 +1168,7 @@ func countSquashableCommitsAbove(commits []*models.Commit, selectedIdx int, reba
|
||||
// For each commit _above_ the selection, ...
|
||||
for i, commit := range commits[0:selectedIdx] {
|
||||
// ... see if it is a fixup commit, and get the base subject it applies to
|
||||
if baseSubject, isFixup := isFixupCommit(commit.Name); isFixup {
|
||||
if baseSubject, isFixup := helpers.IsFixupCommit(commit.Name); isFixup {
|
||||
// Then, for each commit after the fixup, up to and including the
|
||||
// rebase start commit, see if we find the base commit
|
||||
for _, baseCommit := range commits[i+1 : rebaseStartIdx+1] {
|
||||
@@ -1109,33 +1181,6 @@ func countSquashableCommitsAbove(commits []*models.Commit, selectedIdx int, reba
|
||||
return result
|
||||
}
|
||||
|
||||
// Check whether the given subject line is the subject of a fixup commit, and
|
||||
// returns (trimmedSubject, true) if so (where trimmedSubject is the subject
|
||||
// with all fixup prefixes removed), or (subject, false) if not.
|
||||
func isFixupCommit(subject string) (string, bool) {
|
||||
prefixes := []string{"fixup! ", "squash! ", "amend! "}
|
||||
trimPrefix := func(s string) (string, bool) {
|
||||
for _, prefix := range prefixes {
|
||||
if trimmedSubject, ok := strings.CutPrefix(s, prefix); ok {
|
||||
return trimmedSubject, true
|
||||
}
|
||||
}
|
||||
return s, false
|
||||
}
|
||||
|
||||
if subject, wasTrimmed := trimPrefix(subject); wasTrimmed {
|
||||
for {
|
||||
// handle repeated prefixes like "fixup! amend! fixup! Subject"
|
||||
if subject, wasTrimmed = trimPrefix(subject); !wasTrimmed {
|
||||
break
|
||||
}
|
||||
}
|
||||
return subject, true
|
||||
}
|
||||
|
||||
return subject, false
|
||||
}
|
||||
|
||||
func (self *LocalCommitsController) createTag(commit *models.Commit) error {
|
||||
return self.c.Helpers().Tags.OpenCreateTagPrompt(commit.Hash(), func() {})
|
||||
}
|
||||
|
||||
@@ -93,49 +93,3 @@ func Test_countSquashableCommitsAbove(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_isFixupCommit(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
subject string
|
||||
expectedTrimmedSubject string
|
||||
expectedIsFixup bool
|
||||
}{
|
||||
{
|
||||
subject: "Bla",
|
||||
expectedTrimmedSubject: "Bla",
|
||||
expectedIsFixup: false,
|
||||
},
|
||||
{
|
||||
subject: "fixup Bla",
|
||||
expectedTrimmedSubject: "fixup Bla",
|
||||
expectedIsFixup: false,
|
||||
},
|
||||
{
|
||||
subject: "fixup! Bla",
|
||||
expectedTrimmedSubject: "Bla",
|
||||
expectedIsFixup: true,
|
||||
},
|
||||
{
|
||||
subject: "fixup! fixup! Bla",
|
||||
expectedTrimmedSubject: "Bla",
|
||||
expectedIsFixup: true,
|
||||
},
|
||||
{
|
||||
subject: "amend! squash! Bla",
|
||||
expectedTrimmedSubject: "Bla",
|
||||
expectedIsFixup: true,
|
||||
},
|
||||
{
|
||||
subject: "fixup!",
|
||||
expectedTrimmedSubject: "fixup!",
|
||||
expectedIsFixup: false,
|
||||
},
|
||||
}
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.subject, func(t *testing.T) {
|
||||
trimmedSubject, isFixupCommit := isFixupCommit(s.subject)
|
||||
assert.Equal(t, s.expectedTrimmedSubject, trimmedSubject)
|
||||
assert.Equal(t, s.expectedIsFixup, isFixupCommit)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
@@ -42,6 +45,14 @@ func (self *PatchBuildingController) GetKeybindings(opts types.KeybindingsOpts)
|
||||
Description: self.c.Tr.ToggleSelectionForPatch,
|
||||
DisplayOnScreen: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Remove),
|
||||
Handler: self.DiscardSelection,
|
||||
GetDisabledReason: self.getDisabledReasonForDiscard,
|
||||
Description: self.c.Tr.RemoveSelectionFromPatch,
|
||||
Tooltip: self.c.Tr.RemoveSelectionFromPatchTooltip,
|
||||
DisplayOnScreen: true,
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Return),
|
||||
Handler: self.Escape,
|
||||
@@ -168,6 +179,83 @@ func (self *PatchBuildingController) toggleSelection() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *PatchBuildingController) getDisabledReasonForDiscard() *types.DisabledReason {
|
||||
if !self.c.Git().Patch.PatchBuilder.CanRebase {
|
||||
return &types.DisabledReason{Text: self.c.Tr.CanOnlyRemoveLinesFromLocalCommits}
|
||||
}
|
||||
if !self.c.Git().Patch.PatchBuilder.IsEmpty() {
|
||||
return &types.DisabledReason{Text: self.c.Tr.MustClearPatchBeforeRemovingLines}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *PatchBuildingController) DiscardSelection() error {
|
||||
if self.c.UserConfig().Git.DiffContextSize == 0 {
|
||||
return fmt.Errorf(self.c.Tr.Actions.NotEnoughContextToRemoveLines,
|
||||
keybindings.Label(self.c.UserConfig().Keybinding.Universal.IncreaseContextInDiffView))
|
||||
}
|
||||
|
||||
if ok, err := self.c.Helpers().PatchBuilding.ValidateNormalWorkingTreeState(); !ok {
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Confirm(types.ConfirmOpts{
|
||||
Title: self.c.Tr.RemoveLinesFromCommitTitle,
|
||||
Prompt: self.c.Tr.RemoveLinesFromCommitPrompt,
|
||||
HandleConfirm: func() error {
|
||||
return self.removeSelectionFromCommit()
|
||||
},
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *PatchBuildingController) addSelectionToPatch() error {
|
||||
self.context().GetMutex().Lock()
|
||||
defer self.context().GetMutex().Unlock()
|
||||
|
||||
filename := self.c.Contexts().CommitFiles.GetSelectedPath()
|
||||
if filename == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
state := self.context().GetState()
|
||||
lineIndicesToToggle := state.LineIndicesOfAddedOrDeletedLinesInSelectedPatchRange()
|
||||
if len(lineIndicesToToggle) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.c.Git().Patch.PatchBuilder.AddFileLineRange(filename, lineIndicesToToggle)
|
||||
}
|
||||
|
||||
func (self *PatchBuildingController) removeSelectionFromCommit() error {
|
||||
if err := self.addSelectionToPatch(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if self.c.Git().Patch.PatchBuilder.IsEmpty() {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.c.Helpers().PatchBuilding.Escape()
|
||||
|
||||
return self.c.WithWaitingStatus(self.c.Tr.RebasingStatus, func(gocui.Task) error {
|
||||
commitIndex := self.getPatchCommitIndex()
|
||||
self.c.LogAction(self.c.Tr.Actions.RemovePatchFromCommit)
|
||||
err := self.c.Git().Patch.DeletePatchesFromCommit(self.c.Model().Commits, commitIndex)
|
||||
return self.c.Helpers().MergeAndRebase.CheckMergeOrRebase(err)
|
||||
})
|
||||
}
|
||||
|
||||
func (self *PatchBuildingController) getPatchCommitIndex() int {
|
||||
for index, commit := range self.c.Model().Commits {
|
||||
if commit.Hash() == self.c.Git().Patch.PatchBuilder.To {
|
||||
return index
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (self *PatchBuildingController) Escape() error {
|
||||
context := self.c.Contexts().CustomPatchBuilder
|
||||
state := context.GetState()
|
||||
|
||||
@@ -125,7 +125,7 @@ func (self *SubmodulesController) GetOnRenderToMain() func() {
|
||||
if file == nil {
|
||||
task = types.NewRenderStringTask(prefix)
|
||||
} else {
|
||||
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(file, false, !file.HasUnstagedChanges && file.HasStagedChanges)
|
||||
cmdObj := self.c.Git().WorkingTree.WorktreeFileDiffCmdObj(file, false, !file.HasUnstagedChanges && file.HasStagedChanges, nil)
|
||||
task = types.NewRunCommandTaskWithPrefix(cmdObj.GetCmd(), prefix)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,12 +80,12 @@ func (self *SwitchToDiffFilesController) enter() error {
|
||||
}
|
||||
}
|
||||
|
||||
commitFilesContext.ClearFilter()
|
||||
commitFilesContext.ReInit(ref, refsRange)
|
||||
commitFilesContext.SetSelection(0)
|
||||
commitFilesContext.SetCanRebase(canRebase)
|
||||
commitFilesContext.SetParentContext(self.context)
|
||||
commitFilesContext.SetWindowName(self.context.GetWindowName())
|
||||
commitFilesContext.ClearSearchString()
|
||||
commitFilesContext.GetView().TitlePrefix = self.context.GetView().TitlePrefix
|
||||
|
||||
self.c.Refresh(types.RefreshOptions{
|
||||
|
||||
@@ -15,6 +15,8 @@ type ICommitFileTree interface {
|
||||
GetAllItems() []*CommitFileNode
|
||||
GetAllFiles() []*models.CommitFile
|
||||
GetRoot() *CommitFileNode
|
||||
SetTextFilter(filter string, useFuzzySearch bool)
|
||||
GetTextFilter() string
|
||||
}
|
||||
|
||||
type CommitFileTree struct {
|
||||
@@ -23,6 +25,8 @@ type CommitFileTree struct {
|
||||
showTree bool
|
||||
common *common.Common
|
||||
collapsedPaths *CollapsedPaths
|
||||
textFilter string
|
||||
useFuzzySearch bool
|
||||
}
|
||||
|
||||
func (self *CommitFileTree) CollapseAll() {
|
||||
@@ -93,15 +97,34 @@ func (self *CommitFileTree) GetAllFiles() []*models.CommitFile {
|
||||
return self.getFiles()
|
||||
}
|
||||
|
||||
func (self *CommitFileTree) getFilesForDisplay() []*models.CommitFile {
|
||||
files := self.getFiles()
|
||||
if self.textFilter != "" {
|
||||
files = filterCommitFilesByText(files, self.textFilter, self.useFuzzySearch)
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
func (self *CommitFileTree) SetTree() {
|
||||
filesForDisplay := self.getFilesForDisplay()
|
||||
showRootItem := self.common.UserConfig().Gui.ShowRootItemInFileTree
|
||||
if self.showTree {
|
||||
self.tree = BuildTreeFromCommitFiles(self.getFiles(), showRootItem)
|
||||
self.tree = BuildTreeFromCommitFiles(filesForDisplay, showRootItem)
|
||||
} else {
|
||||
self.tree = BuildFlatTreeFromCommitFiles(self.getFiles(), showRootItem)
|
||||
self.tree = BuildFlatTreeFromCommitFiles(filesForDisplay, showRootItem)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *CommitFileTree) SetTextFilter(filter string, useFuzzySearch bool) {
|
||||
self.textFilter = filter
|
||||
self.useFuzzySearch = useFuzzySearch
|
||||
self.SetTree()
|
||||
}
|
||||
|
||||
func (self *CommitFileTree) GetTextFilter() string {
|
||||
return self.textFilter
|
||||
}
|
||||
|
||||
func (self *CommitFileTree) IsCollapsed(path string) bool {
|
||||
return self.collapsedPaths.IsCollapsed(path)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
@@ -39,6 +41,8 @@ type CommitFileTreeViewModel struct {
|
||||
// we set this to true when you're viewing the files within the checked-out branch's commits.
|
||||
// If you're viewing the files of some random other branch we can't do any rebase stuff.
|
||||
canRebase bool
|
||||
|
||||
searchHistory *utils.HistoryBuffer[string]
|
||||
}
|
||||
|
||||
var _ ICommitFileTreeViewModel = &CommitFileTreeViewModel{}
|
||||
@@ -52,6 +56,7 @@ func NewCommitFileTreeViewModel(getFiles func() []*models.CommitFile, common *co
|
||||
ref: nil,
|
||||
refRange: nil,
|
||||
canRebase: false,
|
||||
searchHistory: utils.NewHistoryBuffer[string](1000),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,3 +208,51 @@ func (self *CommitFileTreeViewModel) SelectPath(filepath string, showRootItem bo
|
||||
self.SetSelection(index)
|
||||
}
|
||||
}
|
||||
|
||||
// IFilterableContext methods
|
||||
|
||||
func (self *CommitFileTreeViewModel) SetFilter(filter string, useFuzzySearch bool) {
|
||||
self.ICommitFileTree.SetTextFilter(filter, useFuzzySearch)
|
||||
}
|
||||
|
||||
func (self *CommitFileTreeViewModel) GetFilter() string {
|
||||
return self.ICommitFileTree.GetTextFilter()
|
||||
}
|
||||
|
||||
func (self *CommitFileTreeViewModel) ClearFilter() {
|
||||
selectedNode := self.GetSelected()
|
||||
var selectedPath string
|
||||
if selectedNode != nil {
|
||||
selectedPath = selectedNode.GetInternalPath()
|
||||
}
|
||||
|
||||
self.ICommitFileTree.SetTextFilter("", false)
|
||||
|
||||
if selectedPath != "" {
|
||||
self.ExpandToPath(selectedPath)
|
||||
if idx, found := self.GetIndexForPath(selectedPath); found {
|
||||
self.SetSelection(idx)
|
||||
return
|
||||
}
|
||||
}
|
||||
self.ClampSelection()
|
||||
}
|
||||
|
||||
func (self *CommitFileTreeViewModel) ReApplyFilter(useFuzzySearch bool) {
|
||||
self.ICommitFileTree.SetTextFilter(self.ICommitFileTree.GetTextFilter(), useFuzzySearch)
|
||||
}
|
||||
|
||||
func (self *CommitFileTreeViewModel) IsFiltering() bool {
|
||||
return self.ICommitFileTree.GetTextFilter() != ""
|
||||
}
|
||||
|
||||
// used for type switch
|
||||
func (self *CommitFileTreeViewModel) IsFilterableContext() {}
|
||||
|
||||
func (self *CommitFileTreeViewModel) FilterPrefix(tr *i18n.TranslationSet) string {
|
||||
return tr.FilterPrefix
|
||||
}
|
||||
|
||||
func (self *CommitFileTreeViewModel) GetSearchHistory() *utils.HistoryBuffer[string] {
|
||||
return self.searchHistory
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package filetree
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/sahilm/fuzzy"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type filePathSource struct {
|
||||
files []*models.File
|
||||
}
|
||||
|
||||
func (s *filePathSource) String(i int) string {
|
||||
return s.files[i].Path
|
||||
}
|
||||
|
||||
func (s *filePathSource) Len() int {
|
||||
return len(s.files)
|
||||
}
|
||||
|
||||
func filterFilesByText(files []*models.File, filter string, useFuzzySearch bool) []*models.File {
|
||||
source := &filePathSource{files: files}
|
||||
matches := utils.FindFrom(filter, source, useFuzzySearch)
|
||||
return lo.Map(matches, func(match fuzzy.Match, _ int) *models.File {
|
||||
return files[match.Index]
|
||||
})
|
||||
}
|
||||
|
||||
type commitFilePathSource struct {
|
||||
files []*models.CommitFile
|
||||
}
|
||||
|
||||
func (s *commitFilePathSource) String(i int) string {
|
||||
return s.files[i].Path
|
||||
}
|
||||
|
||||
func (s *commitFilePathSource) Len() int {
|
||||
return len(s.files)
|
||||
}
|
||||
|
||||
func filterCommitFilesByText(files []*models.CommitFile, filter string, useFuzzySearch bool) []*models.CommitFile {
|
||||
source := &commitFilePathSource{files: files}
|
||||
matches := utils.FindFrom(filter, source, useFuzzySearch)
|
||||
return lo.Map(matches, func(match fuzzy.Match, _ int) *models.CommitFile {
|
||||
return files[match.Index]
|
||||
})
|
||||
}
|
||||
@@ -46,8 +46,10 @@ type IFileTree interface {
|
||||
GetFile(path string) *models.File
|
||||
GetAllItems() []*FileNode
|
||||
GetAllFiles() []*models.File
|
||||
GetFilter() FileTreeDisplayFilter
|
||||
GetStatusFilter() FileTreeDisplayFilter
|
||||
GetRoot() *FileNode
|
||||
SetTextFilter(filter string, useFuzzySearch bool)
|
||||
GetTextFilter() string
|
||||
}
|
||||
|
||||
type FileTree struct {
|
||||
@@ -57,6 +59,8 @@ type FileTree struct {
|
||||
common *common.Common
|
||||
filter FileTreeDisplayFilter
|
||||
collapsedPaths *CollapsedPaths
|
||||
textFilter string
|
||||
useFuzzySearch bool
|
||||
}
|
||||
|
||||
var _ IFileTree = &FileTree{}
|
||||
@@ -80,24 +84,31 @@ func (self *FileTree) ExpandToPath(path string) {
|
||||
}
|
||||
|
||||
func (self *FileTree) getFilesForDisplay() []*models.File {
|
||||
var files []*models.File
|
||||
switch self.filter {
|
||||
case DisplayAll:
|
||||
return self.getFiles()
|
||||
files = self.getFiles()
|
||||
case DisplayStaged:
|
||||
return self.FilterFiles(func(file *models.File) bool { return file.HasStagedChanges })
|
||||
files = self.FilterFiles(func(file *models.File) bool { return file.HasStagedChanges })
|
||||
case DisplayUnstaged:
|
||||
return self.FilterFiles(func(file *models.File) bool { return file.HasUnstagedChanges })
|
||||
files = self.FilterFiles(func(file *models.File) bool { return file.HasUnstagedChanges })
|
||||
case DisplayTracked:
|
||||
// untracked but staged files are technically not tracked by git
|
||||
// but including such files in the filtered mode helps see what files are getting committed
|
||||
return self.FilterFiles(func(file *models.File) bool { return file.Tracked || file.HasStagedChanges })
|
||||
files = self.FilterFiles(func(file *models.File) bool { return file.Tracked || file.HasStagedChanges })
|
||||
case DisplayUntracked:
|
||||
return self.FilterFiles(func(file *models.File) bool { return !(file.Tracked || file.HasStagedChanges) })
|
||||
files = self.FilterFiles(func(file *models.File) bool { return !(file.Tracked || file.HasStagedChanges) })
|
||||
case DisplayConflicted:
|
||||
return self.FilterFiles(func(file *models.File) bool { return file.HasMergeConflicts })
|
||||
files = self.FilterFiles(func(file *models.File) bool { return file.HasMergeConflicts })
|
||||
default:
|
||||
panic(fmt.Sprintf("Unexpected files display filter: %d", self.filter))
|
||||
}
|
||||
|
||||
if self.textFilter != "" {
|
||||
files = filterFilesByText(files, self.textFilter, self.useFuzzySearch)
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
func (self *FileTree) ForceShowUntracked() bool {
|
||||
@@ -210,6 +221,16 @@ func (self *FileTree) CollapsedPaths() *CollapsedPaths {
|
||||
return self.collapsedPaths
|
||||
}
|
||||
|
||||
func (self *FileTree) GetFilter() FileTreeDisplayFilter {
|
||||
func (self *FileTree) GetStatusFilter() FileTreeDisplayFilter {
|
||||
return self.filter
|
||||
}
|
||||
|
||||
func (self *FileTree) SetTextFilter(filter string, useFuzzySearch bool) {
|
||||
self.textFilter = filter
|
||||
self.useFuzzySearch = useFuzzySearch
|
||||
self.SetTree()
|
||||
}
|
||||
|
||||
func (self *FileTree) GetTextFilter() string {
|
||||
return self.textFilter
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/common"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context/traits"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
"github.com/jesseduffield/lazygit/pkg/i18n"
|
||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
@@ -24,6 +25,7 @@ type FileTreeViewModel struct {
|
||||
sync.RWMutex
|
||||
types.IListCursor
|
||||
IFileTree
|
||||
searchHistory *utils.HistoryBuffer[string]
|
||||
}
|
||||
|
||||
var _ IFileTreeViewModel = &FileTreeViewModel{}
|
||||
@@ -32,8 +34,9 @@ func NewFileTreeViewModel(getFiles func() []*models.File, common *common.Common,
|
||||
fileTree := NewFileTree(getFiles, common, showTree)
|
||||
listCursor := traits.NewListCursor(fileTree.Len)
|
||||
return &FileTreeViewModel{
|
||||
IFileTree: fileTree,
|
||||
IListCursor: listCursor,
|
||||
IFileTree: fileTree,
|
||||
IListCursor: listCursor,
|
||||
searchHistory: utils.NewHistoryBuffer[string](1000),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,3 +223,51 @@ func (self *FileTreeViewModel) ExpandAll() {
|
||||
self.SetSelectedLineIdx(index)
|
||||
}
|
||||
}
|
||||
|
||||
// IFilterableContext methods
|
||||
|
||||
func (self *FileTreeViewModel) SetFilter(filter string, useFuzzySearch bool) {
|
||||
self.IFileTree.SetTextFilter(filter, useFuzzySearch)
|
||||
}
|
||||
|
||||
func (self *FileTreeViewModel) GetFilter() string {
|
||||
return self.IFileTree.GetTextFilter()
|
||||
}
|
||||
|
||||
func (self *FileTreeViewModel) ClearFilter() {
|
||||
selectedNode := self.GetSelected()
|
||||
var selectedPath string
|
||||
if selectedNode != nil {
|
||||
selectedPath = selectedNode.GetInternalPath()
|
||||
}
|
||||
|
||||
self.IFileTree.SetTextFilter("", false)
|
||||
|
||||
if selectedPath != "" {
|
||||
self.ExpandToPath(selectedPath)
|
||||
if idx, found := self.GetIndexForPath(selectedPath); found {
|
||||
self.SetSelection(idx)
|
||||
return
|
||||
}
|
||||
}
|
||||
self.ClampSelection()
|
||||
}
|
||||
|
||||
func (self *FileTreeViewModel) ReApplyFilter(useFuzzySearch bool) {
|
||||
self.IFileTree.SetTextFilter(self.IFileTree.GetTextFilter(), useFuzzySearch)
|
||||
}
|
||||
|
||||
func (self *FileTreeViewModel) IsFiltering() bool {
|
||||
return self.IFileTree.GetTextFilter() != ""
|
||||
}
|
||||
|
||||
// used for type switch
|
||||
func (self *FileTreeViewModel) IsFilterableContext() {}
|
||||
|
||||
func (self *FileTreeViewModel) FilterPrefix(tr *i18n.TranslationSet) string {
|
||||
return tr.FilterPrefix
|
||||
}
|
||||
|
||||
func (self *FileTreeViewModel) GetSearchHistory() *utils.HistoryBuffer[string] {
|
||||
return self.searchHistory
|
||||
}
|
||||
|
||||
@@ -148,6 +148,7 @@ type Gui struct {
|
||||
integrationTest integrationTypes.IntegrationTest
|
||||
|
||||
afterLayoutFuncs chan func() error
|
||||
gitHubCliState types.GitHubCliState
|
||||
}
|
||||
|
||||
type StateAccessor struct {
|
||||
@@ -221,6 +222,14 @@ func (self *StateAccessor) ClearItemOperation(item types.HasUrn) {
|
||||
delete(self.gui.itemOperations, item.URN())
|
||||
}
|
||||
|
||||
func (self *StateAccessor) GetGitHubCliState() types.GitHubCliState {
|
||||
return self.gui.gitHubCliState
|
||||
}
|
||||
|
||||
func (self *StateAccessor) SetGitHubCliState(value types.GitHubCliState) {
|
||||
self.gui.gitHubCliState = value
|
||||
}
|
||||
|
||||
// we keep track of some stuff from one render to the next to see if certain
|
||||
// things have changed
|
||||
type PrevLayout struct {
|
||||
@@ -581,6 +590,7 @@ func (gui *Gui) resetState(startArgs appTypes.StartArgs) types.Context {
|
||||
Authors: map[string]*models.Author{},
|
||||
MainBranches: git_commands.NewMainBranches(gui.c.Common, gui.os.Cmd),
|
||||
HashPool: &utils.StringPool{},
|
||||
PullRequests: make([]*models.GithubPullRequest, 0),
|
||||
},
|
||||
Modes: &types.Modes{
|
||||
Filtering: filtering.New(startArgs.FilterPath, ""),
|
||||
|
||||
@@ -61,6 +61,8 @@ func (gui *Gui) createMenu(opts types.CreateMenuOptions) error {
|
||||
gui.State.Contexts.Menu.SetKeybindingsTakePrecedence(!opts.KeepConflictingKeybindings)
|
||||
gui.State.Contexts.Menu.SetSelection(0)
|
||||
|
||||
gui.Views.Menu.SetOriginY(0)
|
||||
|
||||
gui.Views.Menu.Title = opts.Title
|
||||
gui.Views.Menu.FgColor = theme.GocuiDefaultTextColor
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ var colorPatterns *colorMatcher
|
||||
func GetBranchListDisplayStrings(
|
||||
branches []*models.Branch,
|
||||
getItemOperation func(item types.HasUrn) types.ItemOperation,
|
||||
pullRequests []*models.GithubPullRequest,
|
||||
remotes []*models.Remote,
|
||||
fullDescription bool,
|
||||
diffName string,
|
||||
viewWidth int,
|
||||
@@ -35,9 +37,15 @@ func GetBranchListDisplayStrings(
|
||||
userConfig *config.UserConfig,
|
||||
worktrees []*models.Worktree,
|
||||
) [][]string {
|
||||
prs := git_commands.GenerateGithubPullRequestMap(
|
||||
pullRequests,
|
||||
branches,
|
||||
remotes,
|
||||
)
|
||||
|
||||
return lo.Map(branches, func(branch *models.Branch, _ int) []string {
|
||||
diffed := branch.Name == diffName
|
||||
return getBranchDisplayStrings(branch, getItemOperation(branch), fullDescription, diffed, viewWidth, tr, userConfig, worktrees, time.Now())
|
||||
return getBranchDisplayStrings(branch, getItemOperation(branch), fullDescription, diffed, viewWidth, tr, userConfig, worktrees, time.Now(), prs)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -52,6 +60,7 @@ func getBranchDisplayStrings(
|
||||
userConfig *config.UserConfig,
|
||||
worktrees []*models.Worktree,
|
||||
now time.Time,
|
||||
prs map[string]*models.GithubPullRequest,
|
||||
) []string {
|
||||
checkedOutByWorkTree := git_commands.CheckedOutByOtherWorktree(b, worktrees)
|
||||
showCommitHash := fullDescription || userConfig.Gui.ShowBranchCommitHash
|
||||
@@ -100,6 +109,7 @@ func getBranchDisplayStrings(
|
||||
if checkedOutByWorkTree {
|
||||
coloredName = fmt.Sprintf("%s %s", coloredName, style.FgDefault.Sprint(worktreeIcon))
|
||||
}
|
||||
|
||||
if len(branchStatus) > 0 {
|
||||
coloredName = fmt.Sprintf("%s %s", coloredName, branchStatus)
|
||||
}
|
||||
@@ -110,14 +120,22 @@ func getBranchDisplayStrings(
|
||||
}
|
||||
|
||||
res := make([]string, 0, 6)
|
||||
|
||||
res = append(res, recencyColor.Sprint(b.Recency))
|
||||
|
||||
if icons.IsIconEnabled() {
|
||||
res = append(res, nameTextStyle.Sprint(icons.IconForBranch(b)))
|
||||
}
|
||||
|
||||
if showCommitHash {
|
||||
res = append(res, utils.ShortHash(b.CommitHash))
|
||||
pr, hasPr := prs[b.Name]
|
||||
if hasPr {
|
||||
if icons.IsIconEnabled() {
|
||||
res = append(res, prColor(pr.State).Sprint(icons.IconForRemoteUrl(pr.Url)))
|
||||
} else {
|
||||
res = append(res, prColor(pr.State).Sprint("●"))
|
||||
}
|
||||
} else {
|
||||
if icons.IsIconEnabled() {
|
||||
res = append(res, style.FgDefault.Sprint(icons.IconForBranch(b)))
|
||||
} else {
|
||||
res = append(res, style.FgDefault.Sprint("●"))
|
||||
}
|
||||
}
|
||||
|
||||
if divergence != "" {
|
||||
@@ -127,8 +145,13 @@ func getBranchDisplayStrings(
|
||||
coloredName += style.FgCyan.Sprint(divergence)
|
||||
}
|
||||
}
|
||||
|
||||
res = append(res, coloredName)
|
||||
|
||||
if showCommitHash {
|
||||
res = append(res, utils.ShortHash(b.CommitHash))
|
||||
}
|
||||
|
||||
if fullDescription {
|
||||
res = append(
|
||||
res,
|
||||
@@ -228,3 +251,18 @@ func SetCustomBranches(customBranchColors map[string]string, isRegex bool) {
|
||||
isRegex: isRegex,
|
||||
}
|
||||
}
|
||||
|
||||
func prColor(state string) style.TextStyle {
|
||||
switch state {
|
||||
case "OPEN":
|
||||
return style.FgGreen
|
||||
case "CLOSED":
|
||||
return style.FgRed
|
||||
case "MERGED":
|
||||
return style.FgMagenta
|
||||
case "DRAFT":
|
||||
return style.FgBlackLighter
|
||||
default:
|
||||
return style.FgDefault
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "none",
|
||||
expected: []string{"1m", "branch_name"},
|
||||
expected: []string{"1m", "●", "branch_name"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{Name: "🍉_special_char", Recency: "1m"},
|
||||
@@ -52,7 +52,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "none",
|
||||
expected: []string{"1m", "🍉_special_char"},
|
||||
expected: []string{"1m", "●", "🍉_special_char"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||
@@ -62,7 +62,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: true,
|
||||
showDivergenceCfg: "none",
|
||||
expected: []string{"1m", "branch_name (worktree)"},
|
||||
expected: []string{"1m", "●", "branch_name (worktree)"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||
@@ -88,7 +88,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "none",
|
||||
expected: []string{"1m", "branch_name ✓"},
|
||||
expected: []string{"1m", "●", "branch_name ✓"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{
|
||||
@@ -104,7 +104,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: true,
|
||||
showDivergenceCfg: "none",
|
||||
expected: []string{"1m", "branch_name (worktree) ↓5↑3"},
|
||||
expected: []string{"1m", "●", "branch_name (worktree) ↓5↑3"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{
|
||||
@@ -118,7 +118,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "onlyArrow",
|
||||
expected: []string{"1m", "branch_name ↓"},
|
||||
expected: []string{"1m", "●", "branch_name ↓"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{
|
||||
@@ -135,7 +135,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "arrowAndNumber",
|
||||
expected: []string{"1m", "branch_name ✓ ↓2"},
|
||||
expected: []string{"1m", "●", "branch_name ✓ ↓2"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{
|
||||
@@ -152,7 +152,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "arrowAndNumber",
|
||||
expected: []string{"1m", "branch_name ↓5↑3 ↓2"},
|
||||
expected: []string{"1m", "●", "branch_name ↓5↑3 ↓2"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||
@@ -162,7 +162,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "none",
|
||||
expected: []string{"1m", "branch_name Pushing |"},
|
||||
expected: []string{"1m", "●", "branch_name Pushing |"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{
|
||||
@@ -181,7 +181,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "none",
|
||||
expected: []string{"1m", "12345678", "branch_name ✓", "origin branch_name", "commit title"},
|
||||
expected: []string{"1m", "●", "branch_name ✓", "12345678", "origin branch_name", "commit title"},
|
||||
},
|
||||
|
||||
// Now tests for how we truncate the branch name when there's not enough room:
|
||||
@@ -193,7 +193,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "none",
|
||||
expected: []string{"1m", "branch_na…"},
|
||||
expected: []string{"1m", "●", "branch_na…"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{Name: "🍉_special_char", Recency: "1m"},
|
||||
@@ -203,7 +203,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "none",
|
||||
expected: []string{"1m", "🍉_special_ch…"},
|
||||
expected: []string{"1m", "●", "🍉_special_ch…"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||
@@ -213,7 +213,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: true,
|
||||
showDivergenceCfg: "none",
|
||||
expected: []string{"1m", "bra… (worktree)"},
|
||||
expected: []string{"1m", "●", "bra… (worktree)"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||
@@ -239,7 +239,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "none",
|
||||
expected: []string{"1m", "branch_… ✓"},
|
||||
expected: []string{"1m", "●", "branch_… ✓"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{
|
||||
@@ -256,7 +256,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "arrowAndNumber",
|
||||
expected: []string{"1m", "branch_n… ↓5↑3 ↓4"},
|
||||
expected: []string{"1m", "●", "branch_n… ↓5↑3 ↓4"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{
|
||||
@@ -272,7 +272,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: true,
|
||||
showDivergenceCfg: "none",
|
||||
expected: []string{"1m", "branch_na… (worktree) ↓5↑3"},
|
||||
expected: []string{"1m", "●", "branch_na… (worktree) ↓5↑3"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{Name: "branch_name", Recency: "1m"},
|
||||
@@ -282,7 +282,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "none",
|
||||
expected: []string{"1m", "branc… Pushing |"},
|
||||
expected: []string{"1m", "●", "branc… Pushing |"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{Name: "abc", Recency: "1m"},
|
||||
@@ -292,7 +292,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "none",
|
||||
expected: []string{"1m", "abc Pushing |"},
|
||||
expected: []string{"1m", "●", "abc Pushing |"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{Name: "ab", Recency: "1m"},
|
||||
@@ -302,7 +302,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "none",
|
||||
expected: []string{"1m", "ab Pushing |"},
|
||||
expected: []string{"1m", "●", "ab Pushing |"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{Name: "a", Recency: "1m"},
|
||||
@@ -312,7 +312,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "none",
|
||||
expected: []string{"1m", "a Pushing |"},
|
||||
expected: []string{"1m", "●", "a Pushing |"},
|
||||
},
|
||||
{
|
||||
branch: &models.Branch{
|
||||
@@ -331,7 +331,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
useIcons: false,
|
||||
checkedOutByWorktree: false,
|
||||
showDivergenceCfg: "none",
|
||||
expected: []string{"1m", "12345678", "bran… ✓", "origin branch_name", "commit title"},
|
||||
expected: []string{"1m", "●", "bran… ✓", "12345678", "origin branch_name", "commit title"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -351,7 +351,7 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run(fmt.Sprintf("getBranchDisplayStrings_%d", i), func(t *testing.T) {
|
||||
strings := getBranchDisplayStrings(s.branch, s.itemOperation, s.fullDescription, false, s.viewWidth, c.Tr, c.UserConfig(), worktrees, time.Time{})
|
||||
strings := getBranchDisplayStrings(s.branch, s.itemOperation, s.fullDescription, false, s.viewWidth, c.Tr, c.UserConfig(), worktrees, time.Time{}, map[string]*models.GithubPullRequest{})
|
||||
assert.Equal(t, s.expected, strings)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -385,7 +385,12 @@ func displayCommit(
|
||||
|
||||
actionString := ""
|
||||
if commit.Action != models.ActionNone {
|
||||
actionString = actionColorMap(commit.Action, commit.Status).Sprint(commit.Action.String())
|
||||
actionStr := commit.Action.String()
|
||||
// Only show the flag for fixup commands (where -C changes the meaning)
|
||||
if commit.ActionFlag != "" && commit.Action == todo.Fixup {
|
||||
actionStr += " " + commit.ActionFlag
|
||||
}
|
||||
actionString = actionColorMap(commit.Action, commit.Status).Sprint(actionStr)
|
||||
}
|
||||
|
||||
tagString := ""
|
||||
|
||||
@@ -79,6 +79,15 @@ func IconForRemote(remote *models.Remote) string {
|
||||
return DEFAULT_REMOTE_ICON
|
||||
}
|
||||
|
||||
func IconForRemoteUrl(url string) string {
|
||||
for domain, icon := range remoteIcons {
|
||||
if strings.Contains(url, domain) {
|
||||
return icon
|
||||
}
|
||||
}
|
||||
return DEFAULT_REMOTE_ICON
|
||||
}
|
||||
|
||||
func IconForStash(stash *models.StashEntry) string {
|
||||
return STASH_ICON
|
||||
}
|
||||
|
||||
+23
-9
@@ -296,6 +296,7 @@ type Model struct {
|
||||
SubCommits []*models.Commit
|
||||
Remotes []*models.Remote
|
||||
Worktrees []*models.Worktree
|
||||
PullRequests []*models.GithubPullRequest
|
||||
|
||||
// FilteredReflogCommits are the ones that appear in the reflog panel.
|
||||
// When in filtering mode we only include the ones that match the given path
|
||||
@@ -326,15 +327,16 @@ type Model struct {
|
||||
}
|
||||
|
||||
type Mutexes struct {
|
||||
RefreshingFilesMutex deadlock.Mutex
|
||||
RefreshingBranchesMutex deadlock.Mutex
|
||||
RefreshingStatusMutex deadlock.Mutex
|
||||
LocalCommitsMutex deadlock.Mutex
|
||||
SubCommitsMutex deadlock.Mutex
|
||||
AuthorsMutex deadlock.Mutex
|
||||
SubprocessMutex deadlock.Mutex
|
||||
PopupMutex deadlock.Mutex
|
||||
PtyMutex deadlock.Mutex
|
||||
RefreshingFilesMutex deadlock.Mutex
|
||||
RefreshingBranchesMutex deadlock.Mutex
|
||||
RefreshingStatusMutex deadlock.Mutex
|
||||
RefreshingPullRequestsMutex deadlock.Mutex
|
||||
LocalCommitsMutex deadlock.Mutex
|
||||
SubCommitsMutex deadlock.Mutex
|
||||
AuthorsMutex deadlock.Mutex
|
||||
SubprocessMutex deadlock.Mutex
|
||||
PopupMutex deadlock.Mutex
|
||||
PtyMutex deadlock.Mutex
|
||||
}
|
||||
|
||||
// A long-running operation associated with an item. For example, we'll show
|
||||
@@ -373,6 +375,8 @@ type IStateAccessor interface {
|
||||
GetItemOperation(item HasUrn) ItemOperation
|
||||
SetItemOperation(item HasUrn, operation ItemOperation)
|
||||
ClearItemOperation(item HasUrn)
|
||||
GetGitHubCliState() GitHubCliState
|
||||
SetGitHubCliState(GitHubCliState)
|
||||
}
|
||||
|
||||
type IRepoStateAccessor interface {
|
||||
@@ -409,3 +413,13 @@ const (
|
||||
SCREEN_HALF
|
||||
SCREEN_FULL
|
||||
)
|
||||
|
||||
// for keeping track of whether our github CLI is installed and on a valid version
|
||||
type GitHubCliState int
|
||||
|
||||
const (
|
||||
UNKNOWN GitHubCliState = iota
|
||||
VALID
|
||||
NOT_INSTALLED
|
||||
INVALID_VERSION
|
||||
)
|
||||
|
||||
@@ -22,6 +22,7 @@ const (
|
||||
COMMIT_FILES
|
||||
// not actually a view. Will refactor this later
|
||||
BISECT_INFO
|
||||
PULL_REQUESTS
|
||||
)
|
||||
|
||||
type RefreshMode int
|
||||
|
||||
@@ -102,7 +102,6 @@ func (gui *Gui) createAllViews() error {
|
||||
|
||||
for _, view := range []*gocui.View{gui.Views.Main, gui.Views.Secondary, gui.Views.Staging, gui.Views.StagingSecondary, gui.Views.PatchBuilding, gui.Views.PatchBuildingSecondary, gui.Views.MergeConflicts} {
|
||||
view.Wrap = true
|
||||
view.IgnoreCarriageReturns = true
|
||||
view.UnderlineHyperLinksOnlyOnHover = true
|
||||
view.AutoRenderHyperLinks = true
|
||||
}
|
||||
|
||||
+40
-4
@@ -164,7 +164,12 @@ type TranslationSet struct {
|
||||
CannotSquashOrFixupMergeCommit string
|
||||
Fixup string
|
||||
FixupTooltip string
|
||||
SureFixupThisCommit string
|
||||
FixupKeepMessage string
|
||||
FixupKeepMessageTooltip string
|
||||
SetFixupMessage string
|
||||
SetFixupMessageTooltip string
|
||||
FixupDiscardMessage string
|
||||
FixupDiscardMessageTooltip string
|
||||
SureSquashThisCommit string
|
||||
Squash string
|
||||
PickCommitTooltip string
|
||||
@@ -295,6 +300,8 @@ type TranslationSet struct {
|
||||
ToggleSelectHunkTooltip string
|
||||
HunkStagingHint string
|
||||
ToggleSelectionForPatch string
|
||||
RemoveSelectionFromPatch string
|
||||
RemoveSelectionFromPatchTooltip string
|
||||
EditHunk string
|
||||
EditHunkTooltip string
|
||||
ToggleStagingView string
|
||||
@@ -316,6 +323,8 @@ type TranslationSet struct {
|
||||
ViewRevertOptions string
|
||||
NotMergingOrRebasing string
|
||||
AlreadyRebasing string
|
||||
NotMidRebase string
|
||||
MustSelectFixupCommit string
|
||||
RecentRepos string
|
||||
MergeOptionsTitle string
|
||||
RebaseOptionsTitle string
|
||||
@@ -427,6 +436,8 @@ type TranslationSet struct {
|
||||
CheckoutCommitFileTooltip string
|
||||
CannotCheckoutWithModifiedFilesErr string
|
||||
CanOnlyDiscardFromLocalCommits string
|
||||
CanOnlyRemoveLinesFromLocalCommits string
|
||||
MustClearPatchBeforeRemovingLines string
|
||||
Remove string
|
||||
DiscardOldFileChangeTooltip string
|
||||
DiscardFileChangesTitle string
|
||||
@@ -597,6 +608,8 @@ type TranslationSet struct {
|
||||
CyclePagersDisabledReason string
|
||||
StartSearch string
|
||||
StartFilter string
|
||||
SelectRemoteRepository string
|
||||
LcSelectingRemote string
|
||||
Keybindings string
|
||||
KeybindingsLegend string
|
||||
KeybindingsMenuSectionLocal string
|
||||
@@ -679,6 +692,8 @@ type TranslationSet struct {
|
||||
BranchUnknown string
|
||||
DiscardChangeTitle string
|
||||
DiscardChangePrompt string
|
||||
RemoveLinesFromCommitTitle string
|
||||
RemoveLinesFromCommitPrompt string
|
||||
CreateNewBranchFromCommit string
|
||||
BuildingPatch string
|
||||
ViewCommits string
|
||||
@@ -690,6 +705,8 @@ type TranslationSet struct {
|
||||
BackToParentRepo string
|
||||
Enter string
|
||||
CopySubmoduleNameToClipboard string
|
||||
MinGhVersionError string
|
||||
FailedToObtainGhVersionError string
|
||||
RemoveSubmodule string
|
||||
RemoveSubmoduleTooltip string
|
||||
RemoveSubmodulePrompt string
|
||||
@@ -897,7 +914,6 @@ type TranslationSet struct {
|
||||
SelectedItemIsNotABranch string
|
||||
SelectedItemDoesNotHaveFiles string
|
||||
MultiSelectNotSupportedForSubmodules string
|
||||
OldCherryPickKeyWarning string
|
||||
CommandDoesNotSupportOpeningInEditor string
|
||||
CustomCommands string
|
||||
NoApplicableCommandsInThisContext string
|
||||
@@ -966,6 +982,7 @@ type Actions struct {
|
||||
CheckoutFile string
|
||||
SquashCommitDown string
|
||||
FixupCommit string
|
||||
FixupCommitKeepMessage string
|
||||
RewordCommit string
|
||||
DropCommit string
|
||||
EditCommit string
|
||||
@@ -1000,6 +1017,7 @@ type Actions struct {
|
||||
ResolveConflictByDeletingFile string
|
||||
NotEnoughContextToStage string
|
||||
NotEnoughContextToDiscard string
|
||||
NotEnoughContextToRemoveLines string
|
||||
NotEnoughContextForCustomPatch string
|
||||
IgnoreExcludeFile string
|
||||
IgnoreFileErr string
|
||||
@@ -1260,7 +1278,12 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
CannotSquashOrFixupFirstCommit: "There's no commit below to squash into",
|
||||
CannotSquashOrFixupMergeCommit: "Cannot squash or fixup a merge commit",
|
||||
Fixup: "Fixup",
|
||||
SureFixupThisCommit: "Are you sure you want to 'fixup' the selected commit(s) into the commit below?",
|
||||
FixupKeepMessage: "Fixup and use this commit's message",
|
||||
FixupKeepMessageTooltip: "Squash the selected commit into the commit below, using this commit's message, discarding the message of the commit below.",
|
||||
SetFixupMessage: "Set fixup message",
|
||||
SetFixupMessageTooltip: "Set the message option for the fixup commit. The -C option means to use this commit's message instead of the target commit's message.",
|
||||
FixupDiscardMessage: "Fixup and discard this commit's message",
|
||||
FixupDiscardMessageTooltip: "Squash the selected commit into the commit below, discarding this commit's message.",
|
||||
SureSquashThisCommit: "Are you sure you want to squash the selected commit(s) into the commit below?",
|
||||
Squash: "Squash",
|
||||
PickCommitTooltip: "Mark the selected commit to be picked (when mid-rebase). This means that the commit will be retained upon continuing the rebase.",
|
||||
@@ -1393,6 +1416,8 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
ToggleSelectHunkTooltip: "Toggle line-by-line vs. hunk selection mode.",
|
||||
HunkStagingHint: englishHunkStagingHint,
|
||||
ToggleSelectionForPatch: `Toggle lines in patch`,
|
||||
RemoveSelectionFromPatch: `Remove lines from commit`,
|
||||
RemoveSelectionFromPatchTooltip: "Remove the selected lines from this commit. This runs an interactive rebase in the background, so you may get a merge conflict if a later commit also changes these lines.",
|
||||
EditHunk: `Edit hunk`,
|
||||
EditHunkTooltip: "Edit selected hunk in external editor.",
|
||||
ToggleStagingView: "Switch view",
|
||||
@@ -1412,6 +1437,8 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
ViewRevertOptions: "View revert options",
|
||||
NotMergingOrRebasing: "You are currently neither rebasing nor merging",
|
||||
AlreadyRebasing: "Can't perform this action during a rebase",
|
||||
NotMidRebase: "This action only works during an interactive rebase",
|
||||
MustSelectFixupCommit: "This action only works on fixup commits",
|
||||
RecentRepos: "Recent repositories",
|
||||
MergeOptionsTitle: "Merge options",
|
||||
RebaseOptionsTitle: "Rebase options",
|
||||
@@ -1528,6 +1555,8 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
CheckoutCommitFileTooltip: "Checkout file. This replaces the file in your working tree with the version from the selected commit.",
|
||||
CannotCheckoutWithModifiedFilesErr: "You have local modifications for the file(s) you are trying to check out. You need to stash or discard these first.",
|
||||
CanOnlyDiscardFromLocalCommits: "Changes can only be discarded from local commits",
|
||||
CanOnlyRemoveLinesFromLocalCommits: "Lines can only be removed from local commits",
|
||||
MustClearPatchBeforeRemovingLines: "Clear the current custom patch first before removing lines from the commit",
|
||||
Remove: "Remove",
|
||||
DiscardOldFileChangeTooltip: "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.",
|
||||
DiscardFileChangesTitle: "Discard file changes",
|
||||
@@ -1701,6 +1730,8 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
CyclePagersDisabledReason: "No other pagers configured",
|
||||
StartSearch: "Search the current view by text",
|
||||
StartFilter: "Filter the current view by text",
|
||||
SelectRemoteRepository: "Select base repository for pull requests",
|
||||
LcSelectingRemote: "Selecting remote",
|
||||
KeybindingsLegend: "Legend: `<c-b>` means ctrl+b, `<a-b>` means alt+b, `B` means shift+b",
|
||||
RenameBranch: "Rename branch",
|
||||
BranchUpstreamOptionsTitle: "Upstream options",
|
||||
@@ -1780,6 +1811,8 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
BranchUnknown: "Branch unknown",
|
||||
DiscardChangeTitle: "Discard change",
|
||||
DiscardChangePrompt: "Are you sure you want to discard this change (git reset)? It is irreversible.\nTo disable this dialogue set the config key of 'gui.skipDiscardChangeWarning' to true",
|
||||
RemoveLinesFromCommitTitle: "Remove lines from commit",
|
||||
RemoveLinesFromCommitPrompt: "Are you sure you want to remove the selected lines from this commit?",
|
||||
CreateNewBranchFromCommit: "Create new branch off of commit",
|
||||
BuildingPatch: "Building patch",
|
||||
ViewCommits: "View commits",
|
||||
@@ -1791,6 +1824,8 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
EnterSubmoduleTooltip: "Enter submodule. After entering the submodule, you can press `{{.escape}}` to escape back to the parent repo.",
|
||||
BackToParentRepo: "Back to parent repo",
|
||||
CopySubmoduleNameToClipboard: "Copy submodule name to clipboard",
|
||||
MinGhVersionError: "GH version must be at least 2.0. Please upgrade your gh version. Alternatively raise an issue at https://github.com/jesseduffield/lazygit/issues for lazygit to be more backwards compatible.",
|
||||
FailedToObtainGhVersionError: "Failed to obtain gh version. Output from running 'gh --version' was: %s",
|
||||
RemoveSubmodule: "Remove submodule",
|
||||
RemoveSubmodulePrompt: "Are you sure you want to remove submodule '%s' and its corresponding directory? This is irreversible.",
|
||||
RemoveSubmoduleTooltip: "Remove the selected submodule and its corresponding directory.",
|
||||
@@ -1993,7 +2028,6 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
SelectedItemIsNotABranch: "Selected item is not a branch",
|
||||
SelectedItemDoesNotHaveFiles: "Selected item does not have files to view",
|
||||
MultiSelectNotSupportedForSubmodules: "Multiselection not supported for submodules",
|
||||
OldCherryPickKeyWarning: "The 'c' key is no longer the default key for copying commits to cherry pick. Please use `{{.copy}}` instead (and `{{.paste}}` to paste). The reason for this change is that the 'v' key for selecting a range of lines when staging is now also used for selecting a range of lines in any list view, meaning that we needed to find a new key for pasting commits, and if we're going to now use `{{.paste}}` for pasting commits, we may as well use `{{.copy}}` for copying them. If you want to configure the keybindings to get the old behaviour, set the following in your config:\n\nkeybinding:\n universal:\n toggleRangeSelect: <something other than v>\n commits:\n cherryPickCopy: 'c'\n pasteCommits: 'v'",
|
||||
CommandDoesNotSupportOpeningInEditor: "This command doesn't support switching to the editor",
|
||||
CustomCommands: "Custom commands",
|
||||
NoApplicableCommandsInThisContext: "(No applicable commands in this context)",
|
||||
@@ -2025,6 +2059,7 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
CheckoutFile: "Checkout file",
|
||||
SquashCommitDown: "Squash commit down",
|
||||
FixupCommit: "Fixup commit",
|
||||
FixupCommitKeepMessage: "Fixup commit (keep message)",
|
||||
RewordCommit: "Reword commit",
|
||||
DropCommit: "Drop commit",
|
||||
EditCommit: "Edit commit",
|
||||
@@ -2061,6 +2096,7 @@ func EnglishTranslationSet() *TranslationSet {
|
||||
ResolveConflictByDeletingFile: "Resolve by deleting file",
|
||||
NotEnoughContextToStage: "Staging or unstaging changes is not possible with a diff context size of 0. Increase the context using '%s'.",
|
||||
NotEnoughContextToDiscard: "Discarding changes is not possible with a diff context size of 0. Increase the context using '%s'.",
|
||||
NotEnoughContextToRemoveLines: "Removing lines from a commit is not possible with a diff context size of 0. Increase the context using '%s'.",
|
||||
NotEnoughContextForCustomPatch: "Creating custom patches is not possible with a diff context size of 0. Increase the context using '%s'.",
|
||||
IgnoreExcludeFile: "Ignore or exclude file",
|
||||
IgnoreFileErr: "Cannot ignore .gitignore",
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"ToggleStagedAll": "すべてステージ",
|
||||
"ToggleStagedAllTooltip": "ワーキングツリー内のすべてのファイルのステージ/アンステージを切り替えます。",
|
||||
"ToggleTreeView": "ファイルツリービューを切り替え",
|
||||
"ToggleTreeViewTooltip": "ファイル表示をフラット表示とツリー表示で切り替えます。フラット表示はすべてのファイルパスを一覧で表示し、ツリー表示はディレクトリごとにファイルをグループ化します。\n\nデフォルトは設定ファイル内の 'gui.showFileTree' キーで変更できます。",
|
||||
"OpenDiffTool": "外部差分ツールを開く(git difftool)",
|
||||
"OpenMergeTool": "外部マージツールを開く",
|
||||
"Refresh": "更新",
|
||||
@@ -144,7 +145,6 @@
|
||||
"CannotSquashOrFixupMergeCommit": "マージコミットをスカッシュまたはフィックスアップすることはできません",
|
||||
"Fixup": "フィックスアップ",
|
||||
"FixupTooltip": "選択したコミットをその下のコミットにマージします。フィックスアップはスカッシュと似ていますが、選択したコミットのメッセージは破棄され、下のコミットのメッセージのみが保持されます。",
|
||||
"SureFixupThisCommit": "選択したコミットを下のコミットに'フィックスアップ'してもよろしいですか?",
|
||||
"SureSquashThisCommit": "選択したコミットを下のコミットにスカッシュしてもよろしいですか?",
|
||||
"Squash": "スカッシュ",
|
||||
"PickCommitTooltip": "選択したコミットをピックするようにマークします(リベース中)。これは、リベースを続行すると、コミットが保持されることを意味します。",
|
||||
@@ -814,7 +814,6 @@
|
||||
"SelectedItemIsNotABranch": "選択された項目はブランチではありません",
|
||||
"SelectedItemDoesNotHaveFiles": "選択された項目には表示するファイルがありません",
|
||||
"MultiSelectNotSupportedForSubmodules": "サブモジュールでは複数選択はサポートされていません",
|
||||
"OldCherryPickKeyWarning": "'c'キーは現在、コミットのコピー(チェリーピック)のデフォルトキーではなくなりました。代わりに`{{.copy}}`キーを使用してコピーし、`{{.paste}}`キーでペーストしてください。\n\nこの変更理由は以下の通りです:\n- 以前は'v'キーがステージング時の行選択にのみ使用されていましたが、現在はすべてのリストビューで範囲選択に使われています\n- そのため、コミットペースト用の新しいキーが必要になり、`{{.paste}}`をペーストに使うなら、`{{.copy}}`をコピーに使う方が直感的です\n\n以前の動作に戻したい場合は、設定ファイルに以下を追加してください:\n\nkeybinding:\n universal:\n toggleRangeSelect: <v以外の何か>\n commits:\n cherryPickCopy: 'c'\n pasteCommits: 'v'",
|
||||
"CommandDoesNotSupportOpeningInEditor": "このコマンドはエディタへの切り替えをサポートしていません",
|
||||
"CustomCommands": "カスタムコマンド",
|
||||
"NoApplicableCommandsInThisContext": "(このコンテキストでは適用可能なコマンドはありません)",
|
||||
|
||||
@@ -77,7 +77,6 @@
|
||||
"CloseCancel": "닫기/취소",
|
||||
"Confirm": "확인",
|
||||
"Quit": "종료",
|
||||
"SureFixupThisCommit": "Are you sure you want to 'fixup' this commit? It will be merged into the commit below",
|
||||
"SureSquashThisCommit": "Are you sure you want to squash this commit into the commit below?",
|
||||
"Squash": "스쿼시",
|
||||
"PickCommitTooltip": "Pick commit (when mid-rebase)",
|
||||
|
||||
@@ -49,7 +49,6 @@
|
||||
"Close": "Sluiten",
|
||||
"CloseCancel": "Sluiten",
|
||||
"Confirm": "Bevestig",
|
||||
"SureFixupThisCommit": "Weet je zeker dat je fixup wil uitvoeren op deze commit? De commit hieronder zol worden squashed in deze",
|
||||
"SureSquashThisCommit": "Weet je zeker dat je deze commit wil samenvoegen met de commit hieronder?",
|
||||
"PickCommitTooltip": "Kies commit (wanneer midden in rebase)",
|
||||
"Reword": "Hernoem commit",
|
||||
|
||||
@@ -100,7 +100,6 @@
|
||||
"CannotSquashOrFixupFirstCommit": "Nie ma commita poniżej do scalenia",
|
||||
"Fixup": "Poprawka",
|
||||
"FixupTooltip": "Włącz wybrany commit do commita poniżej. Podobnie do fixup, ale wiadomość wybranego commita zostanie odrzucona.",
|
||||
"SureFixupThisCommit": "Czy na pewno chcesz 'poprawić' wybrane commit(y) do commita poniżej?",
|
||||
"SureSquashThisCommit": "Czy na pewno chcesz scalić wybrane commit(y) do commita poniżej?",
|
||||
"Squash": "Scal",
|
||||
"PickCommitTooltip": "Oznacz wybrany commit do wybrania (podczas rebazowania). Oznacza to, że commit zostanie zachowany po kontynuacji rebazowania.",
|
||||
@@ -668,7 +667,6 @@
|
||||
"NoItemSelected": "Nie wybrano elementu",
|
||||
"SelectedItemIsNotABranch": "Wybrany element nie jest branch'em",
|
||||
"SelectedItemDoesNotHaveFiles": "Wybrany element nie ma plików do wyświetlenia",
|
||||
"OldCherryPickKeyWarning": "Klawisz 'c' nie jest już domyślnym klawiszem do kopiowania commitów do cherry pick. Proszę użyj `{{.copy}}` zamiast tego (i `{{.paste}}` aby wkleić). Powodem tej zmiany jest to, że klawisz 'v' do wybierania zakresu linii podczas stagingu jest teraz używany również do wybierania zakresu linii w każdym widoku listy, co oznacza, że musieliśmy znaleźć nowy klawisz do wklejania commitów, i jeśli zamierzamy teraz używać `{{.paste}}` do wklejania commitów, możemy równie dobrze użyć `{{.copy}}` do ich kopiowania. Jeśli chcesz skonfigurować klawisze, aby uzyskać stare zachowanie, ustaw następujące w swojej konfiguracji:\n\nkeybinding:\n universal:\n toggleRangeSelect: <coś innego niż v>\n commits:\n cherryPickCopy: 'c'\n pasteCommits: 'v'",
|
||||
"Actions": {
|
||||
"CheckoutCommit": "Przełącz commit",
|
||||
"CheckoutTag": "Przełącz tag",
|
||||
|
||||
@@ -137,7 +137,6 @@
|
||||
"CannotSquashOrFixupMergeCommit": "Não é possível squash ou corrigir um commit de merge",
|
||||
"Fixup": "Fixup",
|
||||
"FixupTooltip": "Faça o commit selecionado no commit abaixo dele. Semelhante para o squash, mas a mensagem do commit selecionado será descartada.",
|
||||
"SureFixupThisCommit": "Tem certeza que deseja 'corrigir' o(s) commit(s) selecionado(s) no commit abaixo?",
|
||||
"SureSquashThisCommit": "Tem certeza que deseja esmagar o(s) commit(s) selecionado(s) no commit abaixo?",
|
||||
"Squash": "Squash",
|
||||
"PickCommitTooltip": "Marque o commit selecionado para ser escolhido (quando meados da base). Isso significa que o commit será mantido ao continuar o rebase.",
|
||||
|
||||
@@ -63,7 +63,6 @@
|
||||
"Quit": "Выйти",
|
||||
"CannotSquashOrFixupFirstCommit": "Ниже нет коммита, который можно было бы объединить",
|
||||
"Fixup": "Объединить несколько коммитов в один отбросив сообщение коммита (Fixup) ",
|
||||
"SureFixupThisCommit": "Вы уверены, что хотите объединить несколько коммитов, отбросив сообщение коммита? Он будет объединён с коммитом ниже",
|
||||
"SureSquashThisCommit": "Вы уверены, что хотите объединить несколько коммитов в нижний коммит?",
|
||||
"Squash": "Объединить коммиты (Squash)",
|
||||
"PickCommitTooltip": "Выбрать коммит (в середине перебазирования)",
|
||||
|
||||
@@ -152,7 +152,6 @@
|
||||
"CannotSquashOrFixupMergeCommit": "无法对合并提交进行压缩或修正",
|
||||
"Fixup": "修正 (fixup)",
|
||||
"FixupTooltip": "将选定的提交合并到其下面的提交中。与压缩类似,但所选提交的消息将被丢弃。",
|
||||
"SureFixupThisCommit": "您确定要“修正”此提交吗?它将合并到下面的提交中",
|
||||
"SureSquashThisCommit": "您确定要将这个提交压缩到下面的提交中吗?",
|
||||
"Squash": "压缩(Squash)",
|
||||
"PickCommitTooltip": "标记选中的提交为 picked(变基过程中)。这意味该提交将在后续的变基中保留。",
|
||||
@@ -249,7 +248,7 @@
|
||||
"RefreshFiles": "刷新文件",
|
||||
"FocusMainView": "聚焦主视图",
|
||||
"Merge": "合并到当前检出的分支",
|
||||
"MergeBranchTooltip": "Merge selected branch into currently checked out branch.",
|
||||
"MergeBranchTooltip": "查看将选中项合并到当前分支的选项(正常合并,压缩合并)",
|
||||
"RegularMergeFastForward": "常规合并(快进)",
|
||||
"RegularMergeFastForwardTooltip": "将 '{{.checkedOutBranch}}' 快进到 '{{.selectedBranch}}',不创建合并提交。",
|
||||
"CannotFastForwardMerge": "无法将 '{{.checkedOutBranch}}' 快进到 '{{.selectedBranch}}'",
|
||||
@@ -413,6 +412,7 @@
|
||||
"ViewItemFiles": "查看提交的文件",
|
||||
"CommitFilesTitle": "提交文件",
|
||||
"CheckoutCommitFileTooltip": "检出文件",
|
||||
"CannotCheckoutWithModifiedFilesErr": "您已有对您试图签出的文件作出的本地修改。您需要先保存或丢弃这些文件。",
|
||||
"CanOnlyDiscardFromLocalCommits": "只能从本地提交中丢弃更改",
|
||||
"Remove": "删除",
|
||||
"DiscardOldFileChangeTooltip": "放弃对此文件的提交变更",
|
||||
@@ -482,7 +482,7 @@
|
||||
"StashTooltip": "贮藏所有变更.若要使用其他贮藏变体,请使用查看贮藏选项快捷键",
|
||||
"StashAllChanges": "将所有变更加入贮藏",
|
||||
"StashStagedChanges": "贮藏已暂存变更",
|
||||
"StashAllChangesKeepIndex": "将已暂存的变更加入贮藏",
|
||||
"StashAllChangesKeepIndex": "贮藏所有更改并保留暂存区",
|
||||
"StashUnstagedChanges": "贮藏未暂存变更",
|
||||
"StashIncludeUntrackedChanges": "贮藏所有变更,包括未跟踪的文件",
|
||||
"StashOptions": "贮藏选项",
|
||||
@@ -882,7 +882,6 @@
|
||||
"SelectedItemIsNotABranch": "选中的条目不是一个分支",
|
||||
"SelectedItemDoesNotHaveFiles": "选中的条目中没有",
|
||||
"MultiSelectNotSupportedForSubmodules": "子模块不支持多选操作",
|
||||
"OldCherryPickKeyWarning": "The 'c' key is no longer the default key for copying commits to cherry pick. Please use `{{.copy}}` instead (and `{{.paste}}` to paste). The reason for this change is that the 'v' key for selecting a range of lines when staging is now also used for selecting a range of lines in any list view, meaning that we needed to find a new key for pasting commits, and if we're going to now use `{{.paste}}` for pasting commits, we may as well use `{{.copy}}` for copying them. If you want to configure the keybindings to get the old behaviour, set the following in your config:\n\nkeybinding:\n universal:\n toggleRangeSelect: <something other than v>\n commits:\n cherryPickCopy: 'c'\n pasteCommits: 'v'",
|
||||
"CommandDoesNotSupportOpeningInEditor": "该命令不支持切换到编辑器",
|
||||
"CustomCommands": "自定义命令",
|
||||
"NoApplicableCommandsInThisContext": "(当前上下文无可用命令)",
|
||||
@@ -951,7 +950,7 @@
|
||||
"Pull": "拉取(Pull)",
|
||||
"OpenFile": "打开文件",
|
||||
"StashAllChanges": "贮藏所有变更",
|
||||
"StashAllChangesKeepIndex": "贮藏所有更改并保持索引",
|
||||
"StashAllChangesKeepIndex": "贮藏所有更改并保留暂存区",
|
||||
"StashStagedChanges": "贮藏暂存的变更",
|
||||
"StashUnstagedChanges": "贮藏所有未暂存更改",
|
||||
"StashIncludeUntrackedChanges": "贮藏所有更改包括未跟踪的文件",
|
||||
|
||||
@@ -83,7 +83,6 @@
|
||||
"Quit": "結束",
|
||||
"CannotSquashOrFixupFirstCommit": "沒有可以壓縮的提交",
|
||||
"Fixup": "修復 (Fixup)",
|
||||
"SureFixupThisCommit": "是否對此提交進行 '修復' ? 其將被合併於以下之提交中",
|
||||
"SureSquashThisCommit": "是否要把這個提交壓縮到下面的提交中?",
|
||||
"Squash": "壓縮 (Squash)",
|
||||
"PickCommitTooltip": "挑選提交 (於變基過程中)",
|
||||
|
||||
@@ -20,7 +20,7 @@ var CreateTag = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
t.Views().Branches().
|
||||
Focus().
|
||||
Lines(
|
||||
MatchesRegexp(`\*\s*new-branch`).IsSelected(),
|
||||
MatchesRegexp(`\*.*new-branch`).IsSelected(),
|
||||
MatchesRegexp(`master`),
|
||||
).
|
||||
SelectNextItem().
|
||||
|
||||
@@ -31,7 +31,7 @@ var DetachedHead = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
|
||||
t.Views().Branches().
|
||||
Lines(
|
||||
MatchesRegexp(`\* new-branch`).IsSelected(),
|
||||
MatchesRegexp(`\*.*new-branch`).IsSelected(),
|
||||
MatchesRegexp(`master`),
|
||||
)
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ var Checkout = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
t.Views().Branches().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("* (HEAD detached at").IsSelected(),
|
||||
MatchesRegexp(`\*.*\(HEAD detached at`).IsSelected(),
|
||||
Contains("branch1"),
|
||||
Contains("branch2"),
|
||||
Contains("master"),
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package commit
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var FindBaseCommitForFixupDisregardFixupsForSameBaseCommit = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Finds the base commit to create a fixup for, disregarding fixup commits for the same base commit",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.
|
||||
EmptyCommit("1st commit").
|
||||
NewBranch("mybranch").
|
||||
CreateFileAndAdd("file1", "1\n2\n3\n").
|
||||
Commit("2nd commit").
|
||||
UpdateFileAndAdd("file1", "1\n2\n3a\n").
|
||||
Commit("fixup! 2nd commit").
|
||||
EmptyCommit("3rd commit").
|
||||
UpdateFileAndAdd("file1", "1a\n2\n3b\n")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Lines(
|
||||
Contains("3rd commit").IsSelected(),
|
||||
Contains("fixup! 2nd commit"),
|
||||
Contains("2nd commit"),
|
||||
Contains("1st commit"),
|
||||
)
|
||||
|
||||
t.Views().Files().
|
||||
Focus().
|
||||
Press(keys.Files.FindBaseCommitForFixup)
|
||||
|
||||
t.Views().Commits().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("3rd commit"),
|
||||
Contains("fixup! 2nd commit"),
|
||||
Contains("2nd commit").IsSelected(),
|
||||
Contains("1st commit"),
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -50,7 +50,7 @@ var Gitignore = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
t.ExpectPopup().Menu().Title(Equals("Ignore or exclude file")).Select(Contains("Add to .git/info/exclude")).Confirm()
|
||||
|
||||
t.FileSystem().FileContent(".gitignore", Equals(""))
|
||||
t.FileSystem().FileContent(".git/info/exclude", Contains("toExclude"))
|
||||
t.FileSystem().FileContent(".git/info/exclude", Contains("/toExclude"))
|
||||
}).
|
||||
SelectNextItem().
|
||||
Press(keys.Files.IgnoreFile).
|
||||
@@ -58,8 +58,8 @@ var Gitignore = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Tap(func() {
|
||||
t.ExpectPopup().Menu().Title(Equals("Ignore or exclude file")).Select(Contains("Add to .gitignore")).Confirm()
|
||||
|
||||
t.FileSystem().FileContent(".gitignore", Equals("toIgnore\n"))
|
||||
t.FileSystem().FileContent(".git/info/exclude", Contains("toExclude"))
|
||||
t.FileSystem().FileContent(".gitignore", Equals("/toIgnore\n"))
|
||||
t.FileSystem().FileContent(".git/info/exclude", Contains("/toExclude"))
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -61,6 +61,6 @@ var GitignoreSpecialCharacters = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Equals(" ?? abc_def"),
|
||||
)
|
||||
|
||||
t.FileSystem().FileContent(".gitignore", Equals("\\#file\nfile#abc\n\\!file\nfile!abc\nabc\\*def\nfile\\[x\\]\n"))
|
||||
t.FileSystem().FileContent(".gitignore", Equals("/\\#file\n/file#abc\n/\\!file\n/file!abc\n/abc\\*def\n/file\\[x\\]\n"))
|
||||
},
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user