2022-01-08 05:00:36 +02:00
|
|
|
package git_commands
|
2020-09-29 12:03:39 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
2023-04-03 12:42:29 +02:00
|
|
|
"github.com/fsmiamoto/git-todo-parser/todo"
|
2020-09-29 12:03:39 +02:00
|
|
|
"github.com/go-errors/errors"
|
2022-03-20 07:19:27 +02:00
|
|
|
"github.com/jesseduffield/generics/slices"
|
2022-05-07 07:23:08 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/app/daemon"
|
2020-09-29 12:28:39 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/models"
|
2021-12-07 12:59:36 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
2023-04-02 19:16:28 +02:00
|
|
|
"github.com/jesseduffield/lazygit/pkg/utils"
|
2023-04-16 07:43:54 +02:00
|
|
|
"github.com/samber/lo"
|
2020-09-29 12:03:39 +02:00
|
|
|
)
|
|
|
|
|
2022-01-02 01:34:33 +02:00
|
|
|
type RebaseCommands struct {
|
2022-01-18 12:26:21 +02:00
|
|
|
*GitCommon
|
|
|
|
commit *CommitCommands
|
|
|
|
workingTree *WorkingTreeCommands
|
2022-01-02 01:34:33 +02:00
|
|
|
|
|
|
|
onSuccessfulContinue func() error
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewRebaseCommands(
|
2022-01-18 12:26:21 +02:00
|
|
|
gitCommon *GitCommon,
|
2022-01-02 01:34:33 +02:00
|
|
|
commitCommands *CommitCommands,
|
|
|
|
workingTreeCommands *WorkingTreeCommands,
|
|
|
|
) *RebaseCommands {
|
|
|
|
return &RebaseCommands{
|
2022-01-18 12:26:21 +02:00
|
|
|
GitCommon: gitCommon,
|
2022-01-02 01:34:33 +02:00
|
|
|
commit: commitCommands,
|
|
|
|
workingTree: workingTreeCommands,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-09 04:36:07 +02:00
|
|
|
func (self *RebaseCommands) RewordCommit(commits []*models.Commit, index int, message string) error {
|
2023-03-03 20:53:15 +02:00
|
|
|
if models.IsHeadCommit(commits, index) {
|
2022-01-09 04:36:07 +02:00
|
|
|
// we've selected the top commit so no rebase is required
|
|
|
|
return self.commit.RewordLastCommit(message)
|
|
|
|
}
|
|
|
|
|
|
|
|
err := self.BeginInteractiveRebaseForCommit(commits, index)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// now the selected commit should be our head so we'll amend it with the new message
|
|
|
|
err = self.commit.RewordLastCommit(message)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return self.ContinueRebase()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *RebaseCommands) RewordCommitInEditor(commits []*models.Commit, index int) (oscommands.ICmdObj, error) {
|
2023-04-16 07:43:54 +02:00
|
|
|
changes := []daemon.ChangeTodoAction{{
|
|
|
|
Sha: commits[index].Sha,
|
|
|
|
NewAction: todo.Reword,
|
|
|
|
}}
|
|
|
|
self.os.LogCommand(logTodoChanges(changes), false)
|
|
|
|
|
2023-04-02 11:07:40 +02:00
|
|
|
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
2023-04-05 19:01:49 +02:00
|
|
|
baseShaOrRoot: getBaseShaOrRoot(commits, index+1),
|
2023-04-16 07:43:54 +02:00
|
|
|
instruction: daemon.NewChangeTodoActionsInstruction(changes),
|
2023-04-02 11:07:40 +02:00
|
|
|
}), nil
|
2020-09-29 12:03:39 +02:00
|
|
|
}
|
|
|
|
|
2022-04-22 16:01:30 +02:00
|
|
|
func (self *RebaseCommands) ResetCommitAuthor(commits []*models.Commit, index int) error {
|
2022-05-08 13:05:01 +02:00
|
|
|
return self.GenericAmend(commits, index, func() error {
|
|
|
|
return self.commit.ResetAuthor()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *RebaseCommands) SetCommitAuthor(commits []*models.Commit, index int, value string) error {
|
|
|
|
return self.GenericAmend(commits, index, func() error {
|
|
|
|
return self.commit.SetAuthor(value)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *RebaseCommands) GenericAmend(commits []*models.Commit, index int, f func() error) error {
|
2023-04-18 17:33:33 +02:00
|
|
|
if models.IsHeadCommit(commits, index) {
|
2022-04-22 16:01:30 +02:00
|
|
|
// we've selected the top commit so no rebase is required
|
2022-05-08 13:05:01 +02:00
|
|
|
return f()
|
2022-04-22 16:01:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
err := self.BeginInteractiveRebaseForCommit(commits, index)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-05-08 13:05:01 +02:00
|
|
|
// now the selected commit should be our head so we'll amend it
|
|
|
|
err = f()
|
2022-04-22 16:01:30 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return self.ContinueRebase()
|
|
|
|
}
|
|
|
|
|
2022-01-02 01:34:33 +02:00
|
|
|
func (self *RebaseCommands) MoveCommitDown(commits []*models.Commit, index int) error {
|
2023-04-06 09:53:10 +02:00
|
|
|
baseShaOrRoot := getBaseShaOrRoot(commits, index+2)
|
2020-09-29 12:03:39 +02:00
|
|
|
|
2023-04-16 07:43:54 +02:00
|
|
|
sha := commits[index].Sha
|
|
|
|
|
|
|
|
self.os.LogCommand(fmt.Sprintf("Moving TODO down: %s", utils.ShortSha(sha)), false)
|
|
|
|
|
2023-04-06 09:53:10 +02:00
|
|
|
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
|
|
|
baseShaOrRoot: baseShaOrRoot,
|
2023-04-16 07:43:54 +02:00
|
|
|
instruction: daemon.NewMoveTodoDownInstruction(sha),
|
2023-04-06 09:53:10 +02:00
|
|
|
overrideEditor: true,
|
|
|
|
}).Run()
|
|
|
|
}
|
2022-03-20 07:19:27 +02:00
|
|
|
|
2023-04-06 09:53:10 +02:00
|
|
|
func (self *RebaseCommands) MoveCommitUp(commits []*models.Commit, index int) error {
|
|
|
|
baseShaOrRoot := getBaseShaOrRoot(commits, index+1)
|
2023-02-22 10:36:31 +02:00
|
|
|
|
2023-04-16 07:43:54 +02:00
|
|
|
sha := commits[index].Sha
|
|
|
|
|
|
|
|
self.os.LogCommand(fmt.Sprintf("Moving TODO up: %s", utils.ShortSha(sha)), false)
|
|
|
|
|
2023-04-02 11:07:40 +02:00
|
|
|
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
|
|
|
baseShaOrRoot: baseShaOrRoot,
|
2023-04-16 07:43:54 +02:00
|
|
|
instruction: daemon.NewMoveTodoUpInstruction(sha),
|
2023-04-02 11:07:40 +02:00
|
|
|
overrideEditor: true,
|
|
|
|
}).Run()
|
2020-09-29 12:03:39 +02:00
|
|
|
}
|
|
|
|
|
2023-04-05 19:01:49 +02:00
|
|
|
func (self *RebaseCommands) InteractiveRebase(commits []*models.Commit, index int, action todo.TodoCommand) error {
|
|
|
|
baseIndex := index + 1
|
|
|
|
if action == todo.Squash || action == todo.Fixup {
|
|
|
|
baseIndex++
|
2020-09-29 12:03:39 +02:00
|
|
|
}
|
|
|
|
|
2023-04-05 19:01:49 +02:00
|
|
|
baseShaOrRoot := getBaseShaOrRoot(commits, baseIndex)
|
|
|
|
|
2023-04-16 07:43:54 +02:00
|
|
|
changes := []daemon.ChangeTodoAction{{
|
|
|
|
Sha: commits[index].Sha,
|
|
|
|
NewAction: action,
|
|
|
|
}}
|
|
|
|
self.os.LogCommand(logTodoChanges(changes), false)
|
|
|
|
|
2023-04-02 11:07:40 +02:00
|
|
|
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
2023-04-16 07:43:54 +02:00
|
|
|
baseShaOrRoot: baseShaOrRoot,
|
2023-04-02 11:07:40 +02:00
|
|
|
overrideEditor: true,
|
2023-04-16 07:43:54 +02:00
|
|
|
instruction: daemon.NewChangeTodoActionsInstruction(changes),
|
2023-04-02 11:07:40 +02:00
|
|
|
}).Run()
|
2020-09-29 12:03:39 +02:00
|
|
|
}
|
|
|
|
|
2023-03-29 00:53:14 +02:00
|
|
|
func (self *RebaseCommands) EditRebase(branchRef string) error {
|
2023-04-16 07:43:54 +02:00
|
|
|
self.os.LogCommand(fmt.Sprintf("Beginning interactive rebase at '%s'", branchRef), false)
|
2023-04-02 11:07:40 +02:00
|
|
|
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
|
|
|
baseShaOrRoot: branchRef,
|
2023-04-16 07:43:54 +02:00
|
|
|
instruction: daemon.NewInsertBreakInstruction(),
|
2023-04-02 11:07:40 +02:00
|
|
|
}).Run()
|
|
|
|
}
|
|
|
|
|
2023-04-16 07:43:54 +02:00
|
|
|
func logTodoChanges(changes []daemon.ChangeTodoAction) string {
|
|
|
|
changeTodoStr := strings.Join(slices.Map(changes, func(c daemon.ChangeTodoAction) string {
|
2023-04-06 11:13:42 +02:00
|
|
|
return fmt.Sprintf("%s:%s", c.Sha, c.NewAction)
|
|
|
|
}), "\n")
|
|
|
|
return fmt.Sprintf("Changing TODO actions: %s", changeTodoStr)
|
|
|
|
}
|
|
|
|
|
2023-04-02 11:07:40 +02:00
|
|
|
type PrepareInteractiveRebaseCommandOpts struct {
|
2023-04-06 11:13:42 +02:00
|
|
|
baseShaOrRoot string
|
2023-04-16 07:43:54 +02:00
|
|
|
instruction daemon.Instruction
|
2023-04-06 11:13:42 +02:00
|
|
|
overrideEditor bool
|
2023-03-29 00:53:14 +02:00
|
|
|
}
|
|
|
|
|
2020-09-29 12:03:39 +02:00
|
|
|
// PrepareInteractiveRebaseCommand returns the cmd for an interactive rebase
|
|
|
|
// we tell git to run lazygit to edit the todo list, and we pass the client
|
|
|
|
// lazygit a todo string to write to the todo file
|
2023-04-02 11:07:40 +02:00
|
|
|
func (self *RebaseCommands) PrepareInteractiveRebaseCommand(opts PrepareInteractiveRebaseCommandOpts) oscommands.ICmdObj {
|
2022-01-05 03:01:59 +02:00
|
|
|
ex := oscommands.GetLazygitPath()
|
2020-09-29 12:03:39 +02:00
|
|
|
|
|
|
|
debug := "FALSE"
|
2022-01-02 01:34:33 +02:00
|
|
|
if self.Debug {
|
2020-09-29 12:03:39 +02:00
|
|
|
debug = "TRUE"
|
|
|
|
}
|
|
|
|
|
2023-04-06 06:46:48 +02:00
|
|
|
rebaseMergesArg := " --rebase-merges"
|
|
|
|
if self.version.IsOlderThan(2, 22, 0) {
|
|
|
|
rebaseMergesArg = ""
|
|
|
|
}
|
|
|
|
cmdStr := fmt.Sprintf("git rebase --interactive --autostash --keep-empty --empty=keep --no-autosquash%s %s",
|
|
|
|
rebaseMergesArg, opts.baseShaOrRoot)
|
2022-08-01 12:04:22 +02:00
|
|
|
self.Log.WithField("command", cmdStr).Debug("RunCommand")
|
2020-09-29 12:03:39 +02:00
|
|
|
|
2022-01-02 01:34:33 +02:00
|
|
|
cmdObj := self.cmd.New(cmdStr)
|
2020-09-29 12:03:39 +02:00
|
|
|
|
|
|
|
gitSequenceEditor := ex
|
2023-04-06 11:13:42 +02:00
|
|
|
|
|
|
|
if opts.instruction != nil {
|
2023-04-16 07:43:54 +02:00
|
|
|
cmdObj.AddEnvVars(daemon.ToEnvVars(opts.instruction)...)
|
2023-04-05 19:01:49 +02:00
|
|
|
} else {
|
|
|
|
gitSequenceEditor = "true"
|
2020-09-29 12:03:39 +02:00
|
|
|
}
|
|
|
|
|
2021-12-07 12:59:36 +02:00
|
|
|
cmdObj.AddEnvVars(
|
2020-09-29 12:03:39 +02:00
|
|
|
"DEBUG="+debug,
|
|
|
|
"LANG=en_US.UTF-8", // Force using EN as language
|
|
|
|
"LC_ALL=en_US.UTF-8", // Force using EN as language
|
|
|
|
"GIT_SEQUENCE_EDITOR="+gitSequenceEditor,
|
|
|
|
)
|
|
|
|
|
2023-04-02 11:07:40 +02:00
|
|
|
if opts.overrideEditor {
|
2021-12-07 12:59:36 +02:00
|
|
|
cmdObj.AddEnvVars("GIT_EDITOR=" + ex)
|
2020-09-29 12:03:39 +02:00
|
|
|
}
|
|
|
|
|
2022-01-09 04:36:07 +02:00
|
|
|
return cmdObj
|
2020-09-29 12:03:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// AmendTo amends the given commit with whatever files are staged
|
2023-04-19 08:19:17 +02:00
|
|
|
func (self *RebaseCommands) AmendTo(commits []*models.Commit, commitIndex int) error {
|
|
|
|
commit := commits[commitIndex]
|
|
|
|
|
2023-02-20 09:29:43 +02:00
|
|
|
if err := self.commit.CreateFixupCommit(commit.Sha); err != nil {
|
2020-09-29 12:03:39 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2023-04-19 08:19:17 +02:00
|
|
|
// Get the sha of the commit we just created
|
|
|
|
fixupSha, err := self.cmd.New("git rev-parse --verify HEAD").RunWithOutput()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
|
|
|
baseShaOrRoot: getBaseShaOrRoot(commits, commitIndex+1),
|
|
|
|
overrideEditor: true,
|
|
|
|
instruction: daemon.NewMoveFixupCommitDownInstruction(commit.Sha, fixupSha),
|
|
|
|
}).Run()
|
2020-09-29 12:03:39 +02:00
|
|
|
}
|
|
|
|
|
2023-04-02 19:16:28 +02:00
|
|
|
// EditRebaseTodo sets the action for a given rebase commit in the git-rebase-todo file
|
|
|
|
func (self *RebaseCommands) EditRebaseTodo(commit *models.Commit, action todo.TodoCommand) error {
|
2023-04-06 07:04:25 +02:00
|
|
|
return utils.EditRebaseTodo(
|
|
|
|
filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo"), commit.Sha, commit.Action, action)
|
2020-09-29 12:03:39 +02:00
|
|
|
}
|
|
|
|
|
2023-04-04 10:23:50 +02:00
|
|
|
// MoveTodoDown moves a rebase todo item down by one position
|
|
|
|
func (self *RebaseCommands) MoveTodoDown(commit *models.Commit) error {
|
|
|
|
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
|
|
|
|
return utils.MoveTodoDown(fileName, commit.Sha, commit.Action)
|
2020-09-29 12:03:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// MoveTodoDown moves a rebase todo item down by one position
|
2023-04-04 10:23:50 +02:00
|
|
|
func (self *RebaseCommands) MoveTodoUp(commit *models.Commit) error {
|
2022-01-02 01:34:33 +02:00
|
|
|
fileName := filepath.Join(self.dotGitDir, "rebase-merge/git-rebase-todo")
|
2023-04-04 10:23:50 +02:00
|
|
|
return utils.MoveTodoUp(fileName, commit.Sha, commit.Action)
|
2020-09-29 12:03:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// SquashAllAboveFixupCommits squashes all fixup! commits above the given one
|
2023-02-20 09:29:43 +02:00
|
|
|
func (self *RebaseCommands) SquashAllAboveFixupCommits(commit *models.Commit) error {
|
|
|
|
shaOrRoot := commit.Sha + "^"
|
|
|
|
if commit.IsFirstCommit() {
|
|
|
|
shaOrRoot = "--root"
|
|
|
|
}
|
|
|
|
|
2022-01-02 01:34:33 +02:00
|
|
|
return self.runSkipEditorCommand(
|
2022-01-07 11:33:34 +02:00
|
|
|
self.cmd.New(
|
|
|
|
fmt.Sprintf(
|
2023-02-20 09:29:43 +02:00
|
|
|
"git rebase --interactive --rebase-merges --autostash --autosquash %s",
|
|
|
|
shaOrRoot,
|
2022-01-07 11:33:34 +02:00
|
|
|
),
|
2020-09-29 12:03:39 +02:00
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// BeginInteractiveRebaseForCommit starts an interactive rebase to edit the current
|
2022-01-09 04:36:07 +02:00
|
|
|
// commit and pick all others. After this you'll want to call `self.ContinueRebase()
|
2022-01-02 01:34:33 +02:00
|
|
|
func (self *RebaseCommands) BeginInteractiveRebaseForCommit(commits []*models.Commit, commitIndex int) error {
|
2020-09-29 12:03:39 +02:00
|
|
|
if len(commits)-1 < commitIndex {
|
|
|
|
return errors.New("index outside of range of commits")
|
|
|
|
}
|
|
|
|
|
|
|
|
// we can make this GPG thing possible it just means we need to do this in two parts:
|
|
|
|
// one where we handle the possibility of a credential request, and the other
|
|
|
|
// where we continue the rebase
|
2022-01-02 01:34:33 +02:00
|
|
|
if self.config.UsingGpg() {
|
|
|
|
return errors.New(self.Tr.DisabledForGPG)
|
2020-09-29 12:03:39 +02:00
|
|
|
}
|
|
|
|
|
2023-04-16 07:43:54 +02:00
|
|
|
changes := []daemon.ChangeTodoAction{{
|
|
|
|
Sha: commits[commitIndex].Sha,
|
|
|
|
NewAction: todo.Edit,
|
|
|
|
}}
|
|
|
|
self.os.LogCommand(logTodoChanges(changes), false)
|
|
|
|
|
2023-04-02 11:07:40 +02:00
|
|
|
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
2023-04-05 19:01:49 +02:00
|
|
|
baseShaOrRoot: getBaseShaOrRoot(commits, commitIndex+1),
|
2023-04-02 11:07:40 +02:00
|
|
|
overrideEditor: true,
|
2023-04-16 07:43:54 +02:00
|
|
|
instruction: daemon.NewChangeTodoActionsInstruction(changes),
|
2023-04-02 11:07:40 +02:00
|
|
|
}).Run()
|
2020-09-29 12:03:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// RebaseBranch interactive rebases onto a branch
|
2022-01-02 01:34:33 +02:00
|
|
|
func (self *RebaseCommands) RebaseBranch(branchName string) error {
|
2023-04-02 11:07:40 +02:00
|
|
|
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{baseShaOrRoot: branchName}).Run()
|
2020-09-29 12:03:39 +02:00
|
|
|
}
|
|
|
|
|
2022-01-07 11:33:34 +02:00
|
|
|
func (self *RebaseCommands) GenericMergeOrRebaseActionCmdObj(commandType string, command string) oscommands.ICmdObj {
|
|
|
|
return self.cmd.New("git " + commandType + " --" + command)
|
|
|
|
}
|
|
|
|
|
2022-01-09 04:36:07 +02:00
|
|
|
func (self *RebaseCommands) ContinueRebase() error {
|
|
|
|
return self.GenericMergeOrRebaseAction("rebase", "continue")
|
|
|
|
}
|
|
|
|
|
|
|
|
func (self *RebaseCommands) AbortRebase() error {
|
|
|
|
return self.GenericMergeOrRebaseAction("rebase", "abort")
|
|
|
|
}
|
|
|
|
|
2020-09-29 12:03:39 +02:00
|
|
|
// GenericMerge takes a commandType of "merge" or "rebase" and a command of "abort", "skip" or "continue"
|
|
|
|
// By default we skip the editor in the case where a commit will be made
|
2022-01-02 01:34:33 +02:00
|
|
|
func (self *RebaseCommands) GenericMergeOrRebaseAction(commandType string, command string) error {
|
2022-01-07 11:33:34 +02:00
|
|
|
err := self.runSkipEditorCommand(self.GenericMergeOrRebaseActionCmdObj(commandType, command))
|
2020-09-29 12:03:39 +02:00
|
|
|
if err != nil {
|
|
|
|
if !strings.Contains(err.Error(), "no rebase in progress") {
|
|
|
|
return err
|
|
|
|
}
|
2022-01-02 01:34:33 +02:00
|
|
|
self.Log.Warn(err)
|
2020-09-29 12:03:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// sometimes we need to do a sequence of things in a rebase but the user needs to
|
|
|
|
// fix merge conflicts along the way. When this happens we queue up the next step
|
|
|
|
// so that after the next successful rebase continue we can continue from where we left off
|
2022-01-02 01:34:33 +02:00
|
|
|
if commandType == "rebase" && command == "continue" && self.onSuccessfulContinue != nil {
|
|
|
|
f := self.onSuccessfulContinue
|
|
|
|
self.onSuccessfulContinue = nil
|
2020-09-29 12:03:39 +02:00
|
|
|
return f()
|
|
|
|
}
|
|
|
|
if command == "abort" {
|
2022-01-02 01:34:33 +02:00
|
|
|
self.onSuccessfulContinue = nil
|
2020-09-29 12:03:39 +02:00
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-01-07 11:33:34 +02:00
|
|
|
func (self *RebaseCommands) runSkipEditorCommand(cmdObj oscommands.ICmdObj) error {
|
2023-04-16 07:43:54 +02:00
|
|
|
instruction := daemon.NewExitImmediatelyInstruction()
|
2022-01-05 03:01:59 +02:00
|
|
|
lazyGitPath := oscommands.GetLazygitPath()
|
2021-12-29 05:33:38 +02:00
|
|
|
return cmdObj.
|
|
|
|
AddEnvVars(
|
|
|
|
"GIT_EDITOR="+lazyGitPath,
|
2023-01-01 05:33:58 +02:00
|
|
|
"GIT_SEQUENCE_EDITOR="+lazyGitPath,
|
2021-12-29 05:33:38 +02:00
|
|
|
"EDITOR="+lazyGitPath,
|
|
|
|
"VISUAL="+lazyGitPath,
|
|
|
|
).
|
2023-04-16 07:43:54 +02:00
|
|
|
AddEnvVars(daemon.ToEnvVars(instruction)...).
|
2021-12-29 05:33:38 +02:00
|
|
|
Run()
|
2020-09-29 12:03:39 +02:00
|
|
|
}
|
2022-01-02 01:34:33 +02:00
|
|
|
|
|
|
|
// DiscardOldFileChanges discards changes to a file from an old commit
|
|
|
|
func (self *RebaseCommands) DiscardOldFileChanges(commits []*models.Commit, commitIndex int, fileName string) error {
|
|
|
|
if err := self.BeginInteractiveRebaseForCommit(commits, commitIndex); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if file exists in previous commit (this command returns an error if the file doesn't exist)
|
|
|
|
if err := self.cmd.New("git cat-file -e HEAD^:" + self.cmd.Quote(fileName)).Run(); err != nil {
|
2022-01-18 12:26:21 +02:00
|
|
|
if err := self.os.Remove(fileName); err != nil {
|
2022-01-02 01:34:33 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := self.workingTree.StageFile(fileName); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else if err := self.workingTree.CheckoutFile("HEAD^", fileName); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// amend the commit
|
|
|
|
err := self.commit.AmendHead()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// continue
|
2022-01-09 04:36:07 +02:00
|
|
|
return self.ContinueRebase()
|
2022-01-02 01:34:33 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// CherryPickCommits begins an interactive rebase with the given shas being cherry picked onto HEAD
|
|
|
|
func (self *RebaseCommands) CherryPickCommits(commits []*models.Commit) error {
|
2023-04-16 07:43:54 +02:00
|
|
|
commitLines := lo.Map(commits, func(commit *models.Commit, _ int) string {
|
|
|
|
return fmt.Sprintf("%s %s", utils.ShortSha(commit.Sha), commit.Name)
|
|
|
|
})
|
|
|
|
self.os.LogCommand(fmt.Sprintf("Cherry-picking commits:\n%s", strings.Join(commitLines, "\n")), false)
|
2022-03-20 07:19:27 +02:00
|
|
|
|
2023-04-02 11:07:40 +02:00
|
|
|
return self.PrepareInteractiveRebaseCommand(PrepareInteractiveRebaseCommandOpts{
|
|
|
|
baseShaOrRoot: "HEAD",
|
2023-04-16 07:43:54 +02:00
|
|
|
instruction: daemon.NewCherryPickCommitsInstruction(commits),
|
2023-04-02 11:07:40 +02:00
|
|
|
}).Run()
|
2022-03-20 07:19:27 +02:00
|
|
|
}
|
|
|
|
|
2023-02-22 10:36:31 +02:00
|
|
|
// we can't start an interactive rebase from the first commit without passing the
|
|
|
|
// '--root' arg
|
|
|
|
func getBaseShaOrRoot(commits []*models.Commit, index int) string {
|
|
|
|
// We assume that the commits slice contains the initial commit of the repo.
|
|
|
|
// Technically this assumption could prove false, but it's unlikely you'll
|
|
|
|
// be starting a rebase from 300 commits ago (which is the original commit limit
|
|
|
|
// at time of writing)
|
|
|
|
if index < len(commits) {
|
|
|
|
return commits[index].Sha
|
|
|
|
} else {
|
|
|
|
return "--root"
|
|
|
|
}
|
|
|
|
}
|