From 943975c25ff6a34a3a1d6bdc5141bdb598948148 Mon Sep 17 00:00:00 2001 From: Anirudh Oppiliappan Date: Sun, 16 Mar 2025 19:39:05 +0200 Subject: [PATCH] knotserver/git/merge: merge options use git apply --- knotserver/git/merge.go | 61 ++++++++++++++++++++++++++++++++++++++--- knotserver/routes.go | 14 ++++++---- types/merge.go | 9 ++++++ 3 files changed, 75 insertions(+), 9 deletions(-) diff --git a/knotserver/git/merge.go b/knotserver/git/merge.go index 90fc462f..7dd2f654 100644 --- a/knotserver/git/merge.go +++ b/knotserver/git/merge.go @@ -24,6 +24,14 @@ type ConflictInfo struct { Reason string } +// MergeOptions specifies the configuration for a merge operation +type MergeOptions struct { + CommitMessage string + CommitBody string + AuthorName string + AuthorEmail string +} + func (e ErrMerge) Error() string { if e.HasConflict { return fmt.Sprintf("merge failed due to conflicts: %s (%d conflicts)", e.Message, len(e.Conflicts)) @@ -74,7 +82,7 @@ func (g *GitRepo) cloneRepository(targetBranch string) (string, error) { return tmpDir, nil } -func (g *GitRepo) applyPatch(tmpDir, patchFile string, checkOnly bool) error { +func (g *GitRepo) applyPatch(tmpDir, patchFile string, checkOnly bool, opts *MergeOptions) error { var stderr bytes.Buffer var cmd *exec.Cmd @@ -82,7 +90,48 @@ func (g *GitRepo) applyPatch(tmpDir, patchFile string, checkOnly bool) error { cmd = exec.Command("git", "-C", tmpDir, "apply", "--check", "-v", patchFile) } else { exec.Command("git", "-C", tmpDir, "config", "advice.mergeConflict", "false").Run() - cmd = exec.Command("git", "-C", tmpDir, "am", patchFile) + + if opts != nil { + applyCmd := exec.Command("git", "-C", tmpDir, "apply", patchFile) + applyCmd.Stderr = &stderr + if err := applyCmd.Run(); err != nil { + return fmt.Errorf("patch application failed: %s", stderr.String()) + } + + stageCmd := exec.Command("git", "-C", tmpDir, "add", ".") + if err := stageCmd.Run(); err != nil { + return fmt.Errorf("failed to stage changes: %w", err) + } + + commitArgs := []string{"-C", tmpDir, "commit"} + + // Set author if provided + authorName := opts.AuthorName + authorEmail := opts.AuthorEmail + + if authorEmail == "" { + authorEmail = "noreply@tangled.sh" + } + + if authorName == "" { + authorName = "Tangled" + } + + if authorName != "" { + commitArgs = append(commitArgs, "--author", fmt.Sprintf("%s <%s>", authorName, authorEmail)) + } + + commitArgs = append(commitArgs, "-m", opts.CommitMessage) + + if opts.CommitBody != "" { + commitArgs = append(commitArgs, "-m", opts.CommitBody) + } + + cmd = exec.Command("git", commitArgs...) + } else { + // If no commit message specified, use git-am which automatically creates a commit + cmd = exec.Command("git", "-C", tmpDir, "am", patchFile) + } } cmd.Stderr = &stderr @@ -122,10 +171,14 @@ func (g *GitRepo) MergeCheck(patchData []byte, targetBranch string) error { } defer os.RemoveAll(tmpDir) - return g.applyPatch(tmpDir, patchFile, true) + return g.applyPatch(tmpDir, patchFile, true, nil) } func (g *GitRepo) Merge(patchData []byte, targetBranch string) error { + return g.MergeWithOptions(patchData, targetBranch, nil) +} + +func (g *GitRepo) MergeWithOptions(patchData []byte, targetBranch string, opts *MergeOptions) error { patchFile, err := g.createTempFileWithPatch(patchData) if err != nil { return &ErrMerge{ @@ -144,7 +197,7 @@ func (g *GitRepo) Merge(patchData []byte, targetBranch string) error { } defer os.RemoveAll(tmpDir) - if err := g.applyPatch(tmpDir, patchFile, false); err != nil { + if err := g.applyPatch(tmpDir, patchFile, false, opts); err != nil { return err } diff --git a/knotserver/routes.go b/knotserver/routes.go index 67e3e796..fb1a0546 100644 --- a/knotserver/routes.go +++ b/knotserver/routes.go @@ -559,10 +559,7 @@ func (h *Handle) RemoveRepo(w http.ResponseWriter, r *http.Request) { func (h *Handle) Merge(w http.ResponseWriter, r *http.Request) { path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) - var data struct { - Patch string `json:"patch"` - Branch string `json:"branch"` - } + data := types.MergeRequest{} if err := json.NewDecoder(r.Body).Decode(&data); err != nil { writeError(w, err.Error(), http.StatusBadRequest) @@ -570,6 +567,13 @@ func (h *Handle) Merge(w http.ResponseWriter, r *http.Request) { return } + mo := &git.MergeOptions{ + AuthorName: data.AuthorName, + AuthorEmail: data.AuthorEmail, + CommitBody: data.CommitBody, + CommitMessage: data.CommitMessage, + } + patch := data.Patch branch := data.Branch gr, err := git.Open(path, branch) @@ -577,7 +581,7 @@ func (h *Handle) Merge(w http.ResponseWriter, r *http.Request) { notFound(w) return } - if err := gr.Merge([]byte(patch), branch); err != nil { + if err := gr.MergeWithOptions([]byte(patch), branch, mo); err != nil { var mergeErr *git.ErrMerge if errors.As(err, &mergeErr) { conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts)) diff --git a/types/merge.go b/types/merge.go index 73bccf0c..c9f25ec8 100644 --- a/types/merge.go +++ b/types/merge.go @@ -11,3 +11,12 @@ type MergeCheckResponse struct { Message string `json:"message"` Error string `json:"error"` } + +type MergeRequest struct { + Patch string `json:"patch"` + AuthorName string `json:"authorName,omitempty"` + AuthorEmail string `json:"authorEmail,omitempty"` + CommitBody string `json:"commitBody,omitempty"` + CommitMessage string `json:"commitMessage,omitempty"` + Branch string `json:"branch"` +} -- 2.51.2