mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-06-04 23:37:41 +02:00
Allow to switch branches in Commit View
When the user checks out a commit which has a local branch ref attached to it, they can select between checking out the branch or checking out the commit as detached head.
This commit is contained in:
parent
03d7bc854e
commit
f4c8287143
@ -280,15 +280,7 @@ func (self *BasicCommitsController) createResetMenu(commit *models.Commit) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (self *BasicCommitsController) checkout(commit *models.Commit) error {
|
func (self *BasicCommitsController) checkout(commit *models.Commit) error {
|
||||||
self.c.Confirm(types.ConfirmOpts{
|
return self.c.Helpers().Refs.CreateCheckoutMenu(commit)
|
||||||
Title: self.c.Tr.CheckoutCommit,
|
|
||||||
Prompt: self.c.Tr.SureCheckoutThisCommit,
|
|
||||||
HandleConfirm: func() error {
|
|
||||||
self.c.LogAction(self.c.Tr.Actions.CheckoutCommit)
|
|
||||||
return self.c.Helpers().Refs.CheckoutRef(commit.Hash, types.CheckoutRefOptions{})
|
|
||||||
},
|
|
||||||
})
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (self *BasicCommitsController) copyRange(*models.Commit) error {
|
func (self *BasicCommitsController) copyRange(*models.Commit) error {
|
||||||
|
@ -18,6 +18,7 @@ type IRefsHelper interface {
|
|||||||
CheckoutRef(ref string, options types.CheckoutRefOptions) error
|
CheckoutRef(ref string, options types.CheckoutRefOptions) error
|
||||||
GetCheckedOutRef() *models.Branch
|
GetCheckedOutRef() *models.Branch
|
||||||
CreateGitResetMenu(ref string) error
|
CreateGitResetMenu(ref string) error
|
||||||
|
CreateCheckoutMenu(commit *models.Commit) error
|
||||||
ResetToRef(ref string, strength string, envVars []string) error
|
ResetToRef(ref string, strength string, envVars []string) error
|
||||||
NewBranch(from string, fromDescription string, suggestedBranchname string) error
|
NewBranch(from string, fromDescription string, suggestedBranchname string) error
|
||||||
}
|
}
|
||||||
@ -271,6 +272,53 @@ func (self *RefsHelper) CreateGitResetMenu(ref string) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (self *RefsHelper) CreateCheckoutMenu(commit *models.Commit) error {
|
||||||
|
branches := lo.Filter(self.c.Model().Branches, func(branch *models.Branch, _ int) bool {
|
||||||
|
return commit.Hash == branch.CommitHash && branch.Name != self.c.Model().CheckedOutBranch
|
||||||
|
})
|
||||||
|
|
||||||
|
hash := commit.Hash
|
||||||
|
var menuItems []*types.MenuItem
|
||||||
|
|
||||||
|
if len(branches) > 0 {
|
||||||
|
menuItems = append(menuItems, lo.Map(branches, func(branch *models.Branch, index int) *types.MenuItem {
|
||||||
|
var key types.Key
|
||||||
|
if index < 9 {
|
||||||
|
key = rune(index + 1 + '0') // Convert 1-based index to key
|
||||||
|
}
|
||||||
|
return &types.MenuItem{
|
||||||
|
LabelColumns: []string{fmt.Sprintf(self.c.Tr.Actions.CheckoutBranchAtCommit, branch.Name)},
|
||||||
|
OnPress: func() error {
|
||||||
|
self.c.LogAction(self.c.Tr.Actions.CheckoutBranch)
|
||||||
|
return self.CheckoutRef(branch.RefName(), types.CheckoutRefOptions{})
|
||||||
|
},
|
||||||
|
Key: key,
|
||||||
|
}
|
||||||
|
})...)
|
||||||
|
} else {
|
||||||
|
menuItems = append(menuItems, &types.MenuItem{
|
||||||
|
LabelColumns: []string{self.c.Tr.Actions.CheckoutBranch},
|
||||||
|
OnPress: func() error { return nil },
|
||||||
|
DisabledReason: &types.DisabledReason{Text: self.c.Tr.NoBranchesFoundAtCommitTooltip},
|
||||||
|
Key: '1',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
menuItems = append(menuItems, &types.MenuItem{
|
||||||
|
LabelColumns: []string{fmt.Sprintf(self.c.Tr.Actions.CheckoutCommitAsDetachedHead, utils.ShortHash(hash))},
|
||||||
|
OnPress: func() error {
|
||||||
|
self.c.LogAction(self.c.Tr.Actions.CheckoutCommit)
|
||||||
|
return self.CheckoutRef(hash, types.CheckoutRefOptions{})
|
||||||
|
},
|
||||||
|
Key: 'd',
|
||||||
|
})
|
||||||
|
|
||||||
|
return self.c.Menu(types.CreateMenuOptions{
|
||||||
|
Title: self.c.Tr.Actions.CheckoutBranchOrCommit,
|
||||||
|
Items: menuItems,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggestedBranchName string) error {
|
func (self *RefsHelper) NewBranch(from string, fromFormattedName string, suggestedBranchName string) error {
|
||||||
message := utils.ResolvePlaceholderString(
|
message := utils.ResolvePlaceholderString(
|
||||||
self.c.Tr.NewBranchNameBranchOff,
|
self.c.Tr.NewBranchNameBranchOff,
|
||||||
|
@ -527,6 +527,7 @@ type TranslationSet struct {
|
|||||||
FetchingRemoteStatus string
|
FetchingRemoteStatus string
|
||||||
CheckoutCommit string
|
CheckoutCommit string
|
||||||
CheckoutCommitTooltip string
|
CheckoutCommitTooltip string
|
||||||
|
NoBranchesFoundAtCommitTooltip string
|
||||||
SureCheckoutThisCommit string
|
SureCheckoutThisCommit string
|
||||||
GitFlowOptions string
|
GitFlowOptions string
|
||||||
NotAGitFlowBranch string
|
NotAGitFlowBranch string
|
||||||
@ -860,8 +861,11 @@ type Log struct {
|
|||||||
|
|
||||||
type Actions struct {
|
type Actions struct {
|
||||||
CheckoutCommit string
|
CheckoutCommit string
|
||||||
|
CheckoutBranchAtCommit string
|
||||||
|
CheckoutCommitAsDetachedHead string
|
||||||
CheckoutTag string
|
CheckoutTag string
|
||||||
CheckoutBranch string
|
CheckoutBranch string
|
||||||
|
CheckoutBranchOrCommit string
|
||||||
ForceCheckoutBranch string
|
ForceCheckoutBranch string
|
||||||
DeleteLocalBranch string
|
DeleteLocalBranch string
|
||||||
Merge string
|
Merge string
|
||||||
@ -1522,21 +1526,22 @@ func EnglishTranslationSet() *TranslationSet {
|
|||||||
DeleteRemoteTagPrompt: "Are you sure you want to delete the remote tag '{{.tagName}}' from '{{.upstream}}'?",
|
DeleteRemoteTagPrompt: "Are you sure you want to delete the remote tag '{{.tagName}}' from '{{.upstream}}'?",
|
||||||
PushTagTitle: "Remote to push tag '{{.tagName}}' to:",
|
PushTagTitle: "Remote to push tag '{{.tagName}}' to:",
|
||||||
// Using 'push tag' rather than just 'push' to disambiguate from a global push
|
// Using 'push tag' rather than just 'push' to disambiguate from a global push
|
||||||
PushTag: "Push tag",
|
PushTag: "Push tag",
|
||||||
PushTagTooltip: "Push the selected tag to a remote. You'll be prompted to select a remote.",
|
PushTagTooltip: "Push the selected tag to a remote. You'll be prompted to select a remote.",
|
||||||
NewTag: "New tag",
|
NewTag: "New tag",
|
||||||
NewTagTooltip: "Create new tag from current commit. You'll be prompted to enter a tag name and optional description.",
|
NewTagTooltip: "Create new tag from current commit. You'll be prompted to enter a tag name and optional description.",
|
||||||
CreatingTag: "Creating tag",
|
CreatingTag: "Creating tag",
|
||||||
ForceTag: "Force Tag",
|
ForceTag: "Force Tag",
|
||||||
ForceTagPrompt: "The tag '{{.tagName}}' exists already. Press {{.cancelKey}} to cancel, or {{.confirmKey}} to overwrite.",
|
ForceTagPrompt: "The tag '{{.tagName}}' exists already. Press {{.cancelKey}} to cancel, or {{.confirmKey}} to overwrite.",
|
||||||
FetchRemoteTooltip: "Fetch updates from the remote repository. This retrieves new commits and branches without merging them into your local branches.",
|
FetchRemoteTooltip: "Fetch updates from the remote repository. This retrieves new commits and branches without merging them into your local branches.",
|
||||||
FetchingRemoteStatus: "Fetching remote",
|
FetchingRemoteStatus: "Fetching remote",
|
||||||
CheckoutCommit: "Checkout commit",
|
CheckoutCommit: "Checkout commit",
|
||||||
CheckoutCommitTooltip: "Checkout the selected commit as a detached HEAD.",
|
CheckoutCommitTooltip: "Checkout the selected commit as a detached HEAD.",
|
||||||
SureCheckoutThisCommit: "Are you sure you want to checkout this commit?",
|
NoBranchesFoundAtCommitTooltip: "No branches found at selected commit.",
|
||||||
GitFlowOptions: "Show git-flow options",
|
SureCheckoutThisCommit: "Are you sure you want to checkout this commit?",
|
||||||
NotAGitFlowBranch: "This does not seem to be a git flow branch",
|
GitFlowOptions: "Show git-flow options",
|
||||||
NewGitFlowBranchPrompt: "New {{.branchType}} name:",
|
NotAGitFlowBranch: "This does not seem to be a git flow branch",
|
||||||
|
NewGitFlowBranchPrompt: "New {{.branchType}} name:",
|
||||||
|
|
||||||
IgnoreTracked: "Ignore tracked file",
|
IgnoreTracked: "Ignore tracked file",
|
||||||
IgnoreTrackedPrompt: "Are you sure you want to ignore a tracked file?",
|
IgnoreTrackedPrompt: "Are you sure you want to ignore a tracked file?",
|
||||||
@ -1822,9 +1827,12 @@ func EnglishTranslationSet() *TranslationSet {
|
|||||||
Actions: Actions{
|
Actions: Actions{
|
||||||
// TODO: combine this with the original keybinding descriptions (those are all in lowercase atm)
|
// TODO: combine this with the original keybinding descriptions (those are all in lowercase atm)
|
||||||
CheckoutCommit: "Checkout commit",
|
CheckoutCommit: "Checkout commit",
|
||||||
|
CheckoutBranchAtCommit: "Checkout branch '%s'",
|
||||||
|
CheckoutCommitAsDetachedHead: "Checkout commit %s as detached head",
|
||||||
CheckoutTag: "Checkout tag",
|
CheckoutTag: "Checkout tag",
|
||||||
CheckoutBranch: "Checkout branch",
|
CheckoutBranch: "Checkout branch",
|
||||||
ForceCheckoutBranch: "Force checkout branch",
|
ForceCheckoutBranch: "Force checkout branch",
|
||||||
|
CheckoutBranchOrCommit: "Checkout branch or commit",
|
||||||
DeleteLocalBranch: "Delete local branch",
|
DeleteLocalBranch: "Delete local branch",
|
||||||
Merge: "Merge",
|
Merge: "Merge",
|
||||||
SquashMerge: "Squash merge",
|
SquashMerge: "Squash merge",
|
||||||
|
69
pkg/integration/tests/commit/checkout.go
Normal file
69
pkg/integration/tests/commit/checkout.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package commit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/jesseduffield/lazygit/pkg/config"
|
||||||
|
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Checkout = NewIntegrationTest(NewIntegrationTestArgs{
|
||||||
|
Description: "Checkout a commit as a detached head, or checkout an existing branch at a commit",
|
||||||
|
ExtraCmdArgs: []string{},
|
||||||
|
Skip: false,
|
||||||
|
SetupConfig: func(config *config.AppConfig) {},
|
||||||
|
SetupRepo: func(shell *Shell) {
|
||||||
|
shell.EmptyCommit("one")
|
||||||
|
shell.EmptyCommit("two")
|
||||||
|
shell.NewBranch("branch1")
|
||||||
|
shell.NewBranch("branch2")
|
||||||
|
shell.EmptyCommit("three")
|
||||||
|
shell.EmptyCommit("four")
|
||||||
|
},
|
||||||
|
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||||
|
t.Views().Commits().
|
||||||
|
Focus().
|
||||||
|
Lines(
|
||||||
|
Contains("four").IsSelected(),
|
||||||
|
Contains("three"),
|
||||||
|
Contains("two"),
|
||||||
|
Contains("one"),
|
||||||
|
).
|
||||||
|
PressPrimaryAction()
|
||||||
|
|
||||||
|
t.ExpectPopup().Menu().
|
||||||
|
Title(Contains("Checkout branch or commit")).
|
||||||
|
Lines(
|
||||||
|
Contains("Checkout branch").IsSelected(),
|
||||||
|
MatchesRegexp("Checkout commit [a-f0-9]+ as detached head"),
|
||||||
|
Contains("Cancel"),
|
||||||
|
).
|
||||||
|
Tooltip(Contains("Disabled: No branches found at selected commit.")).
|
||||||
|
Select(MatchesRegexp("Checkout commit [a-f0-9]+ as detached head")).
|
||||||
|
Confirm()
|
||||||
|
t.Views().Branches().Lines(
|
||||||
|
Contains("* (HEAD detached at"),
|
||||||
|
Contains("branch2"),
|
||||||
|
Contains("branch1"),
|
||||||
|
Contains("master"),
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Views().Commits().
|
||||||
|
NavigateToLine(Contains("two")).
|
||||||
|
PressPrimaryAction()
|
||||||
|
|
||||||
|
t.ExpectPopup().Menu().
|
||||||
|
Title(Contains("Checkout branch or commit")).
|
||||||
|
Lines(
|
||||||
|
Contains("Checkout branch 'branch1'").IsSelected(),
|
||||||
|
Contains("Checkout branch 'master'"),
|
||||||
|
MatchesRegexp("Checkout commit [a-f0-9]+ as detached head"),
|
||||||
|
Contains("Cancel"),
|
||||||
|
).
|
||||||
|
Select(Contains("Checkout branch 'master'")).
|
||||||
|
Confirm()
|
||||||
|
t.Views().Branches().Lines(
|
||||||
|
Contains("master"),
|
||||||
|
Contains("branch2"),
|
||||||
|
Contains("branch1"),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
@ -28,9 +28,9 @@ var Checkout = NewIntegrationTest(NewIntegrationTestArgs{
|
|||||||
SelectNextItem().
|
SelectNextItem().
|
||||||
PressPrimaryAction().
|
PressPrimaryAction().
|
||||||
Tap(func() {
|
Tap(func() {
|
||||||
t.ExpectPopup().Confirmation().
|
t.ExpectPopup().Menu().
|
||||||
Title(Contains("Checkout commit")).
|
Title(Contains("Checkout branch or commit")).
|
||||||
Content(Contains("Are you sure you want to checkout this commit?")).
|
Select(MatchesRegexp("Checkout commit [a-f0-9]+ as detached head")).
|
||||||
Confirm()
|
Confirm()
|
||||||
}).
|
}).
|
||||||
TopLines(
|
TopLines(
|
||||||
|
@ -84,6 +84,7 @@ var tests = []*components.IntegrationTest{
|
|||||||
commit.AddCoAuthorWhileCommitting,
|
commit.AddCoAuthorWhileCommitting,
|
||||||
commit.Amend,
|
commit.Amend,
|
||||||
commit.AutoWrapMessage,
|
commit.AutoWrapMessage,
|
||||||
|
commit.Checkout,
|
||||||
commit.Commit,
|
commit.Commit,
|
||||||
commit.CommitMultiline,
|
commit.CommitMultiline,
|
||||||
commit.CommitSwitchToEditor,
|
commit.CommitSwitchToEditor,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user