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 }