Skip to content

Commit d79aa6e

Browse files
Delegate git primitives and safety redaction to git-harness.
Follow-up to #19: remove duplicated operations helpers and internal/safety in favor of git-harness v0.3.1. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 241c851 commit d79aa6e

6 files changed

Lines changed: 47 additions & 243 deletions

File tree

cmd/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import (
2222
"github.com/git-rain/git-rain/internal/config"
2323
"github.com/git-rain/git-rain/internal/git"
2424
"github.com/git-rain/git-rain/internal/registry"
25-
"github.com/git-rain/git-rain/internal/safety"
25+
"github.com/git-fire/git-harness/safety"
2626
"github.com/git-rain/git-rain/internal/sessionlog"
2727
"github.com/git-rain/git-rain/internal/ui"
2828
)

internal/git/operations.go

Lines changed: 15 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -2,158 +2,48 @@ package git
22

33
import (
44
"fmt"
5-
"os/exec"
65
"strings"
76

8-
"github.com/git-rain/git-rain/internal/safety"
7+
harnessgit "github.com/git-fire/git-harness/git"
8+
harnessafety "github.com/git-fire/git-harness/safety"
99
)
1010

11-
// Worktree represents a git worktree
12-
type Worktree struct {
13-
Path string // Absolute path to worktree
14-
Branch string // Current branch in this worktree
15-
Head string // Current HEAD SHA
16-
IsMain bool // True if this is the main worktree
17-
}
11+
// Worktree is a git worktree (delegates to git-harness).
12+
type Worktree = harnessgit.Worktree
1813

19-
// getCommitSHA returns the SHA of a commit ref
2014
func getCommitSHA(repoPath, ref string) (string, error) {
21-
cmd := exec.Command("git", "rev-parse", ref)
22-
cmd.Dir = repoPath
23-
24-
output, err := cmd.Output()
25-
if err != nil {
26-
return "", fmt.Errorf("git rev-parse failed for %s: %w", ref, err)
27-
}
28-
29-
sha := strings.TrimSpace(string(output))
30-
return sha, nil
15+
return harnessgit.GetCommitSHA(repoPath, ref)
3116
}
3217

3318
// GetCommitSHA returns the SHA for a ref in the repository.
3419
func GetCommitSHA(repoPath, ref string) (string, error) {
35-
return getCommitSHA(repoPath, ref)
20+
return harnessgit.GetCommitSHA(repoPath, ref)
3621
}
3722

38-
// GetCurrentBranch returns the currently checked out branch
23+
// GetCurrentBranch returns the currently checked out branch.
3924
func GetCurrentBranch(repoPath string) (string, error) {
40-
cmd := exec.Command("git", "branch", "--show-current")
41-
cmd.Dir = repoPath
42-
43-
output, err := cmd.Output()
44-
if err != nil {
45-
return "", fmt.Errorf("git branch --show-current failed: %w", err)
46-
}
47-
48-
branch := strings.TrimSpace(string(output))
49-
if branch == "" {
50-
return "", fmt.Errorf("not on any branch (detached HEAD?)")
51-
}
52-
53-
return branch, nil
25+
return harnessgit.GetCurrentBranch(repoPath)
5426
}
5527

56-
// HasStagedChanges checks if there are staged changes
28+
// HasStagedChanges checks if there are staged changes.
5729
func HasStagedChanges(repoPath string) (bool, error) {
58-
cmd := exec.Command("git", "diff", "--cached", "--quiet")
59-
cmd.Dir = repoPath
60-
61-
err := cmd.Run()
62-
if err != nil {
63-
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
64-
return true, nil
65-
}
66-
return false, fmt.Errorf("git diff --cached --quiet failed: %w", err)
67-
}
68-
69-
return false, nil
30+
return harnessgit.HasStagedChanges(repoPath)
7031
}
7132

