-
-
Notifications
You must be signed in to change notification settings - Fork 68
Expand file tree
/
Copy pathcommit_split.go
More file actions
129 lines (111 loc) · 3.6 KB
/
Copy pathcommit_split.go
File metadata and controls
129 lines (111 loc) · 3.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package main
import (
"context"
"errors"
"fmt"
"go.abhg.dev/gs/internal/git"
"go.abhg.dev/gs/internal/handler/restack"
"go.abhg.dev/gs/internal/silog"
"go.abhg.dev/gs/internal/spice"
"go.abhg.dev/gs/internal/text"
)
type commitSplitCmd struct {
Message string `short:"m" xor:"commit-message-source" placeholder:"MSG" help:"Use the given message as the commit message."`
MessageFile string `short:"F" xor:"commit-message-source" placeholder:"FILE" help:"Read the commit message from the given file."`
NoVerify bool `help:"Bypass pre-commit and commit-msg hooks."`
Restack spice.AutoRestackMode `negatable:"" default:"upstack" config:"commitSplit.restack" enum:"none,upstack" help:"Whether to restack upstack branches."`
}
func (*commitSplitCmd) Help() string {
return text.Dedent(`
Interactively select hunks from the current commit
to split into new commits below it.
Branches upstack are restacked as needed.
Use --no-restack to disable automatic restacking,
or the 'spice.commitSplit.restack' configuration option
to change the default.
`)
}
func (cmd *commitSplitCmd) Run(
ctx context.Context,
log *silog.Logger,
repo *git.Repository,
wt *git.Worktree,
restackHandler RestackHandler,
) (err error) {
head, err := wt.Head(ctx)
if err != nil {
return fmt.Errorf("get HEAD: %w", err)
}
parent, err := repo.PeelToCommit(ctx, head.String()+"^")
if err != nil {
return fmt.Errorf("get HEAD^: %w", err)
}
if err := wt.Reset(ctx, parent.String(), git.ResetOptions{
Mode: git.ResetMixed, // don't touch the working tree
}); err != nil {
return fmt.Errorf("reset to HEAD^: %w", err)
}
defer func() {
if err != nil {
// The operation may have failed
// because the user pressed Ctrl-C.
// That would invalidate the current context.
// Create an uncanceled context to perform the rollback.
ctx := context.WithoutCancel(ctx)
log.Warn("Rolling back to previous commit", "commit", head)
err = errors.Join(err, wt.Reset(ctx, head.String(), git.ResetOptions{
Mode: git.ResetMixed,
}))
}
}()
log.Info("Select hunks to extract into a new commit")
// Can't use 'git add' here because reset will have unstaged
// new files, which 'git add' will ignore.
if err := wt.Reset(ctx, head.String(), git.ResetOptions{Patch: true}); err != nil {
return fmt.Errorf("select hunks: %w", err)
}
if err := wt.Commit(ctx, git.CommitRequest{
Message: cmd.Message,
MessageFile: cmd.MessageFile,
NoVerify: cmd.NoVerify,
}); err != nil {
return fmt.Errorf("commit: %w", err)
}
if err := wt.Reset(ctx, head.String(), git.ResetOptions{
Paths: []string{"."}, // reset index to remaining changes
}); err != nil {
return fmt.Errorf("select hunks: %w", err)
}
// Commit will move HEAD to the new commit,
// updating branch ref if necessary.
if err := wt.Commit(ctx, git.CommitRequest{
ReuseMessage: head.String(),
NoVerify: cmd.NoVerify,
}); err != nil {
return fmt.Errorf("commit: %w", err)
}
if _, err := wt.RebaseState(ctx); err == nil {
// In the middle of a rebase.
// Don't restack upstack branches.
log.Debug("A rebase is in progress, skipping restack")
return nil
}
currentBranch, err := wt.CurrentBranch(ctx)
if err != nil {
// No restack needed if we're in a detached head state.
if errors.Is(err, git.ErrDetachedHead) {
log.Debug("HEAD is detached, skipping restack")
return nil
}
return fmt.Errorf("get current branch: %w", err)
}
if cmd.Restack.None() {
return nil
}
return restackHandler.RestackUpstack(ctx, &restack.UpstackRequest{
Branch: currentBranch,
Options: &restack.UpstackOptions{
SkipStart: true,
},
})
}