···
···
"github.com/dgraph-io/ristretto"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
16
+
"tangled.org/core/patchutil"
17
+
"tangled.org/core/types"
type MergeCheckCache struct {
···
165
-
func (g *GitRepo) applyPatch(tmpDir, patchFile string, opts MergeOptions) error {
168
+
func (g *GitRepo) applyPatch(patchData, patchFile string, opts MergeOptions) error {
// configure default git user before merge
170
-
exec.Command("git", "-C", tmpDir, "config", "user.name", opts.CommitterName).Run()
171
-
exec.Command("git", "-C", tmpDir, "config", "user.email", opts.CommitterEmail).Run()
172
-
exec.Command("git", "-C", tmpDir, "config", "advice.mergeConflict", "false").Run()
173
+
exec.Command("git", "-C", g.path, "config", "user.name", opts.CommitterName).Run()
174
+
exec.Command("git", "-C", g.path, "config", "user.email", opts.CommitterEmail).Run()
175
+
exec.Command("git", "-C", g.path, "config", "advice.mergeConflict", "false").Run()
// if patch is a format-patch, apply using 'git am'
176
-
cmd = exec.Command("git", "-C", tmpDir, "am", patchFile)
178
-
// else, apply using 'git apply' and commit it manually
179
-
applyCmd := exec.Command("git", "-C", tmpDir, "apply", patchFile)
180
-
applyCmd.Stderr = &stderr
181
-
if err := applyCmd.Run(); err != nil {
182
-
return fmt.Errorf("patch application failed: %s", stderr.String())
179
+
return g.applyMailbox(patchData)
185
-
stageCmd := exec.Command("git", "-C", tmpDir, "add", ".")
186
-
if err := stageCmd.Run(); err != nil {
187
-
return fmt.Errorf("failed to stage changes: %w", err)
182
+
// else, apply using 'git apply' and commit it manually
183
+
applyCmd := exec.Command("git", "-C", g.path, "apply", patchFile)
184
+
applyCmd.Stderr = &stderr
185
+
if err := applyCmd.Run(); err != nil {
186
+
return fmt.Errorf("patch application failed: %s", stderr.String())
190
-
commitArgs := []string{"-C", tmpDir, "commit"}
189
+
stageCmd := exec.Command("git", "-C", g.path, "add", ".")
190
+
if err := stageCmd.Run(); err != nil {
191
+
return fmt.Errorf("failed to stage changes: %w", err)
192
-
// Set author if provided
193
-
authorName := opts.AuthorName
194
-
authorEmail := opts.AuthorEmail
194
+
commitArgs := []string{"-C", g.path, "commit"}
196
-
if authorName != "" && authorEmail != "" {
197
-
commitArgs = append(commitArgs, "--author", fmt.Sprintf("%s <%s>", authorName, authorEmail))
199
-
// else, will default to knot's global user.name & user.email configured via `KNOT_GIT_USER_*` env variables
196
+
// Set author if provided
197
+
authorName := opts.AuthorName
198
+
authorEmail := opts.AuthorEmail
201
-
commitArgs = append(commitArgs, "-m", opts.CommitMessage)
200
+
if authorName != "" && authorEmail != "" {
201
+
commitArgs = append(commitArgs, "--author", fmt.Sprintf("%s <%s>", authorName, authorEmail))
203
+
// else, will default to knot's global user.name & user.email configured via `KNOT_GIT_USER_*` env variables
203
-
if opts.CommitBody != "" {
204
-
commitArgs = append(commitArgs, "-m", opts.CommitBody)
205
+
commitArgs = append(commitArgs, "-m", opts.CommitMessage)
207
-
cmd = exec.Command("git", commitArgs...)
207
+
if opts.CommitBody != "" {
208
+
commitArgs = append(commitArgs, "-m", opts.CommitBody)
211
+
cmd = exec.Command("git", commitArgs...)
if err := cmd.Run(); err != nil {
···
219
-
func (g *GitRepo) MergeCheck(patchData []byte, targetBranch string) error {
222
+
func (g *GitRepo) applyMailbox(patchData string) error {
223
+
fps, err := patchutil.ExtractPatches(patchData)
225
+
return fmt.Errorf("failed to extract patches: %w", err)
228
+
// apply each patch one by one
229
+
// update the newly created commit object to add the change-id header
231
+
for i, p := range fps {
232
+
newCommit, err := g.applySingleMailbox(p)
237
+
log.Printf("applying mailbox patch %d/%d: committed %s\n", i+1, total, newCommit.String())
243
+
func (g *GitRepo) applySingleMailbox(singlePatch types.FormatPatch) (plumbing.Hash, error) {
244
+
tmpPatch, err := g.createTempFileWithPatch(singlePatch.Raw)
246
+
return plumbing.ZeroHash, fmt.Errorf("failed to create temporary patch file for singluar mailbox patch: %w", err)
249
+
var stderr bytes.Buffer
250
+
cmd := exec.Command("git", "-C", g.path, "am", tmpPatch)
251
+
cmd.Stderr = &stderr
253
+
head, err := g.r.Head()
255
+
return plumbing.ZeroHash, err
257
+
log.Println("head before apply", head.Hash().String())
259
+
if err := cmd.Run(); err != nil {
260
+
return plumbing.ZeroHash, fmt.Errorf("patch application failed: %s", stderr.String())
263
+
if err := g.Refresh(); err != nil {
264
+
return plumbing.ZeroHash, fmt.Errorf("failed to refresh repository state: %w", err)
267
+
head, err = g.r.Head()
269
+
return plumbing.ZeroHash, err
271
+
log.Println("head after apply", head.Hash().String())
273
+
newHash := head.Hash()
274
+
if changeId, err := singlePatch.ChangeId(); err != nil {
276
+
} else if updatedHash, err := g.setChangeId(head.Hash(), changeId); err != nil {
277
+
return plumbing.ZeroHash, err
279
+
newHash = updatedHash
282
+
return newHash, nil
285
+
func (g *GitRepo) setChangeId(hash plumbing.Hash, changeId string) (plumbing.Hash, error) {
286
+
log.Printf("updating change ID of %s to %s\n", hash.String(), changeId)
287
+
obj, err := g.r.CommitObject(hash)
289
+
return plumbing.ZeroHash, fmt.Errorf("failed to get commit object for hash %s: %w", hash.String(), err)
292
+
// write the change-id header
293
+
obj.ExtraHeaders["change-id"] = []byte(changeId)
295
+
// create a new object
296
+
dest := g.r.Storer.NewEncodedObject()
297
+
if err := obj.Encode(dest); err != nil {
298
+
return plumbing.ZeroHash, fmt.Errorf("failed to create new object: %w", err)
301
+
// store the new object
302
+
newHash, err := g.r.Storer.SetEncodedObject(dest)
304
+
return plumbing.ZeroHash, fmt.Errorf("failed to store new object: %w", err)
307
+
log.Printf("hash changed from %s to %s\n", obj.Hash.String(), newHash.String())
309
+
// find the branch that HEAD is pointing to
310
+
ref, err := g.r.Head()
312
+
return plumbing.ZeroHash, fmt.Errorf("failed to fetch HEAD: %w", err)
315
+
// and update that branch to point to new commit
316
+
if ref.Name().IsBranch() {
317
+
err = g.r.Storer.SetReference(plumbing.NewHashReference(ref.Name(), newHash))
319
+
return plumbing.ZeroHash, fmt.Errorf("failed to update HEAD: %w", err)
323
+
// new hash of commit
324
+
return newHash, nil
327
+
func (g *GitRepo) MergeCheck(patchData string, targetBranch string) error {
if val, ok := mergeCheckCache.Get(g, patchData, targetBranch); ok {
···
defer os.RemoveAll(tmpDir)
266
-
if err := g.applyPatch(tmpDir, patchFile, opts); err != nil {
374
+
tmpRepo, err := PlainOpen(tmpDir)
379
+
if err := tmpRepo.applyPatch(patchData, patchFile, opts); err != nil {