summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-11 18:34:32 +0200
committerPaul Buetow <paul@buetow.org>2026-03-11 18:34:32 +0200
commit9570df7b5426b50c4256d0804cc8a30b14d77039 (patch)
treeadafdb51b9a3b76f3fe015728ecd3bbd69df14ac
parent9706e53620045c20bf732054a286183c70f77581 (diff)
fix(sync): stop relying on process cwd
-rw-r--r--internal/showcase/showcase.go102
-rw-r--r--internal/showcase/showcase_test.go27
-rw-r--r--internal/sync/branch_analyzer.go3
-rw-r--r--internal/sync/branch_sync.go8
-rw-r--r--internal/sync/git_operations.go74
-rw-r--r--internal/sync/git_operations_test.go19
-rw-r--r--internal/sync/repository_setup.go14
-rw-r--r--internal/sync/sync.go40
8 files changed, 139 insertions, 148 deletions
diff --git a/internal/showcase/showcase.go b/internal/showcase/showcase.go
index 5ec43d3..7fef655 100644
--- a/internal/showcase/showcase.go
+++ b/internal/showcase/showcase.go
@@ -194,6 +194,24 @@ func runCommandWithTimeout(name string, args ...string) (string, error) {
return string(out), nil
}
+func findReadmeContent(repoPath string) ([]byte, string, bool) {
+ readmeFiles := []string{
+ "README.md", "readme.md", "Readme.md",
+ "README.MD", "README.txt", "readme.txt",
+ "README", "readme",
+ }
+
+ for _, readmeFile := range readmeFiles {
+ path := filepath.Join(repoPath, readmeFile)
+ content, err := os.ReadFile(path)
+ if err == nil {
+ return content, readmeFile, true
+ }
+ }
+
+ return nil, "", false
+}
+
// getRepositories returns a list of repository directories in the work directory
func (g *Generator) getRepositories() ([]string, error) {
entries, err := os.ReadDir(g.workDir)
@@ -281,16 +299,7 @@ func (g *Generator) generateProjectSummary(repoName string, forceRegenerate bool
}
}
- // Change to repository directory
- originalDir, err := os.Getwd()
- if err != nil {
- return nil, fmt.Errorf("failed to get current directory: %w", err)
- }
- defer os.Chdir(originalDir)
-
- if err := os.Chdir(repoPath); err != nil {
- return nil, fmt.Errorf("failed to change to repository directory: %w", err)
- }
+ readmeContent, readmeFile, readmeFound := findReadmeContent(repoPath)
// Always extract metadata (not cached)
fmt.Printf("Extracting repository metadata...\n")
@@ -314,25 +323,9 @@ func (g *Generator) generateProjectSummary(repoName string, forceRegenerate bool
case "amp":
// Use README content as stdin and pass the prompt as --execute argument
fmt.Printf("Running amp command (stdin payload)\n")
- // Find README file
- readmeFiles := []string{
- "README.md", "readme.md", "Readme.md",
- "README.MD", "README.txt", "readme.txt",
- "README", "readme",
- }
- var readmeContent []byte
- var readmeFound bool
- for _, readmeFile := range readmeFiles {
- content, err := os.ReadFile(readmeFile)
- if err == nil {
- readmeContent = content
- readmeFound = true
- fmt.Printf(" Using %s as input\n", readmeFile)
- break
- }
- }
if readmeFound {
fmt.Printf(" echo <README content> | amp --execute \"%s\"\n", prompt)
+ fmt.Printf(" Using %s as input\n", readmeFile)
cmd = exec.Command("amp", "--execute", prompt)
cmd.Stdin = strings.NewReader(string(readmeContent))
} else {
@@ -346,25 +339,9 @@ func (g *Generator) generateProjectSummary(repoName string, forceRegenerate bool
case "hexai":
// Use README content as stdin and pass the prompt as argument
fmt.Printf("Running hexai command (stdin payload)\n")
- // Find README file
- readmeFiles := []string{
- "README.md", "readme.md", "Readme.md",
- "README.MD", "README.txt", "readme.txt",
- "README", "readme",
- }
- var readmeContent []byte
- var readmeFound bool
- for _, readmeFile := range readmeFiles {
- content, err := os.ReadFile(readmeFile)
- if err == nil {
- readmeContent = content
- readmeFound = true
- fmt.Printf(" Using %s as input\n", readmeFile)
- break
- }
- }
if readmeFound {
fmt.Printf(" echo <README content> | hexai \"%s\"\n", prompt)
+ fmt.Printf(" Using %s as input\n", readmeFile)
cmd = exec.Command("hexai", prompt)
cmd.Stdin = strings.NewReader(string(readmeContent))
} else {
@@ -375,27 +352,9 @@ func (g *Generator) generateProjectSummary(repoName string, forceRegenerate bool
// For aichat, we need to read README.md and pipe it to aichat
fmt.Printf("Running aichat command:\n")
- // Find README file
- readmeFiles := []string{
- "README.md", "readme.md", "Readme.md",
- "README.MD", "README.txt", "readme.txt",
- "README", "readme",
- }
-
- var readmeContent []byte
- var readmeFound bool
- for _, readmeFile := range readmeFiles {
- content, err := os.ReadFile(readmeFile)
- if err == nil {
- readmeContent = content
- readmeFound = true
- fmt.Printf(" Using %s as input\n", readmeFile)
- break
- }
- }
-
if readmeFound {
fmt.Printf(" echo <README content> | aichat \"%s\"\n", prompt)
+ fmt.Printf(" Using %s as input\n", readmeFile)
cmd = exec.Command("aichat", prompt)
cmd.Stdin = strings.NewReader(string(readmeContent))
} else {
@@ -408,6 +367,7 @@ func (g *Generator) generateProjectSummary(repoName string, forceRegenerate bool
}
if cmd != nil {
+ cmd.Dir = repoPath
if output, err := cmd.Output(); err == nil {
summary = strings.TrimSpace(string(output))
}
@@ -415,18 +375,10 @@ func (g *Generator) generateProjectSummary(repoName string, forceRegenerate bool
// Fallback: create a minimal summary from README if AI unavailable/failed
if summary == "" {
- readmeFiles := []string{
- "README.md", "readme.md", "Readme.md",
- "README.MD", "README.txt", "readme.txt",
- "README", "readme",
- }
- for _, readmeFile := range readmeFiles {
- if content, err := os.ReadFile(readmeFile); err == nil {
- parts := strings.Split(strings.TrimSpace(string(content)), "\n\n")
- if len(parts) > 0 {
- summary = strings.TrimSpace(parts[0])
- break
- }
+ if readmeFound {
+ parts := strings.Split(strings.TrimSpace(string(readmeContent)), "\n\n")
+ if len(parts) > 0 {
+ summary = strings.TrimSpace(parts[0])
}
}
if summary == "" {
diff --git a/internal/showcase/showcase_test.go b/internal/showcase/showcase_test.go
index 3fce064..f480bdb 100644
--- a/internal/showcase/showcase_test.go
+++ b/internal/showcase/showcase_test.go
@@ -1,6 +1,8 @@
package showcase
import (
+ "os"
+ "path/filepath"
"reflect"
"strings"
"testing"
@@ -122,3 +124,28 @@ func TestFormatGemtext_IncludesRankHistoryInHeader(t *testing.T) {
t.Fatalf("rank history was not rendered in header: %s", content)
}
}
+
+func TestFindReadmeContent_UsesRepoPathWithoutChangingCWD(t *testing.T) {
+ t.Parallel()
+
+ repoPath := filepath.Join(t.TempDir(), "repo")
+ if err := os.MkdirAll(repoPath, 0755); err != nil {
+ t.Fatalf("failed to create repo dir: %v", err)
+ }
+
+ readmePath := filepath.Join(repoPath, "README.md")
+ if err := os.WriteFile(readmePath, []byte("repo summary"), 0644); err != nil {
+ t.Fatalf("failed to write readme: %v", err)
+ }
+
+ content, readmeFile, found := findReadmeContent(repoPath)
+ if !found {
+ t.Fatal("expected README to be found")
+ }
+ if readmeFile != "README.md" {
+ t.Fatalf("expected README.md, got %q", readmeFile)
+ }
+ if string(content) != "repo summary" {
+ t.Fatalf("unexpected README content: %q", string(content))
+ }
+}
diff --git a/internal/sync/branch_analyzer.go b/internal/sync/branch_analyzer.go
index 7499996..04a60d7 100644
--- a/internal/sync/branch_analyzer.go
+++ b/internal/sync/branch_analyzer.go
@@ -3,7 +3,6 @@ package sync
import (
"fmt"
"os"
- "os/exec"
"path/filepath"
"strconv"
"strings"
@@ -183,7 +182,7 @@ func (s *Syncer) getLastCommitTime(remoteName, branch string) (time.Time, error)
}
// Get Unix timestamp of last commit
- cmd := exec.Command("git", "log", "-1", "--format=%ct", ref)
+ cmd := gitCommand(s.repoPath(), "log", "-1", "--format=%ct", ref)
output, err := cmd.Output()
if err != nil {
return time.Time{}, err
diff --git a/internal/sync/branch_sync.go b/internal/sync/branch_sync.go
index eafb551..a667ee2 100644
--- a/internal/sync/branch_sync.go
+++ b/internal/sync/branch_sync.go
@@ -24,7 +24,7 @@ func (s *Syncer) trackRemotesWithBranch(branch string, remotes map[string]*confi
}
// mergeFromRemotes merges changes from all remotes that have the branch
-func mergeFromRemotes(branch string, remotesWithBranch map[string]bool) error {
+func mergeFromRemotes(repoPath, branch string, remotesWithBranch map[string]bool) error {
if len(remotesWithBranch) == 0 {
fmt.Printf(" Branch %s is local only, will push to all remotes\n", branch)
return nil
@@ -32,7 +32,7 @@ func mergeFromRemotes(branch string, remotesWithBranch map[string]bool) error {
// Merge changes from all remotes that have this branch
for remoteName := range remotesWithBranch {
- if err := mergeBranch(remoteName, branch); err != nil {
+ if err := mergeBranch(repoPath, remoteName, branch); err != nil {
return err
}
}
@@ -41,7 +41,7 @@ func mergeFromRemotes(branch string, remotesWithBranch map[string]bool) error {
}
// pushToAllRemotes pushes the branch to all configured remotes
-func pushToAllRemotes(branch string, remotes map[string]*config.Organization, remotesWithBranch map[string]bool) error {
+func pushToAllRemotes(repoPath, branch string, remotes map[string]*config.Organization, remotesWithBranch map[string]bool) error {
for remoteName, org := range remotes {
// Check if this remote has the branch
remoteHasBranch := remotesWithBranch[remoteName]
@@ -52,7 +52,7 @@ func pushToAllRemotes(branch string, remotes map[string]*config.Organization, re
fmt.Printf(" Pushing to %s (%s)...\n", remoteName, org.Host)
}
- if err := pushBranchWithBackupSupport(remoteName, branch, remoteHasBranch, org); err != nil {
+ if err := pushBranchWithBackupSupport(repoPath, remoteName, branch, remoteHasBranch, org); err != nil {
return err
}
}
diff --git a/internal/sync/git_operations.go b/internal/sync/git_operations.go
index 0bd698e..62a530b 100644
--- a/internal/sync/git_operations.go
+++ b/internal/sync/git_operations.go
@@ -11,9 +11,17 @@ import (
"codeberg.org/snonux/gitsyncer/internal/config"
)
+func gitCommand(repoPath string, args ...string) *exec.Cmd {
+ cmd := exec.Command("git", args...)
+ if repoPath != "" {
+ cmd.Dir = repoPath
+ }
+ return cmd
+}
+
// checkForMergeConflicts checks if the repository has merge conflicts
-func checkForMergeConflicts() (bool, string, error) {
- cmd := exec.Command("git", "status", "--porcelain")
+func checkForMergeConflicts(repoPath string) (bool, string, error) {
+ cmd := gitCommand(repoPath, "status", "--porcelain")
output, err := cmd.Output()
if err != nil {
return false, "", err
@@ -28,21 +36,21 @@ func checkForMergeConflicts() (bool, string, error) {
}
// stashChanges stashes uncommitted changes
-func stashChanges() error {
+func stashChanges(repoPath string) error {
fmt.Println(" Stashing uncommitted changes...")
- return exec.Command("git", "stash", "push", "-m", "gitsyncer-auto-stash").Run()
+ return gitCommand(repoPath, "stash", "push", "-m", "gitsyncer-auto-stash").Run()
}
// popStash attempts to pop the stash (used in defer)
-func popStash() {
- exec.Command("git", "stash", "pop").Run()
+func popStash(repoPath string) {
+ gitCommand(repoPath, "stash", "pop").Run()
}
// mergeBranch merges a branch from a remote
-func mergeBranch(remoteName, branch string) error {
+func mergeBranch(repoPath, remoteName, branch string) error {
fmt.Printf(" Merging from %s/%s...\n", remoteName, branch)
- cmd := exec.Command("git", "merge", fmt.Sprintf("%s/%s", remoteName, branch), "--no-edit")
+ cmd := gitCommand(repoPath, "merge", fmt.Sprintf("%s/%s", remoteName, branch), "--no-edit")
output, err := cmd.CombinedOutput()
if err != nil {
@@ -57,8 +65,8 @@ func mergeBranch(remoteName, branch string) error {
}
// pushBranch pushes a branch to a remote
-func pushBranch(remoteName, branch string, remoteHasBranch bool) error {
- cmd := exec.Command("git", "push", remoteName, branch, "--tags")
+func pushBranch(repoPath, remoteName, branch string, remoteHasBranch bool) error {
+ cmd := gitCommand(repoPath, "push", remoteName, branch, "--tags")
output, err := cmd.CombinedOutput()
if err != nil {
@@ -74,7 +82,7 @@ func pushBranch(remoteName, branch string, remoteHasBranch bool) error {
if isBranchMissing(outputStr) {
fmt.Printf(" Creating new branch on %s\n", remoteName)
// Try again with -u flag to set upstream
- cmd = exec.Command("git", "push", "-u", remoteName, branch, "--tags")
+ cmd = gitCommand(repoPath, "push", "-u", remoteName, branch, "--tags")
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to push to %s: %w", remoteName, err)
}
@@ -103,8 +111,8 @@ func isBranchMissing(output string) bool {
}
// getRemotesList extracts unique remote names from git remote -v output
-func getRemotesList() (map[string]bool, error) {
- cmd := exec.Command("git", "remote", "-v")
+func getRemotesList(repoPath string) (map[string]bool, error) {
+ cmd := gitCommand(repoPath, "remote", "-v")
output, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("failed to list remotes: %w", err)
@@ -126,14 +134,14 @@ func getRemotesList() (map[string]bool, error) {
}
// fetchRemote fetches from a single remote with error handling
-func fetchRemote(remote string) error {
- cmd := exec.Command("git", "fetch", remote, "--prune", "--tags")
+func fetchRemote(repoPath, remote string) error {
+ cmd := gitCommand(repoPath, "fetch", remote, "--prune", "--tags")
output, err := cmd.CombinedOutput()
if err != nil {
// Check if it's a tag conflict error
if bytes.Contains(output, []byte("would clobber existing tag")) {
- return handleTagConflict(remote, output)
+ return handleTagConflict(repoPath, remote, output)
}
// Check if it's because the repository doesn't exist
@@ -147,7 +155,7 @@ func fetchRemote(remote string) error {
}
// handleTagConflict provides a detailed error message for tag conflicts.
-func handleTagConflict(remote string, output []byte) error {
+func handleTagConflict(repoPath, remote string, output []byte) error {
var conflictDetails strings.Builder
conflictDetails.WriteString("tag conflict detected while fetching from remote: ")
conflictDetails.WriteString(remote)
@@ -159,8 +167,8 @@ func handleTagConflict(remote string, output []byte) error {
for _, match := range matches {
if len(match) > 1 {
tag := string(match[1])
- localHash, _ := getTagCommitHash(tag, "local")
- remoteHash, _ := getTagCommitHash(tag, remote)
+ localHash, _ := getTagCommitHash(repoPath, tag, "local")
+ remoteHash, _ := getTagCommitHash(repoPath, tag, remote)
conflictDetails.WriteString(fmt.Sprintf("\n - Tag: %s\n Local: %s\n Remote: %s", tag, localHash, remoteHash))
}
}
@@ -169,12 +177,12 @@ func handleTagConflict(remote string, output []byte) error {
}
// getTagCommitHash retrieves the commit hash for a given tag, either locally or from a remote.
-func getTagCommitHash(tag, source string) (string, error) {
+func getTagCommitHash(repoPath, tag, source string) (string, error) {
var cmd *exec.Cmd
if source == "local" {
- cmd = exec.Command("git", "rev-parse", tag+"^{\\}")
+ cmd = gitCommand(repoPath, "rev-parse", tag+"^{\\}")
} else {
- cmd = exec.Command("git", "ls-remote", "--tags", source, tag)
+ cmd = gitCommand(repoPath, "ls-remote", "--tags", source, tag)
}
output, err := cmd.Output()
@@ -187,8 +195,8 @@ func getTagCommitHash(tag, source string) (string, error) {
}
// checkoutExistingBranch tries to checkout an existing branch
-func checkoutExistingBranch(branch string) error {
- cmd := exec.Command("git", "checkout", branch)
+func checkoutExistingBranch(repoPath, branch string) error {
+ cmd := gitCommand(repoPath, "checkout", branch)
output, err := cmd.CombinedOutput()
if err != nil {
fmt.Printf(" Initial checkout failed: %s\n", strings.TrimSpace(string(output)))
@@ -198,8 +206,8 @@ func checkoutExistingBranch(branch string) error {
}
// createTrackingBranch creates a new branch tracking a remote branch
-func createTrackingBranch(branch, remoteName string) error {
- cmd := exec.Command("git", "checkout", "-b", branch, fmt.Sprintf("%s/%s", remoteName, branch))
+func createTrackingBranch(repoPath, branch, remoteName string) error {
+ cmd := gitCommand(repoPath, "checkout", "-b", branch, fmt.Sprintf("%s/%s", remoteName, branch))
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed to create tracking branch: %s", string(output))
@@ -265,8 +273,8 @@ func createSSHBareRepository(sshHost, repoPath string) error {
}
// pushBranchWithBackupSupport pushes a branch to a remote, creating SSH repos if needed
-func pushBranchWithBackupSupport(remoteName, branch string, remoteHasBranch bool, org *config.Organization) error {
- cmd := exec.Command("git", "push", remoteName, branch, "--tags")
+func pushBranchWithBackupSupport(repoPath, remoteName, branch string, remoteHasBranch bool, org *config.Organization) error {
+ cmd := gitCommand(repoPath, "push", remoteName, branch, "--tags")
output, err := cmd.CombinedOutput()
if err != nil {
@@ -276,7 +284,7 @@ func pushBranchWithBackupSupport(remoteName, branch string, remoteHasBranch bool
// If it's an SSH backup location, try to create the repository
if org.BackupLocation && org.IsSSH() {
// Get the repository name from the remote URL
- remoteURL, err := getRemoteURL(remoteName)
+ remoteURL, err := getRemoteURL(repoPath, remoteName)
if err != nil {
return fmt.Errorf("failed to get remote URL: %w", err)
}
@@ -293,7 +301,7 @@ func pushBranchWithBackupSupport(remoteName, branch string, remoteHasBranch bool
}
// Try pushing again
- cmd = exec.Command("git", "push", remoteName, branch, "--tags")
+ cmd = gitCommand(repoPath, "push", remoteName, branch, "--tags")
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to push after creating repository: %w", err)
}
@@ -310,7 +318,7 @@ func pushBranchWithBackupSupport(remoteName, branch string, remoteHasBranch bool
if isBranchMissing(outputStr) {
fmt.Printf(" Creating new branch on %s\n", remoteName)
// Try again with -u flag to set upstream
- cmd = exec.Command("git", "push", "-u", remoteName, branch, "--tags")
+ cmd = gitCommand(repoPath, "push", "-u", remoteName, branch, "--tags")
if err := cmd.Run(); err != nil {
return fmt.Errorf("failed to push to %s: %w", remoteName, err)
}
@@ -328,8 +336,8 @@ func pushBranchWithBackupSupport(remoteName, branch string, remoteHasBranch bool
}
// getRemoteURL gets the URL for a given remote
-func getRemoteURL(remoteName string) (string, error) {
- cmd := exec.Command("git", "remote", "get-url", remoteName)
+func getRemoteURL(repoPath, remoteName string) (string, error) {
+ cmd := gitCommand(repoPath, "remote", "get-url", remoteName)
output, err := cmd.Output()
if err != nil {
return "", err
diff --git a/internal/sync/git_operations_test.go b/internal/sync/git_operations_test.go
new file mode 100644
index 0000000..03ddb81
--- /dev/null
+++ b/internal/sync/git_operations_test.go
@@ -0,0 +1,19 @@
+package sync
+
+import "testing"
+
+func TestGitCommand_SetsDir(t *testing.T) {
+ cmd := gitCommand("/tmp/example-repo", "status")
+
+ if cmd.Dir != "/tmp/example-repo" {
+ t.Fatalf("expected command dir to be set, got %q", cmd.Dir)
+ }
+}
+
+func TestGitCommand_LeavesDirEmptyForGlobalCommands(t *testing.T) {
+ cmd := gitCommand("", "ls-remote", "--tags", "origin", "v1.0.0")
+
+ if cmd.Dir != "" {
+ t.Fatalf("expected empty dir for global command, got %q", cmd.Dir)
+ }
+}
diff --git a/internal/sync/repository_setup.go b/internal/sync/repository_setup.go
index 7e2c40e..e255274 100644
--- a/internal/sync/repository_setup.go
+++ b/internal/sync/repository_setup.go
@@ -96,20 +96,6 @@ func (s *Syncer) setupExistingRepository(repoPath string) error {
return nil
}
-// changeToRepoDirectory changes to the repository directory and returns a function to restore the original directory
-func changeToRepoDirectory(repoPath string) (func(), error) {
- originalDir, err := os.Getwd()
- if err != nil {
- return nil, fmt.Errorf("failed to get current directory: %w", err)
- }
-
- if err := os.Chdir(repoPath); err != nil {
- return nil, fmt.Errorf("failed to change to repository directory: %w", err)
- }
-
- return func() { os.Chdir(originalDir) }, nil
-}
-
// getRemotesMap creates a map of remote names to organizations
func (s *Syncer) getRemotesMap() map[string]*config.Organization {
remotes := make(map[string]*config.Organization)
diff --git a/internal/sync/sync.go b/internal/sync/sync.go
index 0f2f479..ba03e0f 100644
--- a/internal/sync/sync.go
+++ b/internal/sync/sync.go
@@ -60,13 +60,6 @@ func (s *Syncer) SyncRepository(repoName string) error {
return err
}
- // Change to repository directory
- restoreDir, err := changeToRepoDirectory(repoPath)
- if err != nil {
- return err
- }
- defer restoreDir()
-
// Fetch all remotes
fmt.Printf("Fetching updates from all remotes...\n")
if err := s.fetchAll(); err != nil {
@@ -114,6 +107,10 @@ func (s *Syncer) SyncRepository(repoName string) error {
return nil
}
+func (s *Syncer) repoPath() string {
+ return filepath.Join(s.workDir, s.repoName)
+}
+
// EnsureRepositoryCloned ensures a repository is cloned locally without syncing
// This is used for showcase-only mode
func (s *Syncer) EnsureRepositoryCloned(repoName string) error {
@@ -219,7 +216,7 @@ func (s *Syncer) addRemote(repoPath string, org *config.Organization) error {
// Note: We use individual fetches instead of --all to handle missing repositories gracefully
func (s *Syncer) fetchAll() error {
// Get list of remotes
- remotes, err := getRemotesList()
+ remotes, err := getRemotesList(s.repoPath())
if err != nil {
return err
}
@@ -247,7 +244,7 @@ func (s *Syncer) fetchAll() error {
}
fmt.Printf("Fetching %s\n", remote)
- if err := fetchRemote(remote); err != nil {
+ if err := fetchRemote(s.repoPath(), remote); err != nil {
return err
}
}
@@ -257,7 +254,7 @@ func (s *Syncer) fetchAll() error {
// getAllBranches gets all unique branches from all remotes
func (s *Syncer) getAllBranches() ([]string, error) {
- cmd := exec.Command("git", "branch", "-r")
+ cmd := gitCommand(s.repoPath(), "branch", "-r")
output, err := cmd.Output()
if err != nil {
return nil, err
@@ -274,13 +271,15 @@ func (s *Syncer) getAllBranches() ([]string, error) {
// syncBranch synchronizes a specific branch across all remotes
func (s *Syncer) syncBranch(branch string, remotes map[string]*config.Organization) error {
+ repoPath := s.repoPath()
+
// Handle merge conflicts and uncommitted changes
stashed, err := s.handleWorkingDirectoryState()
if err != nil {
return err
}
if stashed {
- defer popStash()
+ defer popStash(repoPath)
}
// Create or checkout the branch
@@ -292,33 +291,34 @@ func (s *Syncer) syncBranch(branch string, remotes map[string]*config.Organizati
remotesWithBranch := s.trackRemotesWithBranch(branch, remotes)
// Merge changes from remotes
- if err := mergeFromRemotes(branch, remotesWithBranch); err != nil {
+ if err := mergeFromRemotes(repoPath, branch, remotesWithBranch); err != nil {
return err
}
// Push to all remotes
- return pushToAllRemotes(branch, remotes, remotesWithBranch)
+ return pushToAllRemotes(repoPath, branch, remotes, remotesWithBranch)
}
// handleWorkingDirectoryState checks for conflicts and stashes changes if needed
// Returns true if changes were stashed
func (s *Syncer) handleWorkingDirectoryState() (bool, error) {
- hasConflicts, statusStr, err := checkForMergeConflicts()
+ repoPath := s.repoPath()
+ hasConflicts, statusStr, err := checkForMergeConflicts(repoPath)
if err != nil || statusStr == "" {
return false, nil
}
if hasConflicts {
// Get absolute path for clarity
- absPath, err := filepath.Abs(s.workDir)
+ absPath, err := filepath.Abs(repoPath)
if err != nil {
- absPath = s.workDir
+ absPath = repoPath
}
return false, fmt.Errorf("repository has unresolved merge conflicts\nPlease resolve conflicts in: %s\nOr delete the directory to start fresh: rm -rf %s", absPath, absPath)
}
// If we have uncommitted changes but no conflicts, try to stash them
- if err := stashChanges(); err != nil {
+ if err := stashChanges(repoPath); err != nil {
return false, fmt.Errorf("failed to stash changes: %w", err)
}
return true, nil
@@ -327,7 +327,7 @@ func (s *Syncer) handleWorkingDirectoryState() (bool, error) {
// checkoutBranch checks out a branch, creating it if necessary
func (s *Syncer) checkoutBranch(branch string) error {
// First try to checkout existing branch
- if err := checkoutExistingBranch(branch); err == nil {
+ if err := checkoutExistingBranch(s.repoPath(), branch); err == nil {
return nil
}
@@ -337,7 +337,7 @@ func (s *Syncer) checkoutBranch(branch string) error {
remoteName := s.getRemoteName(org)
if s.remoteBranchExists(remoteName, branch) {
- return createTrackingBranch(branch, remoteName)
+ return createTrackingBranch(s.repoPath(), branch, remoteName)
}
}
@@ -346,7 +346,7 @@ func (s *Syncer) checkoutBranch(branch string) error {
// remoteBranchExists checks if a branch exists on a remote
func (s *Syncer) remoteBranchExists(remoteName, branch string) bool {
- cmd := exec.Command("git", "branch", "-r", "--list", fmt.Sprintf("%s/%s", remoteName, branch))
+ cmd := gitCommand(s.repoPath(), "branch", "-r", "--list", fmt.Sprintf("%s/%s", remoteName, branch))
output, err := cmd.Output()
if err != nil {
return false