diff options
author | Runxi Yu <me@runxiyu.org> | 2025-04-02 08:49:03 +0800 |
---|---|---|
committer | Runxi Yu <me@runxiyu.org> | 2025-04-02 09:24:37 +0800 |
commit | c32389d7d54f3fe66d32f849c02c5e75b7d476c8 (patch) | |
tree | 6a28b81c35e6ac602cb10ca24869b8bc2342134c /lmtp_handle_patch.go | |
parent | LMTP: Fix mistake in command arguments (diff) | |
download | forge-c32389d7d54f3fe66d32f849c02c5e75b7d476c8.tar.gz forge-c32389d7d54f3fe66d32f849c02c5e75b7d476c8.tar.zst forge-c32389d7d54f3fe66d32f849c02c5e75b7d476c8.zip |
LMTP: Actually apply patches from email
Diffstat (limited to 'lmtp_handle_patch.go')
-rw-r--r-- | lmtp_handle_patch.go | 108 |
1 files changed, 74 insertions, 34 deletions
diff --git a/lmtp_handle_patch.go b/lmtp_handle_patch.go index 138b592..d69424b 100644 --- a/lmtp_handle_patch.go +++ b/lmtp_handle_patch.go @@ -5,16 +5,16 @@ package main import ( "bytes" - // "crypto/rand" - // "fmt" + "crypto/rand" + "encoding/hex" "os" "os/exec" + "strings" + "time" "github.com/bluekeyes/go-gitdiff/gitdiff" "github.com/emersion/go-message" "github.com/go-git/go-git/v5" - "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/object" ) func lmtpHandlePatch(session *lmtpSession, groupPath []string, repoName string, email *message.Entity) (err error) { @@ -24,6 +24,11 @@ func lmtpHandlePatch(session *lmtpSession, groupPath []string, repoName string, return } + var header *gitdiff.PatchHeader + if header, err = gitdiff.ParsePatchHeader(preamble); err != nil { + return + } + var repo *git.Repository var fsPath string repo, _, _, fsPath, err = openRepo(session.ctx, groupPath, repoName) @@ -31,61 +36,96 @@ func lmtpHandlePatch(session *lmtpSession, groupPath []string, repoName string, return } - var headRef *plumbing.Reference - if headRef, err = repo.Head(); err != nil { + headRef, err := repo.Head() + if err != nil { return } - - var headCommit *object.Commit - if headCommit, err = repo.CommitObject(headRef.Hash()); err != nil { + headCommit, err := repo.CommitObject(headRef.Hash()) + if err != nil { return } - - var headTree *object.Tree - if headTree, err = headCommit.Tree(); err != nil { + headTree, err := headCommit.Tree() + if err != nil { return } - // TODO: Try to not shell out + headTreeHash := headTree.Hash.String() + blobUpdates := make(map[string][]byte) for _, diffFile := range diffFiles { - var sourceFile *object.File - if sourceFile, err = headTree.File(diffFile.OldName); err != nil { + sourceFile, err := headTree.File(diffFile.OldName) + if err != nil { return err } - var sourceString string - if sourceString, err = sourceFile.Contents(); err != nil { + sourceString, err := sourceFile.Contents() + if err != nil { return err } - hashBuf := bytes.Buffer{} - patchedBuf := bytes.Buffer{} + sourceBuf := bytes.NewReader(stringToBytes(sourceString)) - if err = gitdiff.Apply(&patchedBuf, sourceBuf, diffFile); err != nil { + var patchedBuf bytes.Buffer + if err := gitdiff.Apply(&patchedBuf, sourceBuf, diffFile); err != nil { return err } - proc := exec.CommandContext(session.ctx, "git", "hash-object", "-w", "-t", "blob", "--stdin") - proc.Env = append(os.Environ(), "GIT_DIR="+fsPath) - proc.Stdout = &hashBuf - proc.Stdin = &patchedBuf - if err = proc.Start(); err != nil { + + var hashBuf bytes.Buffer + + // It's really difficult to do this via go-git so we're just + // going to use upstream git for now. + // TODO + cmd := exec.CommandContext(session.ctx, "git", "hash-object", "-w", "-t", "blob", "--stdin") + cmd.Env = append(os.Environ(), "GIT_DIR="+fsPath) + cmd.Stdout = &hashBuf + cmd.Stdin = &patchedBuf + if err := cmd.Run(); err != nil { return err } - if err = proc.Wait(); err != nil { + + newHashStr := strings.TrimSpace(hashBuf.String()) + newHash, err := hex.DecodeString(newHashStr) + if err != nil { return err } - newHash := hashBuf.Bytes() - if len(newHash) != 20*2+1 { // TODO: Hardcoded from the size of plumbing.Hash - panic("unexpected hash size") + + blobUpdates[diffFile.NewName] = newHash + if diffFile.NewName != diffFile.OldName { + blobUpdates[diffFile.OldName] = nil // Mark for deletion. } - // TODO: Add to tree } - // contribBranchName := rand.Text() + newTreeSha, err := buildTreeRecursive(session.ctx, fsPath, headTreeHash, blobUpdates) + if err != nil { + return err + } + + commitMsg := header.Title + if header.Body != "" { + commitMsg += "\n\n" + header.Body + } + + env := append(os.Environ(), + "GIT_DIR="+fsPath, + "GIT_AUTHOR_NAME="+header.Author.Name, + "GIT_AUTHOR_EMAIL="+header.Author.Email, + "GIT_AUTHOR_DATE="+header.AuthorDate.Format(time.RFC3339), + ) + commitCmd := exec.CommandContext(session.ctx, "git", "commit-tree", newTreeSha, "-p", headCommit.Hash.String(), "-m", commitMsg) + commitCmd.Env = env - // TODO: Store the branch + var commitOut bytes.Buffer + commitCmd.Stdout = &commitOut + if err := commitCmd.Run(); err != nil { + return err + } + newCommitSha := strings.TrimSpace(commitOut.String()) - // fmt.Println(repo, diffFiles, preamble) - _ = preamble + newBranchName := rand.Text() + + refCmd := exec.CommandContext(session.ctx, "git", "update-ref", "refs/heads/contrib/"+newBranchName, newCommitSha) //#nosec G204 + refCmd.Env = append(os.Environ(), "GIT_DIR="+fsPath) + if err := refCmd.Run(); err != nil { + return err + } return nil } |