feat: add commit footers for pull merge #109

closed
opened by dvjn.dev targeting master from dvjn.dev/core: push-rwutqnoxqxlr
Changed files
+63 -8
appview
knotclient
state
knotserver
types
+7 -6
types/merge.go
··· 13 13 } 14 14 15 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"` 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 + CommitFooters map[string]string `json:"commitFooters,omitempty"` 22 + Branch string `json:"branch"` 22 23 }
+2 -1
appview/knotclient/signer.go
··· 201 201 202 202 func (s *SignedClient) Merge( 203 203 patch []byte, 204 - ownerDid, targetRepo, branch, commitMessage, commitBody, authorName, authorEmail string, 204 + ownerDid, targetRepo, branch, commitMessage, commitBody, authorName, authorEmail string, footers map[string]string, 205 205 ) (*http.Response, error) { 206 206 const ( 207 207 Method = "POST" ··· 215 215 AuthorName: authorName, 216 216 AuthorEmail: authorEmail, 217 217 Patch: string(patch), 218 + CommitFooters: footers, 218 219 } 219 220 220 221 body, _ := json.Marshal(mr)
+8 -1
appview/state/pull.go
··· 1534 1534 log.Printf("failed to get primary email: %s", err) 1535 1535 } 1536 1536 1537 + actor := s.oauth.GetUser(r) // no need to check for nil as this is an authenticated request 1538 + 1539 + var footers = map[string]string{ 1540 + "Pull-id": string(pull.PullAt()), 1541 + "Merged-by": actor.Did, 1542 + } 1543 + 1537 1544 ksClient, err := knotclient.NewSignedClient(f.Knot, secret, s.config.Core.Dev) 1538 1545 if err != nil { 1539 1546 log.Printf("failed to create signed client for %s: %s", f.Knot, err) ··· 1542 1549 } 1543 1550 1544 1551 // Merge the pull request 1545 - resp, err := ksClient.Merge([]byte(pull.LatestPatch()), f.OwnerDid(), f.RepoName, pull.TargetBranch, pull.Title, pull.Body, ident.Handle.String(), email.Address) 1552 + resp, err := ksClient.Merge([]byte(pull.LatestPatch()), f.OwnerDid(), f.RepoName, pull.TargetBranch, pull.Title, pull.Body, ident.Handle.String(), email.Address, footers) 1546 1553 if err != nil { 1547 1554 log.Printf("failed to merge pull request: %s", err) 1548 1555 s.pages.Notice(w, "pull-merge-error", "Failed to merge pull request. Try again later.")
+45
knotserver/git/merge.go
··· 32 32 AuthorName string 33 33 AuthorEmail string 34 34 FormatPatch bool 35 + CommitFooters map[string]string 35 36 } 36 37 37 38 func (e ErrMerge) Error() string { ··· 88 89 var stderr bytes.Buffer 89 90 var cmd *exec.Cmd 90 91 92 + footers := "" 93 + for k, v := range opts.CommitFooters { 94 + footers += fmt.Sprintf("\n%s: %s", k, v) 95 + } 96 + 91 97 if checkOnly { 92 98 cmd = exec.Command("git", "-C", tmpDir, "apply", "--check", "-v", patchFile) 93 99 } else { 94 100 // if patch is a format-patch, apply using 'git am' 95 101 if opts.FormatPatch { 102 + // find head before the change 103 + var beforeHash bytes.Buffer 104 + hashCmd := exec.Command("git", "-C", tmpDir, "rev-parse", "HEAD") 105 + hashCmd.Stdout = &beforeHash 106 + if err := hashCmd.Run(); err != nil { 107 + return fmt.Errorf("failed to get hash before patch: %w", err) 108 + } 109 + beforeCommit := strings.TrimSpace(beforeHash.String()) 110 + 96 111 amCmd := exec.Command("git", "-C", tmpDir, "am", patchFile) 97 112 amCmd.Stderr = &stderr 98 113 if err := amCmd.Run(); err != nil { 99 114 return fmt.Errorf("patch application failed: %s", stderr.String()) 100 115 } 116 + 117 + // command to ammend commit with required footers 118 + amendCmdString := fmt.Sprintf("git commit --amend -m \"$(git log --format=%%B -n1)\" -m \"%s\"", footers) 119 + 120 + // create a temporary script file to hold the amend command (exec 121 + // command can't contain newlines, so we need to create a script) 122 + scriptFile, err := os.CreateTemp("", "rebase-exec-*.sh") 123 + if err != nil { 124 + return fmt.Errorf("failed to create temporary script for rebase: %w", err) 125 + } 126 + defer os.Remove(scriptFile.Name()) 127 + 128 + _, err = scriptFile.WriteString(amendCmdString) 129 + defer scriptFile.Close() 130 + if err != nil { 131 + return fmt.Errorf("failed to write to temporary rebase script: %w", err) 132 + } 133 + 134 + if err := scriptFile.Chmod(0755); err != nil { 135 + return fmt.Errorf("failed to make temporary rebase script executable: %w", err) 136 + } 137 + 138 + rebaseCmd := exec.Command("git", "-C", tmpDir, "rebase", "--exec", "sh "+scriptFile.Name(), beforeCommit) 139 + rebaseCmd.Stderr = &stderr 140 + if err := rebaseCmd.Run(); err != nil { 141 + return fmt.Errorf("rebase failed: %s", stderr.String()) 142 + } 143 + 101 144 return nil 102 145 } 103 146 ··· 139 182 commitArgs = append(commitArgs, "-m", opts.CommitBody) 140 183 } 141 184 185 + commitArgs = append(commitArgs, "-m", footers) 186 + 142 187 cmd = exec.Command("git", commitArgs...) 143 188 } else { 144 189 // If no commit message specified, use git-am which automatically creates a commit
+1
knotserver/routes.go
··· 732 732 AuthorEmail: data.AuthorEmail, 733 733 CommitBody: data.CommitBody, 734 734 CommitMessage: data.CommitMessage, 735 + CommitFooters: data.CommitFooters, 735 736 } 736 737 737 738 patch := data.Patch