Files
simplegit/gitcmd/writefile_temprepo.go
T
2026-07-15 11:06:48 -04:00

143 lines
4.1 KiB
Go

package gitcmd
import (
"fmt"
"os"
"path/filepath"
"strings"
)
func (r *Repository) tempClone() (temp string, cleanup func(), err error) {
dir, err := os.MkdirTemp("", "simplegit-temprepo-*")
if err != nil {
return "", nil, err
}
temp = filepath.Join(dir, "repo.git")
cleanup = func() { _ = os.RemoveAll(dir) }
if _, _, err := NewCommand("clone", "--bare", "--shared").
AddDynamicArguments(r.Path, temp).
RunStdString(r.ctx); err != nil {
cleanup()
return "", nil, fmt.Errorf("temp clone: %w", err)
}
return temp, cleanup, nil
}
func (r *Repository) pushBack(temp, branch, commitSHA, expectedSHA string) error {
if expectedSHA != "" {
cur, err := r.refSHA("refs/heads/" + branch)
if err != nil {
return err
}
if cur != expectedSHA {
return fmt.Errorf("branch %q moved: expected %s, current %s", branch, expectedSHA, cur)
}
}
if _, _, err := NewCommand("push", "origin").
AddDynamicArguments(commitSHA + ":refs/heads/" + branch).
WithDir(temp).RunStdString(r.ctx); err != nil {
return fmt.Errorf("push: %w", err)
}
return nil
}
func (r *Repository) CreateOrUpdateFileViaTempRepo(branch, path, content, message, authorName, authorEmail, expectedSHA string) (*FileCommitResult, error) {
parent, err := r.refSHA("refs/heads/" + branch)
if err != nil {
return nil, err
}
if parent == "" && expectedSHA != "" {
return nil, fmt.Errorf("branch %q does not exist but expectedSHA %s given", branch, expectedSHA)
}
if parent != "" && expectedSHA != "" && parent != expectedSHA {
return nil, fmt.Errorf("branch %q moved: expected %s, current %s", branch, expectedSHA, parent)
}
temp, cleanup, err := r.tempClone()
if err != nil {
return nil, err
}
defer cleanup()
if parent != "" {
if _, _, err := NewCommand("read-tree").AddDynamicArguments(parent).WithDir(temp).RunStdString(r.ctx); err != nil {
return nil, fmt.Errorf("read-tree: %w", err)
}
}
blobSHA, err := r.hashObject(content, temp)
if err != nil {
return nil, err
}
if _, _, err := NewCommand("update-index", "--add", "--replace", "--cacheinfo").
AddDynamicArguments("100644," + blobSHA + "," + path).WithDir(temp).RunStdString(r.ctx); err != nil {
return nil, fmt.Errorf("update-index: %w", err)
}
treeOut, _, err := NewCommand("write-tree").WithDir(temp).RunStdString(r.ctx)
if err != nil {
return nil, fmt.Errorf("write-tree: %w", err)
}
newTree := strings.TrimSpace(treeOut)
var parents []string
if parent != "" {
parents = []string{parent}
}
commitSHA, err := r.commitTree(newTree, message, authorName, authorEmail, temp, parents...)
if err != nil {
return nil, err
}
if err := r.pushBack(temp, branch, commitSHA, expectedSHA); err != nil {
return nil, err
}
commit, err := r.GetCommit(commitSHA)
if err != nil {
return nil, err
}
return &FileCommitResult{Commit: *commit}, nil
}
func (r *Repository) DeleteFileViaTempRepo(branch, path, message, authorName, authorEmail, expectedSHA string) (*FileCommitResult, error) {
parent, err := r.refSHA("refs/heads/" + branch)
if err != nil {
return nil, err
}
if parent == "" {
return nil, fmt.Errorf("branch %q is empty; nothing to delete", branch)
}
if expectedSHA != "" && parent != expectedSHA {
return nil, fmt.Errorf("branch %q moved: expected %s, current %s", branch, expectedSHA, parent)
}
temp, cleanup, err := r.tempClone()
if err != nil {
return nil, err
}
defer cleanup()
if _, _, err := NewCommand("read-tree").AddDynamicArguments(parent).WithDir(temp).RunStdString(r.ctx); err != nil {
return nil, fmt.Errorf("read-tree: %w", err)
}
if err := r.removePathFromIndex(temp, path); err != nil {
return nil, err
}
treeOut, _, err := NewCommand("write-tree").WithDir(temp).RunStdString(r.ctx)
if err != nil {
return nil, fmt.Errorf("write-tree: %w", err)
}
newTree := strings.TrimSpace(treeOut)
commitSHA, err := r.commitTree(newTree, message, authorName, authorEmail, temp, parent)
if err != nil {
return nil, err
}
if err := r.pushBack(temp, branch, commitSHA, expectedSHA); err != nil {
return nil, err
}
commit, err := r.GetCommit(commitSHA)
if err != nil {
return nil, err
}
return &FileCommitResult{Commit: *commit}, nil
}