Compare commits
1 Commits
v0.42.0
...
upstream-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
179f69e034 |
4
.github/workflows/cd.yml
vendored
4
.github/workflows/cd.yml
vendored
@@ -3,7 +3,7 @@ name: Continuous Delivery
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.22.x
|
||||
go-version: 1.21.x
|
||||
- name: Run goreleaser
|
||||
uses: goreleaser/goreleaser-action@v4
|
||||
with:
|
||||
|
||||
18
.github/workflows/ci.yml
vendored
18
.github/workflows/ci.yml
vendored
@@ -1,7 +1,7 @@
|
||||
name: Continuous Integration
|
||||
|
||||
env:
|
||||
GO_VERSION: 1.22
|
||||
GO_VERSION: 1.21
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.22.x
|
||||
go-version: 1.21.x
|
||||
- name: Test code
|
||||
# we're passing -short so that we skip the integration tests, which will be run in parallel below
|
||||
run: |
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.22.x
|
||||
go-version: 1.21.x
|
||||
- name: Print git version
|
||||
run: git --version
|
||||
- name: Test code
|
||||
@@ -115,7 +115,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.22.x
|
||||
go-version: 1.21.x
|
||||
- name: Build linux binary
|
||||
run: |
|
||||
GOOS=linux go build
|
||||
@@ -142,7 +142,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.22.x
|
||||
go-version: 1.21.x
|
||||
- name: Check Vendor Directory
|
||||
# ensure our vendor directory matches up with our go modules
|
||||
run: |
|
||||
@@ -168,7 +168,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.22.x
|
||||
go-version: 1.21.x
|
||||
- name: Lint
|
||||
uses: golangci/golangci-lint-action@v3.7.0
|
||||
with:
|
||||
@@ -188,17 +188,11 @@ jobs:
|
||||
upload-coverage:
|
||||
# List all jobs that produce coverage files
|
||||
needs: [unit-tests, integration-tests]
|
||||
if: github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: 1.22.x
|
||||
|
||||
- name: Download all coverage artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
|
||||
@@ -30,5 +30,5 @@ linters-settings:
|
||||
max-func-lines: 0
|
||||
|
||||
run:
|
||||
go: '1.22'
|
||||
go: '1.21'
|
||||
timeout: 10m
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# docker build -t lazygit .
|
||||
# docker run -it lazygit:latest /bin/sh
|
||||
|
||||
FROM golang:1.22 as build
|
||||
FROM golang:1.21 as build
|
||||
WORKDIR /go/src/github.com/jesseduffield/lazygit/
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
@@ -80,7 +80,6 @@ gui:
|
||||
showIcons: false # deprecated: use nerdFontsVersion instead
|
||||
nerdFontsVersion: "" # nerd fonts version to use ("2" or "3"); empty means don't show nerd font icons
|
||||
showFileIcons: true # for hiding file icons in the file views
|
||||
commitHashLength: 8 # length of commit hash in commits view. 0 shows '*' if NF icons aren't enabled
|
||||
commandLogSize: 8
|
||||
splitDiff: 'auto' # one of 'auto' | 'always'
|
||||
skipRewordInEditorWarning: false # for skipping the confirmation before launching the reword editor
|
||||
@@ -549,15 +548,6 @@ Example:
|
||||
- Branch name: feature/AB-123
|
||||
- Commit message: [AB-123] Adding feature
|
||||
|
||||
```yaml
|
||||
git:
|
||||
commitPrefix:
|
||||
pattern: "^\\w+\\/(\\w+-\\w+).*"
|
||||
replace: '[$1] '
|
||||
```
|
||||
|
||||
If you want repository-specific prefixes, you can map them with `commitPrefixes`. If you have both `commitPrefixes` defined and an entry in `commitPrefixes` for the current repo, the `commitPrefixes` entry is given higher precedence. Repository folder names must be an exact match.
|
||||
|
||||
```yaml
|
||||
git:
|
||||
commitPrefixes:
|
||||
|
||||
@@ -305,7 +305,7 @@ SelectedWorktree
|
||||
CheckedOutBranch
|
||||
```
|
||||
|
||||
To see what fields are available on e.g. the `SelectedFile`, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/gui/services/custom_commands/models.go) (all the modelling lives in the same file).
|
||||
To see what fields are available on e.g. the `SelectedFile`, see [here](https://github.com/jesseduffield/lazygit/blob/master/pkg/commands/models/file.go) (all the modelling lives in the same directory). Note that the custom commands feature does not guarantee backwards compatibility (until we hit Lazygit version 1.0 of course) which means a field you're accessing on an object may no longer be available from one release to the next. Typically however, all you'll need is `{{.SelectedFile.Name}}`, `{{.SelectedLocalCommit.Hash}}` and `{{.SelectedLocalBranch.Name}}`. In the future we will likely introduce a tighter interface that exposes a limited set of fields for each model.
|
||||
|
||||
## Keybinding collisions
|
||||
|
||||
|
||||
@@ -13,6 +13,6 @@ includes interactive rebases, so for example amending a commit in the first
|
||||
branch of the stack will "just work" in the sense that it keeps the other
|
||||
branches properly stacked onto it.
|
||||
|
||||
Lazygit visualizes the individual branch heads in the stack by marking them with a
|
||||
Lazygit visualizes the invidual branch heads in the stack by marking them with a
|
||||
cyan asterisk (or a cyan branch symbol if you are using [nerd
|
||||
fonts](Config.md#display-nerd-fonts-icons)).
|
||||
|
||||
2
go.mod
2
go.mod
@@ -1,6 +1,6 @@
|
||||
module github.com/jesseduffield/lazygit
|
||||
|
||||
go 1.22
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/adrg/xdg v0.4.0
|
||||
|
||||
@@ -134,7 +134,7 @@ func NewGitCommandAux(
|
||||
worktreeCommands := git_commands.NewWorktreeCommands(gitCommon)
|
||||
blameCommands := git_commands.NewBlameCommands(gitCommon)
|
||||
|
||||
branchLoader := git_commands.NewBranchLoader(cmn, gitCommon, cmd, branchCommands.CurrentBranchInfo, configCommands)
|
||||
branchLoader := git_commands.NewBranchLoader(cmn, cmd, branchCommands.CurrentBranchInfo, configCommands)
|
||||
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
|
||||
commitLoader := git_commands.NewCommitLoader(cmn, cmd, statusCommands.RebaseMode, gitCommon)
|
||||
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
|
||||
|
||||
@@ -40,7 +40,6 @@ type BranchInfo struct {
|
||||
// BranchLoader returns a list of Branch objects for the current repo
|
||||
type BranchLoader struct {
|
||||
*common.Common
|
||||
*GitCommon
|
||||
cmd oscommands.ICmdObjBuilder
|
||||
getCurrentBranchInfo func() (BranchInfo, error)
|
||||
config BranchLoaderConfigCommands
|
||||
@@ -48,14 +47,12 @@ type BranchLoader struct {
|
||||
|
||||
func NewBranchLoader(
|
||||
cmn *common.Common,
|
||||
gitCommon *GitCommon,
|
||||
cmd oscommands.ICmdObjBuilder,
|
||||
getCurrentBranchInfo func() (BranchInfo, error),
|
||||
config BranchLoaderConfigCommands,
|
||||
) *BranchLoader {
|
||||
return &BranchLoader{
|
||||
Common: cmn,
|
||||
GitCommon: gitCommon,
|
||||
cmd: cmd,
|
||||
getCurrentBranchInfo: getCurrentBranchInfo,
|
||||
config: config,
|
||||
@@ -64,7 +61,7 @@ func NewBranchLoader(
|
||||
|
||||
// Load the list of branches for the current repo
|
||||
func (self *BranchLoader) Load(reflogCommits []*models.Commit) ([]*models.Branch, error) {
|
||||
branches := self.obtainBranches(self.version.IsAtLeast(2, 22, 0))
|
||||
branches := self.obtainBranches()
|
||||
|
||||
if self.AppState.LocalBranchSortOrder == "recency" {
|
||||
reflogBranches := self.obtainReflogBranches(reflogCommits)
|
||||
@@ -127,7 +124,7 @@ func (self *BranchLoader) Load(reflogCommits []*models.Commit) ([]*models.Branch
|
||||
return branches, nil
|
||||
}
|
||||
|
||||
func (self *BranchLoader) obtainBranches(canUsePushTrack bool) []*models.Branch {
|
||||
func (self *BranchLoader) obtainBranches() []*models.Branch {
|
||||
output, err := self.getRawBranches()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -150,7 +147,7 @@ func (self *BranchLoader) obtainBranches(canUsePushTrack bool) []*models.Branch
|
||||
}
|
||||
|
||||
storeCommitDateAsRecency := self.AppState.LocalBranchSortOrder != "recency"
|
||||
return obtainBranch(split, storeCommitDateAsRecency, canUsePushTrack), true
|
||||
return obtainBranch(split, storeCommitDateAsRecency), true
|
||||
})
|
||||
}
|
||||
|
||||
@@ -186,31 +183,23 @@ var branchFields = []string{
|
||||
"refname:short",
|
||||
"upstream:short",
|
||||
"upstream:track",
|
||||
"push:track",
|
||||
"subject",
|
||||
"objectname",
|
||||
"committerdate:unix",
|
||||
}
|
||||
|
||||
// Obtain branch information from parsed line output of getRawBranches()
|
||||
func obtainBranch(split []string, storeCommitDateAsRecency bool, canUsePushTrack bool) *models.Branch {
|
||||
func obtainBranch(split []string, storeCommitDateAsRecency bool) *models.Branch {
|
||||
headMarker := split[0]
|
||||
fullName := split[1]
|
||||
upstreamName := split[2]
|
||||
track := split[3]
|
||||
pushTrack := split[4]
|
||||
subject := split[5]
|
||||
commitHash := split[6]
|
||||
commitDate := split[7]
|
||||
subject := split[4]
|
||||
commitHash := split[5]
|
||||
commitDate := split[6]
|
||||
|
||||
name := strings.TrimPrefix(fullName, "heads/")
|
||||
aheadForPull, behindForPull, gone := parseUpstreamInfo(upstreamName, track)
|
||||
var aheadForPush, behindForPush string
|
||||
if canUsePushTrack {
|
||||
aheadForPush, behindForPush, _ = parseUpstreamInfo(upstreamName, pushTrack)
|
||||
} else {
|
||||
aheadForPush, behindForPush = aheadForPull, behindForPull
|
||||
}
|
||||
pushables, pullables, gone := parseUpstreamInfo(upstreamName, track)
|
||||
|
||||
recency := ""
|
||||
if storeCommitDateAsRecency {
|
||||
@@ -220,16 +209,14 @@ func obtainBranch(split []string, storeCommitDateAsRecency bool, canUsePushTrack
|
||||
}
|
||||
|
||||
return &models.Branch{
|
||||
Name: name,
|
||||
Recency: recency,
|
||||
AheadForPull: aheadForPull,
|
||||
BehindForPull: behindForPull,
|
||||
AheadForPush: aheadForPush,
|
||||
BehindForPush: behindForPush,
|
||||
UpstreamGone: gone,
|
||||
Head: headMarker == "*",
|
||||
Subject: subject,
|
||||
CommitHash: commitHash,
|
||||
Name: name,
|
||||
Recency: recency,
|
||||
Pushables: pushables,
|
||||
Pullables: pullables,
|
||||
UpstreamGone: gone,
|
||||
Head: headMarker == "*",
|
||||
Subject: subject,
|
||||
CommitHash: commitHash,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,10 +232,10 @@ func parseUpstreamInfo(upstreamName string, track string) (string, string, bool)
|
||||
return "?", "?", true
|
||||
}
|
||||
|
||||
ahead := parseDifference(track, `ahead (\d+)`)
|
||||
behind := parseDifference(track, `behind (\d+)`)
|
||||
pushables := parseDifference(track, `ahead (\d+)`)
|
||||
pullables := parseDifference(track, `behind (\d+)`)
|
||||
|
||||
return ahead, behind, false
|
||||
return pushables, pullables, false
|
||||
}
|
||||
|
||||
func parseDifference(track string, regexStr string) string {
|
||||
|
||||
@@ -25,101 +25,89 @@ func TestObtainBranch(t *testing.T) {
|
||||
scenarios := []scenario{
|
||||
{
|
||||
testName: "TrimHeads",
|
||||
input: []string{"", "heads/a_branch", "", "", "", "subject", "123", timeStamp},
|
||||
input: []string{"", "heads/a_branch", "", "", "subject", "123", timeStamp},
|
||||
storeCommitDateAsRecency: false,
|
||||
expectedBranch: &models.Branch{
|
||||
Name: "a_branch",
|
||||
AheadForPull: "?",
|
||||
BehindForPull: "?",
|
||||
AheadForPush: "?",
|
||||
BehindForPush: "?",
|
||||
Head: false,
|
||||
Subject: "subject",
|
||||
CommitHash: "123",
|
||||
Name: "a_branch",
|
||||
Pushables: "?",
|
||||
Pullables: "?",
|
||||
Head: false,
|
||||
Subject: "subject",
|
||||
CommitHash: "123",
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "NoUpstream",
|
||||
input: []string{"", "a_branch", "", "", "", "subject", "123", timeStamp},
|
||||
input: []string{"", "a_branch", "", "", "subject", "123", timeStamp},
|
||||
storeCommitDateAsRecency: false,
|
||||
expectedBranch: &models.Branch{
|
||||
Name: "a_branch",
|
||||
AheadForPull: "?",
|
||||
BehindForPull: "?",
|
||||
AheadForPush: "?",
|
||||
BehindForPush: "?",
|
||||
Head: false,
|
||||
Subject: "subject",
|
||||
CommitHash: "123",
|
||||
Name: "a_branch",
|
||||
Pushables: "?",
|
||||
Pullables: "?",
|
||||
Head: false,
|
||||
Subject: "subject",
|
||||
CommitHash: "123",
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "IsHead",
|
||||
input: []string{"*", "a_branch", "", "", "", "subject", "123", timeStamp},
|
||||
input: []string{"*", "a_branch", "", "", "subject", "123", timeStamp},
|
||||
storeCommitDateAsRecency: false,
|
||||
expectedBranch: &models.Branch{
|
||||
Name: "a_branch",
|
||||
AheadForPull: "?",
|
||||
BehindForPull: "?",
|
||||
AheadForPush: "?",
|
||||
BehindForPush: "?",
|
||||
Head: true,
|
||||
Subject: "subject",
|
||||
CommitHash: "123",
|
||||
Name: "a_branch",
|
||||
Pushables: "?",
|
||||
Pullables: "?",
|
||||
Head: true,
|
||||
Subject: "subject",
|
||||
CommitHash: "123",
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "IsBehindAndAhead",
|
||||
input: []string{"", "a_branch", "a_remote/a_branch", "[behind 2, ahead 3]", "[behind 2, ahead 3]", "subject", "123", timeStamp},
|
||||
input: []string{"", "a_branch", "a_remote/a_branch", "[behind 2, ahead 3]", "subject", "123", timeStamp},
|
||||
storeCommitDateAsRecency: false,
|
||||
expectedBranch: &models.Branch{
|
||||
Name: "a_branch",
|
||||
AheadForPull: "3",
|
||||
BehindForPull: "2",
|
||||
AheadForPush: "3",
|
||||
BehindForPush: "2",
|
||||
Head: false,
|
||||
Subject: "subject",
|
||||
CommitHash: "123",
|
||||
Name: "a_branch",
|
||||
Pushables: "3",
|
||||
Pullables: "2",
|
||||
Head: false,
|
||||
Subject: "subject",
|
||||
CommitHash: "123",
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "RemoteBranchIsGone",
|
||||
input: []string{"", "a_branch", "a_remote/a_branch", "[gone]", "[gone]", "subject", "123", timeStamp},
|
||||
input: []string{"", "a_branch", "a_remote/a_branch", "[gone]", "subject", "123", timeStamp},
|
||||
storeCommitDateAsRecency: false,
|
||||
expectedBranch: &models.Branch{
|
||||
Name: "a_branch",
|
||||
UpstreamGone: true,
|
||||
AheadForPull: "?",
|
||||
BehindForPull: "?",
|
||||
AheadForPush: "?",
|
||||
BehindForPush: "?",
|
||||
Head: false,
|
||||
Subject: "subject",
|
||||
CommitHash: "123",
|
||||
Name: "a_branch",
|
||||
UpstreamGone: true,
|
||||
Pushables: "?",
|
||||
Pullables: "?",
|
||||
Head: false,
|
||||
Subject: "subject",
|
||||
CommitHash: "123",
|
||||
},
|
||||
},
|
||||
{
|
||||
testName: "WithCommitDateAsRecency",
|
||||
input: []string{"", "a_branch", "", "", "", "subject", "123", timeStamp},
|
||||
input: []string{"", "a_branch", "", "", "subject", "123", timeStamp},
|
||||
storeCommitDateAsRecency: true,
|
||||
expectedBranch: &models.Branch{
|
||||
Name: "a_branch",
|
||||
Recency: "2h",
|
||||
AheadForPull: "?",
|
||||
BehindForPull: "?",
|
||||
AheadForPush: "?",
|
||||
BehindForPush: "?",
|
||||
Head: false,
|
||||
Subject: "subject",
|
||||
CommitHash: "123",
|
||||
Name: "a_branch",
|
||||
Recency: "2h",
|
||||
Pushables: "?",
|
||||
Pullables: "?",
|
||||
Head: false,
|
||||
Subject: "subject",
|
||||
CommitHash: "123",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
branch := obtainBranch(s.input, s.storeCommitDateAsRecency, true)
|
||||
branch := obtainBranch(s.input, s.storeCommitDateAsRecency)
|
||||
assert.EqualValues(t, s.expectedBranch, branch)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ func TestBranchGetCommitDifferences(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildBranchCommands(commonDeps{runner: s.runner})
|
||||
pushables, pullables := instance.GetCommitDifferences("HEAD", "@{u}")
|
||||
@@ -88,6 +89,7 @@ func TestBranchDeleteBranch(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildBranchCommands(commonDeps{runner: s.runner})
|
||||
|
||||
@@ -148,6 +150,7 @@ func TestBranchMerge(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs(s.expected, "", nil)
|
||||
@@ -187,6 +190,7 @@ func TestBranchCheckout(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildBranchCommands(commonDeps{runner: s.runner})
|
||||
s.test(instance.Checkout("test", CheckoutOptions{Force: s.force}))
|
||||
@@ -275,6 +279,7 @@ func TestBranchCurrentBranchInfo(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildBranchCommands(commonDeps{runner: s.runner})
|
||||
s.test(instance.CurrentBranchInfo())
|
||||
|
||||
@@ -211,11 +211,11 @@ func (self *CommitLoader) extractCommitFromLine(line string, showDivergence bool
|
||||
authorEmail := split[3]
|
||||
extraInfo := strings.TrimSpace(split[4])
|
||||
parentHashes := split[5]
|
||||
message := split[6]
|
||||
divergence := models.DivergenceNone
|
||||
if showDivergence {
|
||||
divergence = lo.Ternary(split[6] == "<", models.DivergenceLeft, models.DivergenceRight)
|
||||
divergence = lo.Ternary(split[7] == "<", models.DivergenceLeft, models.DivergenceRight)
|
||||
}
|
||||
message := split[7]
|
||||
|
||||
tags := []string{}
|
||||
|
||||
@@ -505,6 +505,8 @@ func (self *CommitLoader) getExistingMainBranches() []string {
|
||||
|
||||
for i, branchName := range mainBranches {
|
||||
wg.Add(1)
|
||||
i := i
|
||||
branchName := branchName
|
||||
go utils.Safe(func() {
|
||||
defer wg.Done()
|
||||
|
||||
@@ -603,4 +605,4 @@ func (self *CommitLoader) getLogCmd(opts GetCommitsOptions) oscommands.ICmdObj {
|
||||
return self.cmd.New(cmdArgs).DontLog()
|
||||
}
|
||||
|
||||
const prettyFormat = `--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s`
|
||||
const prettyFormat = `--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m`
|
||||
|
||||
@@ -15,16 +15,16 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var commitsOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|>|better typing for rebase mode
|
||||
b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164|1640824515|Jesse Duffield|jessedduffield@gmail.com|origin/better-tests|e94e8fc5b6fab4cb755f|>|fix logging
|
||||
e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c|1640823749|Jesse Duffield|jessedduffield@gmail.com|tag: 123, tag: 456|d8084cd558925eb7c9c3|>|refactor
|
||||
d8084cd558925eb7c9c38afeed5725c21653ab90|1640821426|Jesse Duffield|jessedduffield@gmail.com||65f910ebd85283b5cce9|>|WIP
|
||||
65f910ebd85283b5cce9bf67d03d3f1a9ea3813a|1640821275|Jesse Duffield|jessedduffield@gmail.com||26c07b1ab33860a1a759|>|WIP
|
||||
26c07b1ab33860a1a7591a0638f9925ccf497ffa|1640750752|Jesse Duffield|jessedduffield@gmail.com||3d4470a6c072208722e5|>|WIP
|
||||
3d4470a6c072208722e5ae9a54bcb9634959a1c5|1640748818|Jesse Duffield|jessedduffield@gmail.com||053a66a7be3da43aacdc|>|WIP
|
||||
053a66a7be3da43aacdc7aa78e1fe757b82c4dd2|1640739815|Jesse Duffield|jessedduffield@gmail.com||985fe482e806b172aea4|>|refactoring the config struct`, "|", "\x00", -1)
|
||||
var commitsOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|better typing for rebase mode
|
||||
b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164|1640824515|Jesse Duffield|jessedduffield@gmail.com|origin/better-tests|e94e8fc5b6fab4cb755f|fix logging
|
||||
e94e8fc5b6fab4cb755f29f1bdb3ee5e001df35c|1640823749|Jesse Duffield|jessedduffield@gmail.com|tag: 123, tag: 456|d8084cd558925eb7c9c3|refactor
|
||||
d8084cd558925eb7c9c38afeed5725c21653ab90|1640821426|Jesse Duffield|jessedduffield@gmail.com||65f910ebd85283b5cce9|WIP
|
||||
65f910ebd85283b5cce9bf67d03d3f1a9ea3813a|1640821275|Jesse Duffield|jessedduffield@gmail.com||26c07b1ab33860a1a759|WIP
|
||||
26c07b1ab33860a1a7591a0638f9925ccf497ffa|1640750752|Jesse Duffield|jessedduffield@gmail.com||3d4470a6c072208722e5|WIP
|
||||
3d4470a6c072208722e5ae9a54bcb9634959a1c5|1640748818|Jesse Duffield|jessedduffield@gmail.com||053a66a7be3da43aacdc|WIP
|
||||
053a66a7be3da43aacdc7aa78e1fe757b82c4dd2|1640739815|Jesse Duffield|jessedduffield@gmail.com||985fe482e806b172aea4|refactoring the config struct`, "|", "\x00", -1)
|
||||
|
||||
var singleCommitOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|>|better typing for rebase mode`, "|", "\x00", -1)
|
||||
var singleCommitOutput = strings.Replace(`0eea75e8c631fba6b58135697835d58ba4c18dbc|1640826609|Jesse Duffield|jessedduffield@gmail.com|HEAD -> better-tests|b21997d6b4cbdf84b149|better typing for rebase mode`, "|", "\x00", -1)
|
||||
|
||||
func TestGetCommits(t *testing.T) {
|
||||
type scenario struct {
|
||||
@@ -46,7 +46,7 @@ func TestGetCommits(t *testing.T) {
|
||||
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{},
|
||||
expectedError: nil,
|
||||
@@ -58,7 +58,7 @@ func TestGetCommits(t *testing.T) {
|
||||
opts: GetCommitsOptions{RefName: "refs/heads/mybranch", RefForPushedStatus: "refs/heads/mybranch", IncludeRebaseCommits: false},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"merge-base", "refs/heads/mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"log", "refs/heads/mybranch", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
|
||||
ExpectGitArgs([]string{"log", "refs/heads/mybranch", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{},
|
||||
expectedError: nil,
|
||||
@@ -73,7 +73,7 @@ func TestGetCommits(t *testing.T) {
|
||||
// here it's seeing which commits are yet to be pushed
|
||||
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
// here it's actually getting all the commits in a formatted form, one per line
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, commitsOutput, nil).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, commitsOutput, nil).
|
||||
// here it's testing which of the configured main branches have an upstream
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil). // this one does
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")). // this one doesn't, so it checks origin instead
|
||||
@@ -210,7 +210,7 @@ func TestGetCommits(t *testing.T) {
|
||||
// here it's seeing which commits are yet to be pushed
|
||||
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
// here it's actually getting all the commits in a formatted form, one per line
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
|
||||
// here it's testing which of the configured main branches exist; neither does
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "", errors.New("error")).
|
||||
ExpectGitArgs([]string{"rev-parse", "--verify", "--quiet", "refs/remotes/origin/master"}, "", errors.New("error")).
|
||||
@@ -247,7 +247,7 @@ func TestGetCommits(t *testing.T) {
|
||||
// here it's seeing which commits are yet to be pushed
|
||||
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
// here it's actually getting all the commits in a formatted form, one per line
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--topo-order", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, singleCommitOutput, nil).
|
||||
// here it's testing which of the configured main branches exist
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "master@{u}"}, "refs/remotes/origin/master", nil).
|
||||
ExpectGitArgs([]string{"rev-parse", "--symbolic-full-name", "main@{u}"}, "", errors.New("error")).
|
||||
@@ -283,7 +283,7 @@ func TestGetCommits(t *testing.T) {
|
||||
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", IncludeRebaseCommits: false},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--no-show-signature", "--"}, "", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{},
|
||||
expectedError: nil,
|
||||
@@ -295,7 +295,7 @@ func TestGetCommits(t *testing.T) {
|
||||
opts: GetCommitsOptions{RefName: "HEAD", RefForPushedStatus: "mybranch", FilterPath: "src"},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs([]string{"merge-base", "mybranch", "mybranch@{u}"}, "b21997d6b4cbdf84b149d8e6a2c4d06a8e9ec164", nil).
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%m%x00%s", "--abbrev=40", "--follow", "--no-show-signature", "--", "src"}, "", nil),
|
||||
ExpectGitArgs([]string{"log", "HEAD", "--oneline", "--pretty=format:%H%x00%at%x00%aN%x00%ae%x00%D%x00%p%x00%s%x00%m", "--abbrev=40", "--follow", "--no-show-signature", "--", "src"}, "", nil),
|
||||
|
||||
expectedCommits: []*models.Commit{},
|
||||
expectedError: nil,
|
||||
@@ -303,6 +303,7 @@ func TestGetCommits(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
scenario := scenario
|
||||
t.Run(scenario.testName, func(t *testing.T) {
|
||||
common := utils.NewDummyCommon()
|
||||
common.AppState = &config.AppState{}
|
||||
|
||||
@@ -30,6 +30,7 @@ func TestCommitRewordCommit(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildCommitCommands(commonDeps{runner: s.runner})
|
||||
|
||||
@@ -99,6 +100,7 @@ func TestCommitCommitCmdObj(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
userConfig := config.GetDefaultConfig()
|
||||
userConfig.Git.Commit.SignOff = s.configSignoff
|
||||
@@ -134,6 +136,7 @@ func TestCommitCommitEditorCmdObj(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
userConfig := config.GetDefaultConfig()
|
||||
userConfig.Git.Commit.SignOff = s.configSignoff
|
||||
@@ -168,6 +171,7 @@ func TestCommitCreateFixupCommit(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildCommitCommands(commonDeps{runner: s.runner})
|
||||
s.test(instance.CreateFixupCommit(s.hash))
|
||||
@@ -217,6 +221,7 @@ func TestCommitCreateAmendCommit(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildCommitCommands(commonDeps{runner: s.runner})
|
||||
err := instance.CreateAmendCommit(s.originalSubject, s.newSubject, s.newDescription, s.includeFileChanges)
|
||||
@@ -280,6 +285,7 @@ func TestCommitShowCmdObj(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
userConfig := config.GetDefaultConfig()
|
||||
userConfig.Git.Paging.ExternalDiffCommand = s.extDiffCmd
|
||||
@@ -328,6 +334,7 @@ func TestGetCommitMsg(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildCommitCommands(commonDeps{
|
||||
runner: oscommands.NewFakeRunner(t).ExpectGitArgs([]string{"-c", "log.showsignature=false", "log", "--format=%B", "--max-count=1", "deadbeef"}, s.input, nil),
|
||||
@@ -367,6 +374,7 @@ func TestGetCommitMessageFromHistory(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildCommitCommands(commonDeps{runner: s.runner})
|
||||
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
package git_commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
)
|
||||
import "github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
|
||||
type DiffCommands struct {
|
||||
*GitCommon
|
||||
@@ -17,16 +13,10 @@ func NewDiffCommands(gitCommon *GitCommon) *DiffCommands {
|
||||
}
|
||||
|
||||
func (self *DiffCommands) DiffCmdObj(diffArgs []string) oscommands.ICmdObj {
|
||||
extDiffCmd := self.UserConfig.Git.Paging.ExternalDiffCommand
|
||||
useExtDiff := extDiffCmd != ""
|
||||
|
||||
return self.cmd.New(
|
||||
NewGitCmd("diff").
|
||||
Config("diff.noprefix=false").
|
||||
ConfigIf(useExtDiff, "diff.external="+extDiffCmd).
|
||||
ArgIfElse(useExtDiff, "--ext-diff", "--no-ext-diff").
|
||||
Arg("--submodule").
|
||||
Arg(fmt.Sprintf("--color=%s", self.UserConfig.Git.Paging.ColorArg)).
|
||||
Arg("--submodule", "--no-ext-diff", "--color").
|
||||
Arg(diffArgs...).
|
||||
Dir(self.repoPaths.worktreePath).
|
||||
ToArgv(),
|
||||
|
||||
@@ -172,6 +172,7 @@ func TestFileGetStatusFiles(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
cmd := oscommands.NewDummyCmdObjBuilder(s.runner)
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ func TestStartCmdObj(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildFlowCommands(commonDeps{})
|
||||
|
||||
@@ -68,6 +69,7 @@ func TestFinishCmdObj(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildFlowCommands(commonDeps{
|
||||
gitConfig: git_config.NewFakeGitConfig(s.gitConfigMockResponses),
|
||||
|
||||
@@ -67,6 +67,7 @@ func TestRebaseRebaseBranch(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildRebaseCommands(commonDeps{runner: s.runner, gitVersion: s.gitVersion})
|
||||
s.test(instance.RebaseBranch(s.arg))
|
||||
@@ -88,6 +89,7 @@ func TestRebaseSkipEditorCommand(t *testing.T) {
|
||||
`^GIT_SEQUENCE_EDITOR=.*$`,
|
||||
"^" + daemon.DaemonKindEnvKey + "=" + strconv.Itoa(int(daemon.DaemonKindExitImmediately)) + "$",
|
||||
} {
|
||||
regexStr := regexStr
|
||||
foundMatch := lo.ContainsBy(envVars, func(envVar string) bool {
|
||||
return regexp.MustCompile(regexStr).MatchString(envVar)
|
||||
})
|
||||
@@ -161,6 +163,7 @@ func TestRebaseDiscardOldFileChanges(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildRebaseCommands(commonDeps{
|
||||
runner: s.runner,
|
||||
|
||||
@@ -176,6 +176,7 @@ func TestGetReflogCommits(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
scenario := scenario
|
||||
t.Run(scenario.testName, func(t *testing.T) {
|
||||
builder := &ReflogCommitLoader{
|
||||
Common: utils.NewDummyCommon(),
|
||||
|
||||
@@ -101,6 +101,7 @@ func TestGetRepoPaths(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.Name, func(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t)
|
||||
cmd := oscommands.NewDummyCmdObjBuilder(runner)
|
||||
|
||||
@@ -121,22 +121,9 @@ func (self *StashCommands) StashUnstagedChanges(message string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SaveStagedChanges stashes only the currently staged changes.
|
||||
// SaveStagedChanges stashes only the currently staged changes. This takes a few steps
|
||||
// shoutouts to Joe on https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible
|
||||
func (self *StashCommands) SaveStagedChanges(message string) error {
|
||||
if self.version.IsAtLeast(2, 35, 0) {
|
||||
return self.cmd.New(NewGitCmd("stash").Arg("push").Arg("--staged").Arg("-m", message).ToArgv()).Run()
|
||||
}
|
||||
|
||||
// Git versions older than 2.35.0 don't support the --staged flag, so we
|
||||
// need to fall back to a more complex solution.
|
||||
// Shoutouts to Joe on https://stackoverflow.com/questions/14759748/stashing-only-staged-changes-in-git-is-it-possible
|
||||
//
|
||||
// Note that this method has a few bugs:
|
||||
// - it fails when there are *only* staged changes
|
||||
// - it fails when staged and unstaged changes within a single file are too close together
|
||||
// We don't bother fixing these, because users can simply update git when
|
||||
// they are affected by these issues.
|
||||
|
||||
// wrap in 'writing', which uses a mutex
|
||||
if err := self.cmd.New(
|
||||
NewGitCmd("stash").Arg("--keep-index").ToArgv(),
|
||||
|
||||
@@ -47,6 +47,7 @@ func TestGetStashEntries(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
cmd := oscommands.NewDummyCmdObjBuilder(s.runner)
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ func TestStashStore(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs(s.expected, "", nil)
|
||||
@@ -130,6 +131,7 @@ func TestStashStashEntryCmdObj(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
userConfig := config.GetDefaultConfig()
|
||||
appState := &config.AppState{}
|
||||
@@ -179,6 +181,7 @@ func TestStashRename(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs(s.expectedHashCmd, s.hashResult, nil).
|
||||
|
||||
@@ -86,6 +86,7 @@ func TestSyncPush(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildSyncCommands(commonDeps{})
|
||||
task := gocui.NewFakeTask()
|
||||
@@ -123,6 +124,7 @@ func TestSyncFetch(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildSyncCommands(commonDeps{})
|
||||
instance.UserConfig.Git.FetchAll = s.fetchAllConfig
|
||||
@@ -161,6 +163,7 @@ func TestSyncFetchBackground(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildSyncCommands(commonDeps{})
|
||||
instance.UserConfig.Git.FetchAll = s.fetchAllConfig
|
||||
|
||||
@@ -44,6 +44,7 @@ func TestGetTags(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, scenario := range scenarios {
|
||||
scenario := scenario
|
||||
t.Run(scenario.testName, func(t *testing.T) {
|
||||
loader := &TagLoader{
|
||||
Common: utils.NewDummyCommon(),
|
||||
|
||||
@@ -363,7 +363,7 @@ func (self *WorkingTreeCommands) ResetAndClean() error {
|
||||
return self.RemoveUntrackedFiles()
|
||||
}
|
||||
|
||||
// ResetHard runs `git reset --hard`
|
||||
// ResetHardHead runs `git reset --hard`
|
||||
func (self *WorkingTreeCommands) ResetHard(ref string) error {
|
||||
cmdArgs := NewGitCmd("reset").Arg("--hard", ref).
|
||||
ToArgv()
|
||||
|
||||
@@ -61,6 +61,7 @@ func TestWorkingTreeUnstageFile(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
|
||||
s.test(instance.UnStageFile([]string{"test.txt"}, s.reset))
|
||||
@@ -189,6 +190,7 @@ func TestWorkingTreeDiscardAllFileChanges(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner, removeFile: s.removeFile})
|
||||
err := instance.DiscardAllFileChanges(s.file)
|
||||
@@ -304,6 +306,7 @@ func TestWorkingTreeDiff(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
userConfig := config.GetDefaultConfig()
|
||||
appState := &config.AppState{}
|
||||
@@ -372,6 +375,7 @@ func TestWorkingTreeShowFileDiff(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
userConfig := config.GetDefaultConfig()
|
||||
appState := &config.AppState{}
|
||||
@@ -424,6 +428,7 @@ func TestWorkingTreeCheckoutFile(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
|
||||
|
||||
@@ -454,6 +459,7 @@ func TestWorkingTreeDiscardUnstagedFileChanges(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
|
||||
s.test(instance.DiscardUnstagedFileChanges(s.file))
|
||||
@@ -481,6 +487,7 @@ func TestWorkingTreeDiscardAnyUnstagedFileChanges(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
|
||||
s.test(instance.DiscardAnyUnstagedFileChanges())
|
||||
@@ -508,6 +515,7 @@ func TestWorkingTreeRemoveUntrackedFiles(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
|
||||
s.test(instance.RemoveUntrackedFiles())
|
||||
@@ -537,6 +545,7 @@ func TestWorkingTreeResetHard(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
instance := buildWorkingTreeCommands(commonDeps{runner: s.runner})
|
||||
s.test(instance.ResetHard(s.ref))
|
||||
|
||||
@@ -76,6 +76,8 @@ func (self *WorktreeLoader) GetWorktrees() ([]*models.Worktree, error) {
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(len(worktrees))
|
||||
for _, worktree := range worktrees {
|
||||
worktree := worktree
|
||||
|
||||
go utils.Safe(func() {
|
||||
defer wg.Done()
|
||||
|
||||
|
||||
@@ -181,6 +181,7 @@ branch refs/heads/mybranch-worktree
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
runner := oscommands.NewFakeRunner(t)
|
||||
fs := afero.NewMemMapFs()
|
||||
|
||||
@@ -50,6 +50,7 @@ func TestGetBool(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
fake := NewFakeGitConfig(s.mockResponses)
|
||||
real := NewCachedGitConfig(
|
||||
@@ -86,6 +87,7 @@ func TestGet(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
fake := NewFakeGitConfig(s.mockResponses)
|
||||
real := NewCachedGitConfig(
|
||||
|
||||
@@ -413,6 +413,7 @@ func TestGetPullRequestURL(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
tr := i18n.EnglishTranslationSet()
|
||||
log := &fakes.FakeFieldLogger{}
|
||||
|
||||
@@ -10,14 +10,10 @@ type Branch struct {
|
||||
DisplayName string
|
||||
// indicator of when the branch was last checked out e.g. '2d', '3m'
|
||||
Recency string
|
||||
// how many commits ahead we are from the remote branch (how many commits we can push, assuming we push to our tracked remote branch)
|
||||
AheadForPull string
|
||||
// how many commits ahead we are from the remote branch (how many commits we can push)
|
||||
Pushables string
|
||||
// how many commits behind we are from the remote branch (how many commits we can pull)
|
||||
BehindForPull string
|
||||
// how many commits ahead we are from the branch we're pushing to (which might not be the same as our upstream branch in a triangular workflow)
|
||||
AheadForPush string
|
||||
// how many commits behind we are from the branch we're pushing to (which might not be the same as our upstream branch in a triangular workflow)
|
||||
BehindForPush string
|
||||
Pullables string
|
||||
// whether the remote branch is 'gone' i.e. we're tracking a remote branch that has been deleted
|
||||
UpstreamGone bool
|
||||
// whether this is the current branch. Exactly one branch should have this be true
|
||||
@@ -84,30 +80,26 @@ func (b *Branch) IsTrackingRemote() bool {
|
||||
// we know that the remote branch is not stored locally based on our pushable/pullable
|
||||
// count being question marks.
|
||||
func (b *Branch) RemoteBranchStoredLocally() bool {
|
||||
return b.IsTrackingRemote() && b.AheadForPull != "?" && b.BehindForPull != "?"
|
||||
return b.IsTrackingRemote() && b.Pushables != "?" && b.Pullables != "?"
|
||||
}
|
||||
|
||||
func (b *Branch) RemoteBranchNotStoredLocally() bool {
|
||||
return b.IsTrackingRemote() && b.AheadForPull == "?" && b.BehindForPull == "?"
|
||||
return b.IsTrackingRemote() && b.Pushables == "?" && b.Pullables == "?"
|
||||
}
|
||||
|
||||
func (b *Branch) MatchesUpstream() bool {
|
||||
return b.RemoteBranchStoredLocally() && b.AheadForPull == "0" && b.BehindForPull == "0"
|
||||
return b.RemoteBranchStoredLocally() && b.Pushables == "0" && b.Pullables == "0"
|
||||
}
|
||||
|
||||
func (b *Branch) IsAheadForPull() bool {
|
||||
return b.RemoteBranchStoredLocally() && b.AheadForPull != "0"
|
||||
func (b *Branch) HasCommitsToPush() bool {
|
||||
return b.RemoteBranchStoredLocally() && b.Pushables != "0"
|
||||
}
|
||||
|
||||
func (b *Branch) IsBehindForPull() bool {
|
||||
return b.RemoteBranchStoredLocally() && b.BehindForPull != "0"
|
||||
}
|
||||
|
||||
func (b *Branch) IsBehindForPush() bool {
|
||||
return b.BehindForPush != "" && b.BehindForPush != "0"
|
||||
func (b *Branch) HasCommitsToPull() bool {
|
||||
return b.RemoteBranchStoredLocally() && b.Pullables != "0"
|
||||
}
|
||||
|
||||
// for when we're in a detached head state
|
||||
func (b *Branch) IsRealBranch() bool {
|
||||
return b.AheadForPull != "" && b.BehindForPull != ""
|
||||
return b.Pushables != "" && b.Pullables != ""
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ func (self *cmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line st
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(stdoutPipe)
|
||||
scanner.Split(utils.ScanLinesAndTruncateWhenLongerThanBuffer(bufio.MaxScanTokenSize))
|
||||
scanner.Split(bufio.ScanLines)
|
||||
if err := cmd.Start(); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -178,11 +178,6 @@ func (self *cmdObjRunner) RunAndProcessLines(cmdObj ICmdObj, onLine func(line st
|
||||
}
|
||||
}
|
||||
|
||||
if scanner.Err() != nil {
|
||||
_ = Kill(cmd)
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
_ = cmd.Wait()
|
||||
|
||||
self.log.Infof("%s (%s)", cmdObj.ToString(), time.Since(t))
|
||||
|
||||
@@ -239,13 +239,14 @@ func (c *OSCommand) PipeCommands(cmdObjs ...ICmdObj) error {
|
||||
wg.Add(len(cmds))
|
||||
|
||||
for _, cmd := range cmds {
|
||||
currentCmd := cmd
|
||||
go utils.Safe(func() {
|
||||
stderr, err := cmd.StderrPipe()
|
||||
stderr, err := currentCmd.StderrPipe()
|
||||
if err != nil {
|
||||
c.Log.Error(err)
|
||||
}
|
||||
|
||||
if err := cmd.Start(); err != nil {
|
||||
if err := currentCmd.Start(); err != nil {
|
||||
c.Log.Error(err)
|
||||
}
|
||||
|
||||
@@ -255,7 +256,7 @@ func (c *OSCommand) PipeCommands(cmdObjs ...ICmdObj) error {
|
||||
}
|
||||
}
|
||||
|
||||
if err := cmd.Wait(); err != nil {
|
||||
if err := currentCmd.Wait(); err != nil {
|
||||
c.Log.Error(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -509,6 +509,7 @@ func TestTransform(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
lineIndices := ExpandRange(s.firstLineIndex, s.lastLineIndex)
|
||||
|
||||
@@ -565,6 +566,7 @@ func TestParseAndFormatPlain(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
// here we parse the patch, then format it, and ensure the result
|
||||
// matches the original patch. Note that unified diffs allow omitting
|
||||
@@ -602,6 +604,7 @@ func TestLineNumberOfLine(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
for i, idx := range s.indexes {
|
||||
patch := Parse(s.patchStr)
|
||||
@@ -630,6 +633,7 @@ func TestGetNextStageableLineIndex(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
for i, idx := range s.indexes {
|
||||
patch := Parse(s.patchStr)
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package config
|
||||
|
||||
import "os"
|
||||
|
||||
func GetEditTemplate(osConfig *OSConfig, guessDefaultEditor func() string) (string, bool) {
|
||||
preset := getPreset(osConfig, guessDefaultEditor)
|
||||
template := osConfig.Edit
|
||||
@@ -44,11 +42,9 @@ type editPreset struct {
|
||||
editAtLineTemplate string
|
||||
editAtLineAndWaitTemplate string
|
||||
openDirInEditorTemplate string
|
||||
suspend func() bool
|
||||
suspend bool
|
||||
}
|
||||
|
||||
func returnBool(a bool) func() bool { return (func() bool { return a }) }
|
||||
|
||||
// IF YOU ADD A PRESET TO THIS FUNCTION YOU MUST UPDATE THE `Supported presets` SECTION OF docs/Config.md
|
||||
func getPreset(osConfig *OSConfig, guessDefaultEditor func() string) *editPreset {
|
||||
presets := map[string]*editPreset{
|
||||
@@ -56,15 +52,12 @@ func getPreset(osConfig *OSConfig, guessDefaultEditor func() string) *editPreset
|
||||
"vim": standardTerminalEditorPreset("vim"),
|
||||
"nvim": standardTerminalEditorPreset("nvim"),
|
||||
"nvim-remote": {
|
||||
editTemplate: `[ -z "$NVIM" ] && (nvim -- {{filename}}) || (nvim --server "$NVIM" --remote-send "q" && nvim --server "$NVIM" --remote-tab {{filename}})`,
|
||||
editAtLineTemplate: `[ -z "$NVIM" ] && (nvim +{{line}} -- {{filename}}) || (nvim --server "$NVIM" --remote-send "q" && nvim --server "$NVIM" --remote-tab {{filename}} && nvim --server "$NVIM" --remote-send ":{{line}}<CR>")`,
|
||||
editTemplate: `nvim --server "$NVIM" --remote-tab {{filename}}`,
|
||||
editAtLineTemplate: `nvim --server "$NVIM" --remote-tab {{filename}}; [ -z "$NVIM" ] || nvim --server "$NVIM" --remote-send ":{{line}}<CR>"`,
|
||||
// No remote-wait support yet. See https://github.com/neovim/neovim/pull/17856
|
||||
editAtLineAndWaitTemplate: `nvim +{{line}} {{filename}}`,
|
||||
openDirInEditorTemplate: `[ -z "$NVIM" ] && (nvim -- {{dir}}) || (nvim --server "$NVIM" --remote-send "q" && nvim --server "$NVIM" --remote-tab {{dir}})`,
|
||||
suspend: func() bool {
|
||||
_, ok := os.LookupEnv("NVIM")
|
||||
return !ok
|
||||
},
|
||||
openDirInEditorTemplate: `nvim --server "$NVIM" --remote-tab {{dir}}`,
|
||||
suspend: false,
|
||||
},
|
||||
"lvim": standardTerminalEditorPreset("lvim"),
|
||||
"emacs": standardTerminalEditorPreset("emacs"),
|
||||
@@ -76,42 +69,42 @@ func getPreset(osConfig *OSConfig, guessDefaultEditor func() string) *editPreset
|
||||
editAtLineTemplate: "helix -- {{filename}}:{{line}}",
|
||||
editAtLineAndWaitTemplate: "helix -- {{filename}}:{{line}}",
|
||||
openDirInEditorTemplate: "helix -- {{dir}}",
|
||||
suspend: returnBool(true),
|
||||
suspend: true,
|
||||
},
|
||||
"helix (hx)": {
|
||||
editTemplate: "hx -- {{filename}}",
|
||||
editAtLineTemplate: "hx -- {{filename}}:{{line}}",
|
||||
editAtLineAndWaitTemplate: "hx -- {{filename}}:{{line}}",
|
||||
openDirInEditorTemplate: "hx -- {{dir}}",
|
||||
suspend: returnBool(true),
|
||||
suspend: true,
|
||||
},
|
||||
"vscode": {
|
||||
editTemplate: "code --reuse-window -- {{filename}}",
|
||||
editAtLineTemplate: "code --reuse-window --goto -- {{filename}}:{{line}}",
|
||||
editAtLineAndWaitTemplate: "code --reuse-window --goto --wait -- {{filename}}:{{line}}",
|
||||
openDirInEditorTemplate: "code -- {{dir}}",
|
||||
suspend: returnBool(false),
|
||||
suspend: false,
|
||||
},
|
||||
"sublime": {
|
||||
editTemplate: "subl -- {{filename}}",
|
||||
editAtLineTemplate: "subl -- {{filename}}:{{line}}",
|
||||
editAtLineAndWaitTemplate: "subl --wait -- {{filename}}:{{line}}",
|
||||
openDirInEditorTemplate: "subl -- {{dir}}",
|
||||
suspend: returnBool(false),
|
||||
suspend: false,
|
||||
},
|
||||
"bbedit": {
|
||||
editTemplate: "bbedit -- {{filename}}",
|
||||
editAtLineTemplate: "bbedit +{{line}} -- {{filename}}",
|
||||
editAtLineAndWaitTemplate: "bbedit +{{line}} --wait -- {{filename}}",
|
||||
openDirInEditorTemplate: "bbedit -- {{dir}}",
|
||||
suspend: returnBool(false),
|
||||
suspend: false,
|
||||
},
|
||||
"xcode": {
|
||||
editTemplate: "xed -- {{filename}}",
|
||||
editAtLineTemplate: "xed --line {{line}} -- {{filename}}",
|
||||
editAtLineAndWaitTemplate: "xed --line {{line}} --wait -- {{filename}}",
|
||||
openDirInEditorTemplate: "xed -- {{dir}}",
|
||||
suspend: returnBool(false),
|
||||
suspend: false,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -148,7 +141,7 @@ func standardTerminalEditorPreset(editor string) *editPreset {
|
||||
editAtLineTemplate: editor + " +{{line}} -- {{filename}}",
|
||||
editAtLineAndWaitTemplate: editor + " +{{line}} -- {{filename}}",
|
||||
openDirInEditorTemplate: editor + " -- {{dir}}",
|
||||
suspend: returnBool(true),
|
||||
suspend: true,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -156,5 +149,5 @@ func getEditInTerminal(osConfig *OSConfig, preset *editPreset) bool {
|
||||
if osConfig.SuspendOnEdit != nil {
|
||||
return *osConfig.SuspendOnEdit
|
||||
}
|
||||
return preset.suspend()
|
||||
return preset.suspend
|
||||
}
|
||||
|
||||
@@ -123,8 +123,6 @@ type GuiConfig struct {
|
||||
NerdFontsVersion string `yaml:"nerdFontsVersion" jsonschema:"enum=2,enum=3,enum="`
|
||||
// If true (default), file icons are shown in the file views. Only relevant if NerdFontsVersion is not empty.
|
||||
ShowFileIcons bool `yaml:"showFileIcons"`
|
||||
// Length of commit hash in commits view. 0 shows '*' if NF icons aren't on.
|
||||
CommitHashLength int `yaml:"commitHashLength" jsonschema:"minimum=0"`
|
||||
// If true, show commit hashes alongside branch names in the branches view.
|
||||
ShowBranchCommitHash bool `yaml:"showBranchCommitHash"`
|
||||
// Height of the command log view
|
||||
@@ -222,8 +220,6 @@ type GitConfig struct {
|
||||
// If true, do not allow force pushes
|
||||
DisableForcePushing bool `yaml:"disableForcePushing"`
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix
|
||||
CommitPrefix *CommitPrefixConfig `yaml:"commitPrefix"`
|
||||
// See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix
|
||||
CommitPrefixes map[string]CommitPrefixConfig `yaml:"commitPrefixes"`
|
||||
// If true, parse emoji strings in commit messages e.g. render :rocket: as 🚀
|
||||
// (This should really be under 'gui', not 'git')
|
||||
@@ -679,7 +675,6 @@ func GetDefaultConfig() *UserConfig {
|
||||
ShowIcons: false,
|
||||
NerdFontsVersion: "",
|
||||
ShowFileIcons: true,
|
||||
CommitHashLength: 8,
|
||||
ShowBranchCommitHash: false,
|
||||
CommandLogSize: 8,
|
||||
SplitDiff: "auto",
|
||||
|
||||
@@ -105,6 +105,7 @@ func (self *MenuViewModel) GetNonModelItems() []*NonModelItem {
|
||||
menuItems := self.FilteredListViewModel.GetItems()
|
||||
var prevSection *types.MenuSection = nil
|
||||
for i, menuItem := range menuItems {
|
||||
menuItem := menuItem
|
||||
if menuItem.Section != nil && menuItem.Section != prevSection {
|
||||
if prevSection != nil {
|
||||
result = append(result, &NonModelItem{
|
||||
|
||||
@@ -75,7 +75,7 @@ func NewSubCommitsContext(
|
||||
endIdx,
|
||||
// Don't show the graph in the left/right view; we'd like to, but
|
||||
// it's too complicated:
|
||||
shouldShowGraph(c),
|
||||
shouldShowGraph(c) && viewModel.GetRefToShowDivergenceFrom() == "",
|
||||
git_commands.NewNullBisectInfo(),
|
||||
false,
|
||||
)
|
||||
|
||||
@@ -14,13 +14,10 @@ type SuggestionsContext struct {
|
||||
}
|
||||
|
||||
type SuggestionsContextState struct {
|
||||
Suggestions []*types.Suggestion
|
||||
OnConfirm func() error
|
||||
OnClose func() error
|
||||
OnDeleteSuggestion func() error
|
||||
AsyncHandler *tasks.AsyncHandler
|
||||
|
||||
AllowEditSuggestion bool
|
||||
Suggestions []*types.Suggestion
|
||||
OnConfirm func() error
|
||||
OnClose func() error
|
||||
AsyncHandler *tasks.AsyncHandler
|
||||
|
||||
// FindSuggestions will take a string that the user has typed into a prompt
|
||||
// and return a slice of suggestions which match that string.
|
||||
|
||||
@@ -620,7 +620,7 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
||||
if !branch.RemoteBranchStoredLocally() {
|
||||
return errors.New(self.c.Tr.FwdNoLocalUpstream)
|
||||
}
|
||||
if branch.IsAheadForPull() {
|
||||
if branch.HasCommitsToPush() {
|
||||
return errors.New(self.c.Tr.FwdCommitsToPush)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/context"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/types"
|
||||
)
|
||||
@@ -41,14 +39,6 @@ func (self *ConfirmationController) GetKeybindings(opts types.KeybindingsOpts) [
|
||||
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
|
||||
Handler: func() error {
|
||||
if len(self.c.Contexts().Suggestions.State.Suggestions) > 0 {
|
||||
subtitle := ""
|
||||
if self.c.State().GetRepoState().GetCurrentPopupOpts().HandleDeleteSuggestion != nil {
|
||||
// We assume that whenever things are deletable, they
|
||||
// are also editable, so we show both keybindings
|
||||
subtitle = fmt.Sprintf(self.c.Tr.SuggestionsSubtitle,
|
||||
self.c.UserConfig.Keybinding.Universal.Remove, self.c.UserConfig.Keybinding.Universal.Edit)
|
||||
}
|
||||
self.c.Views().Suggestions.Subtitle = subtitle
|
||||
return self.c.ReplaceContext(self.c.Contexts().Suggestions)
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
@@ -18,7 +17,6 @@ func (self *CustomCommandAction) Call() error {
|
||||
return self.c.Prompt(types.PromptOpts{
|
||||
Title: self.c.Tr.CustomCommand,
|
||||
FindSuggestionsFunc: self.GetCustomCommandsHistorySuggestionsFunc(),
|
||||
AllowEditSuggestion: true,
|
||||
HandleConfirm: func(command string) error {
|
||||
if self.shouldSaveCommand(command) {
|
||||
self.c.GetAppState().CustomCommandsHistory = utils.Limit(
|
||||
@@ -34,34 +32,13 @@ func (self *CustomCommandAction) Call() error {
|
||||
self.c.OS().Cmd.NewShell(command),
|
||||
)
|
||||
},
|
||||
HandleDeleteSuggestion: func(index int) error {
|
||||
// index is the index in the _filtered_ list of suggestions, so we
|
||||
// need to map it back to the full list. There's no really good way
|
||||
// to do this, but fortunately we keep the items in the
|
||||
// CustomCommandsHistory unique, which allows us to simply search
|
||||
// for it by string.
|
||||
item := self.c.Contexts().Suggestions.GetItems()[index].Value
|
||||
fullIndex := lo.IndexOf(self.c.GetAppState().CustomCommandsHistory, item)
|
||||
if fullIndex == -1 {
|
||||
// Should never happen, but better be safe
|
||||
return nil
|
||||
}
|
||||
|
||||
self.c.GetAppState().CustomCommandsHistory = slices.Delete(
|
||||
self.c.GetAppState().CustomCommandsHistory, fullIndex, fullIndex+1)
|
||||
self.c.SaveAppStateAndLogError()
|
||||
self.c.Contexts().Suggestions.RefreshSuggestions()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (self *CustomCommandAction) GetCustomCommandsHistorySuggestionsFunc() func(string) []*types.Suggestion {
|
||||
return func(input string) []*types.Suggestion {
|
||||
history := self.c.GetAppState().CustomCommandsHistory
|
||||
history := self.c.GetAppState().CustomCommandsHistory
|
||||
|
||||
return helpers.FilterFunc(history, self.c.UserConfig.Gui.UseFuzzySearch())(input)
|
||||
}
|
||||
return helpers.FilterFunc(history, self.c.UserConfig.Gui.UseFuzzySearch())
|
||||
}
|
||||
|
||||
// this mimics the shell functionality `ignorespace`
|
||||
|
||||
@@ -17,6 +17,7 @@ func (self *DiffingMenuAction) Call() error {
|
||||
|
||||
menuItems := []*types.MenuItem{}
|
||||
for _, name := range names {
|
||||
name := name
|
||||
menuItems = append(menuItems, []*types.MenuItem{
|
||||
{
|
||||
Label: fmt.Sprintf("%s %s", self.c.Tr.Diff, name),
|
||||
|
||||
@@ -159,7 +159,6 @@ func (self *ConfirmationHelper) prepareConfirmationPanel(
|
||||
suggestionsContext.SetSuggestions(opts.FindSuggestionsFunc(""))
|
||||
suggestionsView.Visible = true
|
||||
suggestionsView.Title = fmt.Sprintf(self.c.Tr.SuggestionsTitle, self.c.UserConfig.Keybinding.Universal.TogglePanel)
|
||||
suggestionsView.Subtitle = ""
|
||||
}
|
||||
|
||||
self.ResizeConfirmationPanel()
|
||||
@@ -224,8 +223,6 @@ func (self *ConfirmationHelper) CreatePopupPanel(ctx goContext.Context, opts typ
|
||||
return err
|
||||
}
|
||||
|
||||
self.c.Contexts().Suggestions.State.AllowEditSuggestion = opts.AllowEditSuggestion
|
||||
|
||||
self.c.State().GetRepoState().SetCurrentPopupOpts(&opts)
|
||||
|
||||
return self.c.PushContext(self.c.Contexts().Confirmation)
|
||||
@@ -273,20 +270,10 @@ func (self *ConfirmationHelper) setKeyBindings(cancel goContext.CancelFunc, opts
|
||||
|
||||
onClose := self.wrappedConfirmationFunction(cancel, opts.HandleClose)
|
||||
|
||||
onDeleteSuggestion := func() error {
|
||||
if opts.HandleDeleteSuggestion == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
idx := self.c.Contexts().Suggestions.GetSelectedLineIdx()
|
||||
return opts.HandleDeleteSuggestion(idx)
|
||||
}
|
||||
|
||||
self.c.Contexts().Confirmation.State.OnConfirm = onConfirm
|
||||
self.c.Contexts().Confirmation.State.OnClose = onClose
|
||||
self.c.Contexts().Suggestions.State.OnConfirm = onSuggestionConfirm
|
||||
self.c.Contexts().Suggestions.State.OnClose = onClose
|
||||
self.c.Contexts().Suggestions.State.OnDeleteSuggestion = onDeleteSuggestion
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -297,7 +284,6 @@ func (self *ConfirmationHelper) clearConfirmationViewKeyBindings() {
|
||||
self.c.Contexts().Confirmation.State.OnClose = noop
|
||||
self.c.Contexts().Suggestions.State.OnConfirm = noop
|
||||
self.c.Contexts().Suggestions.State.OnClose = noop
|
||||
self.c.Contexts().Suggestions.State.OnDeleteSuggestion = noop
|
||||
}
|
||||
|
||||
func (self *ConfirmationHelper) getSelectedSuggestionValue() string {
|
||||
|
||||
@@ -220,9 +220,9 @@ func (self *WorkingTreeHelper) prepareFilesForCommit() error {
|
||||
|
||||
func (self *WorkingTreeHelper) commitPrefixConfigForRepo() *config.CommitPrefixConfig {
|
||||
cfg, ok := self.c.UserConfig.Git.CommitPrefixes[self.c.Git().RepoPaths.RepoName()]
|
||||
if ok {
|
||||
return &cfg
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
return self.c.UserConfig.Git.CommitPrefix
|
||||
return &cfg
|
||||
}
|
||||
|
||||
@@ -801,6 +801,7 @@ func (self *LocalCommitsController) revert(commit *models.Commit) error {
|
||||
func (self *LocalCommitsController) createRevertMergeCommitMenu(commit *models.Commit) error {
|
||||
menuItems := make([]*types.MenuItem, len(commit.Parents))
|
||||
for i, parentHash := range commit.Parents {
|
||||
i := i
|
||||
message, err := self.c.Git().Commit.GetCommitMessageFirstLine(parentHash)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -75,11 +75,6 @@ func (self *StatusController) GetMouseKeybindings(opts types.KeybindingsOpts) []
|
||||
Key: gocui.MouseLeft,
|
||||
Handler: self.onClickMain,
|
||||
},
|
||||
{
|
||||
ViewName: self.Context().GetViewName(),
|
||||
Key: gocui.MouseLeft,
|
||||
Handler: self.onClick,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,11 +95,15 @@ func (self *StatusController) GetOnRenderToMain() func() error {
|
||||
}
|
||||
}
|
||||
|
||||
func (self *StatusController) GetOnClick() func() error {
|
||||
return self.onClick
|
||||
}
|
||||
|
||||
func (self *StatusController) Context() types.Context {
|
||||
return self.c.Contexts().Status
|
||||
}
|
||||
|
||||
func (self *StatusController) onClick(opts gocui.ViewMouseBindingOpts) error {
|
||||
func (self *StatusController) onClick() error {
|
||||
// TODO: move into some abstraction (status is currently not a listViewContext where a lot of this code lives)
|
||||
currentBranch := self.c.Helpers().Refs.GetCheckedOutRef()
|
||||
if currentBranch == nil {
|
||||
@@ -116,20 +115,21 @@ func (self *StatusController) onClick(opts gocui.ViewMouseBindingOpts) error {
|
||||
return err
|
||||
}
|
||||
|
||||
cx, _ := self.c.Views().Status.Cursor()
|
||||
upstreamStatus := presentation.BranchStatus(currentBranch, types.ItemOperationNone, self.c.Tr, time.Now(), self.c.UserConfig)
|
||||
repoName := self.c.Git().RepoPaths.RepoName()
|
||||
workingTreeState := self.c.Git().Status.WorkingTreeState()
|
||||
switch workingTreeState {
|
||||
case enums.REBASE_MODE_REBASING, enums.REBASE_MODE_MERGING:
|
||||
workingTreeStatus := fmt.Sprintf("(%s)", presentation.FormatWorkingTreeStateLower(self.c.Tr, workingTreeState))
|
||||
if cursorInSubstring(opts.X, upstreamStatus+" ", workingTreeStatus) {
|
||||
if cursorInSubstring(cx, upstreamStatus+" ", workingTreeStatus) {
|
||||
return self.c.Helpers().MergeAndRebase.CreateRebaseOptionsMenu()
|
||||
}
|
||||
if cursorInSubstring(opts.X, upstreamStatus+" "+workingTreeStatus+" ", repoName) {
|
||||
if cursorInSubstring(cx, upstreamStatus+" "+workingTreeStatus+" ", repoName) {
|
||||
return self.c.Helpers().Repos.CreateRecentReposMenu()
|
||||
}
|
||||
default:
|
||||
if cursorInSubstring(opts.X, upstreamStatus+" ", repoName) {
|
||||
if cursorInSubstring(cx, upstreamStatus+" ", repoName) {
|
||||
return self.c.Helpers().Repos.CreateRecentReposMenu()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,32 +40,8 @@ func (self *SuggestionsController) GetKeybindings(opts types.KeybindingsOpts) []
|
||||
Handler: func() error { return self.context().State.OnClose() },
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
|
||||
Handler: func() error {
|
||||
self.c.Views().Suggestions.Subtitle = ""
|
||||
return self.c.ReplaceContext(self.c.Contexts().Confirmation)
|
||||
},
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Remove),
|
||||
Handler: func() error {
|
||||
return self.context().State.OnDeleteSuggestion()
|
||||
},
|
||||
},
|
||||
{
|
||||
Key: opts.GetKey(opts.Config.Universal.Edit),
|
||||
Handler: func() error {
|
||||
if self.context().State.AllowEditSuggestion {
|
||||
if selectedItem := self.c.Contexts().Suggestions.GetSelected(); selectedItem != nil {
|
||||
self.c.Contexts().Confirmation.GetView().TextArea.Clear()
|
||||
self.c.Contexts().Confirmation.GetView().TextArea.TypeString(selectedItem.Value)
|
||||
self.c.Contexts().Confirmation.GetView().RenderTextArea()
|
||||
self.c.Contexts().Suggestions.RefreshSuggestions()
|
||||
return self.c.ReplaceContext(self.c.Contexts().Confirmation)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Key: opts.GetKey(opts.Config.Universal.TogglePanel),
|
||||
Handler: func() error { return self.c.ReplaceContext(self.c.Contexts().Confirmation) },
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -87,10 +87,10 @@ func (self *SyncController) branchCheckedOut(f func(*models.Branch) error) func(
|
||||
}
|
||||
|
||||
func (self *SyncController) push(currentBranch *models.Branch) error {
|
||||
// if we are behind our upstream branch we'll ask if the user wants to force push
|
||||
// if we have pullables we'll ask if the user wants to force push
|
||||
if currentBranch.IsTrackingRemote() {
|
||||
opts := pushOpts{}
|
||||
if currentBranch.IsBehindForPush() {
|
||||
if currentBranch.HasCommitsToPull() {
|
||||
return self.requestToForcePush(currentBranch, opts)
|
||||
} else {
|
||||
return self.pushAux(currentBranch, opts)
|
||||
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
// we then do the reverse of what that reflog describes.
|
||||
// When we do this, we create a new reflog entry, and tag it as either an undo or redo
|
||||
// Then, next time we want to undo, we'll use those entries to know which user-initiated
|
||||
// actions we can skip. E.g. if I do three things, A, B, and C, and hit undo twice,
|
||||
// actions we can skip. E.g. if I do do three things, A, B, and C, and hit undo twice,
|
||||
// the reflog will read UUCBA, and when I read the first two undos, I know to skip the following
|
||||
// two user actions, meaning we end up undoing reflog entry C. Redoing works in a similar way.
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ func TestBuildTreeFromFiles(t *testing.T) {
|
||||
expected: &Node[models.File]{
|
||||
Path: "",
|
||||
// it is a little strange that we're not bubbling up our merge conflict
|
||||
// here but we are technically still in tree mode and that's the rule
|
||||
// here but we are technically still in in tree mode and that's the rule
|
||||
Children: []*Node[models.File]{
|
||||
{
|
||||
File: &models.File{Name: "a"},
|
||||
@@ -147,6 +147,7 @@ func TestBuildTreeFromFiles(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
result := BuildTreeFromFiles(s.files)
|
||||
assert.EqualValues(t, s.expected, result)
|
||||
@@ -305,6 +306,7 @@ func TestBuildFlatTreeFromFiles(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
result := BuildFlatTreeFromFiles(s.files)
|
||||
assert.EqualValues(t, s.expected, result)
|
||||
@@ -418,6 +420,7 @@ func TestBuildTreeFromCommitFiles(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
result := BuildTreeFromCommitFiles(s.files)
|
||||
assert.EqualValues(t, s.expected, result)
|
||||
@@ -518,6 +521,7 @@ func TestBuildFlatTreeFromCommitFiles(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
result := BuildFlatTreeFromCommitFiles(s.files)
|
||||
assert.EqualValues(t, s.expected, result)
|
||||
|
||||
@@ -125,6 +125,7 @@ func TestCompress(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
s.root.Compress()
|
||||
assert.EqualValues(t, s.expected, s.root)
|
||||
@@ -154,6 +155,7 @@ func TestGetFile(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
assert.EqualValues(t, s.expected, s.viewModel.GetFile(s.path))
|
||||
})
|
||||
|
||||
@@ -71,6 +71,7 @@ func TestFilterAction(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
mngr := &FileTree{getFiles: func() []*models.File { return s.files }, filter: s.filter}
|
||||
result := mngr.getFilesForDisplay()
|
||||
|
||||
@@ -662,7 +662,7 @@ func (gui *Gui) Run(startArgs appTypes.StartArgs) error {
|
||||
// disable deadlock reporting if we're not running in debug mode, or if
|
||||
// we're debugging an integration test. In this latter case, stopping at
|
||||
// breakpoints and stepping through code can easily take more than 30s.
|
||||
deadlock.Opts.Disable = !gui.Debug || os.Getenv(components.WAIT_FOR_DEBUGGER_ENV_VAR) != ""
|
||||
deadlock.Opts.Disable = !gui.Debug || os.Getenv(components.WAIT_FOR_DEBUGGER_ENV_VAR) == ""
|
||||
|
||||
if err := gui.Config.ReloadUserConfig(); err != nil {
|
||||
return nil
|
||||
|
||||
@@ -99,7 +99,7 @@ func FileHasConflictMarkers(path string) (bool, error) {
|
||||
// Efficiently scans through a file looking for merge conflict markers. Returns true if it does
|
||||
func fileHasConflictMarkersAux(file io.Reader) bool {
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(utils.ScanLinesAndTruncateWhenLongerThanBuffer(bufio.MaxScanTokenSize))
|
||||
scanner.Split(bufio.ScanLines)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Bytes()
|
||||
|
||||
|
||||
@@ -115,6 +115,7 @@ baz
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
assert.EqualValues(t, s.expected, findConflicts(s.content))
|
||||
})
|
||||
|
||||
@@ -122,6 +122,7 @@ func TestNewOrigin(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
assert.EqualValues(t, s.expected, calculateOrigin(s.origin, s.bufferHeight, s.numLines, s.firstLineIdx, s.lastLineIdx, s.selectedLineIdx, s.selectMode))
|
||||
})
|
||||
|
||||
@@ -104,15 +104,13 @@ func (self *PopupHandler) Confirm(opts types.ConfirmOpts) error {
|
||||
|
||||
func (self *PopupHandler) Prompt(opts types.PromptOpts) error {
|
||||
return self.createPopupPanelFn(context.Background(), types.CreatePopupPanelOpts{
|
||||
Title: opts.Title,
|
||||
Prompt: opts.InitialContent,
|
||||
Editable: true,
|
||||
HandleConfirmPrompt: opts.HandleConfirm,
|
||||
HandleClose: opts.HandleClose,
|
||||
HandleDeleteSuggestion: opts.HandleDeleteSuggestion,
|
||||
FindSuggestionsFunc: opts.FindSuggestionsFunc,
|
||||
AllowEditSuggestion: opts.AllowEditSuggestion,
|
||||
Mask: opts.Mask,
|
||||
Title: opts.Title,
|
||||
Prompt: opts.InitialContent,
|
||||
Editable: true,
|
||||
HandleConfirmPrompt: opts.HandleConfirm,
|
||||
HandleClose: opts.HandleClose,
|
||||
FindSuggestionsFunc: opts.FindSuggestionsFunc,
|
||||
Mask: opts.Mask,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -196,11 +196,11 @@ func BranchStatus(
|
||||
}
|
||||
|
||||
result := ""
|
||||
if branch.IsAheadForPull() {
|
||||
result = fmt.Sprintf("↑%s", branch.AheadForPull)
|
||||
if branch.HasCommitsToPush() {
|
||||
result = fmt.Sprintf("↑%s", branch.Pushables)
|
||||
}
|
||||
if branch.IsBehindForPull() {
|
||||
result = fmt.Sprintf("%s↓%s", result, branch.BehindForPull)
|
||||
if branch.HasCommitsToPull() {
|
||||
result = fmt.Sprintf("%s↓%s", result, branch.Pullables)
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
@@ -58,8 +58,8 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
Name: "branch_name",
|
||||
Recency: "1m",
|
||||
UpstreamRemote: "origin",
|
||||
AheadForPull: "0",
|
||||
BehindForPull: "0",
|
||||
Pushables: "0",
|
||||
Pullables: "0",
|
||||
},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
fullDescription: false,
|
||||
@@ -73,8 +73,8 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
Name: "branch_name",
|
||||
Recency: "1m",
|
||||
UpstreamRemote: "origin",
|
||||
AheadForPull: "3",
|
||||
BehindForPull: "5",
|
||||
Pushables: "3",
|
||||
Pullables: "5",
|
||||
},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
fullDescription: false,
|
||||
@@ -99,8 +99,8 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
CommitHash: "1234567890",
|
||||
UpstreamRemote: "origin",
|
||||
UpstreamBranch: "branch_name",
|
||||
AheadForPull: "0",
|
||||
BehindForPull: "0",
|
||||
Pushables: "0",
|
||||
Pullables: "0",
|
||||
Subject: "commit title",
|
||||
},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
@@ -144,8 +144,8 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
Name: "branch_name",
|
||||
Recency: "1m",
|
||||
UpstreamRemote: "origin",
|
||||
AheadForPull: "0",
|
||||
BehindForPull: "0",
|
||||
Pushables: "0",
|
||||
Pullables: "0",
|
||||
},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
fullDescription: false,
|
||||
@@ -159,8 +159,8 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
Name: "branch_name",
|
||||
Recency: "1m",
|
||||
UpstreamRemote: "origin",
|
||||
AheadForPull: "3",
|
||||
BehindForPull: "5",
|
||||
Pushables: "3",
|
||||
Pullables: "5",
|
||||
},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
fullDescription: false,
|
||||
@@ -212,8 +212,8 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
||||
CommitHash: "1234567890",
|
||||
UpstreamRemote: "origin",
|
||||
UpstreamBranch: "branch_name",
|
||||
AheadForPull: "0",
|
||||
BehindForPull: "0",
|
||||
Pushables: "0",
|
||||
Pullables: "0",
|
||||
Subject: "commit title",
|
||||
},
|
||||
itemOperation: types.ItemOperationNone,
|
||||
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
type pipeSetCacheKey struct {
|
||||
commitHash string
|
||||
commitCount int
|
||||
divergence models.Divergence
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -79,76 +78,24 @@ func GetCommitListDisplayStrings(
|
||||
// function expects to be passed the index of the commit in terms of the `commits` slice
|
||||
var getGraphLine func(int) string
|
||||
if showGraph {
|
||||
if len(commits) > 0 && commits[0].Divergence != models.DivergenceNone {
|
||||
// Showing a divergence log; we know we don't have any rebasing
|
||||
// commits in this case. But we need to render separate graphs for
|
||||
// the Local and Remote sections.
|
||||
allGraphLines := []string{}
|
||||
// this is where the graph begins (may be beyond the TODO commits depending on startIdx,
|
||||
// but we'll never include TODO commits as part of the graph because it'll be messy)
|
||||
graphOffset := max(startIdx, rebaseOffset)
|
||||
|
||||
_, localSectionStart, found := lo.FindIndexOf(
|
||||
commits, func(c *models.Commit) bool { return c.Divergence == models.DivergenceLeft })
|
||||
if !found {
|
||||
localSectionStart = len(commits)
|
||||
}
|
||||
|
||||
if localSectionStart > 0 {
|
||||
// we have some remote commits
|
||||
pipeSets := loadPipesets(commits[:localSectionStart])
|
||||
if startIdx < localSectionStart {
|
||||
// some of the remote commits are visible
|
||||
start := startIdx
|
||||
end := min(endIdx, localSectionStart)
|
||||
graphPipeSets := pipeSets[start:end]
|
||||
graphCommits := commits[start:end]
|
||||
graphLines := graph.RenderAux(
|
||||
graphPipeSets,
|
||||
graphCommits,
|
||||
selectedCommitHash,
|
||||
)
|
||||
allGraphLines = append(allGraphLines, graphLines...)
|
||||
}
|
||||
}
|
||||
if localSectionStart < len(commits) {
|
||||
// we have some local commits
|
||||
pipeSets := loadPipesets(commits[localSectionStart:])
|
||||
if localSectionStart < endIdx {
|
||||
// some of the local commits are visible
|
||||
graphOffset := max(startIdx, localSectionStart)
|
||||
pipeSetOffset := max(startIdx-localSectionStart, 0)
|
||||
graphPipeSets := pipeSets[pipeSetOffset : endIdx-localSectionStart]
|
||||
graphCommits := commits[graphOffset:endIdx]
|
||||
graphLines := graph.RenderAux(
|
||||
graphPipeSets,
|
||||
graphCommits,
|
||||
selectedCommitHash,
|
||||
)
|
||||
allGraphLines = append(allGraphLines, graphLines...)
|
||||
}
|
||||
}
|
||||
|
||||
getGraphLine = func(idx int) string {
|
||||
return allGraphLines[idx-startIdx]
|
||||
}
|
||||
} else {
|
||||
// this is where the graph begins (may be beyond the TODO commits depending on startIdx,
|
||||
// but we'll never include TODO commits as part of the graph because it'll be messy)
|
||||
graphOffset := max(startIdx, rebaseOffset)
|
||||
|
||||
pipeSets := loadPipesets(commits[rebaseOffset:])
|
||||
pipeSetOffset := max(startIdx-rebaseOffset, 0)
|
||||
graphPipeSets := pipeSets[pipeSetOffset:max(endIdx-rebaseOffset, 0)]
|
||||
graphCommits := commits[graphOffset:endIdx]
|
||||
graphLines := graph.RenderAux(
|
||||
graphPipeSets,
|
||||
graphCommits,
|
||||
selectedCommitHash,
|
||||
)
|
||||
getGraphLine = func(idx int) string {
|
||||
if idx >= graphOffset {
|
||||
return graphLines[idx-graphOffset]
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
pipeSets := loadPipesets(commits[rebaseOffset:])
|
||||
pipeSetOffset := max(startIdx-rebaseOffset, 0)
|
||||
graphPipeSets := pipeSets[pipeSetOffset:max(endIdx-rebaseOffset, 0)]
|
||||
graphCommits := commits[graphOffset:endIdx]
|
||||
graphLines := graph.RenderAux(
|
||||
graphPipeSets,
|
||||
graphCommits,
|
||||
selectedCommitHash,
|
||||
)
|
||||
getGraphLine = func(idx int) string {
|
||||
if idx >= graphOffset {
|
||||
return graphLines[idx-graphOffset]
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -258,7 +205,6 @@ func loadPipesets(commits []*models.Commit) [][]*graph.Pipe {
|
||||
cacheKey := pipeSetCacheKey{
|
||||
commitHash: commits[0].Hash,
|
||||
commitCount: len(commits),
|
||||
divergence: commits[0].Divergence,
|
||||
}
|
||||
|
||||
pipeSets, ok := pipeSetCache[cacheKey]
|
||||
@@ -366,32 +312,8 @@ func displayCommit(
|
||||
bisectInfo *git_commands.BisectInfo,
|
||||
isYouAreHereCommit bool,
|
||||
) []string {
|
||||
bisectString := getBisectStatusText(bisectStatus, bisectInfo)
|
||||
|
||||
hashString := ""
|
||||
hashColor := getHashColor(commit, diffName, cherryPickedCommitHashSet, bisectStatus, bisectInfo)
|
||||
hashLength := common.UserConfig.Gui.CommitHashLength
|
||||
if hashLength >= len(commit.Hash) {
|
||||
hashString = hashColor.Sprint(commit.Hash)
|
||||
} else if hashLength > 0 {
|
||||
hashString = hashColor.Sprint(commit.Hash[:hashLength])
|
||||
} else if !icons.IsIconEnabled() { // hashLength <= 0
|
||||
hashString = hashColor.Sprint("*")
|
||||
}
|
||||
|
||||
divergenceString := ""
|
||||
if commit.Divergence != models.DivergenceNone {
|
||||
divergenceString = hashColor.Sprint(lo.Ternary(commit.Divergence == models.DivergenceLeft, "↑", "↓"))
|
||||
} else if icons.IsIconEnabled() {
|
||||
divergenceString = hashColor.Sprint(icons.IconForCommit(commit))
|
||||
}
|
||||
|
||||
descriptionString := ""
|
||||
if fullDescription {
|
||||
descriptionString = style.FgBlue.Sprint(
|
||||
utils.UnixToDateSmart(now, commit.UnixTimestamp, timeFormat, shortTimeFormat),
|
||||
)
|
||||
}
|
||||
bisectString := getBisectStatusText(bisectStatus, bisectInfo)
|
||||
|
||||
actionString := ""
|
||||
if commit.Action != models.ActionNone {
|
||||
@@ -446,12 +368,20 @@ func displayCommit(
|
||||
}
|
||||
|
||||
cols := make([]string, 0, 7)
|
||||
if commit.Divergence != models.DivergenceNone {
|
||||
cols = append(cols, hashColor.Sprint(lo.Ternary(commit.Divergence == models.DivergenceLeft, "↑", "↓")))
|
||||
} else if icons.IsIconEnabled() {
|
||||
cols = append(cols, hashColor.Sprint(icons.IconForCommit(commit)))
|
||||
}
|
||||
cols = append(cols, hashColor.Sprint(commit.ShortHash()))
|
||||
cols = append(cols, bisectString)
|
||||
if fullDescription {
|
||||
cols = append(cols, style.FgBlue.Sprint(
|
||||
utils.UnixToDateSmart(now, commit.UnixTimestamp, timeFormat, shortTimeFormat),
|
||||
))
|
||||
}
|
||||
cols = append(
|
||||
cols,
|
||||
divergenceString,
|
||||
hashString,
|
||||
bisectString,
|
||||
descriptionString,
|
||||
actionString,
|
||||
authorFunc(commit.AuthorName),
|
||||
graphLine+mark+tagString+theme.DefaultTextColor.Sprint(name),
|
||||
|
||||
@@ -359,185 +359,6 @@ func TestGetCommitListDisplayStrings(t *testing.T) {
|
||||
hash3 ◯ commit3
|
||||
`),
|
||||
},
|
||||
{
|
||||
testName: "graph in divergence view - all commits visible",
|
||||
commits: []*models.Commit{
|
||||
{Name: "commit1", Hash: "hash1r", Parents: []string{"hash2r"}, Divergence: models.DivergenceRight},
|
||||
{Name: "commit2", Hash: "hash2r", Parents: []string{"hash3r", "hash5r"}, Divergence: models.DivergenceRight},
|
||||
{Name: "commit3", Hash: "hash3r", Parents: []string{"hash4r"}, Divergence: models.DivergenceRight},
|
||||
{Name: "commit1", Hash: "hash1l", Parents: []string{"hash2l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit2", Hash: "hash2l", Parents: []string{"hash3l", "hash4l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit3", Hash: "hash3l", Parents: []string{"hash4l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit4", Hash: "hash4l", Parents: []string{"hash5l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit5", Hash: "hash5l", Parents: []string{"hash6l"}, Divergence: models.DivergenceLeft},
|
||||
},
|
||||
startIdx: 0,
|
||||
endIdx: 8,
|
||||
showGraph: true,
|
||||
bisectInfo: git_commands.NewNullBisectInfo(),
|
||||
cherryPickedCommitHashSet: set.New[string](),
|
||||
showYouAreHereLabel: false,
|
||||
now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
expected: formatExpected(`
|
||||
↓ hash1r ◯ commit1
|
||||
↓ hash2r ⏣─╮ commit2
|
||||
↓ hash3r ◯ │ commit3
|
||||
↑ hash1l ◯ commit1
|
||||
↑ hash2l ⏣─╮ commit2
|
||||
↑ hash3l ◯ │ commit3
|
||||
↑ hash4l ◯─╯ commit4
|
||||
↑ hash5l ◯ commit5
|
||||
`),
|
||||
},
|
||||
{
|
||||
testName: "graph in divergence view - not all remote commits visible",
|
||||
commits: []*models.Commit{
|
||||
{Name: "commit1", Hash: "hash1r", Parents: []string{"hash2r"}, Divergence: models.DivergenceRight},
|
||||
{Name: "commit2", Hash: "hash2r", Parents: []string{"hash3r", "hash5r"}, Divergence: models.DivergenceRight},
|
||||
{Name: "commit3", Hash: "hash3r", Parents: []string{"hash4r"}, Divergence: models.DivergenceRight},
|
||||
{Name: "commit1", Hash: "hash1l", Parents: []string{"hash2l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit2", Hash: "hash2l", Parents: []string{"hash3l", "hash4l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit3", Hash: "hash3l", Parents: []string{"hash4l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit4", Hash: "hash4l", Parents: []string{"hash5l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit5", Hash: "hash5l", Parents: []string{"hash6l"}, Divergence: models.DivergenceLeft},
|
||||
},
|
||||
startIdx: 2,
|
||||
endIdx: 8,
|
||||
showGraph: true,
|
||||
bisectInfo: git_commands.NewNullBisectInfo(),
|
||||
cherryPickedCommitHashSet: set.New[string](),
|
||||
showYouAreHereLabel: false,
|
||||
now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
expected: formatExpected(`
|
||||
↓ hash3r ◯ │ commit3
|
||||
↑ hash1l ◯ commit1
|
||||
↑ hash2l ⏣─╮ commit2
|
||||
↑ hash3l ◯ │ commit3
|
||||
↑ hash4l ◯─╯ commit4
|
||||
↑ hash5l ◯ commit5
|
||||
`),
|
||||
},
|
||||
{
|
||||
testName: "graph in divergence view - not all local commits",
|
||||
commits: []*models.Commit{
|
||||
{Name: "commit1", Hash: "hash1r", Parents: []string{"hash2r"}, Divergence: models.DivergenceRight},
|
||||
{Name: "commit2", Hash: "hash2r", Parents: []string{"hash3r", "hash5r"}, Divergence: models.DivergenceRight},
|
||||
{Name: "commit3", Hash: "hash3r", Parents: []string{"hash4r"}, Divergence: models.DivergenceRight},
|
||||
{Name: "commit1", Hash: "hash1l", Parents: []string{"hash2l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit2", Hash: "hash2l", Parents: []string{"hash3l", "hash4l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit3", Hash: "hash3l", Parents: []string{"hash4l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit4", Hash: "hash4l", Parents: []string{"hash5l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit5", Hash: "hash5l", Parents: []string{"hash6l"}, Divergence: models.DivergenceLeft},
|
||||
},
|
||||
startIdx: 0,
|
||||
endIdx: 5,
|
||||
showGraph: true,
|
||||
bisectInfo: git_commands.NewNullBisectInfo(),
|
||||
cherryPickedCommitHashSet: set.New[string](),
|
||||
showYouAreHereLabel: false,
|
||||
now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
expected: formatExpected(`
|
||||
↓ hash1r ◯ commit1
|
||||
↓ hash2r ⏣─╮ commit2
|
||||
↓ hash3r ◯ │ commit3
|
||||
↑ hash1l ◯ commit1
|
||||
↑ hash2l ⏣─╮ commit2
|
||||
`),
|
||||
},
|
||||
{
|
||||
testName: "graph in divergence view - no remote commits visible",
|
||||
commits: []*models.Commit{
|
||||
{Name: "commit1", Hash: "hash1r", Parents: []string{"hash2r"}, Divergence: models.DivergenceRight},
|
||||
{Name: "commit2", Hash: "hash2r", Parents: []string{"hash3r", "hash5r"}, Divergence: models.DivergenceRight},
|
||||
{Name: "commit3", Hash: "hash3r", Parents: []string{"hash4r"}, Divergence: models.DivergenceRight},
|
||||
{Name: "commit1", Hash: "hash1l", Parents: []string{"hash2l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit2", Hash: "hash2l", Parents: []string{"hash3l", "hash4l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit3", Hash: "hash3l", Parents: []string{"hash4l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit4", Hash: "hash4l", Parents: []string{"hash5l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit5", Hash: "hash5l", Parents: []string{"hash6l"}, Divergence: models.DivergenceLeft},
|
||||
},
|
||||
startIdx: 4,
|
||||
endIdx: 8,
|
||||
showGraph: true,
|
||||
bisectInfo: git_commands.NewNullBisectInfo(),
|
||||
cherryPickedCommitHashSet: set.New[string](),
|
||||
showYouAreHereLabel: false,
|
||||
now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
expected: formatExpected(`
|
||||
↑ hash2l ⏣─╮ commit2
|
||||
↑ hash3l ◯ │ commit3
|
||||
↑ hash4l ◯─╯ commit4
|
||||
↑ hash5l ◯ commit5
|
||||
`),
|
||||
},
|
||||
{
|
||||
testName: "graph in divergence view - no local commits visible",
|
||||
commits: []*models.Commit{
|
||||
{Name: "commit1", Hash: "hash1r", Parents: []string{"hash2r"}, Divergence: models.DivergenceRight},
|
||||
{Name: "commit2", Hash: "hash2r", Parents: []string{"hash3r", "hash5r"}, Divergence: models.DivergenceRight},
|
||||
{Name: "commit3", Hash: "hash3r", Parents: []string{"hash4r"}, Divergence: models.DivergenceRight},
|
||||
{Name: "commit1", Hash: "hash1l", Parents: []string{"hash2l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit2", Hash: "hash2l", Parents: []string{"hash3l", "hash4l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit3", Hash: "hash3l", Parents: []string{"hash4l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit4", Hash: "hash4l", Parents: []string{"hash5l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit5", Hash: "hash5l", Parents: []string{"hash6l"}, Divergence: models.DivergenceLeft},
|
||||
},
|
||||
startIdx: 0,
|
||||
endIdx: 2,
|
||||
showGraph: true,
|
||||
bisectInfo: git_commands.NewNullBisectInfo(),
|
||||
cherryPickedCommitHashSet: set.New[string](),
|
||||
showYouAreHereLabel: false,
|
||||
now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
expected: formatExpected(`
|
||||
↓ hash1r ◯ commit1
|
||||
↓ hash2r ⏣─╮ commit2
|
||||
`),
|
||||
},
|
||||
{
|
||||
testName: "graph in divergence view - no remote commits present",
|
||||
commits: []*models.Commit{
|
||||
{Name: "commit1", Hash: "hash1l", Parents: []string{"hash2l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit2", Hash: "hash2l", Parents: []string{"hash3l", "hash4l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit3", Hash: "hash3l", Parents: []string{"hash4l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit4", Hash: "hash4l", Parents: []string{"hash5l"}, Divergence: models.DivergenceLeft},
|
||||
{Name: "commit5", Hash: "hash5l", Parents: []string{"hash6l"}, Divergence: models.DivergenceLeft},
|
||||
},
|
||||
startIdx: 0,
|
||||
endIdx: 5,
|
||||
showGraph: true,
|
||||
bisectInfo: git_commands.NewNullBisectInfo(),
|
||||
cherryPickedCommitHashSet: set.New[string](),
|
||||
showYouAreHereLabel: false,
|
||||
now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
expected: formatExpected(`
|
||||
↑ hash1l ◯ commit1
|
||||
↑ hash2l ⏣─╮ commit2
|
||||
↑ hash3l ◯ │ commit3
|
||||
↑ hash4l ◯─╯ commit4
|
||||
↑ hash5l ◯ commit5
|
||||
`),
|
||||
},
|
||||
{
|
||||
testName: "graph in divergence view - no local commits present",
|
||||
commits: []*models.Commit{
|
||||
{Name: "commit1", Hash: "hash1r", Parents: []string{"hash2r"}, Divergence: models.DivergenceRight},
|
||||
{Name: "commit2", Hash: "hash2r", Parents: []string{"hash3r", "hash5r"}, Divergence: models.DivergenceRight},
|
||||
{Name: "commit3", Hash: "hash3r", Parents: []string{"hash4r"}, Divergence: models.DivergenceRight},
|
||||
},
|
||||
startIdx: 0,
|
||||
endIdx: 3,
|
||||
showGraph: true,
|
||||
bisectInfo: git_commands.NewNullBisectInfo(),
|
||||
cherryPickedCommitHashSet: set.New[string](),
|
||||
showYouAreHereLabel: false,
|
||||
now: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
expected: formatExpected(`
|
||||
↓ hash1r ◯ commit1
|
||||
↓ hash2r ⏣─╮ commit2
|
||||
↓ hash3r ◯ │ commit3
|
||||
`),
|
||||
},
|
||||
{
|
||||
testName: "custom time format",
|
||||
commits: []*models.Commit{
|
||||
@@ -575,6 +396,7 @@ func TestGetCommitListDisplayStrings(t *testing.T) {
|
||||
common := utils.NewDummyCommon()
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
if !focusing || s.focus {
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
result := GetCommitListDisplayStrings(
|
||||
|
||||
@@ -66,6 +66,7 @@ M file1
|
||||
defer color.ForceSetColorLevel(oldColorLevel)
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
viewModel := filetree.NewFileTree(func() []*models.File { return s.files }, utils.NewDummyLog(), true)
|
||||
viewModel.SetTree()
|
||||
@@ -127,6 +128,7 @@ M file1
|
||||
defer color.ForceSetColorLevel(oldColorLevel)
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
viewModel := filetree.NewCommitFileTreeViewModel(func() []*models.CommitFile { return s.files }, utils.NewDummyLog(), true)
|
||||
viewModel.SetRef(&models.Commit{})
|
||||
|
||||
@@ -84,6 +84,7 @@ func RenderAux(pipeSets [][]*Pipe, commits []*models.Commit, selectedCommitHash
|
||||
wg.Add(maxProcs)
|
||||
|
||||
for i := 0; i < maxProcs; i++ {
|
||||
i := i
|
||||
go func() {
|
||||
from := i * perProc
|
||||
to := (i + 1) * perProc
|
||||
|
||||
@@ -217,6 +217,7 @@ func TestRenderCommitGraph(t *testing.T) {
|
||||
defer color.ForceSetColorLevel(oldColorLevel)
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
getStyle := func(c *models.Commit) style.TextStyle { return style.FgDefault }
|
||||
lines := RenderCommitGraph(test.commits, "blah", getStyle)
|
||||
@@ -453,6 +454,7 @@ func TestRenderPipeSet(t *testing.T) {
|
||||
defer color.ForceSetColorLevel(oldColorLevel)
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
actualStr := renderPipeSet(test.pipes, "selected", test.prevCommit)
|
||||
t.Log("actual cells:")
|
||||
|
||||
@@ -81,6 +81,7 @@ func TestMenuGenerator(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
s.test(NewMenuGenerator(utils.NewDummyCommon()).call(s.cmdOut, s.filter, s.valueFormat, s.labelFormat))
|
||||
})
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
package custom_commands
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/stefanhaller/git-todo-parser/todo"
|
||||
)
|
||||
|
||||
// We create shims for all the model classes in order to get a more stable API
|
||||
// for custom commands. At the moment these are almost identical to the model
|
||||
// classes, but this allows us to add "private" fields to the model classes that
|
||||
// we don't want to expose to custom commands, or rename a model field to a
|
||||
// better name without breaking people's custom commands. In such a case we add
|
||||
// the new, better name to the shim but keep the old one for backwards
|
||||
// compatibility. We already did this for Commit.Sha, which was renamed to Hash.
|
||||
|
||||
type Commit struct {
|
||||
Hash string // deprecated: use Sha
|
||||
Sha string
|
||||
Name string
|
||||
Status models.CommitStatus
|
||||
Action todo.TodoCommand
|
||||
Tags []string
|
||||
ExtraInfo string
|
||||
AuthorName string
|
||||
AuthorEmail string
|
||||
UnixTimestamp int64
|
||||
Divergence models.Divergence
|
||||
Parents []string
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Name string
|
||||
PreviousName string
|
||||
HasStagedChanges bool
|
||||
HasUnstagedChanges bool
|
||||
Tracked bool
|
||||
Added bool
|
||||
Deleted bool
|
||||
HasMergeConflicts bool
|
||||
HasInlineMergeConflicts bool
|
||||
DisplayString string
|
||||
ShortStatus string
|
||||
IsWorktree bool
|
||||
}
|
||||
|
||||
type Branch struct {
|
||||
Name string
|
||||
DisplayName string
|
||||
Recency string
|
||||
Pushables string // deprecated: use AheadForPull
|
||||
Pullables string // deprecated: use BehindForPull
|
||||
AheadForPull string
|
||||
BehindForPull string
|
||||
AheadForPush string
|
||||
BehindForPush string
|
||||
UpstreamGone bool
|
||||
Head bool
|
||||
DetachedHead bool
|
||||
UpstreamRemote string
|
||||
UpstreamBranch string
|
||||
Subject string
|
||||
CommitHash string
|
||||
}
|
||||
|
||||
type RemoteBranch struct {
|
||||
Name string
|
||||
RemoteName string
|
||||
}
|
||||
|
||||
type Remote struct {
|
||||
Name string
|
||||
Urls []string
|
||||
Branches []*RemoteBranch
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
Name string
|
||||
Message string
|
||||
}
|
||||
|
||||
type StashEntry struct {
|
||||
Index int
|
||||
Recency string
|
||||
Name string
|
||||
}
|
||||
|
||||
type CommitFile struct {
|
||||
Name string
|
||||
ChangeStatus string
|
||||
}
|
||||
|
||||
type Worktree struct {
|
||||
IsMain bool
|
||||
IsCurrent bool
|
||||
Path string
|
||||
IsPathMissing bool
|
||||
GitDir string
|
||||
Branch string
|
||||
Name string
|
||||
}
|
||||
@@ -72,6 +72,7 @@ func (self *Resolver) resolvePrompt(
|
||||
func (self *Resolver) resolveMenuOptions(prompt *config.CustomCommandPrompt, resolveTemplate func(string) (string, error)) ([]config.CustomCommandMenuOption, error) {
|
||||
newOptions := make([]config.CustomCommandMenuOption, 0, len(prompt.Options))
|
||||
for _, option := range prompt.Options {
|
||||
option := option
|
||||
newOption, err := self.resolveMenuOption(&option, resolveTemplate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -3,7 +3,7 @@ package custom_commands
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||
"github.com/samber/lo"
|
||||
"github.com/stefanhaller/git-todo-parser/todo"
|
||||
)
|
||||
|
||||
// loads the session state at the time that a custom command is invoked, for use
|
||||
@@ -20,7 +20,22 @@ func NewSessionStateLoader(c *helpers.HelperCommon, refsHelper *helpers.RefsHelp
|
||||
}
|
||||
}
|
||||
|
||||
func commitShimFromModelCommit(commit *models.Commit) *Commit {
|
||||
type Commit struct {
|
||||
Hash string
|
||||
Sha string
|
||||
Name string
|
||||
Status models.CommitStatus
|
||||
Action todo.TodoCommand
|
||||
Tags []string
|
||||
ExtraInfo string
|
||||
AuthorName string
|
||||
AuthorEmail string
|
||||
UnixTimestamp int64
|
||||
Divergence models.Divergence
|
||||
Parents []string
|
||||
}
|
||||
|
||||
func commitWrapperFromModelCommit(commit *models.Commit) *Commit {
|
||||
if commit == nil {
|
||||
return nil
|
||||
}
|
||||
@@ -41,160 +56,39 @@ func commitShimFromModelCommit(commit *models.Commit) *Commit {
|
||||
}
|
||||
}
|
||||
|
||||
func fileShimFromModelFile(file *models.File) *File {
|
||||
if file == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &File{
|
||||
Name: file.Name,
|
||||
PreviousName: file.PreviousName,
|
||||
HasStagedChanges: file.HasStagedChanges,
|
||||
HasUnstagedChanges: file.HasUnstagedChanges,
|
||||
Tracked: file.Tracked,
|
||||
Added: file.Added,
|
||||
Deleted: file.Deleted,
|
||||
HasMergeConflicts: file.HasMergeConflicts,
|
||||
HasInlineMergeConflicts: file.HasInlineMergeConflicts,
|
||||
DisplayString: file.DisplayString,
|
||||
ShortStatus: file.ShortStatus,
|
||||
IsWorktree: file.IsWorktree,
|
||||
}
|
||||
}
|
||||
|
||||
func branchShimFromModelBranch(branch *models.Branch) *Branch {
|
||||
if branch == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Branch{
|
||||
Name: branch.Name,
|
||||
DisplayName: branch.DisplayName,
|
||||
Recency: branch.Recency,
|
||||
Pushables: branch.AheadForPull,
|
||||
Pullables: branch.BehindForPull,
|
||||
AheadForPull: branch.AheadForPull,
|
||||
BehindForPull: branch.BehindForPull,
|
||||
AheadForPush: branch.AheadForPush,
|
||||
BehindForPush: branch.BehindForPush,
|
||||
UpstreamGone: branch.UpstreamGone,
|
||||
Head: branch.Head,
|
||||
DetachedHead: branch.DetachedHead,
|
||||
UpstreamRemote: branch.UpstreamRemote,
|
||||
UpstreamBranch: branch.UpstreamBranch,
|
||||
Subject: branch.Subject,
|
||||
CommitHash: branch.CommitHash,
|
||||
}
|
||||
}
|
||||
|
||||
func remoteBranchShimFromModelRemoteBranch(remoteBranch *models.RemoteBranch) *RemoteBranch {
|
||||
if remoteBranch == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &RemoteBranch{
|
||||
Name: remoteBranch.Name,
|
||||
RemoteName: remoteBranch.RemoteName,
|
||||
}
|
||||
}
|
||||
|
||||
func remoteShimFromModelRemote(remote *models.Remote) *Remote {
|
||||
if remote == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Remote{
|
||||
Name: remote.Name,
|
||||
Urls: remote.Urls,
|
||||
Branches: lo.Map(remote.Branches, func(branch *models.RemoteBranch, _ int) *RemoteBranch {
|
||||
return remoteBranchShimFromModelRemoteBranch(branch)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func tagShimFromModelRemote(tag *models.Tag) *Tag {
|
||||
if tag == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Tag{
|
||||
Name: tag.Name,
|
||||
Message: tag.Message,
|
||||
}
|
||||
}
|
||||
|
||||
func stashEntryShimFromModelRemote(stashEntry *models.StashEntry) *StashEntry {
|
||||
if stashEntry == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &StashEntry{
|
||||
Index: stashEntry.Index,
|
||||
Recency: stashEntry.Recency,
|
||||
Name: stashEntry.Name,
|
||||
}
|
||||
}
|
||||
|
||||
func commitFileShimFromModelRemote(commitFile *models.CommitFile) *CommitFile {
|
||||
if commitFile == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &CommitFile{
|
||||
Name: commitFile.Name,
|
||||
ChangeStatus: commitFile.ChangeStatus,
|
||||
}
|
||||
}
|
||||
|
||||
func worktreeShimFromModelRemote(worktree *models.Worktree) *Worktree {
|
||||
if worktree == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &Worktree{
|
||||
IsMain: worktree.IsMain,
|
||||
IsCurrent: worktree.IsCurrent,
|
||||
Path: worktree.Path,
|
||||
IsPathMissing: worktree.IsPathMissing,
|
||||
GitDir: worktree.GitDir,
|
||||
Branch: worktree.Branch,
|
||||
Name: worktree.Name,
|
||||
}
|
||||
}
|
||||
|
||||
// SessionState captures the current state of the application for use in custom commands
|
||||
type SessionState struct {
|
||||
SelectedLocalCommit *Commit
|
||||
SelectedReflogCommit *Commit
|
||||
SelectedSubCommit *Commit
|
||||
SelectedFile *File
|
||||
SelectedFile *models.File
|
||||
SelectedPath string
|
||||
SelectedLocalBranch *Branch
|
||||
SelectedRemoteBranch *RemoteBranch
|
||||
SelectedRemote *Remote
|
||||
SelectedTag *Tag
|
||||
SelectedStashEntry *StashEntry
|
||||
SelectedCommitFile *CommitFile
|
||||
SelectedLocalBranch *models.Branch
|
||||
SelectedRemoteBranch *models.RemoteBranch
|
||||
SelectedRemote *models.Remote
|
||||
SelectedTag *models.Tag
|
||||
SelectedStashEntry *models.StashEntry
|
||||
SelectedCommitFile *models.CommitFile
|
||||
SelectedCommitFilePath string
|
||||
SelectedWorktree *Worktree
|
||||
CheckedOutBranch *Branch
|
||||
SelectedWorktree *models.Worktree
|
||||
CheckedOutBranch *models.Branch
|
||||
}
|
||||
|
||||
func (self *SessionStateLoader) call() *SessionState {
|
||||
return &SessionState{
|
||||
SelectedFile: fileShimFromModelFile(self.c.Contexts().Files.GetSelectedFile()),
|
||||
SelectedFile: self.c.Contexts().Files.GetSelectedFile(),
|
||||
SelectedPath: self.c.Contexts().Files.GetSelectedPath(),
|
||||
SelectedLocalCommit: commitShimFromModelCommit(self.c.Contexts().LocalCommits.GetSelected()),
|
||||
SelectedReflogCommit: commitShimFromModelCommit(self.c.Contexts().ReflogCommits.GetSelected()),
|
||||
SelectedLocalBranch: branchShimFromModelBranch(self.c.Contexts().Branches.GetSelected()),
|
||||
SelectedRemoteBranch: remoteBranchShimFromModelRemoteBranch(self.c.Contexts().RemoteBranches.GetSelected()),
|
||||
SelectedRemote: remoteShimFromModelRemote(self.c.Contexts().Remotes.GetSelected()),
|
||||
SelectedTag: tagShimFromModelRemote(self.c.Contexts().Tags.GetSelected()),
|
||||
SelectedStashEntry: stashEntryShimFromModelRemote(self.c.Contexts().Stash.GetSelected()),
|
||||
SelectedCommitFile: commitFileShimFromModelRemote(self.c.Contexts().CommitFiles.GetSelectedFile()),
|
||||
SelectedLocalCommit: commitWrapperFromModelCommit(self.c.Contexts().LocalCommits.GetSelected()),
|
||||
SelectedReflogCommit: commitWrapperFromModelCommit(self.c.Contexts().ReflogCommits.GetSelected()),
|
||||
SelectedLocalBranch: self.c.Contexts().Branches.GetSelected(),
|
||||
SelectedRemoteBranch: self.c.Contexts().RemoteBranches.GetSelected(),
|
||||
SelectedRemote: self.c.Contexts().Remotes.GetSelected(),
|
||||
SelectedTag: self.c.Contexts().Tags.GetSelected(),
|
||||
SelectedStashEntry: self.c.Contexts().Stash.GetSelected(),
|
||||
SelectedCommitFile: self.c.Contexts().CommitFiles.GetSelectedFile(),
|
||||
SelectedCommitFilePath: self.c.Contexts().CommitFiles.GetSelectedPath(),
|
||||
SelectedSubCommit: commitShimFromModelCommit(self.c.Contexts().SubCommits.GetSelected()),
|
||||
SelectedWorktree: worktreeShimFromModelRemote(self.c.Contexts().Worktrees.GetSelected()),
|
||||
CheckedOutBranch: branchShimFromModelBranch(self.refsHelper.GetCheckedOutRef()),
|
||||
SelectedSubCommit: commitWrapperFromModelCommit(self.c.Contexts().SubCommits.GetSelected()),
|
||||
SelectedWorktree: self.c.Contexts().Worktrees.GetSelected(),
|
||||
CheckedOutBranch: self.refsHelper.GetCheckedOutRef(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,6 +161,7 @@ func TestMerge(t *testing.T) {
|
||||
defer color.ForceSetColorLevel(oldColorLevel)
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
style := New()
|
||||
for _, other := range s.toMerge {
|
||||
@@ -211,6 +212,7 @@ func TestTemplateFuncMapAddColors(t *testing.T) {
|
||||
defer color.ForceSetColorLevel(oldColorLevel)
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
tmpl, err := template.New("test template").Funcs(TemplateFuncMapAddColors(template.FuncMap{})).Parse(s.tmpl)
|
||||
assert.NoError(t, err)
|
||||
|
||||
@@ -165,18 +165,16 @@ type CreateMenuOptions struct {
|
||||
}
|
||||
|
||||
type CreatePopupPanelOpts struct {
|
||||
HasLoader bool
|
||||
Editable bool
|
||||
Title string
|
||||
Prompt string
|
||||
HandleConfirm func() error
|
||||
HandleConfirmPrompt func(string) error
|
||||
HandleClose func() error
|
||||
HandleDeleteSuggestion func(int) error
|
||||
HasLoader bool
|
||||
Editable bool
|
||||
Title string
|
||||
Prompt string
|
||||
HandleConfirm func() error
|
||||
HandleConfirmPrompt func(string) error
|
||||
HandleClose func() error
|
||||
|
||||
FindSuggestionsFunc func(string) []*Suggestion
|
||||
Mask bool
|
||||
AllowEditSuggestion bool
|
||||
}
|
||||
|
||||
type ConfirmOpts struct {
|
||||
@@ -194,11 +192,9 @@ type PromptOpts struct {
|
||||
InitialContent string
|
||||
FindSuggestionsFunc func(string) []*Suggestion
|
||||
HandleConfirm func(string) error
|
||||
AllowEditSuggestion bool
|
||||
// CAPTURE THIS
|
||||
HandleClose func() error
|
||||
HandleDeleteSuggestion func(int) error
|
||||
Mask bool
|
||||
HandleClose func() error
|
||||
Mask bool
|
||||
}
|
||||
|
||||
type MenuSection struct {
|
||||
|
||||
@@ -631,7 +631,6 @@ type TranslationSet struct {
|
||||
SuggestionsCheatsheetTitle string
|
||||
// Unlike the cheatsheet title above, the real suggestions title has a little message saying press tab to focus
|
||||
SuggestionsTitle string
|
||||
SuggestionsSubtitle string
|
||||
ExtrasTitle string
|
||||
PushingTagStatus string
|
||||
PullRequestURLCopiedToClipboard string
|
||||
@@ -1594,7 +1593,6 @@ func EnglishTranslationSet() TranslationSet {
|
||||
NavigationTitle: "List panel navigation",
|
||||
SuggestionsCheatsheetTitle: "Suggestions",
|
||||
SuggestionsTitle: "Suggestions (press %s to focus)",
|
||||
SuggestionsSubtitle: "(press %s to delete, %s to edit)",
|
||||
ExtrasTitle: "Command log",
|
||||
PushingTagStatus: "Pushing tag",
|
||||
PullRequestURLCopiedToClipboard: "Pull request URL copied to clipboard",
|
||||
|
||||
@@ -82,21 +82,3 @@ func (self *PromptDriver) ConfirmSuggestion(matcher *TextMatcher) {
|
||||
NavigateToLine(matcher).
|
||||
PressEnter()
|
||||
}
|
||||
|
||||
func (self *PromptDriver) DeleteSuggestion(matcher *TextMatcher) *PromptDriver {
|
||||
self.t.press(self.t.keys.Universal.TogglePanel)
|
||||
self.t.Views().Suggestions().
|
||||
IsFocused().
|
||||
NavigateToLine(matcher)
|
||||
self.t.press(self.t.keys.Universal.Remove)
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *PromptDriver) EditSuggestion(matcher *TextMatcher) *PromptDriver {
|
||||
self.t.press(self.t.keys.Universal.TogglePanel)
|
||||
self.t.Views().Suggestions().
|
||||
IsFocused().
|
||||
NavigateToLine(matcher)
|
||||
self.t.press(self.t.keys.Universal.Edit)
|
||||
return self
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ func RunTests(args RunTestArgs) error {
|
||||
}
|
||||
|
||||
for _, test := range args.Tests {
|
||||
test := test
|
||||
|
||||
args.TestWrapper(test, func() error { //nolint: thelper
|
||||
paths := NewPaths(
|
||||
filepath.Join(testDir, test.Name()),
|
||||
|
||||
@@ -195,10 +195,6 @@ func (self *Shell) CreateAnnotatedTag(name string, message string, ref string) *
|
||||
}
|
||||
|
||||
func (self *Shell) PushBranch(upstream, branch string) *Shell {
|
||||
return self.RunCommand([]string{"git", "push", upstream, branch})
|
||||
}
|
||||
|
||||
func (self *Shell) PushBranchAndSetUpstream(upstream, branch string) *Shell {
|
||||
return self.RunCommand([]string{"git", "push", "--set-upstream", upstream, branch})
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,9 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
CloneIntoRemote("origin").
|
||||
EmptyCommit("blah").
|
||||
NewBranch("branch-one").
|
||||
PushBranchAndSetUpstream("origin", "branch-one").
|
||||
PushBranch("origin", "branch-one").
|
||||
NewBranch("branch-two").
|
||||
PushBranchAndSetUpstream("origin", "branch-two").
|
||||
PushBranch("origin", "branch-two").
|
||||
EmptyCommit("deletion blocker").
|
||||
NewBranch("branch-three")
|
||||
},
|
||||
|
||||
@@ -18,7 +18,7 @@ var DeleteRemoteBranchWithCredentialPrompt = NewIntegrationTest(NewIntegrationTe
|
||||
|
||||
shell.NewBranch("mybranch")
|
||||
|
||||
shell.PushBranchAndSetUpstream("origin", "mybranch")
|
||||
shell.PushBranch("origin", "mybranch")
|
||||
|
||||
// actually getting a password prompt is tricky: it requires SSH'ing into localhost under a newly created, restricted, user.
|
||||
// This is not easy to do in a cross-platform way, nor is it easy to do in a docker container.
|
||||
|
||||
@@ -15,7 +15,7 @@ var RebaseToUpstream = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
CloneIntoRemote("origin").
|
||||
EmptyCommit("ensure-master").
|
||||
EmptyCommit("to-be-added"). // <- this will only exist remotely
|
||||
PushBranchAndSetUpstream("origin", "master").
|
||||
PushBranch("origin", "master").
|
||||
HardReset("HEAD~1").
|
||||
NewBranchFrom("base-branch", "master").
|
||||
EmptyCommit("base-branch-commit").
|
||||
|
||||
@@ -15,10 +15,10 @@ var ResetToUpstream = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
CloneIntoRemote("origin").
|
||||
NewBranch("hard-branch").
|
||||
EmptyCommit("hard commit").
|
||||
PushBranchAndSetUpstream("origin", "hard-branch").
|
||||
PushBranch("origin", "hard-branch").
|
||||
NewBranch("soft-branch").
|
||||
EmptyCommit("soft commit").
|
||||
PushBranchAndSetUpstream("origin", "soft-branch").
|
||||
PushBranch("origin", "soft-branch").
|
||||
NewBranch("base").
|
||||
EmptyCommit("base-branch commit").
|
||||
CreateFile("file-1", "content").
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
package commit
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var CommitWithGlobalPrefix = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Commit with defined config commitPrefix",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(testConfig *config.AppConfig) {
|
||||
testConfig.UserConfig.Git.CommitPrefix = &config.CommitPrefixConfig{Pattern: "^\\w+\\/(\\w+-\\w+).*", Replace: "[$1]: "}
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.NewBranch("feature/TEST-001")
|
||||
shell.CreateFile("test-commit-prefix", "This is foo bar")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
IsEmpty()
|
||||
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
PressPrimaryAction().
|
||||
Press(keys.Files.CommitChanges)
|
||||
|
||||
t.ExpectPopup().CommitMessagePanel().
|
||||
Title(Equals("Commit summary")).
|
||||
InitialText(Equals("[TEST-001]: ")).
|
||||
Type("my commit message").
|
||||
Cancel()
|
||||
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Press(keys.Files.CommitChanges)
|
||||
|
||||
t.ExpectPopup().CommitMessagePanel().
|
||||
Title(Equals("Commit summary")).
|
||||
InitialText(Equals("[TEST-001]: my commit message")).
|
||||
Type(". Added something else").
|
||||
Confirm()
|
||||
|
||||
t.Views().Commits().Focus()
|
||||
t.Views().Main().Content(Contains("[TEST-001]: my commit message. Added something else"))
|
||||
},
|
||||
})
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
var CommitWithPrefix = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Commit with defined config commitPrefixes",
|
||||
Description: "Commit with defined config commitPrefix",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(testConfig *config.AppConfig) {
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
package custom_commands
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var DeleteFromHistory = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Delete an entry from the custom commands history",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupRepo: func(shell *Shell) {},
|
||||
SetupConfig: func(cfg *config.AppConfig) {},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
createCustomCommand := func(command string) {
|
||||
t.GlobalPress(keys.Universal.ExecuteCustomCommand)
|
||||
t.ExpectPopup().Prompt().
|
||||
Title(Equals("Custom command:")).
|
||||
Type(command).
|
||||
Confirm()
|
||||
}
|
||||
|
||||
createCustomCommand("echo 1")
|
||||
createCustomCommand("echo 2")
|
||||
createCustomCommand("echo 3")
|
||||
|
||||
t.GlobalPress(keys.Universal.ExecuteCustomCommand)
|
||||
t.ExpectPopup().Prompt().
|
||||
Title(Equals("Custom command:")).
|
||||
SuggestionLines(
|
||||
Contains("3"),
|
||||
Contains("2"),
|
||||
Contains("1"),
|
||||
).
|
||||
DeleteSuggestion(Contains("2")).
|
||||
SuggestionLines(
|
||||
Contains("3"),
|
||||
Contains("1"),
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -1,31 +0,0 @@
|
||||
package custom_commands
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var EditHistory = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Edit an entry from the custom commands history",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupRepo: func(shell *Shell) {},
|
||||
SetupConfig: func(cfg *config.AppConfig) {},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.GlobalPress(keys.Universal.ExecuteCustomCommand)
|
||||
t.ExpectPopup().Prompt().
|
||||
Title(Equals("Custom command:")).
|
||||
Type("echo x").
|
||||
Confirm()
|
||||
|
||||
t.GlobalPress(keys.Universal.ExecuteCustomCommand)
|
||||
t.ExpectPopup().Prompt().
|
||||
Title(Equals("Custom command:")).
|
||||
Type("ec").
|
||||
SuggestionLines(
|
||||
Equals("echo x"),
|
||||
).
|
||||
EditSuggestion(Equals("echo x")).
|
||||
InitialText(Equals("echo x"))
|
||||
},
|
||||
})
|
||||
@@ -1,70 +0,0 @@
|
||||
package stash
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var StashStagedPartialFile = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Stash staged changes when a file is partially staged",
|
||||
ExtraCmdArgs: []string{},
|
||||
GitVersion: AtLeast("git version 2.35.0"),
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateFileAndAdd("file-staged", "line1\nline2\nline3\nline4\n")
|
||||
shell.Commit("initial commit")
|
||||
shell.UpdateFile("file-staged", "line1\nline2 mod\nline3\nline4 mod\n")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
PressEnter()
|
||||
|
||||
t.Views().Staging().
|
||||
Content(
|
||||
Contains(" line1\n-line2\n+line2 mod\n line3\n-line4\n+line4 mod\n"),
|
||||
).
|
||||
PressPrimaryAction().
|
||||
PressPrimaryAction().
|
||||
Content(
|
||||
Contains(" line1\n line2 mod\n line3\n-line4\n+line4 mod\n"),
|
||||
).
|
||||
PressEscape()
|
||||
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Press(keys.Files.ViewStashOptions)
|
||||
|
||||
t.ExpectPopup().Menu().Title(Equals("Stash options")).Select(MatchesRegexp("Stash staged changes$")).Confirm()
|
||||
|
||||
t.ExpectPopup().Prompt().Title(Equals("Stash changes")).Type("my stashed file").Confirm()
|
||||
|
||||
t.Views().Stash().
|
||||
Focus().
|
||||
Lines(
|
||||
Contains("my stashed file"),
|
||||
).
|
||||
PressEnter()
|
||||
|
||||
t.Views().CommitFiles().
|
||||
IsFocused().
|
||||
Lines(
|
||||
Contains("file-staged").IsSelected(),
|
||||
)
|
||||
t.Views().Main().
|
||||
Content(
|
||||
Contains(" line1\n-line2\n+line2 mod\n line3\n line4\n"),
|
||||
)
|
||||
|
||||
t.Views().Files().
|
||||
Lines(
|
||||
Contains("file-staged"),
|
||||
)
|
||||
|
||||
t.Views().Staging().
|
||||
Content(
|
||||
Contains(" line1\n line2\n line3\n-line4\n+line4 mod\n"),
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -1,18 +0,0 @@
|
||||
package status
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var ClickRepoNameToOpenReposMenu = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Click on the repo name in the status side panel to open the recent repositories menu",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Status().Click(1, 0)
|
||||
t.ExpectPopup().Menu().Title(Equals("Recent repositories"))
|
||||
},
|
||||
})
|
||||
@@ -1,35 +0,0 @@
|
||||
package status
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var ClickToFocus = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Click in the status side panel to activate it",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Files().Focus()
|
||||
t.Views().Main().Lines(
|
||||
Contains("No changed files"),
|
||||
)
|
||||
|
||||
t.Views().Status().Click(0, 0)
|
||||
t.Views().Status().IsFocused()
|
||||
t.Views().Main().ContainsLines(
|
||||
Contains(` _`),
|
||||
Contains(` | | (_) |`),
|
||||
Contains(` | | __ _ _____ _ __ _ _| |_`),
|
||||
Contains(" | |/ _` |_ / | | |/ _` | | __|"),
|
||||
Contains(` | | (_| |/ /| |_| | (_| | | |_`),
|
||||
Contains(` |_|\__,_/___|\__, |\__, |_|\__|`),
|
||||
Contains(` __/ | __/ |`),
|
||||
Contains(` |___/ |___/`),
|
||||
Contains(``),
|
||||
Contains(`Copyright `),
|
||||
)
|
||||
},
|
||||
})
|
||||
@@ -1,27 +0,0 @@
|
||||
package status
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var ClickWorkingTreeStateToOpenRebaseOptionsMenu = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Click on the working tree state in the status side panel to open the rebase options menu",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateNCommits(2)
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Focus().
|
||||
Press(keys.Universal.Edit)
|
||||
|
||||
t.Views().Status().
|
||||
Content(Contains("(rebasing) repo")).
|
||||
Click(1, 0)
|
||||
|
||||
t.ExpectPopup().Menu().Title(Equals("Rebase options"))
|
||||
},
|
||||
})
|
||||
@@ -1,65 +0,0 @@
|
||||
package sync
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var ForcePushTriangular = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Push to a remote, requiring a force push because the branch is behind the remote push branch but not the upstream",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
GitVersion: AtLeast("2.22.0"),
|
||||
SetupConfig: func(config *config.AppConfig) {},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.SetConfig("push.default", "current")
|
||||
|
||||
shell.EmptyCommit("one")
|
||||
|
||||
shell.CloneIntoRemote("origin")
|
||||
|
||||
shell.NewBranch("feature")
|
||||
shell.SetBranchUpstream("feature", "origin/master")
|
||||
shell.EmptyCommit("two")
|
||||
shell.PushBranch("origin", "feature")
|
||||
|
||||
// remove the 'two' commit so that we are behind the push branch
|
||||
shell.HardReset("HEAD^")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Commits().
|
||||
Lines(
|
||||
Contains("one"),
|
||||
)
|
||||
|
||||
t.Views().Status().Content(Contains("✓ repo → feature"))
|
||||
|
||||
t.Views().Files().IsFocused().Press(keys.Universal.Push)
|
||||
|
||||
t.ExpectPopup().Confirmation().
|
||||
Title(Equals("Force push")).
|
||||
Content(Equals("Your branch has diverged from the remote branch. Press <esc> to cancel, or <enter> to force push.")).
|
||||
Confirm()
|
||||
|
||||
t.Views().Commits().
|
||||
Lines(
|
||||
Contains("one"),
|
||||
)
|
||||
|
||||
t.Views().Status().Content(Contains("✓ repo → feature"))
|
||||
|
||||
t.Views().Remotes().Focus().
|
||||
Lines(Contains("origin")).
|
||||
PressEnter()
|
||||
|
||||
t.Views().RemoteBranches().IsFocused().
|
||||
Lines(
|
||||
Contains("feature"),
|
||||
Contains("master"),
|
||||
).
|
||||
PressEnter()
|
||||
|
||||
t.Views().SubCommits().IsFocused().
|
||||
Lines(Contains("one"))
|
||||
},
|
||||
})
|
||||
@@ -23,7 +23,6 @@ import (
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests/reflog"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests/staging"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests/stash"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests/status"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests/submodule"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests/sync"
|
||||
"github.com/jesseduffield/lazygit/pkg/integration/tests/tag"
|
||||
@@ -73,7 +72,6 @@ var tests = []*components.IntegrationTest{
|
||||
commit.CommitMultiline,
|
||||
commit.CommitSwitchToEditor,
|
||||
commit.CommitWipWithPrefix,
|
||||
commit.CommitWithGlobalPrefix,
|
||||
commit.CommitWithPrefix,
|
||||
commit.CreateAmendCommit,
|
||||
commit.CreateTag,
|
||||
@@ -105,8 +103,6 @@ var tests = []*components.IntegrationTest{
|
||||
custom_commands.BasicCmdFromConfig,
|
||||
custom_commands.CheckForConflicts,
|
||||
custom_commands.ComplexCmdAtRuntime,
|
||||
custom_commands.DeleteFromHistory,
|
||||
custom_commands.EditHistory,
|
||||
custom_commands.FormPrompts,
|
||||
custom_commands.History,
|
||||
custom_commands.MenuFromCommand,
|
||||
@@ -259,11 +255,7 @@ var tests = []*components.IntegrationTest{
|
||||
stash.StashAndKeepIndex,
|
||||
stash.StashIncludingUntrackedFiles,
|
||||
stash.StashStaged,
|
||||
stash.StashStagedPartialFile,
|
||||
stash.StashUnstaged,
|
||||
status.ClickRepoNameToOpenReposMenu,
|
||||
status.ClickToFocus,
|
||||
status.ClickWorkingTreeStateToOpenRebaseOptionsMenu,
|
||||
submodule.Add,
|
||||
submodule.Enter,
|
||||
submodule.EnterNested,
|
||||
@@ -275,7 +267,6 @@ var tests = []*components.IntegrationTest{
|
||||
sync.ForcePush,
|
||||
sync.ForcePushMultipleMatching,
|
||||
sync.ForcePushMultipleUpstream,
|
||||
sync.ForcePushTriangular,
|
||||
sync.Pull,
|
||||
sync.PullAndSetUpstream,
|
||||
sync.PullMerge,
|
||||
|
||||
@@ -162,7 +162,7 @@ func (self *ViewBufferManager) NewCmdTask(start func() (*exec.Cmd, io.Reader), p
|
||||
done := make(chan struct{})
|
||||
|
||||
scanner := bufio.NewScanner(r)
|
||||
scanner.Split(utils.ScanLinesAndTruncateWhenLongerThanBuffer(bufio.MaxScanTokenSize))
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
lineChan := make(chan []byte)
|
||||
lineWrittenChan := make(chan struct{})
|
||||
|
||||
@@ -1,9 +1,6 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"strings"
|
||||
)
|
||||
import "strings"
|
||||
|
||||
// SplitLines takes a multiline string and splits it on newlines
|
||||
// currently we are also stripping \r's which may have adverse effects for
|
||||
@@ -46,57 +43,3 @@ func EscapeSpecialChars(str string) string {
|
||||
"\v", "\\v",
|
||||
).Replace(str)
|
||||
}
|
||||
|
||||
func dropCR(data []byte) []byte {
|
||||
if len(data) > 0 && data[len(data)-1] == '\r' {
|
||||
return data[0 : len(data)-1]
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// ScanLinesAndTruncateWhenLongerThanBuffer returns a split function that can be
|
||||
// used with bufio.Scanner.Split(). It is very similar to bufio.ScanLines,
|
||||
// except that it will truncate lines that are longer than the scanner's read
|
||||
// buffer (whereas bufio.ScanLines will return an error in that case, which is
|
||||
// often difficult to handle).
|
||||
//
|
||||
// If you are using your own buffer for the scanner, you must set maxBufferSize
|
||||
// to the same value as the max parameter that you passed to scanner.Buffer().
|
||||
// Otherwise, maxBufferSize must be set to bufio.MaxScanTokenSize.
|
||||
func ScanLinesAndTruncateWhenLongerThanBuffer(maxBufferSize int) func(data []byte, atEOF bool) (int, []byte, error) {
|
||||
skipOverRemainderOfLongLine := false
|
||||
|
||||
return func(data []byte, atEOF bool) (int, []byte, error) {
|
||||
if atEOF && len(data) == 0 {
|
||||
// Done
|
||||
return 0, nil, nil
|
||||
}
|
||||
if i := bytes.IndexByte(data, '\n'); i >= 0 {
|
||||
if skipOverRemainderOfLongLine {
|
||||
skipOverRemainderOfLongLine = false
|
||||
return i + 1, nil, nil
|
||||
}
|
||||
return i + 1, dropCR(data[0:i]), nil
|
||||
}
|
||||
if atEOF {
|
||||
if skipOverRemainderOfLongLine {
|
||||
return len(data), nil, nil
|
||||
}
|
||||
|
||||
return len(data), dropCR(data), nil
|
||||
}
|
||||
|
||||
// Buffer is full, so we can't get more data
|
||||
if len(data) >= maxBufferSize {
|
||||
if skipOverRemainderOfLongLine {
|
||||
return len(data), nil, nil
|
||||
}
|
||||
|
||||
skipOverRemainderOfLongLine = true
|
||||
return len(data), data, nil
|
||||
}
|
||||
|
||||
// Request more data.
|
||||
return 0, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -102,65 +100,3 @@ func TestNormalizeLinefeeds(t *testing.T) {
|
||||
assert.EqualValues(t, string(s.expected), NormalizeLinefeeds(string(s.byteArray)))
|
||||
}
|
||||
}
|
||||
|
||||
func TestScanLinesAndTruncateWhenLongerThanBuffer(t *testing.T) {
|
||||
type scenario struct {
|
||||
input string
|
||||
expectedLines []string
|
||||
}
|
||||
|
||||
scenarios := []scenario{
|
||||
{
|
||||
"",
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
"\n",
|
||||
[]string{""},
|
||||
},
|
||||
{
|
||||
"abc",
|
||||
[]string{"abc"},
|
||||
},
|
||||
{
|
||||
"abc\ndef",
|
||||
[]string{"abc", "def"},
|
||||
},
|
||||
{
|
||||
"abc\n\ndef",
|
||||
[]string{"abc", "", "def"},
|
||||
},
|
||||
{
|
||||
"abc\r\ndef\r",
|
||||
[]string{"abc", "def"},
|
||||
},
|
||||
{
|
||||
"abcdef",
|
||||
[]string{"abcde"},
|
||||
},
|
||||
{
|
||||
"abcdef\n",
|
||||
[]string{"abcde"},
|
||||
},
|
||||
{
|
||||
"abcdef\nghijkl\nx",
|
||||
[]string{"abcde", "ghijk", "x"},
|
||||
},
|
||||
{
|
||||
"abc\ndefghijklmnopqrstuvw\nx",
|
||||
[]string{"abc", "defgh", "x"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
scanner := bufio.NewScanner(strings.NewReader(s.input))
|
||||
scanner.Buffer(make([]byte, 5), 5)
|
||||
scanner.Split(ScanLinesAndTruncateWhenLongerThanBuffer(5))
|
||||
result := []string{}
|
||||
for scanner.Scan() {
|
||||
result = append(result, scanner.Text())
|
||||
}
|
||||
assert.NoError(t, scanner.Err())
|
||||
assert.EqualValues(t, s.expectedLines, result)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ func TestNextIndex(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
assert.EqualValues(t, s.expected, NextIndex(s.list, s.element))
|
||||
})
|
||||
@@ -92,6 +93,7 @@ func TestPrevIndex(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
assert.EqualValues(t, s.expected, PrevIndex(s.list, s.element))
|
||||
})
|
||||
@@ -124,6 +126,7 @@ func TestEscapeSpecialChars(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
assert.EqualValues(t, s.expected, EscapeSpecialChars(s.input))
|
||||
})
|
||||
@@ -300,6 +303,7 @@ func TestMoveElement(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
s := s
|
||||
t.Run(s.testName, func(t *testing.T) {
|
||||
assert.EqualValues(t, s.expected, MoveElement(s.list, s.from, s.to))
|
||||
})
|
||||
|
||||
@@ -102,6 +102,7 @@ func TestUpdateYamlValue(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
test := test
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
out, actualErr := UpdateYamlValue([]byte(test.in), test.path, test.value)
|
||||
if test.expectedErr == "" {
|
||||
|
||||
@@ -309,12 +309,6 @@
|
||||
"description": "If true (default), file icons are shown in the file views. Only relevant if NerdFontsVersion is not empty.",
|
||||
"default": true
|
||||
},
|
||||
"commitHashLength": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "Length of commit hash in commits view. 0 shows '*' if NF icons aren't on.",
|
||||
"default": 8
|
||||
},
|
||||
"showBranchCommitHash": {
|
||||
"type": "boolean",
|
||||
"description": "If true, show commit hashes alongside branch names in the branches view."
|
||||
@@ -539,29 +533,6 @@
|
||||
"type": "boolean",
|
||||
"description": "If true, do not allow force pushes"
|
||||
},
|
||||
"commitPrefix": {
|
||||
"properties": {
|
||||
"pattern": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"description": "pattern to match on. E.g. for 'feature/AB-123' to match on the AB-123 use \"^\\\\w+\\\\/(\\\\w+-\\\\w+).*\"",
|
||||
"examples": [
|
||||
"^\\w+\\/(\\w+-\\w+).*"
|
||||
]
|
||||
},
|
||||
"replace": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"description": "Replace directive. E.g. for 'feature/AB-123' to start the commit message with 'AB-123 ' use \"[$1] \"",
|
||||
"examples": [
|
||||
"[$1] "
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"description": "See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#predefined-commit-message-prefix"
|
||||
},
|
||||
"commitPrefixes": {
|
||||
"additionalProperties": {
|
||||
"properties": {
|
||||
|
||||
Reference in New Issue
Block a user