Monorepo for Tangled tangled.org

knotserver/git: rework merge check to also use git am #1033

merged opened by oppi.li targeting master from op/rlrsyllrmmrk

we were using git apply in merge check and git am for the actual merge, but in reality, there are slight behavior changes among the two. this change switches out git apply in mergeCheck to use git am when dealing with mailbox style patches.

Signed-off-by: oppiliappan me@oppi.li

Labels

None yet.

assignee

None yet.

Participants 1
AT URI
at://did:plc:qfpnj4og54vl56wngdriaxug/sh.tangled.repo.pull/3mdifrzrs2722
+37 -31
Diff #1
+30 -30
knotserver/git/merge.go
··· 107 return fmt.Sprintf("merge failed: %s", e.Message) 108 } 109 110 - func (g *GitRepo) createTempFileWithPatch(patchData string) (string, error) { 111 tmpFile, err := os.CreateTemp("", "git-patch-*.patch") 112 if err != nil { 113 return "", fmt.Errorf("failed to create temporary patch file: %w", err) 114 } 115 116 - if _, err := tmpFile.Write([]byte(patchData)); err != nil { 117 tmpFile.Close() 118 os.Remove(tmpFile.Name()) 119 return "", fmt.Errorf("failed to write patch data to temporary file: %w", err) ··· 127 return tmpFile.Name(), nil 128 } 129 130 - func (g *GitRepo) cloneRepository(targetBranch string) (string, error) { 131 tmpDir, err := os.MkdirTemp("", "git-clone-") 132 if err != nil { 133 return "", fmt.Errorf("failed to create temporary directory: %w", err) ··· 147 return tmpDir, nil 148 } 149 150 - func (g *GitRepo) checkPatch(tmpDir, patchFile string) error { 151 - var stderr bytes.Buffer 152 - 153 - cmd := exec.Command("git", "-C", tmpDir, "apply", "--check", "-v", patchFile) 154 - cmd.Stderr = &stderr 155 - 156 - if err := cmd.Run(); err != nil { 157 - conflicts := parseGitApplyErrors(stderr.String()) 158 - return &ErrMerge{ 159 - Message: "patch cannot be applied cleanly", 160 - Conflicts: conflicts, 161 - HasConflict: len(conflicts) > 0, 162 - OtherError: err, 163 - } 164 - } 165 - return nil 166 - } 167 - 168 func (g *GitRepo) applyPatch(patchData, patchFile string, opts MergeOptions) error { 169 var stderr bytes.Buffer 170 var cmd *exec.Cmd ··· 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() 176 177 // if patch is a format-patch, apply using 'git am' 178 if opts.FormatPatch { ··· 213 cmd.Stderr = &stderr 214 215 if err := cmd.Run(); err != nil { 216 - return fmt.Errorf("patch application failed: %s", stderr.String()) 217 } 218 219 return nil ··· 241 } 242 243 func (g *GitRepo) applySingleMailbox(singlePatch types.FormatPatch) (plumbing.Hash, error) { 244 - tmpPatch, err := g.createTempFileWithPatch(singlePatch.Raw) 245 if err != nil { 246 return plumbing.ZeroHash, fmt.Errorf("failed to create temporary patch file for singluar mailbox patch: %w", err) 247 } ··· 257 log.Println("head before apply", head.Hash().String()) 258 259 if err := cmd.Run(); err != nil { 260 - return plumbing.ZeroHash, fmt.Errorf("patch application failed: %s", stderr.String()) 261 } 262 263 if err := g.Refresh(); err != nil { ··· 324 return newHash, nil 325 } 326 327 - func (g *GitRepo) MergeCheck(patchData string, targetBranch string) error { 328 if val, ok := mergeCheckCache.Get(g, patchData, targetBranch); ok { 329 return val 330 } 331 332 - patchFile, err := g.createTempFileWithPatch(patchData) 333 if err != nil { 334 return &ErrMerge{ 335 Message: err.Error(), ··· 338 } 339 defer os.Remove(patchFile) 340 341 - tmpDir, err := g.cloneRepository(targetBranch) 342 if err != nil { 343 return &ErrMerge{ 344 Message: err.Error(), ··· 347 } 348 defer os.RemoveAll(tmpDir) 349 350 - result := g.checkPatch(tmpDir, patchFile) 351 mergeCheckCache.Set(g, patchData, targetBranch, result) 352 return result 353 } 354 355 func (g *GitRepo) MergeWithOptions(patchData string, targetBranch string, opts MergeOptions) error { 356 - patchFile, err := g.createTempFileWithPatch(patchData) 357 if err != nil { 358 return &ErrMerge{ 359 Message: err.Error(), ··· 362 } 363 defer os.Remove(patchFile) 364 365 - tmpDir, err := g.cloneRepository(targetBranch) 366 if err != nil { 367 return &ErrMerge{ 368 Message: err.Error(),
··· 107 return fmt.Sprintf("merge failed: %s", e.Message) 108 } 109 110 + func createTemp(data string) (string, error) { 111 tmpFile, err := os.CreateTemp("", "git-patch-*.patch") 112 if err != nil { 113 return "", fmt.Errorf("failed to create temporary patch file: %w", err) 114 } 115 116 + if _, err := tmpFile.Write([]byte(data)); err != nil { 117 tmpFile.Close() 118 os.Remove(tmpFile.Name()) 119 return "", fmt.Errorf("failed to write patch data to temporary file: %w", err) ··· 127 return tmpFile.Name(), nil 128 } 129 130 + func (g *GitRepo) cloneTemp(targetBranch string) (string, error) { 131 tmpDir, err := os.MkdirTemp("", "git-clone-") 132 if err != nil { 133 return "", fmt.Errorf("failed to create temporary directory: %w", err) ··· 147 return tmpDir, nil 148 } 149 150 func (g *GitRepo) applyPatch(patchData, patchFile string, opts MergeOptions) error { 151 var stderr bytes.Buffer 152 var cmd *exec.Cmd ··· 155 exec.Command("git", "-C", g.path, "config", "user.name", opts.CommitterName).Run() 156 exec.Command("git", "-C", g.path, "config", "user.email", opts.CommitterEmail).Run() 157 exec.Command("git", "-C", g.path, "config", "advice.mergeConflict", "false").Run() 158 + exec.Command("git", "-C", g.path, "config", "advice.amWorkDir", "false").Run() 159 160 // if patch is a format-patch, apply using 'git am' 161 if opts.FormatPatch { ··· 196 cmd.Stderr = &stderr 197 198 if err := cmd.Run(); err != nil { 199 + conflicts := parseGitApplyErrors(stderr.String()) 200 + return &ErrMerge{ 201 + Message: "patch cannot be applied cleanly", 202 + Conflicts: conflicts, 203 + HasConflict: len(conflicts) > 0, 204 + OtherError: err, 205 + } 206 } 207 208 return nil ··· 230 } 231 232 func (g *GitRepo) applySingleMailbox(singlePatch types.FormatPatch) (plumbing.Hash, error) { 233 + tmpPatch, err := createTemp(singlePatch.Raw) 234 if err != nil { 235 return plumbing.ZeroHash, fmt.Errorf("failed to create temporary patch file for singluar mailbox patch: %w", err) 236 } ··· 246 log.Println("head before apply", head.Hash().String()) 247 248 if err := cmd.Run(); err != nil { 249 + conflicts := parseGitApplyErrors(stderr.String()) 250 + return plumbing.ZeroHash, &ErrMerge{ 251 + Message: "patch cannot be applied cleanly", 252 + Conflicts: conflicts, 253 + HasConflict: len(conflicts) > 0, 254 + OtherError: err, 255 + } 256 } 257 258 if err := g.Refresh(); err != nil { ··· 319 return newHash, nil 320 } 321 322 + func (g *GitRepo) MergeCheckWithOptions(patchData string, targetBranch string, mo MergeOptions) error { 323 if val, ok := mergeCheckCache.Get(g, patchData, targetBranch); ok { 324 return val 325 } 326 327 + patchFile, err := createTemp(patchData) 328 if err != nil { 329 return &ErrMerge{ 330 Message: err.Error(), ··· 333 } 334 defer os.Remove(patchFile) 335 336 + tmpDir, err := g.cloneTemp(targetBranch) 337 if err != nil { 338 return &ErrMerge{ 339 Message: err.Error(), ··· 342 } 343 defer os.RemoveAll(tmpDir) 344 345 + tmpRepo, err := PlainOpen(tmpDir) 346 + if err != nil { 347 + return err 348 + } 349 + 350 + result := tmpRepo.applyPatch(patchData, patchFile, mo) 351 mergeCheckCache.Set(g, patchData, targetBranch, result) 352 return result 353 } 354 355 func (g *GitRepo) MergeWithOptions(patchData string, targetBranch string, opts MergeOptions) error { 356 + patchFile, err := createTemp(patchData) 357 if err != nil { 358 return &ErrMerge{ 359 Message: err.Error(), ··· 362 } 363 defer os.Remove(patchFile) 364 365 + tmpDir, err := g.cloneTemp(targetBranch) 366 if err != nil { 367 return &ErrMerge{ 368 Message: err.Error(),
+7 -1
knotserver/xrpc/merge_check.go
··· 9 securejoin "github.com/cyphar/filepath-securejoin" 10 "tangled.org/core/api/tangled" 11 "tangled.org/core/knotserver/git" 12 xrpcerr "tangled.org/core/xrpc/errors" 13 ) 14 ··· 51 return 52 } 53 54 - err = gr.MergeCheck(data.Patch, data.Branch) 55 56 response := tangled.RepoMergeCheck_Output{ 57 Is_conflicted: false,
··· 9 securejoin "github.com/cyphar/filepath-securejoin" 10 "tangled.org/core/api/tangled" 11 "tangled.org/core/knotserver/git" 12 + "tangled.org/core/patchutil" 13 xrpcerr "tangled.org/core/xrpc/errors" 14 ) 15 ··· 52 return 53 } 54 55 + mo := git.MergeOptions{} 56 + mo.CommitterName = x.Config.Git.UserName 57 + mo.CommitterEmail = x.Config.Git.UserEmail 58 + mo.FormatPatch = patchutil.IsFormatPatch(data.Patch) 59 + 60 + err = gr.MergeCheckWithOptions(data.Patch, data.Branch, mo) 61 62 response := tangled.RepoMergeCheck_Output{ 63 Is_conflicted: false,

History

2 rounds 0 comments
sign up or login to add to the discussion
1 commit
expand
knotserver/git: rework merge check to also use git am
2/3 timeout, 1/3 success
expand
expand 0 comments
pull request successfully merged
oppi.li submitted #0
1 commit
expand
knotserver/git: rework merge check to also use git am
2/3 timeout, 1/3 success
expand
expand 0 comments