forked from tangled.org/core
this repo has no description

knotserver/git/merge: merge options use git apply

authored by anirudh.fi and committed by oppi.li 943975c2 26f6bc6b

Changed files
+75 -9
knotserver
types
+57 -4
knotserver/git/merge.go
··· 24 Reason string 25 } 26 27 func (e ErrMerge) Error() string { 28 if e.HasConflict { 29 return fmt.Sprintf("merge failed due to conflicts: %s (%d conflicts)", e.Message, len(e.Conflicts)) ··· 74 return tmpDir, nil 75 } 76 77 - func (g *GitRepo) applyPatch(tmpDir, patchFile string, checkOnly bool) error { 78 var stderr bytes.Buffer 79 var cmd *exec.Cmd 80 ··· 82 cmd = exec.Command("git", "-C", tmpDir, "apply", "--check", "-v", patchFile) 83 } else { 84 exec.Command("git", "-C", tmpDir, "config", "advice.mergeConflict", "false").Run() 85 - cmd = exec.Command("git", "-C", tmpDir, "am", patchFile) 86 } 87 88 cmd.Stderr = &stderr ··· 122 } 123 defer os.RemoveAll(tmpDir) 124 125 - return g.applyPatch(tmpDir, patchFile, true) 126 } 127 128 func (g *GitRepo) Merge(patchData []byte, targetBranch string) error { 129 patchFile, err := g.createTempFileWithPatch(patchData) 130 if err != nil { 131 return &ErrMerge{ ··· 144 } 145 defer os.RemoveAll(tmpDir) 146 147 - if err := g.applyPatch(tmpDir, patchFile, false); err != nil { 148 return err 149 } 150
··· 24 Reason string 25 } 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 + 35 func (e ErrMerge) Error() string { 36 if e.HasConflict { 37 return fmt.Sprintf("merge failed due to conflicts: %s (%d conflicts)", e.Message, len(e.Conflicts)) ··· 82 return tmpDir, nil 83 } 84 85 + func (g *GitRepo) applyPatch(tmpDir, patchFile string, checkOnly bool, opts *MergeOptions) error { 86 var stderr bytes.Buffer 87 var cmd *exec.Cmd 88 ··· 90 cmd = exec.Command("git", "-C", tmpDir, "apply", "--check", "-v", patchFile) 91 } else { 92 exec.Command("git", "-C", tmpDir, "config", "advice.mergeConflict", "false").Run() 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 + } 135 } 136 137 cmd.Stderr = &stderr ··· 171 } 172 defer os.RemoveAll(tmpDir) 173 174 + return g.applyPatch(tmpDir, patchFile, true, nil) 175 } 176 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 { 182 patchFile, err := g.createTempFileWithPatch(patchData) 183 if err != nil { 184 return &ErrMerge{ ··· 197 } 198 defer os.RemoveAll(tmpDir) 199 200 + if err := g.applyPatch(tmpDir, patchFile, false, opts); err != nil { 201 return err 202 } 203
+9 -5
knotserver/routes.go
··· 559 func (h *Handle) Merge(w http.ResponseWriter, r *http.Request) { 560 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 561 562 - var data struct { 563 - Patch string `json:"patch"` 564 - Branch string `json:"branch"` 565 - } 566 567 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 568 writeError(w, err.Error(), http.StatusBadRequest) ··· 570 return 571 } 572 573 patch := data.Patch 574 branch := data.Branch 575 gr, err := git.Open(path, branch) ··· 577 notFound(w) 578 return 579 } 580 - if err := gr.Merge([]byte(patch), branch); err != nil { 581 var mergeErr *git.ErrMerge 582 if errors.As(err, &mergeErr) { 583 conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts))
··· 559 func (h *Handle) Merge(w http.ResponseWriter, r *http.Request) { 560 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 561 562 + data := types.MergeRequest{} 563 564 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 565 writeError(w, err.Error(), http.StatusBadRequest) ··· 567 return 568 } 569 570 + mo := &git.MergeOptions{ 571 + AuthorName: data.AuthorName, 572 + AuthorEmail: data.AuthorEmail, 573 + CommitBody: data.CommitBody, 574 + CommitMessage: data.CommitMessage, 575 + } 576 + 577 patch := data.Patch 578 branch := data.Branch 579 gr, err := git.Open(path, branch) ··· 581 notFound(w) 582 return 583 } 584 + if err := gr.MergeWithOptions([]byte(patch), branch, mo); err != nil { 585 var mergeErr *git.ErrMerge 586 if errors.As(err, &mergeErr) { 587 conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts))
+9
types/merge.go
··· 11 Message string `json:"message"` 12 Error string `json:"error"` 13 }
··· 11 Message string `json:"message"` 12 Error string `json:"error"` 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 + }