1
0
mirror of https://github.com/jesseduffield/lazygit.git synced 2025-01-04 03:48:07 +02:00

support cherry picking commits

This commit is contained in:
Jesse Duffield Duffield 2019-02-24 13:51:52 +11:00
parent 1a19b1412d
commit a8858cbd12
7 changed files with 169 additions and 18 deletions

View File

@ -12,6 +12,7 @@ type Commit struct {
Status string // one of "unpushed", "pushed", "merged", or "rebasing"
DisplayString string
Action string // one of "", "pick", "edit", "squash", "reword", "drop", "fixup"
Copied bool // to know if this commit is ready to be cherry-picked somewhere
}
// GetDisplayStrings is a function.
@ -19,9 +20,14 @@ func (c *Commit) GetDisplayStrings(isFocused bool) []string {
red := color.New(color.FgRed)
yellow := color.New(color.FgYellow)
green := color.New(color.FgGreen)
white := color.New(color.FgWhite)
blue := color.New(color.FgBlue)
cyan := color.New(color.FgCyan)
white := color.New(color.FgWhite)
// for some reason, setting the background to blue pads out the other commits
// horizontally. For the sake of accessibility I'm considering this a feature,
// not a bug
copied := color.New(color.FgCyan, color.BgBlue)
var shaColor *color.Color
switch c.Status {
@ -37,6 +43,10 @@ func (c *Commit) GetDisplayStrings(isFocused bool) []string {
shaColor = white
}
if c.Copied {
shaColor = copied
}
actionString := ""
if c.Action != "" {
actionString = cyan.Sprint(utils.WithPadding(c.Action, 7)) + " "

View File

@ -745,3 +745,18 @@ func (c *GitCommand) MoveTodoDown(index int) error {
func (c *GitCommand) Revert(sha string) error {
return c.OSCommand.RunCommand(fmt.Sprintf("git revert %s", sha))
}
// CherryPickShas begins an interactive rebase with the given shas being cherry picked onto HEAD
func (c *GitCommand) CherryPickShas(shas []string) error {
todo := ""
for _, sha := range shas {
todo = "pick " + sha + "\n" + todo
}
cmd, err := c.PrepareInteractiveRebaseCommand("HEAD", todo, false)
if err != nil {
return err
}
return c.OSCommand.RunPreparedCommand(cmd)
}

View File

@ -23,19 +23,21 @@ import (
// CommitListBuilder returns a list of Branch objects for the current repo
type CommitListBuilder struct {
Log *logrus.Entry
GitCommand *commands.GitCommand
OSCommand *commands.OSCommand
Tr *i18n.Localizer
Log *logrus.Entry
GitCommand *commands.GitCommand
OSCommand *commands.OSCommand
Tr *i18n.Localizer
CherryPickedShas []string
}
// NewCommitListBuilder builds a new commit list builder
func NewCommitListBuilder(log *logrus.Entry, gitCommand *commands.GitCommand, osCommand *commands.OSCommand, tr *i18n.Localizer) (*CommitListBuilder, error) {
func NewCommitListBuilder(log *logrus.Entry, gitCommand *commands.GitCommand, osCommand *commands.OSCommand, tr *i18n.Localizer, cherryPickedShas []string) (*CommitListBuilder, error) {
return &CommitListBuilder{
Log: log,
GitCommand: gitCommand,
OSCommand: osCommand,
Tr: tr,
Log: log,
GitCommand: gitCommand,
OSCommand: osCommand,
Tr: tr,
CherryPickedShas: cherryPickedShas,
}, nil
}
@ -80,7 +82,18 @@ func (c *CommitListBuilder) GetCommits() ([]*commands.Commit, error) {
youAreHere := blue.Sprintf("<-- %s ---", c.Tr.SLocalize("YouAreHere"))
currentCommit.Name = fmt.Sprintf("%s %s", youAreHere, currentCommit.Name)
}
return c.setCommitMergedStatuses(commits)
commits, err = c.setCommitMergedStatuses(commits)
if err != nil {
return nil, err
}
commits, err = c.setCommitCherryPickStatuses(commits)
if err != nil {
return nil, err
}
return commits, nil
}
// git-rebase-todo example:
@ -106,7 +119,7 @@ func (c *CommitListBuilder) getRebasingCommits() ([]*commands.Commit, error) {
commits := []*commands.Commit{}
lines := strings.Split(string(bytesContent), "\n")
for _, line := range lines {
if line == "" {
if line == "" || line == "noop" {
return commits, nil
}
splitLine := strings.Split(line, " ")
@ -144,6 +157,17 @@ func (c *CommitListBuilder) setCommitMergedStatuses(commits []*commands.Commit)
return commits, nil
}
func (c *CommitListBuilder) setCommitCherryPickStatuses(commits []*commands.Commit) ([]*commands.Commit, error) {
for _, commit := range commits {
for _, sha := range c.CherryPickedShas {
if commit.Sha == sha {
commit.Copied = true
}
}
}
return commits, nil
}
func (c *CommitListBuilder) getMergeBase() (string, error) {
currentBranch, err := c.GitCommand.CurrentBranchName()
if err != nil {

View File

@ -2,6 +2,8 @@ package gui
import (
"fmt"
"strconv"
"strings"
"github.com/go-errors/errors"
@ -40,7 +42,7 @@ func (gui *Gui) handleCommitSelect(g *gocui.Gui, v *gocui.View) error {
func (gui *Gui) refreshCommits(g *gocui.Gui) error {
g.Update(func(*gocui.Gui) error {
builder, err := git.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr)
builder, err := git.NewCommitListBuilder(gui.Log, gui.GitCommand, gui.OSCommand, gui.Tr, gui.State.CherryPickedShas)
if err != nil {
return err
}
@ -347,3 +349,67 @@ func (gui *Gui) handleCommitRevert(g *gocui.Gui, v *gocui.View) error {
gui.State.Panels.Commits.SelectedLine++
return gui.refreshCommits(gui.g)
}
func (gui *Gui) handleCopyCommit(g *gocui.Gui, v *gocui.View) error {
// get currently selected commit, add the sha to state.
sha := gui.State.Commits[gui.State.Panels.Commits.SelectedLine].Sha
// we will un-copy it if it's already copied
for index, cherryPickedSha := range gui.State.CherryPickedShas {
if sha == cherryPickedSha {
gui.State.CherryPickedShas = append(gui.State.CherryPickedShas[0:index], gui.State.CherryPickedShas[index+1:]...)
gui.Log.Info("removed copied sha. New shas:\n" + strings.Join(gui.State.CherryPickedShas, "\n"))
return gui.refreshCommits(gui.g)
}
}
gui.addCommitToCherryPickedShas(gui.State.Panels.Commits.SelectedLine)
return gui.refreshCommits(gui.g)
}
func (gui *Gui) addCommitToCherryPickedShas(index int) {
defer func() { gui.Log.Info("new copied shas:\n" + strings.Join(gui.State.CherryPickedShas, "\n")) }()
// not super happy with modifying the state of the Commits array here
// but the alternative would be very tricky
gui.State.Commits[index].Copied = true
newShas := []string{}
for _, commit := range gui.State.Commits {
if commit.Copied {
newShas = append(newShas, commit.Sha)
}
}
gui.State.CherryPickedShas = newShas
}
func (gui *Gui) handleCopyCommitRange(g *gocui.Gui, v *gocui.View) error {
// whenever I add a commit, I need to make sure I retain its order
// find the last commit that is copied that's above our position
// if there are none, startIndex = 0
startIndex := 0
for index, commit := range gui.State.Commits[0:gui.State.Panels.Commits.SelectedLine] {
if commit.Copied {
startIndex = index
}
}
gui.Log.Info("commit copy start index: " + strconv.Itoa(startIndex))
for index := startIndex; index <= gui.State.Panels.Commits.SelectedLine; index++ {
gui.addCommitToCherryPickedShas(index)
}
return gui.refreshCommits(gui.g)
}
// HandlePasteCommits begins a cherry-pick rebase with the commits the user has copied
func (gui *Gui) HandlePasteCommits(g *gocui.Gui, v *gocui.View) error {
return gui.createConfirmationPanel(g, v, gui.Tr.SLocalize("CherryPick"), gui.Tr.SLocalize("SureCherryPick"), func(g *gocui.Gui, v *gocui.View) error {
err := gui.GitCommand.CherryPickShas(gui.State.CherryPickedShas)
return gui.handleGenericMergeCommandResult(err)
}, nil)
}

View File

@ -133,17 +133,19 @@ type guiState struct {
Panels *panelStates
WorkingTreeState string // one of "merging", "rebasing", "normal"
Contexts map[string]string
CherryPickedShas []string
}
// NewGui builds a new gui handler
func NewGui(log *logrus.Entry, gitCommand *commands.GitCommand, oSCommand *commands.OSCommand, tr *i18n.Localizer, config config.AppConfigurer, updater *updates.Updater) (*Gui, error) {
initialState := guiState{
Files: make([]*commands.File, 0),
PreviousView: "files",
Commits: make([]*commands.Commit, 0),
StashEntries: make([]*commands.StashEntry, 0),
Platform: *oSCommand.Platform,
Files: make([]*commands.File, 0),
PreviousView: "files",
Commits: make([]*commands.Commit, 0),
CherryPickedShas: []string{},
StashEntries: make([]*commands.StashEntry, 0),
Platform: *oSCommand.Platform,
Panels: &panelStates{
Files: &filePanelState{SelectedLine: -1},
Branches: &branchPanelState{SelectedLine: 0},

View File

@ -358,6 +358,24 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Modifier: gocui.ModNone,
Handler: gui.handleCommitRevert,
Description: gui.Tr.SLocalize("revertCommit"),
}, {
ViewName: "commits",
Key: 'c',
Modifier: gocui.ModNone,
Handler: gui.handleCopyCommit,
Description: gui.Tr.SLocalize("cherryPickCopy"),
}, {
ViewName: "commits",
Key: 'C',
Modifier: gocui.ModNone,
Handler: gui.handleCopyCommitRange,
Description: gui.Tr.SLocalize("cherryPickCopyRange"),
}, {
ViewName: "commits",
Key: 'v',
Modifier: gocui.ModNone,
Handler: gui.HandlePasteCommits,
Description: gui.Tr.SLocalize("pasteCommits"),
}, {
ViewName: "stash",
Key: gocui.KeySpace,
@ -365,6 +383,7 @@ func (gui *Gui) GetInitialKeybindings() []*Binding {
Handler: gui.handleStashApply,
Description: gui.Tr.SLocalize("apply"),
}, {
ViewName: "stash",
Key: 'g',
Modifier: gocui.ModNone,

View File

@ -571,6 +571,21 @@ func addEnglish(i18nObject *i18n.Bundle) error {
}, &i18n.Message{
ID: "rewordNotSupported",
Other: "rewording commits while interactively rebasing is not currently supported",
}, &i18n.Message{
ID: "cherryPickCopy",
Other: "copy commit (cherry-pick)",
}, &i18n.Message{
ID: "cherryPickCopyRange",
Other: "copy commit range (cherry-pick)",
}, &i18n.Message{
ID: "pasteCommits",
Other: "paste commits (cherry-pick)",
}, &i18n.Message{
ID: "SureCherryPick",
Other: "Are you sure you want to cherry-pick the copied commits onto this branch?",
}, &i18n.Message{
ID: "CherryPick",
Other: "Cherry-Pick",
},
)
}