Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion electron/ipc/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,18 @@ async function remoteTrackingRefExists(repoRoot: string, branch: string): Promis
}
}

/** Check whether a local branch ref exists. */
async function localBranchExists(repoRoot: string, branch: string): Promise<boolean> {
try {
await exec('git', ['rev-parse', '--verify', `refs/heads/${branch}`], {
cwd: repoRoot,
});
return true;
} catch {
return false;
}
}

async function detectMainBranchUncached(repoRoot: string): Promise<string> {
// Try remote HEAD reference first
const branch = await resolveOriginHead(repoRoot);
Expand All @@ -168,10 +180,13 @@ async function detectMainBranchUncached(repoRoot: string): Promise<string> {
}
}

// Check common default branch names
// Check common default branch names (remote-tracking first, then local)
for (const candidate of ['main', 'master']) {
if (await remoteTrackingRefExists(repoRoot, candidate)) return candidate;
}
for (const candidate of ['main', 'master']) {
if (await localBranchExists(repoRoot, candidate)) return candidate;
}
Comment on lines +183 to +189
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New behavior is introduced in detectMainBranchUncached() (fallback to local refs/heads/*) and createWorktree() (start-point ref validation + custom error messages), but there are no tests in this PR exercising these paths. Since electron/ipc/git.test.ts already mocks git commands, add targeted tests for: (1) no-remote + local main exists => returns main; (2) createWorktree with missing baseBranch in a non-empty repo => throws the new “Branch … does not exist” error; (3) empty repo => throws the new “no commits” error.

Copilot uses AI. Check for mistakes.

// Empty repo (no commits yet) — use configured default branch or fall back to "main"
try {
Expand Down Expand Up @@ -393,6 +408,26 @@ export async function createWorktree(
}
}

// Validate the start-point ref exists before attempting worktree creation
const startRef = baseBranch || 'HEAD';
try {
await exec('git', ['rev-parse', '--verify', startRef], { cwd: repoRoot });
} catch {
const isEmptyRepo = await exec('git', ['rev-list', '-n1', '--all'], { cwd: repoRoot })
.then(({ stdout }) => !stdout.trim())
.catch(() => true);
Comment on lines +416 to +418
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The isEmptyRepo detection treats any failure of git rev-list -n1 --all as an empty repository (catch(() => true)), which can mask other problems (e.g., repoRoot not being a git repo, missing permissions, or git not available) and surface a misleading “no commits” error. Consider only classifying as empty when the command succeeds and returns no output; otherwise rethrow (or throw a more specific “not a git repository”/original git error).

Suggested change
const isEmptyRepo = await exec('git', ['rev-list', '-n1', '--all'], { cwd: repoRoot })
.then(({ stdout }) => !stdout.trim())
.catch(() => true);
let isEmptyRepo = false;
try {
const { stdout } = await exec('git', ['rev-list', '-n1', '--all'], { cwd: repoRoot });
isEmptyRepo = !stdout.trim();
} catch (err) {
throw err;
}

Copilot uses AI. Check for mistakes.
if (isEmptyRepo) {
throw new Error(
'Cannot create a worktree in a repository with no commits. ' +
'Please make an initial commit first.',
);
}
throw new Error(
`Branch "${startRef}" does not exist. ` +
'Please select a valid base branch or create the branch first.',
);
}

// Create fresh worktree with new branch
const worktreeArgs = ['worktree', 'add', '-b', branchName, worktreePath];
if (baseBranch) worktreeArgs.push(baseBranch);
Expand Down