Compare commits

..

1 Commits

Author SHA1 Message Date
Stefan Haller
179f69e034 test 2024-04-26 10:03:22 +02:00
99 changed files with 349 additions and 1409 deletions

View File

@@ -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:

View File

@@ -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:

View File

@@ -30,5 +30,5 @@ linters-settings:
max-func-lines: 0
run:
go: '1.22'
go: '1.21'
timeout: 10m

View File

@@ -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

File diff suppressed because one or more lines are too long

View File

@@ -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:

View File

@@ -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

View File

@@ -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)).

0
file.txt Normal file
View File

2
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/jesseduffield/lazygit
go 1.22
go 1.21
require (
github.com/adrg/xdg v0.4.0

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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)
})
}

View File

@@ -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())

View File

@@ -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`

View File

@@ -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{}

View File

@@ -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})

View File

@@ -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(),

View File

@@ -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)

View File

@@ -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),

View File

@@ -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,

View File

@@ -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(),

View File

@@ -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)

View File

@@ -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(),

View File

@@ -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)

View File

@@ -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).

View File

@@ -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

View File

@@ -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(),

View File

@@ -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()

View File

@@ -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))

View File

@@ -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()

View File

@@ -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()

View File

@@ -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(

View File

@@ -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{}

View File

@@ -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 != ""
}

View File

@@ -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))

View File

@@ -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)
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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",

View File

@@ -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{

View File

@@ -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,
)

View File

@@ -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.

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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`

View File

@@ -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),

View File

@@ -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 {

View File

@@ -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
}

View File

@@ -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

View File

@@ -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()
}
}

View File

@@ -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) },
},
}

View File

@@ -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)

View File

@@ -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.

View File

@@ -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)

View File

@@ -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))
})

View File

@@ -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()

View File

@@ -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

View File

@@ -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()

View File

@@ -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))
})

View File

@@ -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))
})

View File

@@ -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,
})
}

View File

@@ -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

View File

@@ -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,

View File

@@ -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),

View File

@@ -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(

View File

@@ -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{})

View File

@@ -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

View File

@@ -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:")

View File

@@ -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))
})

View File

@@ -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
}

View File

@@ -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

View File

@@ -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(),
}
}

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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",

View File

@@ -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
}

View File

@@ -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()),

View File

@@ -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})
}

View File

@@ -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")
},

View File

@@ -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.

View File

@@ -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").

View File

@@ -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").

View File

@@ -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"))
},
})

View File

@@ -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) {

View File

@@ -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"),
)
},
})

View File

@@ -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"))
},
})

View File

@@ -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"),
)
},
})

View File

@@ -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"))
},
})

View File

@@ -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 `),
)
},
})

View File

@@ -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"))
},
})

View File

@@ -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"))
},
})

View File

@@ -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,

View File

@@ -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{})

View File

@@ -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
}
}

View File

@@ -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)
}
}

View File

@@ -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))
})

View File

@@ -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 == "" {

View File

@@ -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": {