@@ -76,6 +76,35 @@ async function hasCommits(repoPath: string): Promise<boolean> {
7676 }
7777}
7878
79+ async function isValidGitRepo ( repoPath : string ) : Promise < boolean > {
80+ try {
81+ await executeCommand ( [ 'git' , '-C' , repoPath , 'rev-parse' , '--git-dir' ] , { silent : true } )
82+ return true
83+ } catch {
84+ return false
85+ }
86+ }
87+
88+ async function checkRepoNameAvailable ( name : string ) : Promise < boolean > {
89+ const reposPath = getReposPath ( )
90+ const targetPath = path . join ( reposPath , name )
91+ try {
92+ await executeCommand ( [ 'test' , '-e' , targetPath ] , { silent : true } )
93+ return false
94+ } catch {
95+ return true
96+ }
97+ }
98+
99+ async function copyRepoToWorkspace ( sourcePath : string , targetName : string ) : Promise < void > {
100+ const reposPath = getReposPath ( )
101+ const targetPath = path . join ( reposPath , targetName )
102+
103+ logger . info ( `Copying repo from ${ sourcePath } to ${ targetPath } ` )
104+ await executeCommand ( [ 'git' , 'clone' , '--local' , sourcePath , targetName ] , { cwd : reposPath } )
105+ logger . info ( `Successfully copied repo to ${ targetPath } ` )
106+ }
107+
79108
80109
81110async function safeGetCurrentBranch ( repoPath : string ) : Promise < string | null > {
@@ -96,6 +125,40 @@ async function safeGetCurrentBranch(repoPath: string): Promise<string | null> {
96125 }
97126}
98127
128+ async function checkoutBranchSafely ( repoPath : string , branch : string ) : Promise < void > {
129+ const sanitizedBranch = branch
130+ . replace ( / ^ r e f s \/ h e a d s \/ / , '' )
131+ . replace ( / ^ r e f s \/ r e m o t e s \/ / , '' )
132+ . replace ( / ^ o r i g i n \/ / , '' )
133+
134+ let localBranchExists = false
135+ try {
136+ await executeCommand ( [ 'git' , '-C' , repoPath , 'rev-parse' , '--verify' , `refs/heads/${ sanitizedBranch } ` ] , { silent : true } )
137+ localBranchExists = true
138+ } catch {
139+ localBranchExists = false
140+ }
141+
142+ let remoteBranchExists = false
143+ try {
144+ await executeCommand ( [ 'git' , '-C' , repoPath , 'rev-parse' , '--verify' , `refs/remotes/origin/${ sanitizedBranch } ` ] , { silent : true } )
145+ remoteBranchExists = true
146+ } catch {
147+ remoteBranchExists = false
148+ }
149+
150+ if ( localBranchExists ) {
151+ logger . info ( `Checking out existing local branch: ${ sanitizedBranch } ` )
152+ await executeCommand ( [ 'git' , '-C' , repoPath , 'checkout' , sanitizedBranch ] )
153+ } else if ( remoteBranchExists ) {
154+ logger . info ( `Checking out remote branch: ${ sanitizedBranch } ` )
155+ await executeCommand ( [ 'git' , '-C' , repoPath , 'checkout' , '-b' , sanitizedBranch , `origin/${ sanitizedBranch } ` ] )
156+ } else {
157+ logger . info ( `Creating new branch: ${ sanitizedBranch } ` )
158+ await executeCommand ( [ 'git' , '-C' , repoPath , 'checkout' , '-b' , sanitizedBranch ] )
159+ }
160+ }
161+
99162function getGitEnv ( database : Database ) : Record < string , string > {
100163 try {
101164 const settingsService = new SettingsService ( database )
@@ -113,17 +176,62 @@ export async function initLocalRepo(
113176 localPath : string ,
114177 branch ?: string
115178) : Promise < Repo > {
116- const normalizedPath = localPath . trim ( ) . replace ( / \/ + $ / , '' )
117- const fullPath = path . resolve ( getReposPath ( ) , normalizedPath )
179+ const normalizedInputPath = localPath . trim ( ) . replace ( / \/ + $ / , '' )
180+
181+ let targetPath : string
182+ let repoLocalPath : string
183+ let sourceWasGitRepo = false
118184
119- const existing = db . getRepoByLocalPath ( database , normalizedPath )
185+ if ( path . isAbsolute ( normalizedInputPath ) ) {
186+ logger . info ( `Absolute path detected: ${ normalizedInputPath } ` )
187+
188+ try {
189+ const exists = await executeCommand ( [ 'test' , '-d' , normalizedInputPath ] , { silent : true } )
190+ . then ( ( ) => true )
191+ . catch ( ( ) => false )
192+
193+ if ( ! exists ) {
194+ throw new Error ( `No such file or directory: '${ normalizedInputPath } '` )
195+ }
196+
197+ const isGit = await isValidGitRepo ( normalizedInputPath )
198+
199+ if ( isGit ) {
200+ sourceWasGitRepo = true
201+ const baseName = path . basename ( normalizedInputPath )
202+
203+ const isAvailable = await checkRepoNameAvailable ( baseName )
204+ if ( ! isAvailable ) {
205+ throw new Error ( `A repository named '${ baseName } ' already exists in the workspace. Please remove it first or use a different source directory.` )
206+ }
207+
208+ repoLocalPath = baseName
209+
210+ logger . info ( `Copying existing git repo from ${ normalizedInputPath } to workspace as ${ baseName } ` )
211+ await copyRepoToWorkspace ( normalizedInputPath , baseName )
212+ targetPath = path . join ( getReposPath ( ) , baseName )
213+ } else {
214+ throw new Error ( `Directory exists but is not a valid Git repository. Please provide either a Git repository path or a simple directory name to create a new empty repository.` )
215+ }
216+ } catch ( error : any ) {
217+ if ( error . message . includes ( 'No such file or directory' ) ) {
218+ throw error
219+ }
220+ throw new Error ( `Failed to process absolute path '${ normalizedInputPath } ': ${ error . message } ` )
221+ }
222+ } else {
223+ repoLocalPath = normalizedInputPath
224+ targetPath = path . join ( getReposPath ( ) , repoLocalPath )
225+ }
226+
227+ const existing = db . getRepoByLocalPath ( database , repoLocalPath )
120228 if ( existing ) {
121- logger . info ( `Local repo already exists in database: ${ normalizedPath } ` )
229+ logger . info ( `Local repo already exists in database: ${ repoLocalPath } ` )
122230 return existing
123231 }
124232
125233 const createRepoInput : CreateRepoInput = {
126- localPath : normalizedPath ,
234+ localPath : repoLocalPath ,
127235 branch : branch || undefined ,
128236 defaultBranch : branch || 'main' ,
129237 cloneStatus : 'cloning' ,
@@ -136,26 +244,36 @@ export async function initLocalRepo(
136244
137245 try {
138246 repo = db . createRepo ( database , createRepoInput )
139- logger . info ( `Created database record for local repo: ${ normalizedPath } (id: ${ repo . id } )` )
247+ logger . info ( `Created database record for local repo: ${ repoLocalPath } (id: ${ repo . id } )` )
140248 } catch ( error : any ) {
141- logger . error ( `Failed to create database record for local repo: ${ normalizedPath } ` , error )
142- throw new Error ( `Failed to register local repository '${ normalizedPath } ': ${ error . message } ` )
249+ logger . error ( `Failed to create database record for local repo: ${ repoLocalPath } ` , error )
250+ throw new Error ( `Failed to register local repository '${ repoLocalPath } ': ${ error . message } ` )
143251 }
144252
145253 try {
146- await ensureDirectoryExists ( fullPath )
147- directoryCreated = true
148- logger . info ( `Created directory for local repo: ${ fullPath } ` )
149-
150- logger . info ( `Initializing git repository: ${ fullPath } ` )
151-
152- await executeCommand ( [ 'git' , 'init' ] , { cwd : fullPath } )
153-
154- if ( branch && branch !== 'main' ) {
155- await executeCommand ( [ 'git' , '-C' , fullPath , 'checkout' , '-b' , branch ] )
254+ if ( ! sourceWasGitRepo ) {
255+ await ensureDirectoryExists ( targetPath )
256+ directoryCreated = true
257+ logger . info ( `Created directory for local repo: ${ targetPath } ` )
258+
259+ logger . info ( `Initializing git repository: ${ targetPath } ` )
260+ await executeCommand ( [ 'git' , 'init' ] , { cwd : targetPath } )
261+
262+ if ( branch && branch !== 'main' ) {
263+ await executeCommand ( [ 'git' , '-C' , targetPath , 'checkout' , '-b' , branch ] )
264+ }
265+ } else {
266+ if ( branch ) {
267+ logger . info ( `Switching to branch ${ branch } for copied repo` )
268+ const currentBranch = await safeGetCurrentBranch ( targetPath )
269+
270+ if ( currentBranch !== branch ) {
271+ await checkoutBranchSafely ( targetPath , branch )
272+ }
273+ }
156274 }
157275
158- const isGitRepo = await executeCommand ( [ 'git' , '-C' , fullPath , 'rev-parse' , '--git-dir' ] )
276+ const isGitRepo = await executeCommand ( [ 'git' , '-C' , targetPath , 'rev-parse' , '--git-dir' ] )
159277 . then ( ( ) => true )
160278 . catch ( ( ) => false )
161279
@@ -164,10 +282,10 @@ export async function initLocalRepo(
164282 }
165283
166284 db . updateRepoStatus ( database , repo . id , 'ready' )
167- logger . info ( `Local git repo ready: ${ normalizedPath } ` )
285+ logger . info ( `Local git repo ready: ${ repoLocalPath } ` )
168286 return { ...repo , cloneStatus : 'ready' }
169287 } catch ( error : any ) {
170- logger . error ( `Failed to initialize local repo, rolling back: ${ normalizedPath } ` , error )
288+ logger . error ( `Failed to initialize local repo, rolling back: ${ repoLocalPath } ` , error )
171289
172290 try {
173291 db . deleteRepo ( database , repo . id )
@@ -176,16 +294,23 @@ export async function initLocalRepo(
176294 logger . error ( `Failed to rollback database record for repo id ${ repo . id } :` , dbError )
177295 }
178296
179- if ( directoryCreated ) {
297+ if ( directoryCreated && ! sourceWasGitRepo ) {
180298 try {
181- await executeCommand ( [ 'rm' , '-rf' , normalizedPath ] , getReposPath ( ) )
182- logger . info ( `Rolled back directory: ${ normalizedPath } ` )
299+ await executeCommand ( [ 'rm' , '-rf' , repoLocalPath ] , getReposPath ( ) )
300+ logger . info ( `Rolled back directory: ${ repoLocalPath } ` )
183301 } catch ( fsError : any ) {
184- logger . error ( `Failed to rollback directory ${ normalizedPath } :` , fsError )
302+ logger . error ( `Failed to rollback directory ${ repoLocalPath } :` , fsError )
303+ }
304+ } else if ( sourceWasGitRepo ) {
305+ try {
306+ await executeCommand ( [ 'rm' , '-rf' , repoLocalPath ] , getReposPath ( ) )
307+ logger . info ( `Cleaned up copied directory: ${ repoLocalPath } ` )
308+ } catch ( fsError : any ) {
309+ logger . error ( `Failed to clean up copied directory ${ repoLocalPath } :` , fsError )
185310 }
186311 }
187312
188- throw new Error ( `Failed to initialize local repository '${ normalizedPath } ': ${ error . message } ` )
313+ throw new Error ( `Failed to initialize local repository '${ repoLocalPath } ': ${ error . message } ` )
189314 }
190315}
191316
@@ -471,32 +596,7 @@ export async function switchBranch(database: Database, repoId: number, branch: s
471596
472597 await executeGitWithFallback ( [ 'git' , '-C' , repoPath , 'fetch' , '--all' ] , { env } )
473598
474- let localBranchExists = false
475- try {
476- await executeCommand ( [ 'git' , '-C' , repoPath , 'rev-parse' , '--verify' , `refs/heads/${ sanitizedBranch } ` ] )
477- localBranchExists = true
478- } catch {
479- localBranchExists = false
480- }
481-
482- let remoteBranchExists = false
483- try {
484- await executeCommand ( [ 'git' , '-C' , repoPath , 'rev-parse' , '--verify' , `refs/remotes/origin/${ sanitizedBranch } ` ] )
485- remoteBranchExists = true
486- } catch {
487- remoteBranchExists = false
488- }
489-
490- if ( localBranchExists ) {
491- logger . info ( `Checking out existing local branch: ${ sanitizedBranch } ` )
492- await executeCommand ( [ 'git' , '-C' , repoPath , 'checkout' , sanitizedBranch ] )
493- } else if ( remoteBranchExists ) {
494- logger . info ( `Checking out remote branch: ${ sanitizedBranch } ` )
495- await executeCommand ( [ 'git' , '-C' , repoPath , 'checkout' , '-b' , sanitizedBranch , `origin/${ sanitizedBranch } ` ] )
496- } else {
497- logger . info ( `Creating new branch: ${ sanitizedBranch } ` )
498- await executeCommand ( [ 'git' , '-C' , repoPath , 'checkout' , '-b' , sanitizedBranch ] )
499- }
599+ await checkoutBranchSafely ( repoPath , sanitizedBranch )
500600
501601 logger . info ( `Successfully switched to branch: ${ sanitizedBranch } ` )
502602 } catch ( error : any ) {
0 commit comments