diff options
Diffstat (limited to 'internal/git/git.go')
| -rw-r--r-- | internal/git/git.go | 91 |
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 +} |