72-
// HasUnstagedChanges checks if there are unstaged changes (including untracked files)
33+
// HasUnstagedChanges checks if there are unstaged changes (including untracked files).
7334
func HasUnstagedChanges(repoPath string) (bool, error) {
74-
cmd := exec.Command("git", "diff", "--quiet")
75-
cmd.Dir = repoPath
76-
77-
err := cmd.Run()
78-
hasModified := false
79-
if err != nil {
80-
if exitErr, ok := err.(*exec.ExitError); ok && exitErr.ExitCode() == 1 {
81-
hasModified = true
82-
} else {
83-
return false, fmt.Errorf("git diff --quiet failed: %w", err)
84-
}
85-
}
86-
87-
cmd = exec.Command("git", "ls-files", "--others", "--exclude-standard")
88-
cmd.Dir = repoPath
89-
output, err := cmd.Output()
90-
if err != nil {
91-
return false, fmt.Errorf("git ls-files failed: %w", err)
92-
}
93-
94-
hasUntracked := len(strings.TrimSpace(string(output))) > 0
95-
96-
return hasModified || hasUntracked, nil
35+
return harnessgit.HasUnstagedChanges(repoPath)
9736
}
9837

99-
// ListWorktrees returns all worktrees for a repository
38+
// ListWorktrees returns all worktrees for a repository.
10039
func ListWorktrees(repoPath string) ([]Worktree, error) {
101-
cmd := exec.Command("git", "worktree", "list", "--porcelain")
102-
cmd.Dir = repoPath
103-
104-
output, err := cmd.Output()
105-
if err != nil {
106-
return nil, fmt.Errorf("git worktree list failed: %w", err)
107-
}
108-
109-
lines := strings.Split(string(output), "\n")
110-
var worktrees []Worktree
111-
var current Worktree
112-
isFirst := true
113-
114-
for _, line := range lines {
115-
line = strings.TrimSpace(line)
116-
if line == "" {
117-
if current.Path != "" {
118-
current.IsMain = isFirst
119-
worktrees = append(worktrees, current)
120-
current = Worktree{}
121-
isFirst = false
122-
}
123-
continue
124-
}
125-
126-
parts := strings.SplitN(line, " ", 2)
127-
if len(parts) < 2 {
128-
continue
129-
}
130-
131-
key := parts[0]
132-
value := parts[1]
133-
134-
switch key {
135-
case "worktree":
136-
current.Path = value
137-
case "HEAD":
138-
current.Head = value
139-
case "branch":
140-
branch := strings.TrimPrefix(value, "refs/heads/")
141-
current.Branch = branch
142-
}
143-
}
144-
145-
if current.Path != "" {
146-
current.IsMain = isFirst
147-
worktrees = append(worktrees, current)
148-
}
149-
150-
return worktrees, nil
40+
return harnessgit.ListWorktrees(repoPath)
15141
}
15242

15343
func commandError(action string, err error, output []byte) error {
15444
out := strings.TrimSpace(string(output))
15545
if out == "" {
15646
return fmt.Errorf("%s failed: %w", action, err)
15747
}
158-
return fmt.Errorf("%s failed: %w (stderr: %s)", action, err, safety.SanitizeText(out))
48+
return fmt.Errorf("%s failed: %w (stderr: %s)", action, err, harnessafety.SanitizeText(out))
15949
}

internal/git/safety_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package git_test
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/git-fire/git-harness/safety"
8+
)
9+
10+
func TestHarnessSanitizeText_FreezeMessagesPreserved(t *testing.T) {
11+
msgs := []string{
12+
"could not reach remote — check your network and try again",
13+
"could not authenticate with remote — check your credentials and try again",
14+
"fetch did not complete — try again when the remote is reachable",
15+
}
16+
for _, msg := range msgs {
17+
got := safety.SanitizeText(msg)
18+
if got != msg {
19+
t.Errorf("freeze message was modified:\n input: %q\n got: %q", msg, got)
20+
}
21+
}
22+
}
23+
24+
func TestHarnessSanitizeText_RedactsCredentials(t *testing.T) {
25+
input := "fatal: https://user:supersecret@github.com/org/repo.git"
26+
got := safety.SanitizeText(input)
27+
if strings.Contains(got, "supersecret") {
28+
t.Errorf("password not redacted: %q", got)
29+
}
30+
}

internal/safety/redact.go

Lines changed: 0 additions & 32 deletions
This file was deleted.

internal/safety/redact_test.go

Lines changed: 0 additions & 84 deletions
This file was deleted.

internal/sessionlog/logger.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"path/filepath"
88
"time"
99

10-
"github.com/git-rain/git-rain/internal/safety"
10+
"github.com/git-fire/git-harness/safety"
1111
)
1212

1313
// LogEntry matches git-fire's structured session event shape.

0 commit comments

Comments
 (0)