mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-10-05 05:26:53 -04:00
Compare commits
24
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b05ba252c | ||
|
|
28474b08ee | ||
|
|
6f13b42279 | ||
|
|
8637587b82 | ||
|
|
f581dc4a56 | ||
|
|
693e9fc152 | ||
|
|
c595833883 | ||
|
|
7807b40322 | ||
|
|
b284970bac | ||
|
|
b46623ebef | ||
|
|
084c0a19bc | ||
|
|
3cee37388c | ||
|
|
a7969aef2c | ||
|
|
39c900c7e7 | ||
|
|
6e247c1583 | ||
|
|
87bf1dbc7f | ||
|
|
1f920ae6ba | ||
|
|
932e01b41a | ||
|
|
373f24c80f | ||
|
|
a548b289ef | ||
|
|
b168fc8cdd | ||
|
|
94845dcf98 | ||
|
|
1af6dff64e | ||
|
|
72de4f436e |
+2
-2
@@ -8,10 +8,10 @@ changelog:
|
|||||||
- feature
|
- feature
|
||||||
- title: Enhancements 🔥
|
- title: Enhancements 🔥
|
||||||
labels:
|
labels:
|
||||||
- improvement
|
- enhancement
|
||||||
- title: Fixes 🔧
|
- title: Fixes 🔧
|
||||||
labels:
|
labels:
|
||||||
- bugfix
|
- bug
|
||||||
- title: Maintenance ⚙️
|
- title: Maintenance ⚙️
|
||||||
labels:
|
labels:
|
||||||
- maintenance
|
- maintenance
|
||||||
|
|||||||
@@ -19,6 +19,10 @@ jobs:
|
|||||||
go-version: 1.18.x
|
go-version: 1.18.x
|
||||||
- name: Run goreleaser
|
- name: Run goreleaser
|
||||||
uses: goreleaser/goreleaser-action@v1
|
uses: goreleaser/goreleaser-action@v1
|
||||||
|
with:
|
||||||
|
distribution: goreleaser
|
||||||
|
version: v1.17.2
|
||||||
|
args: release --clean
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{secrets.GITHUB_API_TOKEN}}
|
GITHUB_TOKEN: ${{secrets.GITHUB_API_TOKEN}}
|
||||||
homebrew:
|
homebrew:
|
||||||
|
|||||||
@@ -204,3 +204,11 @@ jobs:
|
|||||||
- name: errors
|
- name: errors
|
||||||
run: golangci-lint run
|
run: golangci-lint run
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
|
check-required-label:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: mheap/github-action-required-labels@v5
|
||||||
|
with:
|
||||||
|
mode: exactly
|
||||||
|
count: 1
|
||||||
|
labels: "ignore-for-release, feature, enhancement, bug, maintenance, docs, i18n"
|
||||||
|
|||||||
+1
-1
@@ -12,7 +12,7 @@ builds:
|
|||||||
- amd64
|
- amd64
|
||||||
- arm
|
- arm
|
||||||
- arm64
|
- arm64
|
||||||
- 386
|
- '386'
|
||||||
# Default is `-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}`.
|
# Default is `-s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}}`.
|
||||||
ldflags:
|
ldflags:
|
||||||
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.buildSource=binaryRelease
|
- -s -w -X main.version={{.Version}} -X main.commit={{.Commit}} -X main.date={{.Date}} -X main.buildSource=binaryRelease
|
||||||
|
|||||||
@@ -2,18 +2,17 @@
|
|||||||
<img width="536" src="https://user-images.githubusercontent.com/8456633/174470852-339b5011-5800-4bb9-a628-ff230aa8cd4e.png">
|
<img width="536" src="https://user-images.githubusercontent.com/8456633/174470852-339b5011-5800-4bb9-a628-ff230aa8cd4e.png">
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||

