forked from tangled.org/core
Monorepo for Tangled — https://tangled.org

knotserver: git/merge: separate `applyPatch` and `checkPatch`

- we don't need `MergeOptions` for checking a patch

Signed-off-by: Seongmin Lee <boltlessengineer@proton.me>

authored by boltless.me and committed by Tangled 3c066500 cdac8683

Changed files
+62 -61
knotserver
git
+62 -61
knotserver/git/merge.go
··· 12 "github.com/dgraph-io/ristretto" 13 "github.com/go-git/go-git/v5" 14 "github.com/go-git/go-git/v5/plumbing" 15 - "tangled.sh/tangled.sh/core/patchutil" 16 ) 17 18 type MergeCheckCache struct { ··· 143 return tmpDir, nil 144 } 145 146 - func (g *GitRepo) applyPatch(tmpDir, patchFile string, checkOnly bool, opts *MergeOptions) error { 147 var stderr bytes.Buffer 148 var cmd *exec.Cmd 149 150 - if checkOnly { 151 - cmd = exec.Command("git", "-C", tmpDir, "apply", "--check", "-v", patchFile) 152 - } else { 153 - // if patch is a format-patch, apply using 'git am' 154 - if opts.FormatPatch { 155 - amCmd := exec.Command("git", "-C", tmpDir, "am", patchFile) 156 - amCmd.Stderr = &stderr 157 - if err := amCmd.Run(); err != nil { 158 - return fmt.Errorf("patch application failed: %s", stderr.String()) 159 - } 160 - return nil 161 } 162 163 - // else, apply using 'git apply' and commit it manually 164 - exec.Command("git", "-C", tmpDir, "config", "advice.mergeConflict", "false").Run() 165 - if opts != nil { 166 - applyCmd := exec.Command("git", "-C", tmpDir, "apply", patchFile) 167 - applyCmd.Stderr = &stderr 168 - if err := applyCmd.Run(); err != nil { 169 - return fmt.Errorf("patch application failed: %s", stderr.String()) 170 - } 171 172 - stageCmd := exec.Command("git", "-C", tmpDir, "add", ".") 173 - if err := stageCmd.Run(); err != nil { 174 - return fmt.Errorf("failed to stage changes: %w", err) 175 - } 176 177 - commitArgs := []string{"-C", tmpDir, "commit"} 178 179 - // Set author if provided 180 - authorName := opts.AuthorName 181 - authorEmail := opts.AuthorEmail 182 183 - if authorEmail == "" { 184 - authorEmail = "noreply@tangled.sh" 185 - } 186 187 - if authorName == "" { 188 - authorName = "Tangled" 189 - } 190 191 - if authorName != "" { 192 - commitArgs = append(commitArgs, "--author", fmt.Sprintf("%s <%s>", authorName, authorEmail)) 193 - } 194 195 - commitArgs = append(commitArgs, "-m", opts.CommitMessage) 196 197 - if opts.CommitBody != "" { 198 - commitArgs = append(commitArgs, "-m", opts.CommitBody) 199 - } 200 201 - cmd = exec.Command("git", commitArgs...) 202 - } else { 203 - // If no commit message specified, use git-am which automatically creates a commit 204 - cmd = exec.Command("git", "-C", tmpDir, "am", patchFile) 205 - } 206 } 207 208 cmd.Stderr = &stderr 209 210 if err := cmd.Run(); err != nil { 211 - if checkOnly { 212 - conflicts := parseGitApplyErrors(stderr.String()) 213 - return &ErrMerge{ 214 - Message: "patch cannot be applied cleanly", 215 - Conflicts: conflicts, 216 - HasConflict: len(conflicts) > 0, 217 - OtherError: err, 218 - } 219 - } 220 return fmt.Errorf("patch application failed: %s", stderr.String()) 221 } 222 ··· 228 return val 229 } 230 231 - var opts MergeOptions 232 - opts.FormatPatch = patchutil.IsFormatPatch(string(patchData)) 233 - 234 patchFile, err := g.createTempFileWithPatch(patchData) 235 if err != nil { 236 return &ErrMerge{ ··· 249 } 250 defer os.RemoveAll(tmpDir) 251 252 - result := g.applyPatch(tmpDir, patchFile, true, &opts) 253 mergeCheckCache.Set(g, patchData, targetBranch, result) 254 return result 255 } ··· 277 } 278 defer os.RemoveAll(tmpDir) 279 280 - if err := g.applyPatch(tmpDir, patchFile, false, opts); err != nil { 281 return err 282 } 283
··· 12 "github.com/dgraph-io/ristretto" 13 "github.com/go-git/go-git/v5" 14 "github.com/go-git/go-git/v5/plumbing" 15 ) 16 17 type MergeCheckCache struct { ··· 142 return tmpDir, nil 143 } 144 145 + func (g *GitRepo) checkPatch(tmpDir, patchFile string) error { 146 + var stderr bytes.Buffer 147 + 148 + cmd := exec.Command("git", "-C", tmpDir, "apply", "--check", "-v", patchFile) 149 + cmd.Stderr = &stderr 150 + 151 + if err := cmd.Run(); err != nil { 152 + conflicts := parseGitApplyErrors(stderr.String()) 153 + return &ErrMerge{ 154 + Message: "patch cannot be applied cleanly", 155 + Conflicts: conflicts, 156 + HasConflict: len(conflicts) > 0, 157 + OtherError: err, 158 + } 159 + } 160 + return nil 161 + } 162 + 163 + func (g *GitRepo) applyPatch(tmpDir, patchFile string, opts *MergeOptions) error { 164 var stderr bytes.Buffer 165 var cmd *exec.Cmd 166 167 + // if patch is a format-patch, apply using 'git am' 168 + if opts.FormatPatch { 169 + amCmd := exec.Command("git", "-C", tmpDir, "am", patchFile) 170 + amCmd.Stderr = &stderr 171 + if err := amCmd.Run(); err != nil { 172 + return fmt.Errorf("patch application failed: %s", stderr.String()) 173 } 174 + return nil 175 + } 176 177 + // else, apply using 'git apply' and commit it manually 178 + exec.Command("git", "-C", tmpDir, "config", "advice.mergeConflict", "false").Run() 179 + if opts != nil { 180 + applyCmd := exec.Command("git", "-C", tmpDir, "apply", patchFile) 181 + applyCmd.Stderr = &stderr 182 + if err := applyCmd.Run(); err != nil { 183 + return fmt.Errorf("patch application failed: %s", stderr.String()) 184 + } 185 186 + stageCmd := exec.Command("git", "-C", tmpDir, "add", ".") 187 + if err := stageCmd.Run(); err != nil { 188 + return fmt.Errorf("failed to stage changes: %w", err) 189 + } 190 191 + commitArgs := []string{"-C", tmpDir, "commit"} 192 193 + // Set author if provided 194 + authorName := opts.AuthorName 195 + authorEmail := opts.AuthorEmail 196 197 + if authorEmail == "" { 198 + authorEmail = "noreply@tangled.sh" 199 + } 200 201 + if authorName == "" { 202 + authorName = "Tangled" 203 + } 204 205 + if authorName != "" { 206 + commitArgs = append(commitArgs, "--author", fmt.Sprintf("%s <%s>", authorName, authorEmail)) 207 + } 208 209 + commitArgs = append(commitArgs, "-m", opts.CommitMessage) 210 211 + if opts.CommitBody != "" { 212 + commitArgs = append(commitArgs, "-m", opts.CommitBody) 213 + } 214 215 + cmd = exec.Command("git", commitArgs...) 216 + } else { 217 + // If no commit message specified, use git-am which automatically creates a commit 218 + cmd = exec.Command("git", "-C", tmpDir, "am", patchFile) 219 } 220 221 cmd.Stderr = &stderr 222 223 if err := cmd.Run(); err != nil { 224 return fmt.Errorf("patch application failed: %s", stderr.String()) 225 } 226 ··· 232 return val 233 } 234 235 patchFile, err := g.createTempFileWithPatch(patchData) 236 if err != nil { 237 return &ErrMerge{ ··· 250 } 251 defer os.RemoveAll(tmpDir) 252 253 + result := g.checkPatch(tmpDir, patchFile) 254 mergeCheckCache.Set(g, patchData, targetBranch, result) 255 return result 256 } ··· 278 } 279 defer os.RemoveAll(tmpDir) 280 281 + if err := g.applyPatch(tmpDir, patchFile, opts); err != nil { 282 return err 283 } 284