Skip to content

Commit eb331db

Browse files
committed
fix: auto-detect default branch when 'main' doesn't exist
- Uses git ls-remote --symref to detect default branch (main/master/etc) - Falls back gracefully: try requested branch → detect → retry - Fixes repos using 'master' or other non-main default branches - Also fixes stale empty cache dirs from failed clones
1 parent 4d091cc commit eb331db

File tree

1 file changed

+46
-9
lines changed

1 file changed

+46
-9
lines changed

src/utils/git-cache.ts

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { existsSync, mkdirSync, rmSync } from 'node:fs';
1+
import { existsSync, mkdirSync, rmSync, readdirSync } from 'node:fs';
22
import { join } from 'node:path';
33
import { createHash } from 'node:crypto';
44
import { homedir, tmpdir } from 'node:os';
@@ -21,10 +21,35 @@ function cacheKey(url: string, branch: string): string {
2121
return createHash('sha256').update(`${url}#${branch}`).digest('hex').slice(0, 16);
2222
}
2323

24+
function isDirEmpty(dir: string): boolean {
25+
try {
26+
return readdirSync(dir).length === 0;
27+
} catch {
28+
return true;
29+
}
30+
}
31+
32+
function detectDefaultBranch(url: string): string {
33+
try {
34+
const output = execSync(`git ls-remote --symref ${url} HEAD`, {
35+
encoding: 'utf-8',
36+
stdio: ['pipe', 'pipe', 'pipe'],
37+
timeout: 15_000,
38+
});
39+
// Parse: ref: refs/heads/master HEAD
40+
const match = output.match(/ref: refs\/heads\/(\S+)\s+HEAD/);
41+
if (match?.[1]) return match[1];
42+
} catch {
43+
// fallback
44+
}
45+
return 'main';
46+
}
47+
2448
export function resolveRepo(url: string, options: ResolveRepoOptions = {}): ResolveRepoResult {
25-
const branch = options.branch ?? 'main';
49+
const requestedBranch = options.branch ?? 'main';
2650

2751
if (options.noCache) {
52+
const branch = requestedBranch === 'main' ? detectDefaultBranch(url) : requestedBranch;
2853
const dir = join(tmpdir(), `gitagent-${cacheKey(url, branch)}-${Date.now()}`);
2954
cloneRepo(url, branch, dir);
3055
return {
@@ -35,21 +60,33 @@ export function resolveRepo(url: string, options: ResolveRepoOptions = {}): Reso
3560
};
3661
}
3762

38-
const hash = cacheKey(url, branch);
63+
const hash = cacheKey(url, requestedBranch);
3964
const dir = join(CACHE_BASE, hash);
4065

41-
if (existsSync(dir) && !options.refresh) {
66+
// If cached dir exists, is non-empty, and no refresh requested — use it
67+
if (existsSync(dir) && !isDirEmpty(dir) && !options.refresh) {
4268
return { dir };
4369
}
4470

45-
if (existsSync(dir) && options.refresh) {
46-
// Nuke cached clone and re-clone fresh to avoid divergent branch issues
71+
// Clean up stale/empty cache dir
72+
if (existsSync(dir)) {
4773
rmSync(dir, { recursive: true, force: true });
48-
cloneRepo(url, branch, dir);
49-
return { dir };
5074
}
5175

52-
cloneRepo(url, branch, dir);
76+
// Try cloning with requested branch first, fall back to auto-detect
77+
try {
78+
cloneRepo(url, requestedBranch, dir);
79+
} catch {
80+
// Branch not found — auto-detect default branch and retry
81+
if (existsSync(dir)) rmSync(dir, { recursive: true, force: true });
82+
const detectedBranch = detectDefaultBranch(url);
83+
if (detectedBranch !== requestedBranch) {
84+
cloneRepo(url, detectedBranch, dir);
85+
} else {
86+
throw new Error(`Could not clone ${url} — branch "${requestedBranch}" not found`);
87+
}
88+
}
89+
5390
return { dir };
5491
}
5592

0 commit comments

Comments
 (0)