|
A simple terminal UI for git commands
|
||||||
[](https://goreportcard.com/report/github.com/jesseduffield/lazygit)
|
<br/>
|
||||||
[](https://golangci.com)
|
|
||||||
[](http://godoc.org/github.com/jesseduffield/lazygit)
|
|
||||||
[](https://github.com/jesseduffield/lazygit/releases)
|
|
||||||
[](https://github.com/jesseduffield/lazygit/releases/latest)
|
|
||||||
[](https://github.com/Homebrew/homebrew-core/blob/master/Formula/lazygit.rb)
|
|
||||||
|
|
||||||
A simple terminal UI for git commands, written in Go with the [gocui](https://github.com/jroimartin/gocui "gocui") library.
|
[](https://github.com/jesseduffield/lazygit/releases) [](https://goreportcard.com/report/github.com/jesseduffield/lazygit) [](https://golangci.com) [](https://github.com/jesseduffield/lazygit/releases/latest) [](https://github.com/Homebrew/homebrew-core/blob/master/Formula/lazygit.rb)
|
||||||
|
|
||||||

|

|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Sponsors
|
## Sponsors
|
||||||
|
|
||||||
@@ -278,16 +277,18 @@ See the [docs](docs/Custom_Command_Keybindings.md)
|
|||||||
|
|
||||||
- [Video Tutorial](https://youtu.be/VDXvbHZYeKY)
|
- [Video Tutorial](https://youtu.be/VDXvbHZYeKY)
|
||||||
- [Rebase Magic Video Tutorial](https://youtu.be/4XaToVut_hs)
|
- [Rebase Magic Video Tutorial](https://youtu.be/4XaToVut_hs)
|
||||||
- [Twitch Stream](https://www.twitch.tv/jesseduffield)
|
|
||||||
|
|
||||||
## Cool features
|
## Cool features
|
||||||
|
|
||||||
- Adding files easily
|
- Add files easily
|
||||||
- Resolving merge conflicts
|
- Resolve merge conflicts
|
||||||
- Easily check out recent branches
|
- Fuzzy searching
|
||||||
- Scroll through logs/diffs of branches/commits/stash
|
- Git bisect
|
||||||
- Quick pushing/pulling
|
- Amend old commits
|
||||||
- Squash down and reword commits
|
- Slice, dice, squash, and rearrange commits
|
||||||
|
- Submodules support
|
||||||
|
- diff branches easily
|
||||||
|
- Cherry-pick commits like it's copy+paste
|
||||||
|
|
||||||
### Resolving merge conflicts
|
### Resolving merge conflicts
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ require (
|
|||||||
github.com/integrii/flaggy v1.4.0
|
github.com/integrii/flaggy v1.4.0
|
||||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
|
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68
|
||||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
|
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20230719120401-398f4965241f
|
github.com/jesseduffield/gocui v0.3.1-0.20230723014157-03e858e46144
|
||||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
||||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
|
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5
|
||||||
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
|
github.com/jesseduffield/minimal/gitignore v0.3.3-0.20211018110810-9cde264e6b1e
|
||||||
|
|||||||
@@ -72,8 +72,8 @@ github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68 h1:EQP2Tv8T
|
|||||||
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
|
github.com/jesseduffield/generics v0.0.0-20220320043834-727e535cbe68/go.mod h1:+LLj9/WUPAP8LqCchs7P+7X0R98HiFujVFANdNaxhGk=
|
||||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
|
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d h1:bO+OmbreIv91rCe8NmscRwhFSqkDJtzWCPV4Y+SQuXE=
|
||||||
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
|
github.com/jesseduffield/go-git/v5 v5.1.2-0.20221018185014-fdd53fef665d/go.mod h1:nGNEErzf+NRznT+N2SWqmHnDnF9aLgANB1CUNEan09o=
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20230719120401-398f4965241f h1:w/pxI34XepTAx4HwxUu8ipimbVRgSTS+7ahmgFQwH80=
|
github.com/jesseduffield/gocui v0.3.1-0.20230723014157-03e858e46144 h1:gwy5JzP6+PhcPFG1obkUSLGcTkUY88sLKlCPOFjwtak=
|
||||||
github.com/jesseduffield/gocui v0.3.1-0.20230719120401-398f4965241f/go.mod h1:dJ/BEUt3OWtaRg/PmuJWendRqREhre9JQ1SLvqrVJ8s=
|
github.com/jesseduffield/gocui v0.3.1-0.20230723014157-03e858e46144/go.mod h1:dJ/BEUt3OWtaRg/PmuJWendRqREhre9JQ1SLvqrVJ8s=
|
||||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
|
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10 h1:jmpr7KpX2+2GRiE91zTgfq49QvgiqB0nbmlwZ8UnOx0=
|
||||||
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
|
github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10/go.mod h1:aA97kHeNA+sj2Hbki0pvLslmE4CbDyhBeSSTUUnOuVo=
|
||||||
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
|
github.com/jesseduffield/lazycore v0.0.0-20221012050358-03d2e40243c5 h1:CDuQmfOjAtb1Gms6a1p5L2P8RhbLUq5t8aL7PiQd2uY=
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ func localisedTitle(tr *i18n.TranslationSet, str string) string {
|
|||||||
"reflogCommits": tr.ReflogCommitsTitle,
|
"reflogCommits": tr.ReflogCommitsTitle,
|
||||||
"tags": tr.TagsTitle,
|
"tags": tr.TagsTitle,
|
||||||
"commitFiles": tr.CommitFilesTitle,
|
"commitFiles": tr.CommitFilesTitle,
|
||||||
"commitMessage": tr.CommitMessageTitle,
|
"commitMessage": tr.CommitSummaryTitle,
|
||||||
"commitDescription": tr.CommitDescriptionTitle,
|
"commitDescription": tr.CommitDescriptionTitle,
|
||||||
"commits": tr.CommitsTitle,
|
"commits": tr.CommitsTitle,
|
||||||
"confirmation": tr.ConfirmationTitle,
|
"confirmation": tr.ConfirmationTitle,
|
||||||
|
|||||||
@@ -50,13 +50,13 @@ func (self *CommitCommands) ResetToCommit(sha string, strength string, envVars [
|
|||||||
Run()
|
Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitCommands) CommitCmdObj(message string) oscommands.ICmdObj {
|
func (self *CommitCommands) CommitCmdObj(summary string, description string) oscommands.ICmdObj {
|
||||||
messageArgs := self.commitMessageArgs(message)
|
messageArgs := self.commitMessageArgs(summary, description)
|
||||||
|
|
||||||
skipHookPrefix := self.UserConfig.Git.SkipHookPrefix
|
skipHookPrefix := self.UserConfig.Git.SkipHookPrefix
|
||||||
|
|
||||||
cmdArgs := NewGitCmd("commit").
|
cmdArgs := NewGitCmd("commit").
|
||||||
ArgIf(skipHookPrefix != "" && strings.HasPrefix(message, skipHookPrefix), "--no-verify").
|
ArgIf(skipHookPrefix != "" && strings.HasPrefix(summary, skipHookPrefix), "--no-verify").
|
||||||
ArgIf(self.signoffFlag() != "", self.signoffFlag()).
|
ArgIf(self.signoffFlag() != "", self.signoffFlag()).
|
||||||
Arg(messageArgs...).
|
Arg(messageArgs...).
|
||||||
ToArgv()
|
ToArgv()
|
||||||
@@ -69,8 +69,8 @@ func (self *CommitCommands) RewordLastCommitInEditorCmdObj() oscommands.ICmdObj
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RewordLastCommit rewords the topmost commit with the given message
|
// RewordLastCommit rewords the topmost commit with the given message
|
||||||
func (self *CommitCommands) RewordLastCommit(message string) error {
|
func (self *CommitCommands) RewordLastCommit(summary string, description string) error {
|
||||||
messageArgs := self.commitMessageArgs(message)
|
messageArgs := self.commitMessageArgs(summary, description)
|
||||||
|
|
||||||
cmdArgs := NewGitCmd("commit").
|
cmdArgs := NewGitCmd("commit").
|
||||||
Arg("--allow-empty", "--amend", "--only").
|
Arg("--allow-empty", "--amend", "--only").
|
||||||
@@ -80,9 +80,8 @@ func (self *CommitCommands) RewordLastCommit(message string) error {
|
|||||||
return self.cmd.New(cmdArgs).Run()
|
return self.cmd.New(cmdArgs).Run()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitCommands) commitMessageArgs(message string) []string {
|
func (self *CommitCommands) commitMessageArgs(summary string, description string) []string {
|
||||||
msg, description, _ := strings.Cut(message, "\n")
|
args := []string{"-m", summary}
|
||||||
args := []string{"-m", msg}
|
|
||||||
|
|
||||||
if description != "" {
|
if description != "" {
|
||||||
args = append(args, "-m", description)
|
args = append(args, "-m", description)
|
||||||
|
|||||||
@@ -10,20 +10,23 @@ import (
|
|||||||
|
|
||||||
func TestCommitRewordCommit(t *testing.T) {
|
func TestCommitRewordCommit(t *testing.T) {
|
||||||
type scenario struct {
|
type scenario struct {
|
||||||
testName string
|
testName string
|
||||||
runner *oscommands.FakeCmdObjRunner
|
runner *oscommands.FakeCmdObjRunner
|
||||||
input string
|
summary string
|
||||||
|
description string
|
||||||
}
|
}
|
||||||
scenarios := []scenario{
|
scenarios := []scenario{
|
||||||
{
|
{
|
||||||
"Single line reword",
|
"Single line reword",
|
||||||
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test"}, "", nil),
|
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test"}, "", nil),
|
||||||
"test",
|
"test",
|
||||||
|
"",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"Multi line reword",
|
"Multi line reword",
|
||||||
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test", "-m", "line 2\nline 3"}, "", nil),
|
oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"commit", "--allow-empty", "--amend", "--only", "-m", "test", "-m", "line 2\nline 3"}, "", nil),
|
||||||
"test\nline 2\nline 3",
|
"test",
|
||||||
|
"line 2\nline 3",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, s := range scenarios {
|
for _, s := range scenarios {
|
||||||
@@ -31,7 +34,7 @@ func TestCommitRewordCommit(t *testing.T) {
|
|||||||
t.Run(s.testName, func(t *testing.T) {
|
t.Run(s.testName, func(t *testing.T) {
|
||||||
instance := buildCommitCommands(commonDeps{runner: s.runner})
|
instance := buildCommitCommands(commonDeps{runner: s.runner})
|
||||||
|
|
||||||
assert.NoError(t, instance.RewordLastCommit(s.input))
|
assert.NoError(t, instance.RewordLastCommit(s.summary, s.description))
|
||||||
s.runner.CheckForMissingCalls()
|
s.runner.CheckForMissingCalls()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -50,7 +53,8 @@ func TestCommitResetToCommit(t *testing.T) {
|
|||||||
func TestCommitCommitCmdObj(t *testing.T) {
|
func TestCommitCommitCmdObj(t *testing.T) {
|
||||||
type scenario struct {
|
type scenario struct {
|
||||||
testName string
|
testName string
|
||||||
message string
|
summary string
|
||||||
|
description string
|
||||||
configSignoff bool
|
configSignoff bool
|
||||||
configSkipHookPrefix string
|
configSkipHookPrefix string
|
||||||
expectedArgs []string
|
expectedArgs []string
|
||||||
@@ -59,35 +63,36 @@ func TestCommitCommitCmdObj(t *testing.T) {
|
|||||||
scenarios := []scenario{
|
scenarios := []scenario{
|
||||||
{
|
{
|
||||||
testName: "Commit",
|
testName: "Commit",
|
||||||
message: "test",
|
summary: "test",
|
||||||
configSignoff: false,
|
configSignoff: false,
|
||||||
configSkipHookPrefix: "",
|
configSkipHookPrefix: "",
|
||||||
expectedArgs: []string{"commit", "-m", "test"},
|
expectedArgs: []string{"commit", "-m", "test"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
testName: "Commit with --no-verify flag",
|
testName: "Commit with --no-verify flag",
|
||||||
message: "WIP: test",
|
summary: "WIP: test",
|
||||||
configSignoff: false,
|
configSignoff: false,
|
||||||
configSkipHookPrefix: "WIP",
|
configSkipHookPrefix: "WIP",
|
||||||
expectedArgs: []string{"commit", "--no-verify", "-m", "WIP: test"},
|
expectedArgs: []string{"commit", "--no-verify", "-m", "WIP: test"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
testName: "Commit with multiline message",
|
testName: "Commit with multiline message",
|
||||||
message: "line1\nline2",
|
summary: "line1",
|
||||||
|
description: "line2",
|
||||||
configSignoff: false,
|
configSignoff: false,
|
||||||
configSkipHookPrefix: "",
|
configSkipHookPrefix: "",
|
||||||
expectedArgs: []string{"commit", "-m", "line1", "-m", "line2"},
|
expectedArgs: []string{"commit", "-m", "line1", "-m", "line2"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
testName: "Commit with signoff",
|
testName: "Commit with signoff",
|
||||||
message: "test",
|
summary: "test",
|
||||||
configSignoff: true,
|
configSignoff: true,
|
||||||
configSkipHookPrefix: "",
|
configSkipHookPrefix: "",
|
||||||
expectedArgs: []string{"commit", "--signoff", "-m", "test"},
|
expectedArgs: []string{"commit", "--signoff", "-m", "test"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
testName: "Commit with signoff and no-verify",
|
testName: "Commit with signoff and no-verify",
|
||||||
message: "WIP: test",
|
summary: "WIP: test",
|
||||||
configSignoff: true,
|
configSignoff: true,
|
||||||
configSkipHookPrefix: "WIP",
|
configSkipHookPrefix: "WIP",
|
||||||
expectedArgs: []string{"commit", "--no-verify", "--signoff", "-m", "WIP: test"},
|
expectedArgs: []string{"commit", "--no-verify", "--signoff", "-m", "WIP: test"},
|
||||||
@@ -104,7 +109,7 @@ func TestCommitCommitCmdObj(t *testing.T) {
|
|||||||
runner := oscommands.NewFakeRunner(t).ExpectGitArgs(s.expectedArgs, "", nil)
|
runner := oscommands.NewFakeRunner(t).ExpectGitArgs(s.expectedArgs, "", nil)
|
||||||
instance := buildCommitCommands(commonDeps{userConfig: userConfig, runner: runner})
|
instance := buildCommitCommands(commonDeps{userConfig: userConfig, runner: runner})
|
||||||
|
|
||||||
assert.NoError(t, instance.CommitCmdObj(s.message).Run())
|
assert.NoError(t, instance.CommitCmdObj(s.summary, s.description).Run())
|
||||||
runner.CheckForMissingCalls()
|
runner.CheckForMissingCalls()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ func (self *PatchCommands) PullPatchIntoNewCommit(commits []*models.Commit, comm
|
|||||||
|
|
||||||
head_message, _ := self.commit.GetHeadCommitMessage()
|
head_message, _ := self.commit.GetHeadCommitMessage()
|
||||||
new_message := fmt.Sprintf("Split from \"%s\"", head_message)
|
new_message := fmt.Sprintf("Split from \"%s\"", head_message)
|
||||||
if err := self.commit.CommitCmdObj(new_message).Run(); err != nil {
|
if err := self.commit.CommitCmdObj(new_message, "").Run(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ func NewRebaseCommands(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, message string) error {
|
func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, summary string, description string) error {
|
||||||
if models.IsHeadCommit(commits, index) {
|
if models.IsHeadCommit(commits, index) {
|
||||||
// we've selected the top commit so no rebase is required
|
// we've selected the top commit so no rebase is required
|
||||||
return self.commit.RewordLastCommit(message)
|
return self.commit.RewordLastCommit(summary, description)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := self.BeginInteractiveRebaseForCommit(commits, index, false)
|
err := self.BeginInteractiveRebaseForCommit(commits, index, false)
|
||||||
@@ -47,7 +47,7 @@ func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, me
|
|||||||
}
|
}
|
||||||
|
|
||||||
// now the selected commit should be our head so we'll amend it with the new message
|
// now the selected commit should be our head so we'll amend it with the new message
|
||||||
err = self.commit.RewordLastCommit(message)
|
err = self.commit.RewordLastCommit(summary, description)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// A commit author
|
||||||
|
type Author struct {
|
||||||
|
Name string
|
||||||
|
Email string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *Author) Combined() string {
|
||||||
|
return fmt.Sprintf("%s <%s>", self.Name, self.Email)
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ type CommitMessageViewModel struct {
|
|||||||
// the full preserved message (combined summary and description)
|
// the full preserved message (combined summary and description)
|
||||||
preservedMessage string
|
preservedMessage string
|
||||||
// invoked when pressing enter in the commit message panel
|
// invoked when pressing enter in the commit message panel
|
||||||
onConfirm func(string) error
|
onConfirm func(string, string) error
|
||||||
|
|
||||||
// The message typed in before cycling through history
|
// The message typed in before cycling through history
|
||||||
// We store this separately to 'preservedMessage' because 'preservedMessage'
|
// We store this separately to 'preservedMessage' because 'preservedMessage'
|
||||||
@@ -88,15 +88,22 @@ func (self *CommitMessageContext) SetHistoryMessage(message string) {
|
|||||||
self.viewModel.historyMessage = message
|
self.viewModel.historyMessage = message
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitMessageContext) OnConfirm(message string) error {
|
func (self *CommitMessageContext) OnConfirm(summary string, description string) error {
|
||||||
return self.viewModel.onConfirm(message)
|
return self.viewModel.onConfirm(summary, description)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitMessageContext) SetPanelState(index int, title string, preserveMessage bool, onConfirm func(string) error) {
|
func (self *CommitMessageContext) SetPanelState(
|
||||||
|
index int,
|
||||||
|
summaryTitle string,
|
||||||
|
descriptionTitle string,
|
||||||
|
preserveMessage bool,
|
||||||
|
onConfirm func(string, string) error,
|
||||||
|
) {
|
||||||
self.viewModel.selectedindex = index
|
self.viewModel.selectedindex = index
|
||||||
self.viewModel.preserveMessage = preserveMessage
|
self.viewModel.preserveMessage = preserveMessage
|
||||||
self.viewModel.onConfirm = onConfirm
|
self.viewModel.onConfirm = onConfirm
|
||||||
self.GetView().Title = title
|
self.GetView().Title = summaryTitle
|
||||||
|
self.c.Views().CommitDescription.Title = descriptionTitle
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitMessageContext) RenderCommitLength() {
|
func (self *CommitMessageContext) RenderCommitLength() {
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
package context
|
package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/jesseduffield/lazygit/pkg/utils"
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
|
"github.com/sahilm/fuzzy"
|
||||||
|
"github.com/samber/lo"
|
||||||
"github.com/sasha-s/go-deadlock"
|
"github.com/sasha-s/go-deadlock"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -53,6 +57,21 @@ func (self *FilteredList[T]) UnfilteredLen() int {
|
|||||||
return len(self.getList())
|
return len(self.getList())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type fuzzySource[T any] struct {
|
||||||
|
list []T
|
||||||
|
getFilterFields func(T) []string
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ fuzzy.Source = &fuzzySource[string]{}
|
||||||
|
|
||||||
|
func (self *fuzzySource[T]) String(i int) string {
|
||||||
|
return strings.Join(self.getFilterFields(self.list[i]), " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (self *fuzzySource[T]) Len() int {
|
||||||
|
return len(self.list)
|
||||||
|
}
|
||||||
|
|
||||||
func (self *FilteredList[T]) applyFilter() {
|
func (self *FilteredList[T]) applyFilter() {
|
||||||
self.mutex.Lock()
|
self.mutex.Lock()
|
||||||
defer self.mutex.Unlock()
|
defer self.mutex.Unlock()
|
||||||
@@ -60,20 +79,16 @@ func (self *FilteredList[T]) applyFilter() {
|
|||||||
if self.filter == "" {
|
if self.filter == "" {
|
||||||
self.filteredIndices = nil
|
self.filteredIndices = nil
|
||||||
} else {
|
} else {
|
||||||
self.filteredIndices = []int{}
|
source := &fuzzySource[T]{
|
||||||
for i, item := range self.getList() {
|
list: self.getList(),
|
||||||
for _, field := range self.getFilterFields(item) {
|
getFilterFields: self.getFilterFields,
|
||||||
if self.match(field, self.filter) {
|
|
||||||
self.filteredIndices = append(self.filteredIndices, i)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *FilteredList[T]) match(haystack string, needle string) bool {
|
matches := fuzzy.FindFrom(self.filter, source)
|
||||||
return utils.CaseAwareContains(haystack, needle)
|
self.filteredIndices = lo.Map(matches, func(match fuzzy.Match, _ int) int {
|
||||||
|
return match.Index
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *FilteredList[T]) UnfilteredIndex(index int) int {
|
func (self *FilteredList[T]) UnfilteredIndex(index int) int {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ type ListContextTrait struct {
|
|||||||
list types.IList
|
list types.IList
|
||||||
getDisplayStrings func(startIdx int, length int) [][]string
|
getDisplayStrings func(startIdx int, length int) [][]string
|
||||||
// Alignment for each column. If nil, the default is left alignment
|
// Alignment for each column. If nil, the default is left alignment
|
||||||
columnAlignments []utils.Alignment
|
getColumnAlignments func() []utils.Alignment
|
||||||
// Some contexts, like the commit context, will highlight the path from the selected commit
|
// Some contexts, like the commit context, will highlight the path from the selected commit
|
||||||
// to its parents, because it's ambiguous otherwise. For these, we need to refresh the viewport
|
// to its parents, because it's ambiguous otherwise. For these, we need to refresh the viewport
|
||||||
// so that we show the highlighted path.
|
// so that we show the highlighted path.
|
||||||
@@ -82,9 +82,13 @@ func (self *ListContextTrait) HandleFocusLost(opts types.OnFocusLostOpts) error
|
|||||||
// OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view
|
// OnFocus assumes that the content of the context has already been rendered to the view. OnRender is the function which actually renders the content to the view
|
||||||
func (self *ListContextTrait) HandleRender() error {
|
func (self *ListContextTrait) HandleRender() error {
|
||||||
self.list.RefreshSelectedIdx()
|
self.list.RefreshSelectedIdx()
|
||||||
|
var columnAlignments []utils.Alignment
|
||||||
|
if self.getColumnAlignments != nil {
|
||||||
|
columnAlignments = self.getColumnAlignments()
|
||||||
|
}
|
||||||
content := utils.RenderDisplayStrings(
|
content := utils.RenderDisplayStrings(
|
||||||
self.getDisplayStrings(0, self.list.Len()),
|
self.getDisplayStrings(0, self.list.Len()),
|
||||||
self.columnAlignments,
|
columnAlignments,
|
||||||
)
|
)
|
||||||
self.GetViewTrait().SetContent(content)
|
self.GetViewTrait().SetContent(content)
|
||||||
self.c.Render()
|
self.c.Render()
|
||||||
|
|||||||
@@ -35,10 +35,10 @@ func NewMenuContext(
|
|||||||
Focusable: true,
|
Focusable: true,
|
||||||
HasUncontrolledBounds: true,
|
HasUncontrolledBounds: true,
|
||||||
})),
|
})),
|
||||||
getDisplayStrings: viewModel.GetDisplayStrings,
|
getDisplayStrings: viewModel.GetDisplayStrings,
|
||||||
list: viewModel,
|
list: viewModel,
|
||||||
c: c,
|
c: c,
|
||||||
columnAlignments: []utils.Alignment{utils.AlignRight, utils.AlignLeft},
|
getColumnAlignments: func() []utils.Alignment { return viewModel.columnAlignment },
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -54,8 +54,9 @@ func (self *MenuContext) GetSelectedItemId() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type MenuViewModel struct {
|
type MenuViewModel struct {
|
||||||
c *ContextCommon
|
c *ContextCommon
|
||||||
menuItems []*types.MenuItem
|
menuItems []*types.MenuItem
|
||||||
|
columnAlignment []utils.Alignment
|
||||||
*FilteredListViewModel[*types.MenuItem]
|
*FilteredListViewModel[*types.MenuItem]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,8 +74,9 @@ func NewMenuViewModel(c *ContextCommon) *MenuViewModel {
|
|||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *MenuViewModel) SetMenuItems(items []*types.MenuItem) {
|
func (self *MenuViewModel) SetMenuItems(items []*types.MenuItem, columnAlignment []utils.Alignment) {
|
||||||
self.menuItems = items
|
self.menuItems = items
|
||||||
|
self.columnAlignment = columnAlignment
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move into presentation package
|
// TODO: move into presentation package
|
||||||
@@ -135,6 +137,10 @@ func (self *MenuContext) OnMenuPress(selectedItem *types.MenuItem) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if selectedItem == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
if err := selectedItem.OnPress(); err != nil {
|
if err := selectedItem.OnPress(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ func (gui *Gui) resetHelpersAndControllers() {
|
|||||||
Suggestions: suggestionsHelper,
|
Suggestions: suggestionsHelper,
|
||||||
Files: helpers.NewFilesHelper(helperCommon),
|
Files: helpers.NewFilesHelper(helperCommon),
|
||||||
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, refsHelper, commitsHelper, gpgHelper),
|
WorkingTree: helpers.NewWorkingTreeHelper(helperCommon, refsHelper, commitsHelper, gpgHelper),
|
||||||
Tags: helpers.NewTagsHelper(helperCommon),
|
Tags: helpers.NewTagsHelper(helperCommon, commitsHelper),
|
||||||
GPG: helpers.NewGpgHelper(helperCommon),
|
GPG: helpers.NewGpgHelper(helperCommon),
|
||||||
MergeAndRebase: rebaseHelper,
|
MergeAndRebase: rebaseHelper,
|
||||||
MergeConflicts: mergeConflictsHelper,
|
MergeConflicts: mergeConflictsHelper,
|
||||||
|
|||||||
@@ -395,7 +395,7 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *BranchesController) createTag(branch *models.Branch) error {
|
func (self *BranchesController) createTag(branch *models.Branch) error {
|
||||||
return self.c.Helpers().Tags.CreateTagMenu(branch.FullRefName(), func() {})
|
return self.c.Helpers().Tags.OpenCreateTagPrompt(branch.FullRefName(), func() {})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *BranchesController) createResetMenu(selectedBranch *models.Branch) error {
|
func (self *BranchesController) createResetMenu(selectedBranch *models.Branch) error {
|
||||||
|
|||||||
@@ -63,33 +63,44 @@ func (self *CommitsHelper) JoinCommitMessageAndDescription() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitsHelper) UpdateCommitPanelView(message string) {
|
func (self *CommitsHelper) UpdateCommitPanelView(message string) {
|
||||||
// first try the passed in message, if not fallback to context -> view in that order
|
|
||||||
if message != "" {
|
if message != "" {
|
||||||
self.SetMessageAndDescriptionInView(message)
|
self.SetMessageAndDescriptionInView(message)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
message = self.c.Contexts().CommitMessage.GetPreservedMessage()
|
|
||||||
if message != "" {
|
if self.c.Contexts().CommitMessage.GetPreserveMessage() {
|
||||||
self.SetMessageAndDescriptionInView(message)
|
preservedMessage := self.c.Contexts().CommitMessage.GetPreservedMessage()
|
||||||
} else {
|
self.SetMessageAndDescriptionInView(preservedMessage)
|
||||||
self.SetMessageAndDescriptionInView(self.getCommitSummary())
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.SetMessageAndDescriptionInView("")
|
||||||
}
|
}
|
||||||
|
|
||||||
type OpenCommitMessagePanelOpts struct {
|
type OpenCommitMessagePanelOpts struct {
|
||||||
CommitIndex int
|
CommitIndex int
|
||||||
Title string
|
SummaryTitle string
|
||||||
PreserveMessage bool
|
DescriptionTitle string
|
||||||
OnConfirm func(string) error
|
PreserveMessage bool
|
||||||
InitialMessage string
|
OnConfirm func(summary string, description string) error
|
||||||
|
InitialMessage string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitsHelper) OpenCommitMessagePanel(opts *OpenCommitMessagePanelOpts) error {
|
func (self *CommitsHelper) OpenCommitMessagePanel(opts *OpenCommitMessagePanelOpts) error {
|
||||||
|
onConfirm := func(summary string, description string) error {
|
||||||
|
if err := self.CloseCommitMessagePanel(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return opts.OnConfirm(summary, description)
|
||||||
|
}
|
||||||
|
|
||||||
self.c.Contexts().CommitMessage.SetPanelState(
|
self.c.Contexts().CommitMessage.SetPanelState(
|
||||||
opts.CommitIndex,
|
opts.CommitIndex,
|
||||||
opts.Title,
|
opts.SummaryTitle,
|
||||||
|
opts.DescriptionTitle,
|
||||||
opts.PreserveMessage,
|
opts.PreserveMessage,
|
||||||
opts.OnConfirm,
|
onConfirm,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.UpdateCommitPanelView(opts.InitialMessage)
|
self.UpdateCommitPanelView(opts.InitialMessage)
|
||||||
@@ -102,17 +113,16 @@ func (self *CommitsHelper) OnCommitSuccess() {
|
|||||||
if self.c.Contexts().CommitMessage.GetPreserveMessage() {
|
if self.c.Contexts().CommitMessage.GetPreserveMessage() {
|
||||||
self.c.Contexts().CommitMessage.SetPreservedMessage("")
|
self.c.Contexts().CommitMessage.SetPreservedMessage("")
|
||||||
}
|
}
|
||||||
self.SetMessageAndDescriptionInView("")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *CommitsHelper) HandleCommitConfirm() error {
|
func (self *CommitsHelper) HandleCommitConfirm() error {
|
||||||
fullMessage := self.JoinCommitMessageAndDescription()
|
summary, description := self.getCommitSummary(), self.getCommitDescription()
|
||||||
|
|
||||||
if fullMessage == "" {
|
if summary == "" {
|
||||||
return self.c.ErrorMsg(self.c.Tr.CommitWithoutMessageErr)
|
return self.c.ErrorMsg(self.c.Tr.CommitWithoutMessageErr)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := self.c.Contexts().CommitMessage.OnConfirm(fullMessage)
|
err := self.c.Contexts().CommitMessage.OnConfirm(summary, description)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -302,7 +302,12 @@ func (self *ConfirmationHelper) resizeMenu() {
|
|||||||
_, _ = self.c.GocuiGui().SetView(self.c.Views().Menu.Name(), x0, y0, x1, menuBottom, 0)
|
_, _ = self.c.GocuiGui().SetView(self.c.Views().Menu.Name(), x0, y0, x1, menuBottom, 0)
|
||||||
|
|
||||||
tooltipTop := menuBottom + 1
|
tooltipTop := menuBottom + 1
|
||||||
tooltipHeight := getMessageHeight(true, self.c.Contexts().Menu.GetSelected().Tooltip, panelWidth) + 2 // plus 2 for the frame
|
tooltip := ""
|
||||||
|
selectedItem := self.c.Contexts().Menu.GetSelected()
|
||||||
|
if selectedItem != nil {
|
||||||
|
tooltip = selectedItem.Tooltip
|
||||||
|
}
|
||||||
|
tooltipHeight := getMessageHeight(true, tooltip, panelWidth) + 2 // plus 2 for the frame
|
||||||
_, _ = self.c.GocuiGui().SetView(self.c.Views().Tooltip.Name(), x0, tooltipTop, x1, tooltipTop+tooltipHeight-1, 0)
|
_, _ = self.c.GocuiGui().SetView(self.c.Views().Tooltip.Name(), x0, tooltipTop, x1, tooltipTop+tooltipHeight-1, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -266,6 +266,7 @@ func (self *RefreshHelper) refreshCommitsWithLimit() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
self.c.Model().Commits = commits
|
self.c.Model().Commits = commits
|
||||||
|
self.RefreshAuthors(commits)
|
||||||
self.c.Model().WorkingTreeStateAtLastCommitRefresh = self.c.Git().Status.WorkingTreeState()
|
self.c.Model().WorkingTreeStateAtLastCommitRefresh = self.c.Git().Status.WorkingTreeState()
|
||||||
|
|
||||||
return self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
|
return self.c.PostRefreshUpdate(self.c.Contexts().LocalCommits)
|
||||||
@@ -287,10 +288,26 @@ func (self *RefreshHelper) refreshSubCommitsWithLimit() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
self.c.Model().SubCommits = commits
|
self.c.Model().SubCommits = commits
|
||||||
|
self.RefreshAuthors(commits)
|
||||||
|
|
||||||
return self.c.PostRefreshUpdate(self.c.Contexts().SubCommits)
|
return self.c.PostRefreshUpdate(self.c.Contexts().SubCommits)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *RefreshHelper) RefreshAuthors(commits []*models.Commit) {
|
||||||
|
self.c.Mutexes().AuthorsMutex.Lock()
|
||||||
|
defer self.c.Mutexes().AuthorsMutex.Unlock()
|
||||||
|
|
||||||
|
authors := self.c.Model().Authors
|
||||||
|
for _, commit := range commits {
|
||||||
|
if _, ok := authors[commit.AuthorEmail]; !ok {
|
||||||
|
authors[commit.AuthorEmail] = &models.Author{
|
||||||
|
Email: commit.AuthorEmail,
|
||||||
|
Name: commit.AuthorName,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (self *RefreshHelper) refreshCommitFilesContext() error {
|
func (self *RefreshHelper) refreshCommitFilesContext() error {
|
||||||
ref := self.c.Contexts().CommitFiles.GetRef()
|
ref := self.c.Contexts().CommitFiles.GetRef()
|
||||||
to := ref.RefName()
|
to := ref.RefName()
|
||||||
|
|||||||
@@ -176,9 +176,11 @@ func (self *SuggestionsHelper) GetRefsSuggestionsFunc() func(string) []*types.Su
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *SuggestionsHelper) GetAuthorsSuggestionsFunc() func(string) []*types.Suggestion {
|
func (self *SuggestionsHelper) GetAuthorsSuggestionsFunc() func(string) []*types.Suggestion {
|
||||||
authors := lo.Uniq(slices.Map(self.c.Model().Commits, func(commit *models.Commit) string {
|
authors := lo.Map(lo.Values(self.c.Model().Authors), func(author *models.Author, _ int) string {
|
||||||
return fmt.Sprintf("%s <%s>", commit.AuthorName, commit.AuthorEmail)
|
return author.Combined()
|
||||||
}))
|
})
|
||||||
|
|
||||||
|
slices.Sort(authors)
|
||||||
|
|
||||||
return FuzzySearchFunc(authors)
|
return FuzzySearchFunc(authors)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,77 +1,54 @@
|
|||||||
package helpers
|
package helpers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/jesseduffield/gocui"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Helper structs are for defining functionality that could be used by multiple contexts.
|
|
||||||
// For example, here we have a CreateTagMenu which is applicable to both the tags context
|
|
||||||
// and the commits context.
|
|
||||||
|
|
||||||
type TagsHelper struct {
|
type TagsHelper struct {
|
||||||
c *HelperCommon
|
c *HelperCommon
|
||||||
|
commitsHelper *CommitsHelper
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTagsHelper(c *HelperCommon) *TagsHelper {
|
func NewTagsHelper(c *HelperCommon, commitsHelper *CommitsHelper) *TagsHelper {
|
||||||
return &TagsHelper{
|
return &TagsHelper{
|
||||||
c: c,
|
c: c,
|
||||||
|
commitsHelper: commitsHelper,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *TagsHelper) CreateTagMenu(ref string, onCreate func()) error {
|
func (self *TagsHelper) OpenCreateTagPrompt(ref string, onCreate func()) error {
|
||||||
return self.c.Menu(types.CreateMenuOptions{
|
onConfirm := func(tagName string, description string) error {
|
||||||
Title: self.c.Tr.TagMenuTitle,
|
return self.c.WithWaitingStatus(self.c.Tr.CreatingTag, func(gocui.Task) error {
|
||||||
Items: []*types.MenuItem{
|
if description != "" {
|
||||||
{
|
self.c.LogAction(self.c.Tr.Actions.CreateAnnotatedTag)
|
||||||
Label: self.c.Tr.LightweightTag,
|
if err := self.c.Git().Tag.CreateAnnotated(tagName, ref, description); err != nil {
|
||||||
OnPress: func() error {
|
return self.c.Error(err)
|
||||||
return self.handleCreateLightweightTag(ref, onCreate)
|
}
|
||||||
},
|
} else {
|
||||||
},
|
self.c.LogAction(self.c.Tr.Actions.CreateLightweightTag)
|
||||||
{
|
if err := self.c.Git().Tag.CreateLightweight(tagName, ref); err != nil {
|
||||||
Label: self.c.Tr.AnnotatedTag,
|
return self.c.Error(err)
|
||||||
OnPress: func() error {
|
}
|
||||||
return self.handleCreateAnnotatedTag(ref, onCreate)
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *TagsHelper) afterTagCreate(onCreate func()) error {
|
|
||||||
onCreate()
|
|
||||||
return self.c.Refresh(types.RefreshOptions{
|
|
||||||
Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *TagsHelper) handleCreateAnnotatedTag(ref string, onCreate func()) error {
|
|
||||||
return self.c.Prompt(types.PromptOpts{
|
|
||||||
Title: self.c.Tr.TagNameTitle,
|
|
||||||
HandleConfirm: func(tagName string) error {
|
|
||||||
return self.c.Prompt(types.PromptOpts{
|
|
||||||
Title: self.c.Tr.TagMessageTitle,
|
|
||||||
HandleConfirm: func(msg string) error {
|
|
||||||
self.c.LogAction(self.c.Tr.Actions.CreateAnnotatedTag)
|
|
||||||
if err := self.c.Git().Tag.CreateAnnotated(tagName, ref, msg); err != nil {
|
|
||||||
return self.c.Error(err)
|
|
||||||
}
|
|
||||||
return self.afterTagCreate(onCreate)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (self *TagsHelper) handleCreateLightweightTag(ref string, onCreate func()) error {
|
|
||||||
return self.c.Prompt(types.PromptOpts{
|
|
||||||
Title: self.c.Tr.TagNameTitle,
|
|
||||||
HandleConfirm: func(tagName string) error {
|
|
||||||
self.c.LogAction(self.c.Tr.Actions.CreateLightweightTag)
|
|
||||||
if err := self.c.Git().Tag.CreateLightweight(tagName, ref); err != nil {
|
|
||||||
return self.c.Error(err)
|
|
||||||
}
|
}
|
||||||
return self.afterTagCreate(onCreate)
|
|
||||||
|
self.commitsHelper.OnCommitSuccess()
|
||||||
|
|
||||||
|
return self.c.Refresh(types.RefreshOptions{
|
||||||
|
Mode: types.ASYNC, Scope: []types.RefreshableView{types.COMMITS, types.TAGS},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.commitsHelper.OpenCommitMessagePanel(
|
||||||
|
&OpenCommitMessagePanelOpts{
|
||||||
|
CommitIndex: context.NoCommitIndex,
|
||||||
|
InitialMessage: "",
|
||||||
|
SummaryTitle: self.c.Tr.TagNameTitle,
|
||||||
|
DescriptionTitle: self.c.Tr.TagMessageTitle,
|
||||||
|
PreserveMessage: false,
|
||||||
|
OnConfirm: onConfirm,
|
||||||
},
|
},
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -99,19 +99,19 @@ func (self *WorkingTreeHelper) HandleCommitPressWithMessage(initialMessage strin
|
|||||||
|
|
||||||
return self.commitsHelper.OpenCommitMessagePanel(
|
return self.commitsHelper.OpenCommitMessagePanel(
|
||||||
&OpenCommitMessagePanelOpts{
|
&OpenCommitMessagePanelOpts{
|
||||||
CommitIndex: context.NoCommitIndex,
|
CommitIndex: context.NoCommitIndex,
|
||||||
InitialMessage: initialMessage,
|
InitialMessage: initialMessage,
|
||||||
Title: self.c.Tr.CommitSummary,
|
SummaryTitle: self.c.Tr.CommitSummaryTitle,
|
||||||
PreserveMessage: true,
|
DescriptionTitle: self.c.Tr.CommitDescriptionTitle,
|
||||||
OnConfirm: self.handleCommit,
|
PreserveMessage: true,
|
||||||
|
OnConfirm: self.handleCommit,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *WorkingTreeHelper) handleCommit(message string) error {
|
func (self *WorkingTreeHelper) handleCommit(summary string, description string) error {
|
||||||
cmdObj := self.c.Git().Commit.CommitCmdObj(message)
|
cmdObj := self.c.Git().Commit.CommitCmdObj(summary, description)
|
||||||
self.c.LogAction(self.c.Tr.Actions.Commit)
|
self.c.LogAction(self.c.Tr.Actions.Commit)
|
||||||
_ = self.commitsHelper.PopCommitMessageContexts()
|
|
||||||
return self.gpgHelper.WithGpgHandling(cmdObj, self.c.Tr.CommittingStatus, func() error {
|
return self.gpgHelper.WithGpgHandling(cmdObj, self.c.Tr.CommittingStatus, func() error {
|
||||||
self.commitsHelper.OnCommitSuccess()
|
self.commitsHelper.OnCommitSuccess()
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -267,22 +267,22 @@ func (self *LocalCommitsController) reword(commit *models.Commit) error {
|
|||||||
|
|
||||||
return self.c.Helpers().Commits.OpenCommitMessagePanel(
|
return self.c.Helpers().Commits.OpenCommitMessagePanel(
|
||||||
&helpers.OpenCommitMessagePanelOpts{
|
&helpers.OpenCommitMessagePanelOpts{
|
||||||
CommitIndex: self.context().GetSelectedLineIdx(),
|
CommitIndex: self.context().GetSelectedLineIdx(),
|
||||||
InitialMessage: commitMessage,
|
InitialMessage: commitMessage,
|
||||||
Title: self.c.Tr.Actions.RewordCommit,
|
SummaryTitle: self.c.Tr.Actions.RewordCommit,
|
||||||
PreserveMessage: false,
|
DescriptionTitle: self.c.Tr.CommitDescriptionTitle,
|
||||||
OnConfirm: self.handleReword,
|
PreserveMessage: false,
|
||||||
|
OnConfirm: self.handleReword,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *LocalCommitsController) handleReword(message string) error {
|
func (self *LocalCommitsController) handleReword(summary string, description string) error {
|
||||||
err := self.c.Git().Rebase.RewordCommit(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), message)
|
err := self.c.Git().Rebase.RewordCommit(self.c.Model().Commits, self.c.Contexts().LocalCommits.GetSelectedLineIdx(), summary, description)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return self.c.Error(err)
|
return self.c.Error(err)
|
||||||
}
|
}
|
||||||
self.c.Helpers().Commits.OnCommitSuccess()
|
self.c.Helpers().Commits.OnCommitSuccess()
|
||||||
_ = self.c.Helpers().Commits.PopCommitMessageContexts()
|
|
||||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -682,7 +682,7 @@ func (self *LocalCommitsController) squashAllAboveFixupCommits(commit *models.Co
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *LocalCommitsController) createTag(commit *models.Commit) error {
|
func (self *LocalCommitsController) createTag(commit *models.Commit) error {
|
||||||
return self.c.Helpers().Tags.CreateTagMenu(commit.Sha, func() {})
|
return self.c.Helpers().Tags.OpenCreateTagPrompt(commit.Sha, func() {})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *LocalCommitsController) openSearch() error {
|
func (self *LocalCommitsController) openSearch() error {
|
||||||
|
|||||||
@@ -53,7 +53,9 @@ func (self *MenuController) GetOnClick() func() error {
|
|||||||
func (self *MenuController) GetOnFocus() func(types.OnFocusOpts) error {
|
func (self *MenuController) GetOnFocus() func(types.OnFocusOpts) error {
|
||||||
return func(types.OnFocusOpts) error {
|
return func(types.OnFocusOpts) error {
|
||||||
selectedMenuItem := self.context().GetSelected()
|
selectedMenuItem := self.context().GetSelected()
|
||||||
self.c.Views().Tooltip.SetContent(selectedMenuItem.Tooltip)
|
if selectedMenuItem != nil {
|
||||||
|
self.c.Views().Tooltip.SetContent(selectedMenuItem.Tooltip)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"github.com/jesseduffield/generics/slices"
|
"github.com/jesseduffield/generics/slices"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
"github.com/jesseduffield/lazygit/pkg/gui/keybindings"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
||||||
"github.com/samber/lo"
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -37,9 +38,10 @@ func (self *OptionsMenuAction) Call() error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
return self.c.Menu(types.CreateMenuOptions{
|
return self.c.Menu(types.CreateMenuOptions{
|
||||||
Title: self.c.Tr.Keybindings,
|
Title: self.c.Tr.Keybindings,
|
||||||
Items: menuItems,
|
Items: menuItems,
|
||||||
HideCancel: true,
|
HideCancel: true,
|
||||||
|
ColumnAlignment: []utils.Alignment{utils.AlignRight, utils.AlignLeft},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ func (self *SwitchToSubCommitsController) viewCommits() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.setSubCommits(commits)
|
self.setSubCommits(commits)
|
||||||
|
self.c.Helpers().Refresh.RefreshAuthors(commits)
|
||||||
|
|
||||||
subCommitsContext := self.c.Contexts().SubCommits
|
subCommitsContext := self.c.Contexts().SubCommits
|
||||||
subCommitsContext.SetSelectedLineIdx(0)
|
subCommitsContext.SetSelectedLineIdx(0)
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ func (self *TagsController) createResetMenu(tag *models.Tag) error {
|
|||||||
|
|
||||||
func (self *TagsController) create() error {
|
func (self *TagsController) create() error {
|
||||||
// leaving commit SHA blank so that we're just creating the tag for the current commit
|
// leaving commit SHA blank so that we're just creating the tag for the current commit
|
||||||
return self.c.Helpers().Tags.CreateTagMenu("", func() { self.context().SetSelectedLineIdx(0) })
|
return self.c.Helpers().Tags.OpenCreateTagPrompt("", func() { self.context().SetSelectedLineIdx(0) })
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *TagsController) withSelectedTag(f func(tag *models.Tag) error) func() error {
|
func (self *TagsController) withSelectedTag(f func(tag *models.Tag) error) func() error {
|
||||||
|
|||||||
@@ -349,6 +349,7 @@ func (gui *Gui) resetState(startArgs appTypes.StartArgs, reuseState bool) types.
|
|||||||
ReflogCommits: make([]*models.Commit, 0),
|
ReflogCommits: make([]*models.Commit, 0),
|
||||||
BisectInfo: git_commands.NewNullBisectInfo(),
|
BisectInfo: git_commands.NewNullBisectInfo(),
|
||||||
FilesTrie: patricia.NewTrie(),
|
FilesTrie: patricia.NewTrie(),
|
||||||
|
Authors: map[string]*models.Author{},
|
||||||
},
|
},
|
||||||
Modes: &types.Modes{
|
Modes: &types.Modes{
|
||||||
Filtering: filtering.New(startArgs.FilterPath),
|
Filtering: filtering.New(startArgs.FilterPath),
|
||||||
@@ -456,6 +457,7 @@ func NewGui(
|
|||||||
SyncMutex: &deadlock.Mutex{},
|
SyncMutex: &deadlock.Mutex{},
|
||||||
LocalCommitsMutex: &deadlock.Mutex{},
|
LocalCommitsMutex: &deadlock.Mutex{},
|
||||||
SubCommitsMutex: &deadlock.Mutex{},
|
SubCommitsMutex: &deadlock.Mutex{},
|
||||||
|
AuthorsMutex: &deadlock.Mutex{},
|
||||||
SubprocessMutex: &deadlock.Mutex{},
|
SubprocessMutex: &deadlock.Mutex{},
|
||||||
PopupMutex: &deadlock.Mutex{},
|
PopupMutex: &deadlock.Mutex{},
|
||||||
PtyMutex: &deadlock.Mutex{},
|
PtyMutex: &deadlock.Mutex{},
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ func (gui *Gui) createMenu(opts types.CreateMenuOptions) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gui.State.Contexts.Menu.SetMenuItems(opts.Items)
|
gui.State.Contexts.Menu.SetMenuItems(opts.Items, opts.ColumnAlignment)
|
||||||
gui.State.Contexts.Menu.SetSelectedLineIdx(0)
|
gui.State.Contexts.Menu.SetSelectedLineIdx(0)
|
||||||
|
|
||||||
gui.Views.Menu.Title = opts.Title
|
gui.Views.Menu.Title = opts.Title
|
||||||
|
|||||||
@@ -133,9 +133,10 @@ type IPopupHandler interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CreateMenuOptions struct {
|
type CreateMenuOptions struct {
|
||||||
Title string
|
Title string
|
||||||
Items []*MenuItem
|
Items []*MenuItem
|
||||||
HideCancel bool
|
HideCancel bool
|
||||||
|
ColumnAlignment []utils.Alignment
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreatePopupPanelOpts struct {
|
type CreatePopupPanelOpts struct {
|
||||||
@@ -216,6 +217,8 @@ type Model struct {
|
|||||||
|
|
||||||
// for displaying suggestions while typing in a file name
|
// for displaying suggestions while typing in a file name
|
||||||
FilesTrie *patricia.Trie
|
FilesTrie *patricia.Trie
|
||||||
|
|
||||||
|
Authors map[string]*models.Author
|
||||||
}
|
}
|
||||||
|
|
||||||
// if you add a new mutex here be sure to instantiate it. We're using pointers to
|
// if you add a new mutex here be sure to instantiate it. We're using pointers to
|
||||||
@@ -227,6 +230,7 @@ type Mutexes struct {
|
|||||||
SyncMutex *deadlock.Mutex
|
SyncMutex *deadlock.Mutex
|
||||||
LocalCommitsMutex *deadlock.Mutex
|
LocalCommitsMutex *deadlock.Mutex
|
||||||
SubCommitsMutex *deadlock.Mutex
|
SubCommitsMutex *deadlock.Mutex
|
||||||
|
AuthorsMutex *deadlock.Mutex
|
||||||
SubprocessMutex *deadlock.Mutex
|
SubprocessMutex *deadlock.Mutex
|
||||||
PopupMutex *deadlock.Mutex
|
PopupMutex *deadlock.Mutex
|
||||||
PtyMutex *deadlock.Mutex
|
PtyMutex *deadlock.Mutex
|
||||||
|
|||||||
+3
-4
@@ -181,7 +181,7 @@ func chineseTranslationSet() TranslationSet {
|
|||||||
RecentRepos: "最近的仓库",
|
RecentRepos: "最近的仓库",
|
||||||
MergeOptionsTitle: "合并选项",
|
MergeOptionsTitle: "合并选项",
|
||||||
RebaseOptionsTitle: "变基选项",
|
RebaseOptionsTitle: "变基选项",
|
||||||
CommitMessageTitle: "提交讯息",
|
CommitSummaryTitle: "提交讯息",
|
||||||
LocalBranchesTitle: "分支页面",
|
LocalBranchesTitle: "分支页面",
|
||||||
SearchTitle: "搜索",
|
SearchTitle: "搜索",
|
||||||
TagsTitle: "标签页面",
|
TagsTitle: "标签页面",
|
||||||
@@ -305,8 +305,8 @@ func chineseTranslationSet() TranslationSet {
|
|||||||
EditRemote: "编辑远程仓库",
|
EditRemote: "编辑远程仓库",
|
||||||
TagCommit: "标签提交",
|
TagCommit: "标签提交",
|
||||||
TagMenuTitle: "创建标签",
|
TagMenuTitle: "创建标签",
|
||||||
TagNameTitle: "标签名称:",
|
TagNameTitle: "标签名称",
|
||||||
TagMessageTitle: "标签消息:",
|
TagMessageTitle: "标签消息",
|
||||||
AnnotatedTag: "附注标签",
|
AnnotatedTag: "附注标签",
|
||||||
LightweightTag: "轻量标签",
|
LightweightTag: "轻量标签",
|
||||||
DeleteTag: "删除标签",
|
DeleteTag: "删除标签",
|
||||||
@@ -315,7 +315,6 @@ func chineseTranslationSet() TranslationSet {
|
|||||||
PushTagTitle: "将 {{.tagName}} 推送到远程仓库:",
|
PushTagTitle: "将 {{.tagName}} 推送到远程仓库:",
|
||||||
PushTag: "推送标签",
|
PushTag: "推送标签",
|
||||||
CreateTag: "创建标签",
|
CreateTag: "创建标签",
|
||||||
CreateTagTitle: "标签名称:",
|
|
||||||
FetchRemote: "抓取远程仓库",
|
FetchRemote: "抓取远程仓库",
|
||||||
FetchingRemoteStatus: "抓取远程仓库中",
|
FetchingRemoteStatus: "抓取远程仓库中",
|
||||||
CheckoutCommit: "检出提交",
|
CheckoutCommit: "检出提交",
|
||||||
|
|||||||
+2
-3
@@ -146,7 +146,7 @@ func dutchTranslationSet() TranslationSet {
|
|||||||
RecentRepos: "Recente repositories",
|
RecentRepos: "Recente repositories",
|
||||||
MergeOptionsTitle: "Merge opties",
|
MergeOptionsTitle: "Merge opties",
|
||||||
RebaseOptionsTitle: "Rebase opties",
|
RebaseOptionsTitle: "Rebase opties",
|
||||||
CommitMessageTitle: "Commit bericht",
|
CommitSummaryTitle: "Commit bericht",
|
||||||
LocalBranchesTitle: "Branches",
|
LocalBranchesTitle: "Branches",
|
||||||
SearchTitle: "Zoek",
|
SearchTitle: "Zoek",
|
||||||
TagsTitle: "Tags",
|
TagsTitle: "Tags",
|
||||||
@@ -263,14 +263,13 @@ func dutchTranslationSet() TranslationSet {
|
|||||||
SetUpstreamMessage: "Weet je zeker dat je de upstream branch van '{{.checkedOut}}' naar '{{.selected}}' wilt zetten",
|
SetUpstreamMessage: "Weet je zeker dat je de upstream branch van '{{.checkedOut}}' naar '{{.selected}}' wilt zetten",
|
||||||
EditRemote: "Wijzig remote",
|
EditRemote: "Wijzig remote",
|
||||||
TagCommit: "Tag commit",
|
TagCommit: "Tag commit",
|
||||||
TagNameTitle: "Tag naam:",
|
TagNameTitle: "Tag naam",
|
||||||
DeleteTag: "Verwijder tag",
|
DeleteTag: "Verwijder tag",
|
||||||
DeleteTagTitle: "Verwijder tag",
|
DeleteTagTitle: "Verwijder tag",
|
||||||
DeleteTagPrompt: "Weet je zeker dat je '{{.tagName}}' wil verwijderen?",
|
DeleteTagPrompt: "Weet je zeker dat je '{{.tagName}}' wil verwijderen?",
|
||||||
PushTagTitle: "Remote om tag '{{.tagName}}' te pushen naar:",
|
PushTagTitle: "Remote om tag '{{.tagName}}' te pushen naar:",
|
||||||
PushTag: "Push tag",
|
PushTag: "Push tag",
|
||||||
CreateTag: "Creëer tag",
|
CreateTag: "Creëer tag",
|
||||||
CreateTagTitle: "Tag naam:",
|
|
||||||
FetchRemote: "Fetch remote",
|
FetchRemote: "Fetch remote",
|
||||||
FetchingRemoteStatus: "Remote fetchen",
|
FetchingRemoteStatus: "Remote fetchen",
|
||||||
CheckoutCommit: "Checkout commit",
|
CheckoutCommit: "Checkout commit",
|
||||||
|
|||||||
+6
-6
@@ -192,7 +192,7 @@ type TranslationSet struct {
|
|||||||
RecentRepos string
|
RecentRepos string
|
||||||
MergeOptionsTitle string
|
MergeOptionsTitle string
|
||||||
RebaseOptionsTitle string
|
RebaseOptionsTitle string
|
||||||
CommitMessageTitle string
|
CommitSummaryTitle string
|
||||||
CommitDescriptionTitle string
|
CommitDescriptionTitle string
|
||||||
CommitDescriptionSubTitle string
|
CommitDescriptionSubTitle string
|
||||||
LocalBranchesTitle string
|
LocalBranchesTitle string
|
||||||
@@ -355,7 +355,7 @@ type TranslationSet struct {
|
|||||||
PushTagTitle string
|
PushTagTitle string
|
||||||
PushTag string
|
PushTag string
|
||||||
CreateTag string
|
CreateTag string
|
||||||
CreateTagTitle string
|
CreatingTag string
|
||||||
FetchRemote string
|
FetchRemote string
|
||||||
FetchingRemoteStatus string
|
FetchingRemoteStatus string
|
||||||
CheckoutCommit string
|
CheckoutCommit string
|
||||||
@@ -884,7 +884,7 @@ func EnglishTranslationSet() TranslationSet {
|
|||||||
RecentRepos: "Recent repositories",
|
RecentRepos: "Recent repositories",
|
||||||
MergeOptionsTitle: "Merge options",
|
MergeOptionsTitle: "Merge options",
|
||||||
RebaseOptionsTitle: "Rebase options",
|
RebaseOptionsTitle: "Rebase options",
|
||||||
CommitMessageTitle: "Commit summary",
|
CommitSummaryTitle: "Commit summary",
|
||||||
CommitDescriptionTitle: "Commit description",
|
CommitDescriptionTitle: "Commit description",
|
||||||
CommitDescriptionSubTitle: "Press {{.togglePanelKeyBinding}} to toggle focus",
|
CommitDescriptionSubTitle: "Press {{.togglePanelKeyBinding}} to toggle focus",
|
||||||
LocalBranchesTitle: "Local branches",
|
LocalBranchesTitle: "Local branches",
|
||||||
@@ -1039,8 +1039,8 @@ func EnglishTranslationSet() TranslationSet {
|
|||||||
EditRemote: "Edit remote",
|
EditRemote: "Edit remote",
|
||||||
TagCommit: "Tag commit",
|
TagCommit: "Tag commit",
|
||||||
TagMenuTitle: "Create tag",
|
TagMenuTitle: "Create tag",
|
||||||
TagNameTitle: "Tag name:",
|
TagNameTitle: "Tag name",
|
||||||
TagMessageTitle: "Tag message:",
|
TagMessageTitle: "Tag description",
|
||||||
AnnotatedTag: "Annotated tag",
|
AnnotatedTag: "Annotated tag",
|
||||||
LightweightTag: "Lightweight tag",
|
LightweightTag: "Lightweight tag",
|
||||||
DeleteTag: "Delete tag",
|
DeleteTag: "Delete tag",
|
||||||
@@ -1049,7 +1049,7 @@ func EnglishTranslationSet() TranslationSet {
|
|||||||
PushTagTitle: "Remote to push tag '{{.tagName}}' to:",
|
PushTagTitle: "Remote to push tag '{{.tagName}}' to:",
|
||||||
PushTag: "Push tag",
|
PushTag: "Push tag",
|
||||||
CreateTag: "Create tag",
|
CreateTag: "Create tag",
|
||||||
CreateTagTitle: "Tag name:",
|
CreatingTag: "Creating tag",
|
||||||
FetchRemote: "Fetch remote",
|
FetchRemote: "Fetch remote",
|
||||||
FetchingRemoteStatus: "Fetching remote",
|
FetchingRemoteStatus: "Fetching remote",
|
||||||
CheckoutCommit: "Checkout commit",
|
CheckoutCommit: "Checkout commit",
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ func japaneseTranslationSet() TranslationSet {
|
|||||||
RecentRepos: "最近使用したリポジトリ",
|
RecentRepos: "最近使用したリポジトリ",
|
||||||
// MergeOptionsTitle: "Merge Options",
|
// MergeOptionsTitle: "Merge Options",
|
||||||
// RebaseOptionsTitle: "Rebase Options",
|
// RebaseOptionsTitle: "Rebase Options",
|
||||||
CommitMessageTitle: "コミットメッセージ",
|
CommitSummaryTitle: "コミットメッセージ",
|
||||||
LocalBranchesTitle: "ブランチ",
|
LocalBranchesTitle: "ブランチ",
|
||||||
SearchTitle: "検索",
|
SearchTitle: "検索",
|
||||||
TagsTitle: "タグ",
|
TagsTitle: "タグ",
|
||||||
@@ -315,8 +315,8 @@ func japaneseTranslationSet() TranslationSet {
|
|||||||
EditRemote: "リモートを編集",
|
EditRemote: "リモートを編集",
|
||||||
TagCommit: "タグを作成",
|
TagCommit: "タグを作成",
|
||||||
TagMenuTitle: "タグを作成",
|
TagMenuTitle: "タグを作成",
|
||||||
TagNameTitle: "タグ名:",
|
TagNameTitle: "タグ名",
|
||||||
TagMessageTitle: "タグメッセージ: ",
|
TagMessageTitle: "タグメッセージ",
|
||||||
AnnotatedTag: "注釈付きタグ",
|
AnnotatedTag: "注釈付きタグ",
|
||||||
LightweightTag: "軽量タグ",
|
LightweightTag: "軽量タグ",
|
||||||
DeleteTag: "タグを削除",
|
DeleteTag: "タグを削除",
|
||||||
@@ -325,7 +325,6 @@ func japaneseTranslationSet() TranslationSet {
|
|||||||
PushTagTitle: "リモートにタグ '{{.tagName}}' をpush",
|
PushTagTitle: "リモートにタグ '{{.tagName}}' をpush",
|
||||||
PushTag: "タグをpush",
|
PushTag: "タグをpush",
|
||||||
CreateTag: "タグを作成",
|
CreateTag: "タグを作成",
|
||||||
CreateTagTitle: "タグ名:",
|
|
||||||
FetchRemote: "リモートをfetch",
|
FetchRemote: "リモートをfetch",
|
||||||
FetchingRemoteStatus: "リモートをfetch",
|
FetchingRemoteStatus: "リモートをfetch",
|
||||||
CheckoutCommit: "コミットをチェックアウト",
|
CheckoutCommit: "コミットをチェックアウト",
|
||||||
|
|||||||
+3
-4
@@ -182,7 +182,7 @@ func koreanTranslationSet() TranslationSet {
|
|||||||
RecentRepos: "최근에 사용한 저장소",
|
RecentRepos: "최근에 사용한 저장소",
|
||||||
MergeOptionsTitle: "Merge options",
|
MergeOptionsTitle: "Merge options",
|
||||||
RebaseOptionsTitle: "Rebase options",
|
RebaseOptionsTitle: "Rebase options",
|
||||||
CommitMessageTitle: "커밋메시지",
|
CommitSummaryTitle: "커밋메시지",
|
||||||
LocalBranchesTitle: "브랜치",
|
LocalBranchesTitle: "브랜치",
|
||||||
SearchTitle: "검색",
|
SearchTitle: "검색",
|
||||||
TagsTitle: "태그",
|
TagsTitle: "태그",
|
||||||
@@ -310,8 +310,8 @@ func koreanTranslationSet() TranslationSet {
|
|||||||
EditRemote: "Remote를 수정",
|
EditRemote: "Remote를 수정",
|
||||||
TagCommit: "Tag commit",
|
TagCommit: "Tag commit",
|
||||||
TagMenuTitle: "태그 작성",
|
TagMenuTitle: "태그 작성",
|
||||||
TagNameTitle: "태그 이름:",
|
TagNameTitle: "태그 이름",
|
||||||
TagMessageTitle: "태그 메시지: ",
|
TagMessageTitle: "태그 메시지",
|
||||||
AnnotatedTag: "Annotated tag",
|
AnnotatedTag: "Annotated tag",
|
||||||
LightweightTag: "Lightweight tag",
|
LightweightTag: "Lightweight tag",
|
||||||
DeleteTag: "태그 삭제",
|
DeleteTag: "태그 삭제",
|
||||||
@@ -320,7 +320,6 @@ func koreanTranslationSet() TranslationSet {
|
|||||||
PushTagTitle: "원격에 태그 '{{.tagName}}' 를 푸시",
|
PushTagTitle: "원격에 태그 '{{.tagName}}' 를 푸시",
|
||||||
PushTag: "태그를 push",
|
PushTag: "태그를 push",
|
||||||
CreateTag: "태그를 생성",
|
CreateTag: "태그를 생성",
|
||||||
CreateTagTitle: "태그 이름:",
|
|
||||||
FetchRemote: "원격을 업데이트",
|
FetchRemote: "원격을 업데이트",
|
||||||
FetchingRemoteStatus: "원격을 업데이트 중",
|
FetchingRemoteStatus: "원격을 업데이트 중",
|
||||||
CheckoutCommit: "커밋을 체크아웃",
|
CheckoutCommit: "커밋을 체크아웃",
|
||||||
|
|||||||
+3
-4
@@ -216,7 +216,7 @@ func RussianTranslationSet() TranslationSet {
|
|||||||
RecentRepos: "Последние репозитории",
|
RecentRepos: "Последние репозитории",
|
||||||
MergeOptionsTitle: "Параметры слияния",
|
MergeOptionsTitle: "Параметры слияния",
|
||||||
RebaseOptionsTitle: "Параметры перебазирования",
|
RebaseOptionsTitle: "Параметры перебазирования",
|
||||||
CommitMessageTitle: "Сводка коммита",
|
CommitSummaryTitle: "Сводка коммита",
|
||||||
CommitDescriptionTitle: "Описание коммита",
|
CommitDescriptionTitle: "Описание коммита",
|
||||||
CommitDescriptionSubTitle: "Нажмите вкладку, чтобы переключить фокус",
|
CommitDescriptionSubTitle: "Нажмите вкладку, чтобы переключить фокус",
|
||||||
LocalBranchesTitle: "Локальные Ветки",
|
LocalBranchesTitle: "Локальные Ветки",
|
||||||
@@ -371,8 +371,8 @@ func RussianTranslationSet() TranslationSet {
|
|||||||
EditRemote: "Редактировать удалённый репозитории",
|
EditRemote: "Редактировать удалённый репозитории",
|
||||||
TagCommit: "Пометить коммит тегом",
|
TagCommit: "Пометить коммит тегом",
|
||||||
TagMenuTitle: "Создать тег",
|
TagMenuTitle: "Создать тег",
|
||||||
TagNameTitle: "Название тега:",
|
TagNameTitle: "Название тега",
|
||||||
TagMessageTitle: "Сообщения тега:",
|
TagMessageTitle: "Сообщения тега",
|
||||||
AnnotatedTag: "Аннотированный тег",
|
AnnotatedTag: "Аннотированный тег",
|
||||||
LightweightTag: "Легковесный тег",
|
LightweightTag: "Легковесный тег",
|
||||||
DeleteTag: "Удалить тег",
|
DeleteTag: "Удалить тег",
|
||||||
@@ -381,7 +381,6 @@ func RussianTranslationSet() TranslationSet {
|
|||||||
PushTagTitle: "Удалённый репозитории для отправки тега '{{.tagName}}' в:",
|
PushTagTitle: "Удалённый репозитории для отправки тега '{{.tagName}}' в:",
|
||||||
PushTag: "Отправить тег",
|
PushTag: "Отправить тег",
|
||||||
CreateTag: "Создать тег",
|
CreateTag: "Создать тег",
|
||||||
CreateTagTitle: "Название тега:",
|
|
||||||
FetchRemote: "Получение изменения из удалённого репозитория",
|
FetchRemote: "Получение изменения из удалённого репозитория",
|
||||||
FetchingRemoteStatus: "Получение статуса удалённого репозитория",
|
FetchingRemoteStatus: "Получение статуса удалённого репозитория",
|
||||||
CheckoutCommit: "Переключить коммит",
|
CheckoutCommit: "Переключить коммит",
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ func traditionalChineseTranslationSet() TranslationSet {
|
|||||||
RecentRepos: "最近的版本庫",
|
RecentRepos: "最近的版本庫",
|
||||||
MergeOptionsTitle: "合併選項",
|
MergeOptionsTitle: "合併選項",
|
||||||
RebaseOptionsTitle: "變基選項",
|
RebaseOptionsTitle: "變基選項",
|
||||||
CommitMessageTitle: "提交摘要",
|
CommitSummaryTitle: "提交摘要",
|
||||||
CommitDescriptionTitle: "提交描述",
|
CommitDescriptionTitle: "提交描述",
|
||||||
CommitDescriptionSubTitle: "按 tab 切換焦點",
|
CommitDescriptionSubTitle: "按 tab 切換焦點",
|
||||||
LocalBranchesTitle: "本地分支",
|
LocalBranchesTitle: "本地分支",
|
||||||
@@ -398,8 +398,8 @@ func traditionalChineseTranslationSet() TranslationSet {
|
|||||||
EditRemote: "編輯遠端",
|
EditRemote: "編輯遠端",
|
||||||
TagCommit: "打標籤到提交",
|
TagCommit: "打標籤到提交",
|
||||||
TagMenuTitle: "建立標籤",
|
TagMenuTitle: "建立標籤",
|
||||||
TagNameTitle: "標籤名稱:",
|
TagNameTitle: "標籤名稱",
|
||||||
TagMessageTitle: "標籤訊息:",
|
TagMessageTitle: "標籤訊息",
|
||||||
AnnotatedTag: "附註標籤",
|
AnnotatedTag: "附註標籤",
|
||||||
LightweightTag: "輕量標籤",
|
LightweightTag: "輕量標籤",
|
||||||
DeleteTag: "刪除標籤",
|
DeleteTag: "刪除標籤",
|
||||||
@@ -408,7 +408,6 @@ func traditionalChineseTranslationSet() TranslationSet {
|
|||||||
PushTagTitle: "推送標籤 '{{.tagName}}' 至遠端:",
|
PushTagTitle: "推送標籤 '{{.tagName}}' 至遠端:",
|
||||||
PushTag: "推送標籤",
|
PushTag: "推送標籤",
|
||||||
CreateTag: "建立標籤",
|
CreateTag: "建立標籤",
|
||||||
CreateTagTitle: "標籤名稱:",
|
|
||||||
FetchRemote: "擷取遠端",
|
FetchRemote: "擷取遠端",
|
||||||
FetchingRemoteStatus: "正在擷取遠端",
|
FetchingRemoteStatus: "正在擷取遠端",
|
||||||
CheckoutCommit: "檢出提交",
|
CheckoutCommit: "檢出提交",
|
||||||
|
|||||||
@@ -23,3 +23,9 @@ func (self *CommitDescriptionPanelDriver) AddNewline() *CommitDescriptionPanelDr
|
|||||||
self.t.press(self.t.keys.Universal.Confirm)
|
self.t.press(self.t.keys.Universal.Confirm)
|
||||||
return self
|
return self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *CommitDescriptionPanelDriver) Title(expected *TextMatcher) *CommitDescriptionPanelDriver {
|
||||||
|
self.getViewDriver().Title(expected)
|
||||||
|
|
||||||
|
return self
|
||||||
|
}
|
||||||
|
|||||||
@@ -539,6 +539,7 @@ func (self *ViewDriver) FilterOrSearch(text string) *ViewDriver {
|
|||||||
self.Press(self.t.keys.Universal.StartSearch).
|
self.Press(self.t.keys.Universal.StartSearch).
|
||||||
Tap(func() {
|
Tap(func() {
|
||||||
self.t.ExpectSearch().
|
self.t.ExpectSearch().
|
||||||
|
Clear().
|
||||||
Type(text).
|
Type(text).
|
||||||
Confirm()
|
Confirm()
|
||||||
|
|
||||||
|
|||||||
@@ -26,13 +26,8 @@ var CreateTag = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
SelectNextItem().
|
SelectNextItem().
|
||||||
Press(keys.Branches.CreateTag)
|
Press(keys.Branches.CreateTag)
|
||||||
|
|
||||||
t.ExpectPopup().Menu().
|
t.ExpectPopup().CommitMessagePanel().
|
||||||
Title(Equals("Create tag")).
|
Title(Equals("Tag name")).
|
||||||
Select(Contains("Lightweight")).
|
|
||||||
Confirm()
|
|
||||||
|
|
||||||
t.ExpectPopup().Prompt().
|
|
||||||
Title(Equals("Tag name:")).
|
|
||||||
Type("new-tag").
|
Type("new-tag").
|
||||||
Confirm()
|
Confirm()
|
||||||
|
|
||||||
|
|||||||
@@ -23,13 +23,8 @@ var CreateTag = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
).
|
).
|
||||||
Press(keys.Commits.CreateTag)
|
Press(keys.Commits.CreateTag)
|
||||||
|
|
||||||
t.ExpectPopup().Menu().
|
t.ExpectPopup().CommitMessagePanel().
|
||||||
Title(Equals("Create tag")).
|
Title(Equals("Tag name")).
|
||||||
Select(Contains("Lightweight")).
|
|
||||||
Confirm()
|
|
||||||
|
|
||||||
t.ExpectPopup().Prompt().
|
|
||||||
Title(Equals("Tag name:")).
|
|
||||||
Type("new-tag").
|
Type("new-tag").
|
||||||
Confirm()
|
Confirm()
|
||||||
|
|
||||||
|
|||||||
@@ -5,29 +5,58 @@ import (
|
|||||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Originally we only suggested authors present in the current branch, but now
|
||||||
|
// we include authors from other branches whose commits you've looked at in the
|
||||||
|
// lazygit session.
|
||||||
|
|
||||||
var SetAuthor = NewIntegrationTest(NewIntegrationTestArgs{
|
var SetAuthor = NewIntegrationTest(NewIntegrationTestArgs{
|
||||||
Description: "Set author on a commit",
|
Description: "Set author on a commit",
|
||||||
ExtraCmdArgs: []string{},
|
ExtraCmdArgs: []string{},
|
||||||
Skip: false,
|
Skip: false,
|
||||||
SetupConfig: func(config *config.AppConfig) {},
|
SetupConfig: func(config *config.AppConfig) {},
|
||||||
SetupRepo: func(shell *Shell) {
|
SetupRepo: func(shell *Shell) {
|
||||||
|
shell.NewBranch("original")
|
||||||
|
|
||||||
shell.SetConfig("user.email", "Bill@example.com")
|
shell.SetConfig("user.email", "Bill@example.com")
|
||||||
shell.SetConfig("user.name", "Bill Smith")
|
shell.SetConfig("user.name", "Bill Smith")
|
||||||
|
|
||||||
shell.EmptyCommit("one")
|
shell.EmptyCommit("one")
|
||||||
|
|
||||||
|
shell.NewBranch("other")
|
||||||
|
|
||||||
shell.SetConfig("user.email", "John@example.com")
|
shell.SetConfig("user.email", "John@example.com")
|
||||||
shell.SetConfig("user.name", "John Smith")
|
shell.SetConfig("user.name", "John Smith")
|
||||||
|
|
||||||
shell.EmptyCommit("two")
|
shell.EmptyCommit("two")
|
||||||
|
|
||||||
|
shell.Checkout("original")
|
||||||
},
|
},
|
||||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||||
t.Views().Commits().
|
t.Views().Commits().
|
||||||
Focus().
|
Focus().
|
||||||
|
Lines(
|
||||||
|
Contains("BS").Contains("one").IsSelected(),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Views().Branches().
|
||||||
|
Focus().
|
||||||
|
Lines(
|
||||||
|
Contains("original").IsSelected(),
|
||||||
|
Contains("other"),
|
||||||
|
).
|
||||||
|
NavigateToLine(Contains("other")).
|
||||||
|
PressEnter()
|
||||||
|
|
||||||
|
// ensuring we get these commit authors as suggestions
|
||||||
|
t.Views().SubCommits().
|
||||||
|
IsFocused().
|
||||||
Lines(
|
Lines(
|
||||||
Contains("JS").Contains("two").IsSelected(),
|
Contains("JS").Contains("two").IsSelected(),
|
||||||
Contains("BS").Contains("one"),
|
Contains("BS").Contains("one"),
|
||||||
).
|
)
|
||||||
|
|
||||||
|
t.Views().Commits().
|
||||||
|
Focus().
|
||||||
Press(keys.Commits.ResetCommitAuthor).
|
Press(keys.Commits.ResetCommitAuthor).
|
||||||
Tap(func() {
|
Tap(func() {
|
||||||
t.ExpectPopup().Menu().
|
t.ExpectPopup().Menu().
|
||||||
@@ -38,14 +67,13 @@ var SetAuthor = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
t.ExpectPopup().Prompt().
|
t.ExpectPopup().Prompt().
|
||||||
Title(Contains("Set author")).
|
Title(Contains("Set author")).
|
||||||
SuggestionLines(
|
SuggestionLines(
|
||||||
Contains("John Smith"),
|
|
||||||
Contains("Bill Smith"),
|
Contains("Bill Smith"),
|
||||||
|
Contains("John Smith"),
|
||||||
).
|
).
|
||||||
ConfirmSuggestion(Contains("John Smith"))
|
ConfirmSuggestion(Contains("John Smith"))
|
||||||
}).
|
}).
|
||||||
Lines(
|
Lines(
|
||||||
Contains("JS").Contains("two").IsSelected(),
|
Contains("JS").Contains("one").IsSelected(),
|
||||||
Contains("BS").Contains("one"),
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package filter_and_search
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||||
|
)
|
||||||
|
|
||||||
|
var FilterFuzzy = NewIntegrationTest(NewIntegrationTestArgs{
|
||||||
|
Description: "Verify that fuzzy filtering works (not just exact matches)",
|
||||||
|
ExtraCmdArgs: []string{},
|
||||||
|
Skip: false,
|
||||||
|
SetupConfig: func(config *config.AppConfig) {},
|
||||||
|
SetupRepo: func(shell *Shell) {
|
||||||
|
shell.NewBranch("this-is-my-branch")
|
||||||
|
shell.EmptyCommit("first commit")
|
||||||
|
shell.NewBranch("other-branch")
|
||||||
|
},
|
||||||
|
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||||
|
t.Views().Branches().
|
||||||
|
Focus().
|
||||||
|
Lines(
|
||||||
|
Contains(`other-branch`).IsSelected(),
|
||||||
|
Contains(`this-is-my-branch`),
|
||||||
|
).
|
||||||
|
FilterOrSearch("timb"). // using first letters of words
|
||||||
|
Lines(
|
||||||
|
Contains(`this-is-my-branch`).IsSelected(),
|
||||||
|
).
|
||||||
|
FilterOrSearch("brnch"). // allows missing letter
|
||||||
|
Lines(
|
||||||
|
Contains(`other-branch`).IsSelected(),
|
||||||
|
Contains(`this-is-my-branch`),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package tag
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||||
|
)
|
||||||
|
|
||||||
|
var CreateWhileCommitting = NewIntegrationTest(NewIntegrationTestArgs{
|
||||||
|
Description: "Draft a commit message, escape out, and make a tag. Verify the draft message doesn't appear in the tag create prompt",
|
||||||
|
ExtraCmdArgs: []string{},
|
||||||
|
Skip: false,
|
||||||
|
SetupConfig: func(config *config.AppConfig) {},
|
||||||
|
SetupRepo: func(shell *Shell) {
|
||||||
|
shell.EmptyCommit("initial commit")
|
||||||
|
shell.CreateFileAndAdd("file.txt", "file contents")
|
||||||
|
},
|
||||||
|
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||||
|
t.Views().Files().
|
||||||
|
Press(keys.Files.CommitChanges).
|
||||||
|
Tap(func() {
|
||||||
|
t.ExpectPopup().CommitMessagePanel().
|
||||||
|
Title(Equals("Commit summary")).
|
||||||
|
Type("draft message").
|
||||||
|
Cancel()
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Views().Tags().
|
||||||
|
Focus().
|
||||||
|
IsEmpty().
|
||||||
|
Press(keys.Universal.New).
|
||||||
|
Tap(func() {
|
||||||
|
t.ExpectPopup().CommitMessagePanel().
|
||||||
|
Title(Equals("Tag name")).
|
||||||
|
InitialText(Equals(""))
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -19,19 +19,13 @@ var CrudAnnotated = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
IsEmpty().
|
IsEmpty().
|
||||||
Press(keys.Universal.New).
|
Press(keys.Universal.New).
|
||||||
Tap(func() {
|
Tap(func() {
|
||||||
t.ExpectPopup().Menu().
|
t.ExpectPopup().CommitMessagePanel().
|
||||||
Title(Equals("Create tag")).
|
Title(Equals("Tag name")).
|
||||||
Select(Contains("Annotated")).
|
|
||||||
Confirm()
|
|
||||||
|
|
||||||
t.ExpectPopup().Prompt().
|
|
||||||
Title(Equals("Tag name:")).
|
|
||||||
Type("new-tag").
|
Type("new-tag").
|
||||||
Confirm()
|
SwitchToDescription().
|
||||||
|
Title(Equals("Tag description")).
|
||||||
t.ExpectPopup().Prompt().
|
|
||||||
Title(Equals("Tag message:")).
|
|
||||||
Type("message").
|
Type("message").
|
||||||
|
SwitchToSummary().
|
||||||
Confirm()
|
Confirm()
|
||||||
}).
|
}).
|
||||||
Lines(
|
Lines(
|
||||||
@@ -44,6 +38,13 @@ var CrudAnnotated = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
Content(Equals("Are you sure you want to delete tag 'new-tag'?")).
|
Content(Equals("Are you sure you want to delete tag 'new-tag'?")).
|
||||||
Confirm()
|
Confirm()
|
||||||
}).
|
}).
|
||||||
IsEmpty()
|
IsEmpty().
|
||||||
|
Press(keys.Universal.New).
|
||||||
|
Tap(func() {
|
||||||
|
// confirm content is cleared on next tag create
|
||||||
|
t.ExpectPopup().CommitMessagePanel().
|
||||||
|
Title(Equals("Tag name")).
|
||||||
|
InitialText(Equals(""))
|
||||||
|
})
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -19,13 +19,8 @@ var CrudLightweight = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
IsEmpty().
|
IsEmpty().
|
||||||
Press(keys.Universal.New).
|
Press(keys.Universal.New).
|
||||||
Tap(func() {
|
Tap(func() {
|
||||||
t.ExpectPopup().Menu().
|
t.ExpectPopup().CommitMessagePanel().
|
||||||
Title(Equals("Create tag")).
|
Title(Equals("Tag name")).
|
||||||
Select(Contains("Lightweight")).
|
|
||||||
Confirm()
|
|
||||||
|
|
||||||
t.ExpectPopup().Prompt().
|
|
||||||
Title(Equals("Tag name:")).
|
|
||||||
Type("new-tag").
|
Type("new-tag").
|
||||||
Confirm()
|
Confirm()
|
||||||
}).
|
}).
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ var tests = []*components.IntegrationTest{
|
|||||||
file.RememberCommitMessageAfterFail,
|
file.RememberCommitMessageAfterFail,
|
||||||
filter_and_search.FilterCommitFiles,
|
filter_and_search.FilterCommitFiles,
|
||||||
filter_and_search.FilterFiles,
|
filter_and_search.FilterFiles,
|
||||||
|
filter_and_search.FilterFuzzy,
|
||||||
filter_and_search.FilterMenu,
|
filter_and_search.FilterMenu,
|
||||||
filter_and_search.FilterRemoteBranches,
|
filter_and_search.FilterRemoteBranches,
|
||||||
filter_and_search.NestedFilter,
|
filter_and_search.NestedFilter,
|
||||||
@@ -206,11 +207,13 @@ var tests = []*components.IntegrationTest{
|
|||||||
sync.PushWithCredentialPrompt,
|
sync.PushWithCredentialPrompt,
|
||||||
sync.RenameBranchAndPull,
|
sync.RenameBranchAndPull,
|
||||||
tag.Checkout,
|
tag.Checkout,
|
||||||
|
tag.CreateWhileCommitting,
|
||||||
tag.CrudAnnotated,
|
tag.CrudAnnotated,
|
||||||
tag.CrudLightweight,
|
tag.CrudLightweight,
|
||||||
tag.Reset,
|
tag.Reset,
|
||||||
ui.Accordion,
|
ui.Accordion,
|
||||||
ui.DoublePopup,
|
ui.DoublePopup,
|
||||||
|
ui.EmptyMenu,
|
||||||
ui.SwitchTabFromMenu,
|
ui.SwitchTabFromMenu,
|
||||||
undo.UndoCheckoutAndDrop,
|
undo.UndoCheckoutAndDrop,
|
||||||
undo.UndoDrop,
|
undo.UndoDrop,
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||||
|
)
|
||||||
|
|
||||||
|
var EmptyMenu = NewIntegrationTest(NewIntegrationTestArgs{
|
||||||
|
Description: "Verify that we don't crash on an empty menu",
|
||||||
|
ExtraCmdArgs: []string{},
|
||||||
|
Skip: false,
|
||||||
|
SetupConfig: func(config *config.AppConfig) {},
|
||||||
|
SetupRepo: func(shell *Shell) {
|
||||||
|
},
|
||||||
|
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||||
|
t.Views().Files().
|
||||||
|
IsFocused().
|
||||||
|
Press(keys.Universal.OptionMenu)
|
||||||
|
|
||||||
|
t.Views().Menu().
|
||||||
|
IsFocused().
|
||||||
|
// a string that filters everything out
|
||||||
|
FilterOrSearch("ljasldkjaslkdjalskdjalsdjaslkd").
|
||||||
|
IsEmpty().
|
||||||
|
Press(keys.Universal.Select)
|
||||||
|
|
||||||
|
// back in the files view, selecting the non-existing menu item was a no-op
|
||||||
|
t.Views().Files().
|
||||||
|
IsFocused()
|
||||||
|
},
|
||||||
|
})
|
||||||
+30
-3
@@ -164,6 +164,29 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
|||||||
scanner := bufio.NewScanner(r)
|
scanner := bufio.NewScanner(r)
|
||||||
scanner.Split(bufio.ScanLines)
|
scanner.Split(bufio.ScanLines)
|
||||||
|
|
||||||
|
lineChan := make(chan []byte)
|
||||||
|
lineWrittenChan := make(chan struct{})
|
||||||
|
|
||||||
|
// We're reading from the scanner in a separate goroutine because on windows
|
||||||
|
// if running git through a shim, we sometimes kill the parent process without
|
||||||
|
// killing its children, meaning the scanner blocks forever. This solution
|
||||||
|
// leaves us with a dead goroutine, but it's better than blocking all
|
||||||
|
// rendering to main views.
|
||||||
|
go utils.Safe(func() {
|
||||||
|
defer close(lineChan)
|
||||||
|
for scanner.Scan() {
|
||||||
|
select {
|
||||||
|
case <-opts.Stop:
|
||||||
|
return
|
||||||
|
case lineChan <- scanner.Bytes():
|
||||||
|
// We need to confirm the data has been fed into the view before we
|
||||||
|
// pull more from the scanner because the scanner uses the same backing
|
||||||
|
// array and we don't want to be mutating that while it's being written
|
||||||
|
<-lineWrittenChan
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
loaded := false
|
loaded := false
|
||||||
|
|
||||||
go utils.Safe(func() {
|
go utils.Safe(func() {
|
||||||
@@ -203,13 +226,15 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
|||||||
break outer
|
break outer
|
||||||
case linesToRead := <-self.readLines:
|
case linesToRead := <-self.readLines:
|
||||||
for i := 0; i < linesToRead.Total; i++ {
|
for i := 0; i < linesToRead.Total; i++ {
|
||||||
|
var ok bool
|
||||||
|
var line []byte
|
||||||
select {
|
select {
|
||||||
case <-opts.Stop:
|
case <-opts.Stop:
|
||||||
break outer
|
break outer
|
||||||
default:
|
case line, ok = <-lineChan:
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
ok := scanner.Scan()
|
|
||||||
loadingMutex.Lock()
|
loadingMutex.Lock()
|
||||||
if !loaded {
|
if !loaded {
|
||||||
self.beforeStart()
|
self.beforeStart()
|
||||||
@@ -226,7 +251,8 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
|||||||
self.onEndOfInput()
|
self.onEndOfInput()
|
||||||
break outer
|
break outer
|
||||||
}
|
}
|
||||||
writeToView(append(scanner.Bytes(), '\n'))
|
writeToView(append(line, '\n'))
|
||||||
|
lineWrittenChan <- struct{}{}
|
||||||
|
|
||||||
if i+1 == linesToRead.InitialRefreshAfter {
|
if i+1 == linesToRead.InitialRefreshAfter {
|
||||||
// We have read enough lines to fill the view, so do a first refresh
|
// We have read enough lines to fill the view, so do a first refresh
|
||||||
@@ -253,6 +279,7 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
|||||||
onDone()
|
onDone()
|
||||||
|
|
||||||
close(done)
|
close(done)
|
||||||
|
close(lineWrittenChan)
|
||||||
})
|
})
|
||||||
|
|
||||||
self.readLines <- linesToRead
|
self.readLines <- linesToRead
|
||||||
|
|||||||
+42
-4
@@ -1265,14 +1265,52 @@ func lineWrap(line []cell, columns int) [][]cell {
|
|||||||
|
|
||||||
var n int
|
var n int
|
||||||
var offset int
|
var offset int
|
||||||
|
lastWhitespaceIndex := -1
|
||||||
lines := make([][]cell, 0, 1)
|
lines := make([][]cell, 0, 1)
|
||||||
for i := range line {
|
for i := range line {
|
||||||
rw := runewidth.RuneWidth(line[i].chr)
|
currChr := line[i].chr
|
||||||
|
rw := runewidth.RuneWidth(currChr)
|
||||||
n += rw
|
n += rw
|
||||||
|
// if currChr == 'g' {
|
||||||
|
// panic(n)
|
||||||
|
// }
|
||||||
if n > columns {
|
if n > columns {
|
||||||
n = rw
|
// This code is convoluted but we've got comprehensive tests so feel free to do whatever you want
|
||||||
lines = append(lines, line[offset:i])
|
// to the code to simplify it so long as our tests still pass.
|
||||||
offset = i
|
if currChr == ' ' {
|
||||||
|
// if the line ends in a space, we'll omit it. This means there'll be no
|
||||||
|
// way to distinguish between a clean break and a mid-word break, but
|
||||||
|
// I think it's worth it.
|
||||||
|
lines = append(lines, line[offset:i])
|
||||||
|
offset = i + 1
|
||||||
|
n = 0
|
||||||
|
} else if currChr == '-' {
|
||||||
|
// if the last character is hyphen and the width of line is equal to the columns
|
||||||
|
lines = append(lines, line[offset:i])
|
||||||
|
offset = i
|
||||||
|
n = rw
|
||||||
|
} else if lastWhitespaceIndex != -1 && lastWhitespaceIndex+1 != i {
|
||||||
|
// if there is a space in the line and the line is not breaking at a space/hyphen
|
||||||
|
if line[lastWhitespaceIndex].chr == '-' {
|
||||||
|
// if break occurs at hyphen, we'll retain the hyphen
|
||||||
|
lines = append(lines, line[offset:lastWhitespaceIndex+1])
|
||||||
|
offset = lastWhitespaceIndex + 1
|
||||||
|
n = i - offset
|
||||||
|
} else {
|
||||||
|
// if break occurs at space, we'll omit the space
|
||||||
|
lines = append(lines, line[offset:lastWhitespaceIndex])
|
||||||
|
offset = lastWhitespaceIndex + 1
|
||||||
|
n = i - offset + 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// in this case we're breaking mid-word
|
||||||
|
lines = append(lines, line[offset:i])
|
||||||
|
offset = i
|
||||||
|
n = rw
|
||||||
|
}
|
||||||
|
lastWhitespaceIndex = -1
|
||||||
|
} else if line[i].chr == ' ' || line[i].chr == '-' {
|
||||||
|
lastWhitespaceIndex = i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
@@ -172,7 +172,7 @@ github.com/jesseduffield/go-git/v5/utils/merkletrie/filesystem
|
|||||||
github.com/jesseduffield/go-git/v5/utils/merkletrie/index
|
github.com/jesseduffield/go-git/v5/utils/merkletrie/index
|
||||||
github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame
|
github.com/jesseduffield/go-git/v5/utils/merkletrie/internal/frame
|
||||||
github.com/jesseduffield/go-git/v5/utils/merkletrie/noder
|
github.com/jesseduffield/go-git/v5/utils/merkletrie/noder
|
||||||
# github.com/jesseduffield/gocui v0.3.1-0.20230719120401-398f4965241f
|
# github.com/jesseduffield/gocui v0.3.1-0.20230723014157-03e858e46144
|
||||||
## explicit; go 1.12
|
## explicit; go 1.12
|
||||||
github.com/jesseduffield/gocui
|
github.com/jesseduffield/gocui
|
||||||
# github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
# github.com/jesseduffield/kill v0.0.0-20220618033138-bfbe04675d10
|
||||||
|
|||||||
Reference in New Issue
Block a user