diff options
| -rw-r--r-- | internal/git/git.go | 6 | ||||
| -rw-r--r-- | internal/git/git_test.go | 66 |
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") + } +} |
