package gitcmd import ( "fmt" "os" "strings" ) type FileCommitResult struct { Commit Commit `json:"commit"` } func (r *Repository) CreateOrUpdateFile(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) } blobSHA, err := r.hashObject(content, r.Path) if err != nil { return nil, err } newTree, err := r.withTempIndex(func(idx string) (string, error) { return r.buildTree(idx, parent, path, blobSHA, false) }) if err != nil { return nil, err } var parents []string if parent != "" { parents = []string{parent} } commitSHA, err := r.commitTree(newTree, message, authorName, authorEmail, r.Path, parents...) if err != nil { return nil, err } if err := r.updateRef("refs/heads/"+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) DeleteFile(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) } newTree, err := r.withTempIndex(func(idx string) (string, error) { return r.buildTree(idx, parent, path, "", true) }) if err != nil { return nil, err } commitSHA, err := r.commitTree(newTree, message, authorName, authorEmail, r.Path, parent) if err != nil { return nil, err } if err := r.updateRef("refs/heads/"+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) withTempIndex(fn func(idx string) (string, error)) (string, error) { f, err := os.CreateTemp("", "simplegit-index-*") if err != nil { return "", err } name := f.Name() f.Close() _ = os.Remove(name) defer os.Remove(name) return fn(name) } func (r *Repository) buildTree(indexFile, parent, path, blobSHA string, remove bool) (string, error) { env := "GIT_INDEX_FILE=" + indexFile if parent != "" { if _, _, err := NewCommand("read-tree"). AddDynamicArguments(parent). WithDir(r.Path).WithEnv(env). RunStdString(r.ctx); err != nil { return "", fmt.Errorf("read-tree: %w", err) } } if remove { if err := r.removePathFromIndex(r.Path, path, env); err != nil { return "", err } } else { if _, _, err := NewCommand("update-index", "--add", "--replace", "--cacheinfo"). AddDynamicArguments("100644," + blobSHA + "," + path). WithDir(r.Path).WithEnv(env). RunStdString(r.ctx); err != nil { return "", fmt.Errorf("update-index --cacheinfo: %w", err) } } out, _, err := NewCommand("write-tree"). WithDir(r.Path).WithEnv(env). RunStdString(r.ctx) if err != nil { return "", fmt.Errorf("write-tree: %w", err) } return strings.TrimSpace(out), nil }