+57
-4
knotserver/git/merge.go
+57
-4
knotserver/git/merge.go
···
24
24
Reason string
25
25
}
26
26
27
+
// MergeOptions specifies the configuration for a merge operation
28
+
type MergeOptions struct {
29
+
CommitMessage string
30
+
CommitBody string
31
+
AuthorName string
32
+
AuthorEmail string
33
+
}
34
+
27
35
func (e ErrMerge) Error() string {
28
36
if e.HasConflict {
29
37
return fmt.Sprintf("merge failed due to conflicts: %s (%d conflicts)", e.Message, len(e.Conflicts))
···
74
82
return tmpDir, nil
75
83
}
76
84
77
-
func (g *GitRepo) applyPatch(tmpDir, patchFile string, checkOnly bool) error {
85
+
func (g *GitRepo) applyPatch(tmpDir, patchFile string, checkOnly bool, opts *MergeOptions) error {
78
86
var stderr bytes.Buffer
79
87
var cmd *exec.Cmd
80
88
···
82
90
cmd = exec.Command("git", "-C", tmpDir, "apply", "--check", "-v", patchFile)
83
91
} else {
84
92
exec.Command("git", "-C", tmpDir, "config", "advice.mergeConflict", "false").Run()
85
-
cmd = exec.Command("git", "-C", tmpDir, "am", patchFile)
93
+
94
+
if opts != nil {
95
+
applyCmd := exec.Command("git", "-C", tmpDir, "apply", patchFile)
96
+
applyCmd.Stderr = &stderr
97
+
if err := applyCmd.Run(); err != nil {
98
+
return fmt.Errorf("patch application failed: %s", stderr.String())
99
+
}
100
+
101
+
stageCmd := exec.Command("git", "-C", tmpDir, "add", ".")
102
+
if err := stageCmd.Run(); err != nil {
103
+
return fmt.Errorf("failed to stage changes: %w", err)
104
+
}
105
+
106
+
commitArgs := []string{"-C", tmpDir, "commit"}
107
+
108
+
// Set author if provided
109
+
authorName := opts.AuthorName
110
+
authorEmail := opts.AuthorEmail
111
+
112
+
if authorEmail == "" {
113
+
authorEmail = "noreply@tangled.sh"
114
+
}
115
+
116
+
if authorName == "" {
117
+
authorName = "Tangled"
118
+
}
119
+
120
+
if authorName != "" {
121
+
commitArgs = append(commitArgs, "--author", fmt.Sprintf("%s <%s>", authorName, authorEmail))
122
+
}
123
+
124
+
commitArgs = append(commitArgs, "-m", opts.CommitMessage)
125
+
126
+
if opts.CommitBody != "" {
127
+
commitArgs = append(commitArgs, "-m", opts.CommitBody)
128
+
}
129
+
130
+
cmd = exec.Command("git", commitArgs...)
131
+
} else {
132
+
// If no commit message specified, use git-am which automatically creates a commit
133
+
cmd = exec.Command("git", "-C", tmpDir, "am", patchFile)
134
+
}
86
135
}
87
136
88
137
cmd.Stderr = &stderr
···
122
171
}
123
172
defer os.RemoveAll(tmpDir)
124
173
125
-
return g.applyPatch(tmpDir, patchFile, true)
174
+
return g.applyPatch(tmpDir, patchFile, true, nil)
126
175
}
127
176
128
177
func (g *GitRepo) Merge(patchData []byte, targetBranch string) error {
178
+
return g.MergeWithOptions(patchData, targetBranch, nil)
179
+
}
180
+
181
+
func (g *GitRepo) MergeWithOptions(patchData []byte, targetBranch string, opts *MergeOptions) error {
129
182
patchFile, err := g.createTempFileWithPatch(patchData)
130
183
if err != nil {
131
184
return &ErrMerge{
···
144
197
}
145
198
defer os.RemoveAll(tmpDir)
146
199
147
-
if err := g.applyPatch(tmpDir, patchFile, false); err != nil {
200
+
if err := g.applyPatch(tmpDir, patchFile, false, opts); err != nil {
148
201
return err
149
202
}
150
203
+9
-5
knotserver/routes.go
+9
-5
knotserver/routes.go
···
559
559
func (h *Handle) Merge(w http.ResponseWriter, r *http.Request) {
560
560
path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
561
561
562
-
var data struct {
563
-
Patch string `json:"patch"`
564
-
Branch string `json:"branch"`
565
-
}
562
+
data := types.MergeRequest{}
566
563
567
564
if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
568
565
writeError(w, err.Error(), http.StatusBadRequest)
···
570
567
return
571
568
}
572
569
570
+
mo := &git.MergeOptions{
571
+
AuthorName: data.AuthorName,
572
+
AuthorEmail: data.AuthorEmail,
573
+
CommitBody: data.CommitBody,
574
+
CommitMessage: data.CommitMessage,
575
+
}
576
+
573
577
patch := data.Patch
574
578
branch := data.Branch
575
579
gr, err := git.Open(path, branch)
···
577
581
notFound(w)
578
582
return
579
583
}
580
-
if err := gr.Merge([]byte(patch), branch); err != nil {
584
+
if err := gr.MergeWithOptions([]byte(patch), branch, mo); err != nil {
581
585
var mergeErr *git.ErrMerge
582
586
if errors.As(err, &mergeErr) {
583
587
conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts))
+9
types/merge.go
+9
types/merge.go
···
11
11
Message string `json:"message"`
12
12
Error string `json:"error"`
13
13
}
14
+
15
+
type MergeRequest struct {
16
+
Patch string `json:"patch"`
17
+
AuthorName string `json:"authorName,omitempty"`
18
+
AuthorEmail string `json:"authorEmail,omitempty"`
19
+
CommitBody string `json:"commitBody,omitempty"`
20
+
CommitMessage string `json:"commitMessage,omitempty"`
21
+
Branch string `json:"branch"`
22
+
}