+7
-6
types/merge.go
+7
-6
types/merge.go
···
13
13
}
14
14
15
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"`
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
+
CommitFooters map[string]string `json:"commitFooters,omitempty"`
22
+
Branch string `json:"branch"`
22
23
}
+2
-1
appview/knotclient/signer.go
+2
-1
appview/knotclient/signer.go
···
201
201
202
202
func (s *SignedClient) Merge(
203
203
patch []byte,
204
-
ownerDid, targetRepo, branch, commitMessage, commitBody, authorName, authorEmail string,
204
+
ownerDid, targetRepo, branch, commitMessage, commitBody, authorName, authorEmail string, footers map[string]string,
205
205
) (*http.Response, error) {
206
206
const (
207
207
Method = "POST"
···
215
215
AuthorName: authorName,
216
216
AuthorEmail: authorEmail,
217
217
Patch: string(patch),
218
+
CommitFooters: footers,
218
219
}
219
220
220
221
body, _ := json.Marshal(mr)
+8
-1
appview/state/pull.go
+8
-1
appview/state/pull.go
···
1534
1534
log.Printf("failed to get primary email: %s", err)
1535
1535
}
1536
1536
1537
+
actor := s.oauth.GetUser(r) // no need to check for nil as this is an authenticated request
1538
+
1539
+
var footers = map[string]string{
1540
+
"Pull-id": string(pull.PullAt()),
1541
+
"Merged-by": actor.Did,
1542
+
}
1543
+
1537
1544
ksClient, err := knotclient.NewSignedClient(f.Knot, secret, s.config.Core.Dev)
1538
1545
if err != nil {
1539
1546
log.Printf("failed to create signed client for %s: %s", f.Knot, err)
···
1542
1549
}
1543
1550
1544
1551
// Merge the pull request
1545
-
resp, err := ksClient.Merge([]byte(pull.LatestPatch()), f.OwnerDid(), f.RepoName, pull.TargetBranch, pull.Title, pull.Body, ident.Handle.String(), email.Address)
1552
+
resp, err := ksClient.Merge([]byte(pull.LatestPatch()), f.OwnerDid(), f.RepoName, pull.TargetBranch, pull.Title, pull.Body, ident.Handle.String(), email.Address, footers)
1546
1553
if err != nil {
1547
1554
log.Printf("failed to merge pull request: %s", err)
1548
1555
s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
+45
knotserver/git/merge.go
+45
knotserver/git/merge.go
···
32
32
AuthorName string
33
33
AuthorEmail string
34
34
FormatPatch bool
35
+
CommitFooters map[string]string
35
36
}
36
37
37
38
func (e ErrMerge) Error() string {
···
88
89
var stderr bytes.Buffer
89
90
var cmd *exec.Cmd
90
91
92
+
footers := ""
93
+
for k, v := range opts.CommitFooters {
94
+
footers += fmt.Sprintf("\n%s: %s", k, v)
95
+
}
96
+
91
97
if checkOnly {
92
98
cmd = exec.Command("git", "-C", tmpDir, "apply", "--check", "-v", patchFile)
93
99
} else {
94
100
// if patch is a format-patch, apply using 'git am'
95
101
if opts.FormatPatch {
102
+
// find head before the change
103
+
var beforeHash bytes.Buffer
104
+
hashCmd := exec.Command("git", "-C", tmpDir, "rev-parse", "HEAD")
105
+
hashCmd.Stdout = &beforeHash
106
+
if err := hashCmd.Run(); err != nil {
107
+
return fmt.Errorf("failed to get hash before patch: %w", err)
108
+
}
109
+
beforeCommit := strings.TrimSpace(beforeHash.String())
110
+
96
111
amCmd := exec.Command("git", "-C", tmpDir, "am", patchFile)
97
112
amCmd.Stderr = &stderr
98
113
if err := amCmd.Run(); err != nil {
99
114
return fmt.Errorf("patch application failed: %s", stderr.String())
100
115
}
116
+
117
+
// command to ammend commit with required footers
118
+
amendCmdString := fmt.Sprintf("git commit --amend -m \"$(git log --format=%%B -n1)\" -m \"%s\"", footers)
119
+
120
+
// create a temporary script file to hold the amend command (exec
121
+
// command can't contain newlines, so we need to create a script)
122
+
scriptFile, err := os.CreateTemp("", "rebase-exec-*.sh")
123
+
if err != nil {
124
+
return fmt.Errorf("failed to create temporary script for rebase: %w", err)
125
+
}
126
+
defer os.Remove(scriptFile.Name())
127
+
128
+
_, err = scriptFile.WriteString(amendCmdString)
129
+
defer scriptFile.Close()
130
+
if err != nil {
131
+
return fmt.Errorf("failed to write to temporary rebase script: %w", err)
132
+
}
133
+
134
+
if err := scriptFile.Chmod(0755); err != nil {
135
+
return fmt.Errorf("failed to make temporary rebase script executable: %w", err)
136
+
}
137
+
138
+
rebaseCmd := exec.Command("git", "-C", tmpDir, "rebase", "--exec", "sh "+scriptFile.Name(), beforeCommit)
139
+
rebaseCmd.Stderr = &stderr
140
+
if err := rebaseCmd.Run(); err != nil {
141
+
return fmt.Errorf("rebase failed: %s", stderr.String())
142
+
}
143
+
101
144
return nil
102
145
}
103
146
···
139
182
commitArgs = append(commitArgs, "-m", opts.CommitBody)
140
183
}
141
184
185
+
commitArgs = append(commitArgs, "-m", footers)
186
+
142
187
cmd = exec.Command("git", commitArgs...)
143
188
} else {
144
189
// If no commit message specified, use git-am which automatically creates a commit