1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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
}
|