summaryrefslogtreecommitdiff
path: root/internal/git/git.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/git/git.go')
-rw-r--r--internal/git/git.go91
1 files changed, 91 insertions, 0 deletions
diff --git a/internal/git/git.go b/internal/git/git.go
index 6d29fda..1680561 100644
--- a/internal/git/git.go
+++ b/internal/git/git.go
@@ -1,2 +1,93 @@
// Package git wraps git operations used by geheim to manage the secret store.
+// It mirrors the Git module from the original Ruby implementation (geheim.rb lines 79-123),
+// running real git subprocesses rather than using a Go git library.
package git
+
+import (
+ "bufio"
+ "bytes"
+ "context"
+ "fmt"
+ "os/exec"
+ "path/filepath"
+)
+
+// Git provides git operations scoped to the secret store's data directory.
+type Git struct {
+ dataDir string
+}
+
+// New creates a Git helper for the given data directory.
+func New(dataDir string) *Git {
+ return &Git{dataDir: dataDir}
+}
+
+// Add stages a single file for the next commit.
+// It changes the working directory to the file's parent so that git add
+// receives only the base name, matching the Ruby Dir.chdir pattern.
+func (g *Git) Add(ctx context.Context, filePath string) error {
+ return run(ctx, filepath.Dir(filePath), "git", "add", filepath.Base(filePath))
+}
+
+// Remove stages a file deletion for the next commit using git rm.
+// Like Add, it operates in the file's parent directory.
+func (g *Git) Remove(ctx context.Context, filePath string) error {
+ return run(ctx, filepath.Dir(filePath), "git", "rm", filepath.Base(filePath))
+}
+
+// Status prints the current git status of the data directory.
+func (g *Git) Status(ctx context.Context) error {
+ return run(ctx, g.dataDir, "git", "status")
+}
+
+// Commit records all staged changes with a deliberately vague commit message
+// so that secret names are not exposed in commit history.
+func (g *Git) Commit(ctx context.Context) error {
+ return run(ctx, g.dataDir, "git", "commit", "-a", "-m",
+ "Changing stuff, not telling what in commit history")
+}
+
+// Reset discards all uncommitted changes in the data directory.
+func (g *Git) Reset(ctx context.Context) error {
+ return run(ctx, g.dataDir, "git", "reset", "--hard")
+}
+
+// Sync pulls from and pushes to each configured remote repository in order,
+// then prints the final status. This keeps multiple machines in sync.
+func (g *Git) Sync(ctx context.Context, syncRepos []string) error {
+ fmt.Printf("> Synchronising %s\n", g.dataDir)
+
+ for _, repo := range syncRepos {
+ if err := run(ctx, g.dataDir, "git", "pull", repo, "master"); err != nil {
+ return err
+ }
+ if err := run(ctx, g.dataDir, "git", "push", repo, "master"); err != nil {
+ return err
+ }
+ }
+
+ return run(ctx, g.dataDir, "git", "status")
+}
+
+// run executes a git command in the given directory, printing each line of
+// combined stdout+stderr with a "> " prefix so the user can follow progress.
+// It returns an error if the command exits with a non-zero status.
+func run(ctx context.Context, dir string, args ...string) error {
+ cmd := exec.CommandContext(ctx, args[0], args[1:]...)
+ cmd.Dir = dir
+
+ var buf bytes.Buffer
+ cmd.Stdout = &buf
+ cmd.Stderr = &buf
+
+ err := cmd.Run()
+
+ // Print all output lines regardless of whether the command succeeded,
+ // so the user sees error messages from git itself.
+ scanner := bufio.NewScanner(&buf)
+ for scanner.Scan() {
+ fmt.Printf("> %s\n", scanner.Text())
+ }
+
+ return err
+}