Skip to content

Commit ced353d

Browse files
authored
Fix an issue where if clone fails once it'll fail subsequently for empty dir (#80)
1 parent bcea121 commit ced353d

File tree

4 files changed

+118
-64
lines changed

4 files changed

+118
-64
lines changed

clients/git/client.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,56 @@ func GetRepoRoot() (string, error) {
125125

126126
// IsGitRepository checks if the current directory is a git repository
127127
func IsGitRepository() bool {
128+
return IsGitRepositoryDir("")
129+
}
130+
131+
// IsGitRepositoryDir checks if the specified directory is a git repository.
132+
// If dir is empty, it uses the current directory.
133+
func IsGitRepositoryDir(dir string) bool {
128134
cmd := exec.Command("git", "rev-parse", "--git-dir")
135+
if dir != "" {
136+
cmd.Dir = dir
137+
}
129138
err := cmd.Run()
130139
return err == nil
131140
}
132141

142+
// InitRepository initializes a new git repository in the specified directory.
143+
// If dir is empty, it uses the current directory.
144+
func InitRepository(dir string) error {
145+
cmd := exec.Command("git", "init")
146+
if dir != "" {
147+
cmd.Dir = dir
148+
}
149+
output, err := cmd.CombinedOutput()
150+
if err != nil {
151+
return clierrors.WrapError("git init failed: "+string(output), err)
152+
}
153+
return nil
154+
}
155+
156+
// SetRemoteURL sets or updates the origin remote URL.
157+
// If the remote doesn't exist, it adds it. If it exists, it updates it.
158+
func SetRemoteURL(dir, remoteName, url string) error {
159+
// First try to set the URL (works if remote exists)
160+
cmd := exec.Command("git", "remote", "set-url", remoteName, url)
161+
if dir != "" {
162+
cmd.Dir = dir
163+
}
164+
if err := cmd.Run(); err != nil {
165+
// Remote doesn't exist, add it
166+
cmd = exec.Command("git", "remote", "add", remoteName, url)
167+
if dir != "" {
168+
cmd.Dir = dir
169+
}
170+
output, err := cmd.CombinedOutput()
171+
if err != nil {
172+
return clierrors.WrapError("failed to add remote: "+string(output), err)
173+
}
174+
}
175+
return nil
176+
}
177+
133178
// HasUncommittedChanges checks if there are uncommitted changes in the repository
134179
func HasUncommittedChanges() (bool, error) {
135180
// Check for staged and unstaged changes

cmd/app/clone.go

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88

99
"github.com/charmbracelet/huh"
1010
"github.com/major-technology/cli/clients/api"
11-
"github.com/major-technology/cli/clients/git"
1211
"github.com/major-technology/cli/clients/token"
1312
"github.com/major-technology/cli/errors"
1413
"github.com/major-technology/cli/singletons"
@@ -62,26 +61,19 @@ func runClone(cmd *cobra.Command) error {
6261
// Determine the repository directory (use the repository name for git operations)
6362
repoDir := filepath.Join(".", selectedApp.GithubRepositoryName)
6463

65-
// Check if either directory exists
66-
var gitErr error
64+
// Determine which directory to use (prefer desiredDir, fall back to repoDir if it exists)
6765
var workingDir string
68-
69-
// Check if desired directory exists
7066
if _, err := os.Stat(desiredDir); err == nil {
7167
workingDir = desiredDir
72-
cmd.Printf("Directory '%s' already exists. Pulling latest changes...\n", workingDir)
73-
gitErr = git.Pull(workingDir)
7468
} else if _, err := os.Stat(repoDir); err == nil {
7569
workingDir = repoDir
76-
cmd.Printf("Directory '%s' already exists. Pulling latest changes...\n", workingDir)
77-
gitErr = git.Pull(workingDir)
7870
} else {
79-
// Neither directory exists, clone directly to desired directory
8071
workingDir = desiredDir
81-
cmd.Printf("Directory '%s' does not exist. Cloning repository...\n", workingDir)
82-
_, gitErr = cloneRepository(selectedApp.CloneURLSSH, selectedApp.CloneURLHTTPS, workingDir)
8372
}
8473

74+
// Ensure the directory is a properly configured git repository
75+
gitErr := ensureGitRepository(cmd, workingDir, selectedApp.CloneURLSSH, selectedApp.CloneURLHTTPS)
76+
8577
// Handle git authentication errors
8678
if gitErr != nil {
8779
if isGitAuthError(gitErr) {
@@ -91,7 +83,7 @@ func runClone(cmd *cobra.Command) error {
9183
}
9284

9385
// For some reason, there's a race where the repo is still not available for clones
94-
gitErr = pullOrCloneWithRetries(cmd, workingDir, selectedApp.CloneURLSSH, selectedApp.CloneURLHTTPS)
86+
gitErr = ensureGitRepositoryWithRetries(cmd, workingDir, selectedApp.CloneURLSSH, selectedApp.CloneURLHTTPS)
9587
// Check if retry succeeded
9688
if gitErr != nil {
9789
return errors.ErrorGitRepositoryAccessFailed

cmd/app/helper.go

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,55 @@ func getApplicationAndOrgIDFromDir(dir string) (string, string, error) {
6767
return appResp.ApplicationID, appResp.OrganizationID, nil
6868
}
6969

70+
// getPreferredCloneURL returns the preferred clone URL based on SSH availability
71+
func getPreferredCloneURL(sshURL, httpsURL string) (url string, method string, err error) {
72+
if utils.CanUseSSH() && sshURL != "" {
73+
return sshURL, "SSH", nil
74+
}
75+
if httpsURL != "" {
76+
return httpsURL, "HTTPS", nil
77+
}
78+
return "", "", fmt.Errorf("no valid clone method available")
79+
}
80+
81+
// ensureGitRepository ensures a directory is a properly configured git repository.
82+
// If the directory doesn't exist, it clones the repo.
83+
// If it exists but isn't a git repo, it initializes git and sets origin.
84+
// If it exists and is a git repo, it ensures origin is set correctly and pulls.
85+
// Returns the working directory path and any error.
86+
func ensureGitRepository(cmd *cobra.Command, targetDir, sshURL, httpsURL string) error {
87+
cloneURL, cloneMethod, err := getPreferredCloneURL(sshURL, httpsURL)
88+
if err != nil {
89+
return err
90+
}
91+
92+
// Check if directory exists
93+
if _, err := os.Stat(targetDir); os.IsNotExist(err) {
94+
// Directory doesn't exist - clone fresh
95+
cmd.Printf("Cloning repository to '%s' using %s...\n", targetDir, cloneMethod)
96+
return git.Clone(cloneURL, targetDir)
97+
}
98+
99+
// Directory exists - check if it's a git repo
100+
if !git.IsGitRepositoryDir(targetDir) {
101+
// Not a git repo - initialize and set origin
102+
cmd.Printf("Directory '%s' exists but is not a git repository. Initializing...\n", targetDir)
103+
if err := git.InitRepository(targetDir); err != nil {
104+
return errors.WrapError("failed to initialize git repository", err)
105+
}
106+
}
107+
108+
// Ensure origin is set correctly
109+
cmd.Printf("Ensuring git origin is configured correctly...\n")
110+
if err := git.SetRemoteURL(targetDir, "origin", cloneURL); err != nil {
111+
return errors.WrapError("failed to set git origin", err)
112+
}
113+
114+
// Pull latest changes
115+
cmd.Printf("Pulling latest changes...\n")
116+
return git.Pull(targetDir)
117+
}
118+
70119
// cloneRepository clones a repository using SSH or HTTPS based on availability
71120
// Returns the clone method used ("SSH" or "HTTPS") and any error
72121
func cloneRepository(sshURL, httpsURL, targetDir string) (string, error) {
@@ -125,8 +174,8 @@ func isGitAuthError(err error) bool {
125174
return false
126175
}
127176

128-
// pullOrCloneWithRetries retries a git clone or pull operation with exponential backoff
129-
func pullOrCloneWithRetries(cmd *cobra.Command, workingDir, sshURL, httpsURL string) error {
177+
// ensureGitRepositoryWithRetries retries ensureGitRepository with exponential backoff
178+
func ensureGitRepositoryWithRetries(cmd *cobra.Command, workingDir, sshURL, httpsURL string) error {
130179
maxRetries := 3
131180
baseDelay := 200 * time.Millisecond
132181

@@ -136,17 +185,7 @@ func pullOrCloneWithRetries(cmd *cobra.Command, workingDir, sshURL, httpsURL str
136185
time.Sleep(delay)
137186
}
138187

139-
var err error
140-
if _, statErr := os.Stat(workingDir); statErr == nil {
141-
// Directory exists, pull
142-
cmd.Printf("Pulling latest changes...\n")
143-
err = git.Pull(workingDir)
144-
} else {
145-
// Directory doesn't exist, clone
146-
cmd.Printf("Cloning repository...\n")
147-
_, err = cloneRepository(sshURL, httpsURL, workingDir)
148-
}
149-
188+
err := ensureGitRepository(cmd, workingDir, sshURL, httpsURL)
150189
if err == nil {
151190
return nil
152191
}

cmd/app/link.go

Lines changed: 16 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,8 @@ package app
22

33
import (
44
"fmt"
5-
"os"
65

76
"github.com/charmbracelet/lipgloss"
8-
"github.com/major-technology/cli/clients/git"
97
mjrToken "github.com/major-technology/cli/clients/token"
108
"github.com/major-technology/cli/cmd/user"
119
"github.com/major-technology/cli/errors"
@@ -54,44 +52,24 @@ func runLink(cmd *cobra.Command, applicationID string) error {
5452
return errors.WrapError("failed to store default organization", err)
5553
}
5654

57-
// Step 3: Clone the repository
58-
desiredDir := sanitizeDirName(appInfo.Name)
59-
workingDir := desiredDir
60-
61-
// Check if directory exists
62-
if _, err := os.Stat(desiredDir); err == nil {
63-
cmd.Printf("Directory '%s' already exists. Pulling latest changes...\n", workingDir)
64-
if gitErr := git.Pull(workingDir); gitErr != nil {
65-
if isGitAuthError(gitErr) {
66-
// Ensure repository access
67-
if err := utils.EnsureRepositoryAccess(cmd, applicationID, appInfo.CloneURLSSH, appInfo.CloneURLHTTPS); err != nil {
68-
return errors.WrapError("failed to ensure repository access", err)
69-
}
70-
// Retry
71-
if err := git.Pull(workingDir); err != nil {
72-
return errors.ErrorGitRepositoryAccessFailed
73-
}
74-
} else {
75-
return errors.ErrorGitCloneFailed
55+
// Step 3: Clone the repository or ensure existing directory is properly set up
56+
workingDir := sanitizeDirName(appInfo.Name)
57+
58+
// Ensure the directory is a properly configured git repository
59+
gitErr := ensureGitRepository(cmd, workingDir, appInfo.CloneURLSSH, appInfo.CloneURLHTTPS)
60+
if gitErr != nil {
61+
if isGitAuthError(gitErr) {
62+
// Ensure repository access
63+
if err := utils.EnsureRepositoryAccess(cmd, applicationID, appInfo.CloneURLSSH, appInfo.CloneURLHTTPS); err != nil {
64+
return errors.WrapError("failed to ensure repository access", err)
7665
}
77-
}
78-
} else {
79-
cmd.Printf("Cloning repository to '%s'...\n", workingDir)
80-
_, gitErr := cloneRepository(appInfo.CloneURLSSH, appInfo.CloneURLHTTPS, workingDir)
81-
if gitErr != nil {
82-
if isGitAuthError(gitErr) {
83-
// Ensure repository access
84-
if err := utils.EnsureRepositoryAccess(cmd, applicationID, appInfo.CloneURLSSH, appInfo.CloneURLHTTPS); err != nil {
85-
return errors.WrapError("failed to ensure repository access", err)
86-
}
87-
// Retry with retries
88-
gitErr = pullOrCloneWithRetries(cmd, workingDir, appInfo.CloneURLSSH, appInfo.CloneURLHTTPS)
89-
if gitErr != nil {
90-
return errors.ErrorGitRepositoryAccessFailed
91-
}
92-
} else {
93-
return errors.ErrorGitCloneFailed
66+
// Retry with retries
67+
gitErr = ensureGitRepositoryWithRetries(cmd, workingDir, appInfo.CloneURLSSH, appInfo.CloneURLHTTPS)
68+
if gitErr != nil {
69+
return errors.ErrorGitRepositoryAccessFailed
9470
}
71+
} else {
72+
return errors.ErrorGitCloneFailed
9573
}
9674
}
9775

0 commit comments

Comments
 (0)