summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--internal/git/git.go6
-rw-r--r--internal/git/git_test.go66
2 files changed, 72 insertions, 0 deletions
diff --git a/internal/git/git.go b/internal/git/git.go
index 1680561..af4a394 100644
--- a/internal/git/git.go
+++ b/internal/git/git.go
@@ -72,6 +72,12 @@ func (g *Git) Sync(ctx context.Context, syncRepos []string) error {
// 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.
+//
+// Output is intentionally buffered: all output is captured into a bytes.Buffer
+// and printed only after the subprocess exits. This matches the Ruby reference
+// implementation (which uses backtick capture) and keeps the "> " prefix logic
+// simple. For long-running operations like git pull the user sees no output
+// until the command completes — an acceptable tradeoff for a personal tool.
func run(ctx context.Context, dir string, args ...string) error {
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
cmd.Dir = dir
diff --git a/internal/git/git_test.go b/internal/git/git_test.go
index 1fb531c..87d2ecf 100644
--- a/internal/git/git_test.go
+++ b/internal/git/git_test.go
@@ -198,3 +198,69 @@ func TestCommit_nothing_to_commit(t *testing.T) {
t.Fatal("expected error when committing with nothing to commit, got nil")
}
}
+
+// TestRemove_nonexistent_file verifies that Remove returns an error when the
+// target is not tracked by git, because git rm exits non-zero in that case.
+func TestRemove_nonexistent_file(t *testing.T) {
+ dir := initRepo(t)
+ g := git.New(dir)
+ ctx := context.Background()
+
+ err := g.Remove(ctx, filepath.Join(dir, "ghost.age"))
+ if err == nil {
+ t.Fatal("expected error when removing a non-tracked file, got nil")
+ }
+}
+
+// TestSync verifies the pull-push-status loop using two local repos so no
+// real network is needed. A bare repo acts as the remote; a working repo
+// with an initial commit pushes to it, then Sync pulls and pushes again.
+func TestSync(t *testing.T) {
+ ctx := context.Background()
+
+ runcmd := func(args ...string) {
+ t.Helper()
+ out, err := exec.Command(args[0], args[1:]...).CombinedOutput()
+ if err != nil {
+ t.Fatalf("%v: %v\n%s", args, err, out)
+ }
+ }
+
+ // Create a working directory with an initial commit on master.
+ workDir := t.TempDir()
+ runcmd("git", "init", "--initial-branch=master", workDir)
+ runcmd("git", "-C", workDir, "config", "user.email", "test@geheim.test")
+ runcmd("git", "-C", workDir, "config", "user.name", "Geheim Test")
+ path := filepath.Join(workDir, "init.txt")
+ if err := os.WriteFile(path, []byte("init"), 0o600); err != nil {
+ t.Fatalf("WriteFile: %v", err)
+ }
+ runcmd("git", "-C", workDir, "add", ".")
+ runcmd("git", "-C", workDir, "commit", "-m", "init")
+
+ // Create a bare repo and push the initial commit into it so master exists.
+ bareDir := t.TempDir()
+ runcmd("git", "init", "--bare", "--initial-branch=master", bareDir)
+ runcmd("git", "-C", workDir, "remote", "add", "localremote", bareDir)
+ runcmd("git", "-C", workDir, "push", "localremote", "master")
+
+ g := git.New(workDir)
+ if err := g.Sync(ctx, []string{"localremote"}); err != nil {
+ t.Fatalf("Sync: %v", err)
+ }
+}
+
+// TestSync_bad_remote verifies that Sync returns an error when a configured
+// remote does not exist, rather than silently succeeding.
+func TestSync_bad_remote(t *testing.T) {
+ dir := initRepo(t)
+ // Create an initial commit so the repo has a valid HEAD.
+ writeFile(t, dir, "init.txt", "init")
+ commitAll(t, dir, "init")
+
+ g := git.New(dir)
+ err := g.Sync(context.Background(), []string{"nonexistent-remote"})
+ if err == nil {
+ t.Fatal("expected error when syncing with a nonexistent remote, got nil")
+ }
+}