source dump of claude code
at main 162 lines 5.1 kB view raw
1import { realpath } from 'fs/promises' 2import { getOriginalCwd } from '../bootstrap/state.js' 3import { getGlobalConfig, saveGlobalConfig } from './config.js' 4import { logForDebugging } from './debug.js' 5import { 6 detectCurrentRepository, 7 parseGitHubRepository, 8} from './detectRepository.js' 9import { pathExists } from './file.js' 10import { getRemoteUrlForDir } from './git/gitFilesystem.js' 11import { findGitRoot } from './git.js' 12 13/** 14 * Updates the GitHub repository path mapping in global config. 15 * Called at startup (fire-and-forget) to track known local paths for repos. 16 * This is non-blocking and errors are logged silently. 17 * 18 * Stores the git root (not cwd) so the mapping always points to the 19 * repository root regardless of which subdirectory the user launched from. 20 * If the path is already tracked, it is promoted to the front of the list 21 * so the most recently used clone appears first. 22 */ 23export async function updateGithubRepoPathMapping(): Promise<void> { 24 try { 25 const repo = await detectCurrentRepository() 26 if (!repo) { 27 logForDebugging( 28 'Not in a GitHub repository, skipping path mapping update', 29 ) 30 return 31 } 32 33 // Use the git root as the canonical path for this repo clone. 34 // This ensures we always store the repo root, not an arbitrary subdirectory. 35 const cwd = getOriginalCwd() 36 const gitRoot = findGitRoot(cwd) 37 const basePath = gitRoot ?? cwd 38 39 // Resolve symlinks for canonical storage 40 let currentPath: string 41 try { 42 currentPath = (await realpath(basePath)).normalize('NFC') 43 } catch { 44 currentPath = basePath 45 } 46 47 // Normalize repo key to lowercase for case-insensitive matching 48 const repoKey = repo.toLowerCase() 49 50 const config = getGlobalConfig() 51 const existingPaths = config.githubRepoPaths?.[repoKey] ?? [] 52 53 if (existingPaths[0] === currentPath) { 54 // Already at the front — nothing to do 55 logForDebugging(`Path ${currentPath} already tracked for repo ${repoKey}`) 56 return 57 } 58 59 // Remove if present elsewhere (to promote to front), then prepend 60 const withoutCurrent = existingPaths.filter(p => p !== currentPath) 61 const updatedPaths = [currentPath, ...withoutCurrent] 62 63 saveGlobalConfig(current => ({ 64 ...current, 65 githubRepoPaths: { 66 ...current.githubRepoPaths, 67 [repoKey]: updatedPaths, 68 }, 69 })) 70 71 logForDebugging(`Added ${currentPath} to tracked paths for repo ${repoKey}`) 72 } catch (error) { 73 logForDebugging(`Error updating repo path mapping: ${error}`) 74 // Silently fail - this is non-blocking startup work 75 } 76} 77 78/** 79 * Gets known local paths for a given GitHub repository. 80 * @param repo The repository in "owner/repo" format 81 * @returns Array of known absolute paths, or empty array if none 82 */ 83export function getKnownPathsForRepo(repo: string): string[] { 84 const config = getGlobalConfig() 85 const repoKey = repo.toLowerCase() 86 return config.githubRepoPaths?.[repoKey] ?? [] 87} 88 89/** 90 * Filters paths to only those that exist on the filesystem. 91 * @param paths Array of absolute paths to check 92 * @returns Array of paths that exist 93 */ 94export async function filterExistingPaths(paths: string[]): Promise<string[]> { 95 const results = await Promise.all(paths.map(pathExists)) 96 return paths.filter((_, i) => results[i]) 97} 98 99/** 100 * Validates that a path contains the expected GitHub repository. 101 * @param path Absolute path to check 102 * @param expectedRepo Expected repository in "owner/repo" format 103 * @returns true if the path contains the expected repo, false otherwise 104 */ 105export async function validateRepoAtPath( 106 path: string, 107 expectedRepo: string, 108): Promise<boolean> { 109 try { 110 const remoteUrl = await getRemoteUrlForDir(path) 111 if (!remoteUrl) { 112 return false 113 } 114 115 const actualRepo = parseGitHubRepository(remoteUrl) 116 if (!actualRepo) { 117 return false 118 } 119 120 // Case-insensitive comparison 121 return actualRepo.toLowerCase() === expectedRepo.toLowerCase() 122 } catch { 123 return false 124 } 125} 126 127/** 128 * Removes a path from the tracked paths for a given repository. 129 * Used when a path is found to be invalid during selection. 130 * @param repo The repository in "owner/repo" format 131 * @param pathToRemove The path to remove from tracking 132 */ 133export function removePathFromRepo(repo: string, pathToRemove: string): void { 134 const config = getGlobalConfig() 135 const repoKey = repo.toLowerCase() 136 const existingPaths = config.githubRepoPaths?.[repoKey] ?? [] 137 138 const updatedPaths = existingPaths.filter(path => path !== pathToRemove) 139 140 if (updatedPaths.length === existingPaths.length) { 141 // Path wasn't in the list, nothing to do 142 return 143 } 144 145 const updatedMapping = { ...config.githubRepoPaths } 146 147 if (updatedPaths.length === 0) { 148 // Remove the repo key entirely if no paths remain 149 delete updatedMapping[repoKey] 150 } else { 151 updatedMapping[repoKey] = updatedPaths 152 } 153 154 saveGlobalConfig(current => ({ 155 ...current, 156 githubRepoPaths: updatedMapping, 157 })) 158 159 logForDebugging( 160 `Removed ${pathToRemove} from tracked paths for repo ${repoKey}`, 161 ) 162}