feat: implement initial commit creation for empty git repositories

- Added a function to ensure that a git repository has at least one commit before executing worktree commands. This function creates an empty initial commit with a predefined message if the repository is empty.
- Updated the create route handler to call this function, ensuring smooth operation when adding worktrees to repositories without existing commits.
- Introduced integration tests to verify the creation of the initial commit when no commits are present in the repository.
This commit is contained in:
Cody Seibert
2025-12-18 21:36:50 -05:00
parent 340e76c3ed
commit 18a2ed2a44
3 changed files with 109 additions and 1 deletions

View File

@@ -13,6 +13,9 @@ import {
const logger = createLogger("Worktree");
const execAsync = promisify(exec);
export const AUTOMAKER_INITIAL_COMMIT_MESSAGE =
"chore: automaker initial commit";
/**
* Normalize path separators to forward slashes for cross-platform consistency.
* This ensures paths from `path.join()` (backslashes on Windows) match paths
@@ -73,3 +76,30 @@ export function logWorktreeError(
// Re-export shared utilities
export { getErrorMessageShared as getErrorMessage };
export const logError = createLogError(logger);
/**
* Ensure the repository has at least one commit so git commands that rely on HEAD work.
* Returns true if an empty commit was created, false if the repo already had commits.
*/
export async function ensureInitialCommit(repoPath: string): Promise<boolean> {
try {
await execAsync("git rev-parse --verify HEAD", { cwd: repoPath });
return false;
} catch {
try {
await execAsync(
`git commit --allow-empty -m "${AUTOMAKER_INITIAL_COMMIT_MESSAGE}"`,
{ cwd: repoPath }
);
logger.info(
`[Worktree] Created initial empty commit to enable worktrees in ${repoPath}`
);
return true;
} catch (error) {
const reason = getErrorMessageShared(error);
throw new Error(
`Failed to create initial git commit. Please commit manually and retry. ${reason}`
);
}
}
}

View File

@@ -12,7 +12,13 @@ import { exec } from "child_process";
import { promisify } from "util";
import path from "path";
import { mkdir } from "fs/promises";
import { isGitRepo, getErrorMessage, logError, normalizePath } from "../common.js";
import {
isGitRepo,
getErrorMessage,
logError,
normalizePath,
ensureInitialCommit,
} from "../common.js";
import { trackBranch } from "./branch-tracking.js";
const execAsync = promisify(exec);
@@ -93,6 +99,9 @@ export function createCreateHandler() {
return;
}
// Ensure the repository has at least one commit so worktree commands referencing HEAD succeed
await ensureInitialCommit(projectPath);
// First, check if git already has a worktree for this branch (anywhere)
const existingWorktree = await findExistingWorktreeForBranch(projectPath, branchName);
if (existingWorktree) {