mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-07-03 00:57:52 +02:00
Correctly request force-pushing in a triangular workflow (#3528)
- **PR Description** Some people push to a different branch (or even remote) than they pull from. One example is described in #3437. Our logic of when to request a force push is not appropriate for these workflows: we check the configured upstream branch for divergence, but that's the one you pull from. We should instead check the push-to branch for divergence. Fixes #3437.
This commit is contained in:
@ -305,7 +305,7 @@ SelectedWorktree
|
|||||||
CheckedOutBranch
|
CheckedOutBranch
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
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).
|
||||||
|
|
||||||
## Keybinding collisions
|
## Keybinding collisions
|
||||||
|
|
||||||
|
@ -134,7 +134,7 @@ func NewGitCommandAux(
|
|||||||
worktreeCommands := git_commands.NewWorktreeCommands(gitCommon)
|
worktreeCommands := git_commands.NewWorktreeCommands(gitCommon)
|
||||||
blameCommands := git_commands.NewBlameCommands(gitCommon)
|
blameCommands := git_commands.NewBlameCommands(gitCommon)
|
||||||
|
|
||||||
branchLoader := git_commands.NewBranchLoader(cmn, cmd, branchCommands.CurrentBranchInfo, configCommands)
|
branchLoader := git_commands.NewBranchLoader(cmn, gitCommon, cmd, branchCommands.CurrentBranchInfo, configCommands)
|
||||||
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
|
commitFileLoader := git_commands.NewCommitFileLoader(cmn, cmd)
|
||||||
commitLoader := git_commands.NewCommitLoader(cmn, cmd, statusCommands.RebaseMode, gitCommon)
|
commitLoader := git_commands.NewCommitLoader(cmn, cmd, statusCommands.RebaseMode, gitCommon)
|
||||||
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
|
reflogCommitLoader := git_commands.NewReflogCommitLoader(cmn, cmd)
|
||||||
|
@ -40,6 +40,7 @@ type BranchInfo struct {
|
|||||||
// BranchLoader returns a list of Branch objects for the current repo
|
// BranchLoader returns a list of Branch objects for the current repo
|
||||||
type BranchLoader struct {
|
type BranchLoader struct {
|
||||||
*common.Common
|
*common.Common
|
||||||
|
*GitCommon
|
||||||
cmd oscommands.ICmdObjBuilder
|
cmd oscommands.ICmdObjBuilder
|
||||||
getCurrentBranchInfo func() (BranchInfo, error)
|
getCurrentBranchInfo func() (BranchInfo, error)
|
||||||
config BranchLoaderConfigCommands
|
config BranchLoaderConfigCommands
|
||||||
@ -47,12 +48,14 @@ type BranchLoader struct {
|
|||||||
|
|
||||||
func NewBranchLoader(
|
func NewBranchLoader(
|
||||||
cmn *common.Common,
|
cmn *common.Common,
|
||||||
|
gitCommon *GitCommon,
|
||||||
cmd oscommands.ICmdObjBuilder,
|
cmd oscommands.ICmdObjBuilder,
|
||||||
getCurrentBranchInfo func() (BranchInfo, error),
|
getCurrentBranchInfo func() (BranchInfo, error),
|
||||||
config BranchLoaderConfigCommands,
|
config BranchLoaderConfigCommands,
|
||||||
) *BranchLoader {
|
) *BranchLoader {
|
||||||
return &BranchLoader{
|
return &BranchLoader{
|
||||||
Common: cmn,
|
Common: cmn,
|
||||||
|
GitCommon: gitCommon,
|
||||||
cmd: cmd,
|
cmd: cmd,
|
||||||
getCurrentBranchInfo: getCurrentBranchInfo,
|
getCurrentBranchInfo: getCurrentBranchInfo,
|
||||||
config: config,
|
config: config,
|
||||||
@ -61,7 +64,7 @@ func NewBranchLoader(
|
|||||||
|
|
||||||
// Load the list of branches for the current repo
|
// Load the list of branches for the current repo
|
||||||
func (self *BranchLoader) Load(reflogCommits []*models.Commit) ([]*models.Branch, error) {
|
func (self *BranchLoader) Load(reflogCommits []*models.Commit) ([]*models.Branch, error) {
|
||||||
branches := self.obtainBranches()
|
branches := self.obtainBranches(self.version.IsAtLeast(2, 22, 0))
|
||||||
|
|
||||||
if self.AppState.LocalBranchSortOrder == "recency" {
|
if self.AppState.LocalBranchSortOrder == "recency" {
|
||||||
reflogBranches := self.obtainReflogBranches(reflogCommits)
|
reflogBranches := self.obtainReflogBranches(reflogCommits)
|
||||||
@ -124,7 +127,7 @@ func (self *BranchLoader) Load(reflogCommits []*models.Commit) ([]*models.Branch
|
|||||||
return branches, nil
|
return branches, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *BranchLoader) obtainBranches() []*models.Branch {
|
func (self *BranchLoader) obtainBranches(canUsePushTrack bool) []*models.Branch {
|
||||||
output, err := self.getRawBranches()
|
output, err := self.getRawBranches()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
@ -147,7 +150,7 @@ func (self *BranchLoader) obtainBranches() []*models.Branch {
|
|||||||
}
|
}
|
||||||
|
|
||||||
storeCommitDateAsRecency := self.AppState.LocalBranchSortOrder != "recency"
|
storeCommitDateAsRecency := self.AppState.LocalBranchSortOrder != "recency"
|
||||||
return obtainBranch(split, storeCommitDateAsRecency), true
|
return obtainBranch(split, storeCommitDateAsRecency, canUsePushTrack), true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,23 +186,31 @@ var branchFields = []string{
|
|||||||
"refname:short",
|
"refname:short",
|
||||||
"upstream:short",
|
"upstream:short",
|
||||||
"upstream:track",
|
"upstream:track",
|
||||||
|
"push:track",
|
||||||
"subject",
|
"subject",
|
||||||
"objectname",
|
"objectname",
|
||||||
"committerdate:unix",
|
"committerdate:unix",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtain branch information from parsed line output of getRawBranches()
|
// Obtain branch information from parsed line output of getRawBranches()
|
||||||
func obtainBranch(split []string, storeCommitDateAsRecency bool) *models.Branch {
|
func obtainBranch(split []string, storeCommitDateAsRecency bool, canUsePushTrack bool) *models.Branch {
|
||||||
headMarker := split[0]
|
headMarker := split[0]
|
||||||
fullName := split[1]
|
fullName := split[1]
|
||||||
upstreamName := split[2]
|
upstreamName := split[2]
|
||||||
track := split[3]
|
track := split[3]
|
||||||
subject := split[4]
|
pushTrack := split[4]
|
||||||
commitHash := split[5]
|
subject := split[5]
|
||||||
commitDate := split[6]
|
commitHash := split[6]
|
||||||
|
commitDate := split[7]
|
||||||
|
|
||||||
name := strings.TrimPrefix(fullName, "heads/")
|
name := strings.TrimPrefix(fullName, "heads/")
|
||||||
pushables, pullables, gone := parseUpstreamInfo(upstreamName, track)
|
aheadForPull, behindForPull, gone := parseUpstreamInfo(upstreamName, track)
|
||||||
|
var aheadForPush, behindForPush string
|
||||||
|
if canUsePushTrack {
|
||||||
|
aheadForPush, behindForPush, _ = parseUpstreamInfo(upstreamName, pushTrack)
|
||||||
|
} else {
|
||||||
|
aheadForPush, behindForPush = aheadForPull, behindForPull
|
||||||
|
}
|
||||||
|
|
||||||
recency := ""
|
recency := ""
|
||||||
if storeCommitDateAsRecency {
|
if storeCommitDateAsRecency {
|
||||||
@ -209,14 +220,16 @@ func obtainBranch(split []string, storeCommitDateAsRecency bool) *models.Branch
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &models.Branch{
|
return &models.Branch{
|
||||||
Name: name,
|
Name: name,
|
||||||
Recency: recency,
|
Recency: recency,
|
||||||
Pushables: pushables,
|
AheadForPull: aheadForPull,
|
||||||
Pullables: pullables,
|
BehindForPull: behindForPull,
|
||||||
UpstreamGone: gone,
|
AheadForPush: aheadForPush,
|
||||||
Head: headMarker == "*",
|
BehindForPush: behindForPush,
|
||||||
Subject: subject,
|
UpstreamGone: gone,
|
||||||
CommitHash: commitHash,
|
Head: headMarker == "*",
|
||||||
|
Subject: subject,
|
||||||
|
CommitHash: commitHash,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,10 +245,10 @@ func parseUpstreamInfo(upstreamName string, track string) (string, string, bool)
|
|||||||
return "?", "?", true
|
return "?", "?", true
|
||||||
}
|
}
|
||||||
|
|
||||||
pushables := parseDifference(track, `ahead (\d+)`)
|
ahead := parseDifference(track, `ahead (\d+)`)
|
||||||
pullables := parseDifference(track, `behind (\d+)`)
|
behind := parseDifference(track, `behind (\d+)`)
|
||||||
|
|
||||||
return pushables, pullables, false
|
return ahead, behind, false
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseDifference(track string, regexStr string) string {
|
func parseDifference(track string, regexStr string) string {
|
||||||
|
@ -25,89 +25,101 @@ func TestObtainBranch(t *testing.T) {
|
|||||||
scenarios := []scenario{
|
scenarios := []scenario{
|
||||||
{
|
{
|
||||||
testName: "TrimHeads",
|
testName: "TrimHeads",
|
||||||
input: []string{"", "heads/a_branch", "", "", "subject", "123", timeStamp},
|
input: []string{"", "heads/a_branch", "", "", "", "subject", "123", timeStamp},
|
||||||
storeCommitDateAsRecency: false,
|
storeCommitDateAsRecency: false,
|
||||||
expectedBranch: &models.Branch{
|
expectedBranch: &models.Branch{
|
||||||
Name: "a_branch",
|
Name: "a_branch",
|
||||||
Pushables: "?",
|
AheadForPull: "?",
|
||||||
Pullables: "?",
|
BehindForPull: "?",
|
||||||
Head: false,
|
AheadForPush: "?",
|
||||||
Subject: "subject",
|
BehindForPush: "?",
|
||||||
CommitHash: "123",
|
Head: false,
|
||||||
|
Subject: "subject",
|
||||||
|
CommitHash: "123",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
testName: "NoUpstream",
|
testName: "NoUpstream",
|
||||||
input: []string{"", "a_branch", "", "", "subject", "123", timeStamp},
|
input: []string{"", "a_branch", "", "", "", "subject", "123", timeStamp},
|
||||||
storeCommitDateAsRecency: false,
|
storeCommitDateAsRecency: false,
|
||||||
expectedBranch: &models.Branch{
|
expectedBranch: &models.Branch{
|
||||||
Name: "a_branch",
|
Name: "a_branch",
|
||||||
Pushables: "?",
|
AheadForPull: "?",
|
||||||
Pullables: "?",
|
BehindForPull: "?",
|
||||||
Head: false,
|
AheadForPush: "?",
|
||||||
Subject: "subject",
|
BehindForPush: "?",
|
||||||
CommitHash: "123",
|
Head: false,
|
||||||
|
Subject: "subject",
|
||||||
|
CommitHash: "123",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
testName: "IsHead",
|
testName: "IsHead",
|
||||||
input: []string{"*", "a_branch", "", "", "subject", "123", timeStamp},
|
input: []string{"*", "a_branch", "", "", "", "subject", "123", timeStamp},
|
||||||
storeCommitDateAsRecency: false,
|
storeCommitDateAsRecency: false,
|
||||||
expectedBranch: &models.Branch{
|
expectedBranch: &models.Branch{
|
||||||
Name: "a_branch",
|
Name: "a_branch",
|
||||||
Pushables: "?",
|
AheadForPull: "?",
|
||||||
Pullables: "?",
|
BehindForPull: "?",
|
||||||
Head: true,
|
AheadForPush: "?",
|
||||||
Subject: "subject",
|
BehindForPush: "?",
|
||||||
CommitHash: "123",
|
Head: true,
|
||||||
|
Subject: "subject",
|
||||||
|
CommitHash: "123",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
testName: "IsBehindAndAhead",
|
testName: "IsBehindAndAhead",
|
||||||
input: []string{"", "a_branch", "a_remote/a_branch", "[behind 2, ahead 3]", "subject", "123", timeStamp},
|
input: []string{"", "a_branch", "a_remote/a_branch", "[behind 2, ahead 3]", "[behind 2, ahead 3]", "subject", "123", timeStamp},
|
||||||
storeCommitDateAsRecency: false,
|
storeCommitDateAsRecency: false,
|
||||||
expectedBranch: &models.Branch{
|
expectedBranch: &models.Branch{
|
||||||
Name: "a_branch",
|
Name: "a_branch",
|
||||||
Pushables: "3",
|
AheadForPull: "3",
|
||||||
Pullables: "2",
|
BehindForPull: "2",
|
||||||
Head: false,
|
AheadForPush: "3",
|
||||||
Subject: "subject",
|
BehindForPush: "2",
|
||||||
CommitHash: "123",
|
Head: false,
|
||||||
|
Subject: "subject",
|
||||||
|
CommitHash: "123",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
testName: "RemoteBranchIsGone",
|
testName: "RemoteBranchIsGone",
|
||||||
input: []string{"", "a_branch", "a_remote/a_branch", "[gone]", "subject", "123", timeStamp},
|
input: []string{"", "a_branch", "a_remote/a_branch", "[gone]", "[gone]", "subject", "123", timeStamp},
|
||||||
storeCommitDateAsRecency: false,
|
storeCommitDateAsRecency: false,
|
||||||
expectedBranch: &models.Branch{
|
expectedBranch: &models.Branch{
|
||||||
Name: "a_branch",
|
Name: "a_branch",
|
||||||
UpstreamGone: true,
|
UpstreamGone: true,
|
||||||
Pushables: "?",
|
AheadForPull: "?",
|
||||||
Pullables: "?",
|
BehindForPull: "?",
|
||||||
Head: false,
|
AheadForPush: "?",
|
||||||
Subject: "subject",
|
BehindForPush: "?",
|
||||||
CommitHash: "123",
|
Head: false,
|
||||||
|
Subject: "subject",
|
||||||
|
CommitHash: "123",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
testName: "WithCommitDateAsRecency",
|
testName: "WithCommitDateAsRecency",
|
||||||
input: []string{"", "a_branch", "", "", "subject", "123", timeStamp},
|
input: []string{"", "a_branch", "", "", "", "subject", "123", timeStamp},
|
||||||
storeCommitDateAsRecency: true,
|
storeCommitDateAsRecency: true,
|
||||||
expectedBranch: &models.Branch{
|
expectedBranch: &models.Branch{
|
||||||
Name: "a_branch",
|
Name: "a_branch",
|
||||||
Recency: "2h",
|
Recency: "2h",
|
||||||
Pushables: "?",
|
AheadForPull: "?",
|
||||||
Pullables: "?",
|
BehindForPull: "?",
|
||||||
Head: false,
|
AheadForPush: "?",
|
||||||
Subject: "subject",
|
BehindForPush: "?",
|
||||||
CommitHash: "123",
|
Head: false,
|
||||||
|
Subject: "subject",
|
||||||
|
CommitHash: "123",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, s := range scenarios {
|
for _, s := range scenarios {
|
||||||
t.Run(s.testName, func(t *testing.T) {
|
t.Run(s.testName, func(t *testing.T) {
|
||||||
branch := obtainBranch(s.input, s.storeCommitDateAsRecency)
|
branch := obtainBranch(s.input, s.storeCommitDateAsRecency, true)
|
||||||
assert.EqualValues(t, s.expectedBranch, branch)
|
assert.EqualValues(t, s.expectedBranch, branch)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,14 @@ type Branch struct {
|
|||||||
DisplayName string
|
DisplayName string
|
||||||
// indicator of when the branch was last checked out e.g. '2d', '3m'
|
// indicator of when the branch was last checked out e.g. '2d', '3m'
|
||||||
Recency string
|
Recency string
|
||||||
// how many commits ahead we are from the remote branch (how many commits we can push)
|
// how many commits ahead we are from the remote branch (how many commits we can push, assuming we push to our tracked remote branch)
|
||||||
Pushables string
|
AheadForPull string
|
||||||
// how many commits behind we are from the remote branch (how many commits we can pull)
|
// how many commits behind we are from the remote branch (how many commits we can pull)
|
||||||
Pullables string
|
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
|
||||||
// whether the remote branch is 'gone' i.e. we're tracking a remote branch that has been deleted
|
// whether the remote branch is 'gone' i.e. we're tracking a remote branch that has been deleted
|
||||||
UpstreamGone bool
|
UpstreamGone bool
|
||||||
// whether this is the current branch. Exactly one branch should have this be true
|
// whether this is the current branch. Exactly one branch should have this be true
|
||||||
@ -80,26 +84,30 @@ func (b *Branch) IsTrackingRemote() bool {
|
|||||||
// we know that the remote branch is not stored locally based on our pushable/pullable
|
// we know that the remote branch is not stored locally based on our pushable/pullable
|
||||||
// count being question marks.
|
// count being question marks.
|
||||||
func (b *Branch) RemoteBranchStoredLocally() bool {
|
func (b *Branch) RemoteBranchStoredLocally() bool {
|
||||||
return b.IsTrackingRemote() && b.Pushables != "?" && b.Pullables != "?"
|
return b.IsTrackingRemote() && b.AheadForPull != "?" && b.BehindForPull != "?"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Branch) RemoteBranchNotStoredLocally() bool {
|
func (b *Branch) RemoteBranchNotStoredLocally() bool {
|
||||||
return b.IsTrackingRemote() && b.Pushables == "?" && b.Pullables == "?"
|
return b.IsTrackingRemote() && b.AheadForPull == "?" && b.BehindForPull == "?"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Branch) MatchesUpstream() bool {
|
func (b *Branch) MatchesUpstream() bool {
|
||||||
return b.RemoteBranchStoredLocally() && b.Pushables == "0" && b.Pullables == "0"
|
return b.RemoteBranchStoredLocally() && b.AheadForPull == "0" && b.BehindForPull == "0"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Branch) HasCommitsToPush() bool {
|
func (b *Branch) IsAheadForPull() bool {
|
||||||
return b.RemoteBranchStoredLocally() && b.Pushables != "0"
|
return b.RemoteBranchStoredLocally() && b.AheadForPull != "0"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Branch) HasCommitsToPull() bool {
|
func (b *Branch) IsBehindForPull() bool {
|
||||||
return b.RemoteBranchStoredLocally() && b.Pullables != "0"
|
return b.RemoteBranchStoredLocally() && b.BehindForPull != "0"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Branch) IsBehindForPush() bool {
|
||||||
|
return b.BehindForPush != "" && b.BehindForPush != "0"
|
||||||
}
|
}
|
||||||
|
|
||||||
// for when we're in a detached head state
|
// for when we're in a detached head state
|
||||||
func (b *Branch) IsRealBranch() bool {
|
func (b *Branch) IsRealBranch() bool {
|
||||||
return b.Pushables != "" && b.Pullables != ""
|
return b.AheadForPull != "" && b.BehindForPull != ""
|
||||||
}
|
}
|
||||||
|
@ -620,7 +620,7 @@ func (self *BranchesController) fastForward(branch *models.Branch) error {
|
|||||||
if !branch.RemoteBranchStoredLocally() {
|
if !branch.RemoteBranchStoredLocally() {
|
||||||
return errors.New(self.c.Tr.FwdNoLocalUpstream)
|
return errors.New(self.c.Tr.FwdNoLocalUpstream)
|
||||||
}
|
}
|
||||||
if branch.HasCommitsToPush() {
|
if branch.IsAheadForPull() {
|
||||||
return errors.New(self.c.Tr.FwdCommitsToPush)
|
return errors.New(self.c.Tr.FwdCommitsToPush)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,10 +87,10 @@ func (self *SyncController) branchCheckedOut(f func(*models.Branch) error) func(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *SyncController) push(currentBranch *models.Branch) error {
|
func (self *SyncController) push(currentBranch *models.Branch) error {
|
||||||
// if we have pullables we'll ask if the user wants to force push
|
// if we are behind our upstream branch we'll ask if the user wants to force push
|
||||||
if currentBranch.IsTrackingRemote() {
|
if currentBranch.IsTrackingRemote() {
|
||||||
opts := pushOpts{}
|
opts := pushOpts{}
|
||||||
if currentBranch.HasCommitsToPull() {
|
if currentBranch.IsBehindForPush() {
|
||||||
return self.requestToForcePush(currentBranch, opts)
|
return self.requestToForcePush(currentBranch, opts)
|
||||||
} else {
|
} else {
|
||||||
return self.pushAux(currentBranch, opts)
|
return self.pushAux(currentBranch, opts)
|
||||||
|
@ -196,11 +196,11 @@ func BranchStatus(
|
|||||||
}
|
}
|
||||||
|
|
||||||
result := ""
|
result := ""
|
||||||
if branch.HasCommitsToPush() {
|
if branch.IsAheadForPull() {
|
||||||
result = fmt.Sprintf("↑%s", branch.Pushables)
|
result = fmt.Sprintf("↑%s", branch.AheadForPull)
|
||||||
}
|
}
|
||||||
if branch.HasCommitsToPull() {
|
if branch.IsBehindForPull() {
|
||||||
result = fmt.Sprintf("%s↓%s", result, branch.Pullables)
|
result = fmt.Sprintf("%s↓%s", result, branch.BehindForPull)
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
@ -58,8 +58,8 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
|||||||
Name: "branch_name",
|
Name: "branch_name",
|
||||||
Recency: "1m",
|
Recency: "1m",
|
||||||
UpstreamRemote: "origin",
|
UpstreamRemote: "origin",
|
||||||
Pushables: "0",
|
AheadForPull: "0",
|
||||||
Pullables: "0",
|
BehindForPull: "0",
|
||||||
},
|
},
|
||||||
itemOperation: types.ItemOperationNone,
|
itemOperation: types.ItemOperationNone,
|
||||||
fullDescription: false,
|
fullDescription: false,
|
||||||
@ -73,8 +73,8 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
|||||||
Name: "branch_name",
|
Name: "branch_name",
|
||||||
Recency: "1m",
|
Recency: "1m",
|
||||||
UpstreamRemote: "origin",
|
UpstreamRemote: "origin",
|
||||||
Pushables: "3",
|
AheadForPull: "3",
|
||||||
Pullables: "5",
|
BehindForPull: "5",
|
||||||
},
|
},
|
||||||
itemOperation: types.ItemOperationNone,
|
itemOperation: types.ItemOperationNone,
|
||||||
fullDescription: false,
|
fullDescription: false,
|
||||||
@ -99,8 +99,8 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
|||||||
CommitHash: "1234567890",
|
CommitHash: "1234567890",
|
||||||
UpstreamRemote: "origin",
|
UpstreamRemote: "origin",
|
||||||
UpstreamBranch: "branch_name",
|
UpstreamBranch: "branch_name",
|
||||||
Pushables: "0",
|
AheadForPull: "0",
|
||||||
Pullables: "0",
|
BehindForPull: "0",
|
||||||
Subject: "commit title",
|
Subject: "commit title",
|
||||||
},
|
},
|
||||||
itemOperation: types.ItemOperationNone,
|
itemOperation: types.ItemOperationNone,
|
||||||
@ -144,8 +144,8 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
|||||||
Name: "branch_name",
|
Name: "branch_name",
|
||||||
Recency: "1m",
|
Recency: "1m",
|
||||||
UpstreamRemote: "origin",
|
UpstreamRemote: "origin",
|
||||||
Pushables: "0",
|
AheadForPull: "0",
|
||||||
Pullables: "0",
|
BehindForPull: "0",
|
||||||
},
|
},
|
||||||
itemOperation: types.ItemOperationNone,
|
itemOperation: types.ItemOperationNone,
|
||||||
fullDescription: false,
|
fullDescription: false,
|
||||||
@ -159,8 +159,8 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
|||||||
Name: "branch_name",
|
Name: "branch_name",
|
||||||
Recency: "1m",
|
Recency: "1m",
|
||||||
UpstreamRemote: "origin",
|
UpstreamRemote: "origin",
|
||||||
Pushables: "3",
|
AheadForPull: "3",
|
||||||
Pullables: "5",
|
BehindForPull: "5",
|
||||||
},
|
},
|
||||||
itemOperation: types.ItemOperationNone,
|
itemOperation: types.ItemOperationNone,
|
||||||
fullDescription: false,
|
fullDescription: false,
|
||||||
@ -212,8 +212,8 @@ func Test_getBranchDisplayStrings(t *testing.T) {
|
|||||||
CommitHash: "1234567890",
|
CommitHash: "1234567890",
|
||||||
UpstreamRemote: "origin",
|
UpstreamRemote: "origin",
|
||||||
UpstreamBranch: "branch_name",
|
UpstreamBranch: "branch_name",
|
||||||
Pushables: "0",
|
AheadForPull: "0",
|
||||||
Pullables: "0",
|
BehindForPull: "0",
|
||||||
Subject: "commit title",
|
Subject: "commit title",
|
||||||
},
|
},
|
||||||
itemOperation: types.ItemOperationNone,
|
itemOperation: types.ItemOperationNone,
|
||||||
|
100
pkg/gui/services/custom_commands/models.go
Normal file
100
pkg/gui/services/custom_commands/models.go
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
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
|
||||||
|
}
|
@ -3,7 +3,7 @@ package custom_commands
|
|||||||
import (
|
import (
|
||||||
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
||||||
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
"github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers"
|
||||||
"github.com/stefanhaller/git-todo-parser/todo"
|
"github.com/samber/lo"
|
||||||
)
|
)
|
||||||
|
|
||||||
// loads the session state at the time that a custom command is invoked, for use
|
// loads the session state at the time that a custom command is invoked, for use
|
||||||
@ -20,22 +20,7 @@ func NewSessionStateLoader(c *helpers.HelperCommon, refsHelper *helpers.RefsHelp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type Commit struct {
|
func commitShimFromModelCommit(commit *models.Commit) *Commit {
|
||||||
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 {
|
if commit == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -56,39 +41,160 @@ func commitWrapperFromModelCommit(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
|
// SessionState captures the current state of the application for use in custom commands
|
||||||
type SessionState struct {
|
type SessionState struct {
|
||||||
SelectedLocalCommit *Commit
|
SelectedLocalCommit *Commit
|
||||||
SelectedReflogCommit *Commit
|
SelectedReflogCommit *Commit
|
||||||
SelectedSubCommit *Commit
|
SelectedSubCommit *Commit
|
||||||
SelectedFile *models.File
|
SelectedFile *File
|
||||||
SelectedPath string
|
SelectedPath string
|
||||||
SelectedLocalBranch *models.Branch
|
SelectedLocalBranch *Branch
|
||||||
SelectedRemoteBranch *models.RemoteBranch
|
SelectedRemoteBranch *RemoteBranch
|
||||||
SelectedRemote *models.Remote
|
SelectedRemote *Remote
|
||||||
SelectedTag *models.Tag
|
SelectedTag *Tag
|
||||||
SelectedStashEntry *models.StashEntry
|
SelectedStashEntry *StashEntry
|
||||||
SelectedCommitFile *models.CommitFile
|
SelectedCommitFile *CommitFile
|
||||||
SelectedCommitFilePath string
|
SelectedCommitFilePath string
|
||||||
SelectedWorktree *models.Worktree
|
SelectedWorktree *Worktree
|
||||||
CheckedOutBranch *models.Branch
|
CheckedOutBranch *Branch
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *SessionStateLoader) call() *SessionState {
|
func (self *SessionStateLoader) call() *SessionState {
|
||||||
return &SessionState{
|
return &SessionState{
|
||||||
SelectedFile: self.c.Contexts().Files.GetSelectedFile(),
|
SelectedFile: fileShimFromModelFile(self.c.Contexts().Files.GetSelectedFile()),
|
||||||
SelectedPath: self.c.Contexts().Files.GetSelectedPath(),
|
SelectedPath: self.c.Contexts().Files.GetSelectedPath(),
|
||||||
SelectedLocalCommit: commitWrapperFromModelCommit(self.c.Contexts().LocalCommits.GetSelected()),
|
SelectedLocalCommit: commitShimFromModelCommit(self.c.Contexts().LocalCommits.GetSelected()),
|
||||||
SelectedReflogCommit: commitWrapperFromModelCommit(self.c.Contexts().ReflogCommits.GetSelected()),
|
SelectedReflogCommit: commitShimFromModelCommit(self.c.Contexts().ReflogCommits.GetSelected()),
|
||||||
SelectedLocalBranch: self.c.Contexts().Branches.GetSelected(),
|
SelectedLocalBranch: branchShimFromModelBranch(self.c.Contexts().Branches.GetSelected()),
|
||||||
SelectedRemoteBranch: self.c.Contexts().RemoteBranches.GetSelected(),
|
SelectedRemoteBranch: remoteBranchShimFromModelRemoteBranch(self.c.Contexts().RemoteBranches.GetSelected()),
|
||||||
SelectedRemote: self.c.Contexts().Remotes.GetSelected(),
|
SelectedRemote: remoteShimFromModelRemote(self.c.Contexts().Remotes.GetSelected()),
|
||||||
SelectedTag: self.c.Contexts().Tags.GetSelected(),
|
SelectedTag: tagShimFromModelRemote(self.c.Contexts().Tags.GetSelected()),
|
||||||
SelectedStashEntry: self.c.Contexts().Stash.GetSelected(),
|
SelectedStashEntry: stashEntryShimFromModelRemote(self.c.Contexts().Stash.GetSelected()),
|
||||||
SelectedCommitFile: self.c.Contexts().CommitFiles.GetSelectedFile(),
|
SelectedCommitFile: commitFileShimFromModelRemote(self.c.Contexts().CommitFiles.GetSelectedFile()),
|
||||||
SelectedCommitFilePath: self.c.Contexts().CommitFiles.GetSelectedPath(),
|
SelectedCommitFilePath: self.c.Contexts().CommitFiles.GetSelectedPath(),
|
||||||
SelectedSubCommit: commitWrapperFromModelCommit(self.c.Contexts().SubCommits.GetSelected()),
|
SelectedSubCommit: commitShimFromModelCommit(self.c.Contexts().SubCommits.GetSelected()),
|
||||||
SelectedWorktree: self.c.Contexts().Worktrees.GetSelected(),
|
SelectedWorktree: worktreeShimFromModelRemote(self.c.Contexts().Worktrees.GetSelected()),
|
||||||
CheckedOutBranch: self.refsHelper.GetCheckedOutRef(),
|
CheckedOutBranch: branchShimFromModelBranch(self.refsHelper.GetCheckedOutRef()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,6 +195,10 @@ func (self *Shell) CreateAnnotatedTag(name string, message string, ref string) *
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *Shell) PushBranch(upstream, branch string) *Shell {
|
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})
|
return self.RunCommand([]string{"git", "push", "--set-upstream", upstream, branch})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,9 +15,9 @@ var Delete = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
CloneIntoRemote("origin").
|
CloneIntoRemote("origin").
|
||||||
EmptyCommit("blah").
|
EmptyCommit("blah").
|
||||||
NewBranch("branch-one").
|
NewBranch("branch-one").
|
||||||
PushBranch("origin", "branch-one").
|
PushBranchAndSetUpstream("origin", "branch-one").
|
||||||
NewBranch("branch-two").
|
NewBranch("branch-two").
|
||||||
PushBranch("origin", "branch-two").
|
PushBranchAndSetUpstream("origin", "branch-two").
|
||||||
EmptyCommit("deletion blocker").
|
EmptyCommit("deletion blocker").
|
||||||
NewBranch("branch-three")
|
NewBranch("branch-three")
|
||||||
},
|
},
|
||||||
|
@ -18,7 +18,7 @@ var DeleteRemoteBranchWithCredentialPrompt = NewIntegrationTest(NewIntegrationTe
|
|||||||
|
|
||||||
shell.NewBranch("mybranch")
|
shell.NewBranch("mybranch")
|
||||||
|
|
||||||
shell.PushBranch("origin", "mybranch")
|
shell.PushBranchAndSetUpstream("origin", "mybranch")
|
||||||
|
|
||||||
// actually getting a password prompt is tricky: it requires SSH'ing into localhost under a newly created, restricted, user.
|
// 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.
|
// This is not easy to do in a cross-platform way, nor is it easy to do in a docker container.
|
||||||
|
@ -15,7 +15,7 @@ var RebaseToUpstream = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
CloneIntoRemote("origin").
|
CloneIntoRemote("origin").
|
||||||
EmptyCommit("ensure-master").
|
EmptyCommit("ensure-master").
|
||||||
EmptyCommit("to-be-added"). // <- this will only exist remotely
|
EmptyCommit("to-be-added"). // <- this will only exist remotely
|
||||||
PushBranch("origin", "master").
|
PushBranchAndSetUpstream("origin", "master").
|
||||||
HardReset("HEAD~1").
|
HardReset("HEAD~1").
|
||||||
NewBranchFrom("base-branch", "master").
|
NewBranchFrom("base-branch", "master").
|
||||||
EmptyCommit("base-branch-commit").
|
EmptyCommit("base-branch-commit").
|
||||||
|
@ -15,10 +15,10 @@ var ResetToUpstream = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
CloneIntoRemote("origin").
|
CloneIntoRemote("origin").
|
||||||
NewBranch("hard-branch").
|
NewBranch("hard-branch").
|
||||||
EmptyCommit("hard commit").
|
EmptyCommit("hard commit").
|
||||||
PushBranch("origin", "hard-branch").
|
PushBranchAndSetUpstream("origin", "hard-branch").
|
||||||
NewBranch("soft-branch").
|
NewBranch("soft-branch").
|
||||||
EmptyCommit("soft commit").
|
EmptyCommit("soft commit").
|
||||||
PushBranch("origin", "soft-branch").
|
PushBranchAndSetUpstream("origin", "soft-branch").
|
||||||
NewBranch("base").
|
NewBranch("base").
|
||||||
EmptyCommit("base-branch commit").
|
EmptyCommit("base-branch commit").
|
||||||
CreateFile("file-1", "content").
|
CreateFile("file-1", "content").
|
||||||
|
65
pkg/integration/tests/sync/force_push_triangular.go
Normal file
65
pkg/integration/tests/sync/force_push_triangular.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
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"))
|
||||||
|
},
|
||||||
|
})
|
@ -275,6 +275,7 @@ var tests = []*components.IntegrationTest{
|
|||||||
sync.ForcePush,
|
sync.ForcePush,
|
||||||
sync.ForcePushMultipleMatching,
|
sync.ForcePushMultipleMatching,
|
||||||
sync.ForcePushMultipleUpstream,
|
sync.ForcePushMultipleUpstream,
|
||||||
|
sync.ForcePushTriangular,
|
||||||
sync.Pull,
|
sync.Pull,
|
||||||
sync.PullAndSetUpstream,
|
sync.PullAndSetUpstream,
|
||||||
sync.PullMerge,
|
sync.PullMerge,
|
||||||
|
Reference in New Issue
Block a user