Commit 0ba65c4540b7
Changed files (4)
dots/pi/agent/extensions/git/index.ts
@@ -296,7 +296,7 @@ export default function (pi: ExtensionAPI) {
name: "git_worktree",
label: "Git Worktree",
description:
- "Manage git worktrees in ~/.local/share/worktrees/<org>/<repo>/<branch>. Create isolated working directories for different branches without switching in the main repository. USE WHEN starting feature work that needs isolation OR working on multiple branches simultaneously.",
+ "Manage git worktrees in ~/.local/share/worktrees/<org>/<repo>/<branch>. Uses lazyworktree when available for consistent org/repo/name layout, falls back to raw git. Create isolated working directories for different branches without switching in the main repository. USE WHEN starting feature work that needs isolation OR working on multiple branches simultaneously.",
parameters: Type.Object({
action: Type.Union([
Type.Literal("list"),
dots/pi/agent/extensions/git/worktree.ts
@@ -1,5 +1,6 @@
// =============================================================================
// Git Worktree Management
+// Uses lazyworktree when available, falls back to raw git commands.
// =============================================================================
import { execSync } from "child_process";
@@ -18,6 +19,27 @@ import {
ensureWorktreeDir,
} from "./utils.js";
+// =============================================================================
+// Lazyworktree Detection
+// =============================================================================
+
+let _hasLazyworktree: boolean | null = null;
+
+function hasLazyworktree(): boolean {
+ if (_hasLazyworktree === null) {
+ try {
+ execSync("command -v lazyworktree", {
+ encoding: "utf-8",
+ stdio: "pipe",
+ });
+ _hasLazyworktree = true;
+ } catch {
+ _hasLazyworktree = false;
+ }
+ }
+ return _hasLazyworktree;
+}
+
// =============================================================================
// List Worktrees
// =============================================================================
@@ -43,9 +65,9 @@ export function listWorktrees(cwd: string = process.cwd()): WorktreeInfo[] {
function parseWorktreeList(output: string): WorktreeInfo[] {
const worktrees: WorktreeInfo[] = [];
const lines = output.trim().split("\n");
-
+
let current: Partial<WorktreeInfo> = {};
-
+
for (const line of lines) {
if (line === "") {
if (current.path) {
@@ -110,6 +132,41 @@ export function createWorktree(options: WorktreeCreateOptions, cwd: string = pro
throw new Error("Not in a git repository");
}
+ // If a custom path is provided, skip lazyworktree and use raw git
+ if (!options.path && hasLazyworktree()) {
+ return createWithLazyworktree(options, repoRoot);
+ }
+
+ return createWithGit(options, repoRoot, cwd);
+}
+
+function createWithLazyworktree(options: WorktreeCreateOptions, repoRoot: string): string {
+ try {
+ const output = execSync(
+ `lazyworktree create --from-branch ${options.branch} --silent`,
+ {
+ cwd: repoRoot,
+ encoding: "utf-8",
+ stdio: "pipe",
+ }
+ ).trim();
+
+ // lazyworktree prints the worktree path to stdout
+ if (!output) {
+ throw new Error("lazyworktree returned empty output");
+ }
+
+ return output;
+ } catch (error: any) {
+ // If lazyworktree fails, fall back to raw git
+ if (error.message?.includes("lazyworktree returned empty output")) {
+ throw error;
+ }
+ return createWithGit({ ...options }, repoRoot, repoRoot);
+ }
+}
+
+function createWithGit(options: WorktreeCreateOptions, repoRoot: string, cwd: string): string {
// Ensure worktree directory exists
ensureWorktreeDir(cwd);
@@ -154,7 +211,7 @@ export function createWorktree(options: WorktreeCreateOptions, cwd: string = pro
execSync(cmd, {
cwd: repoRoot,
encoding: "utf-8",
- stdio: "pipe", // Capture output instead of inheriting
+ stdio: "pipe",
});
return worktreePath;
@@ -188,18 +245,31 @@ export function removeWorktree(options: WorktreeRemoveOptions, cwd: string = pro
);
}
- // Build command
+ // Try lazyworktree first for removal (handles branch cleanup too)
+ if (hasLazyworktree()) {
+ try {
+ execSync(`lazyworktree delete --silent "${worktree.path}"`, {
+ cwd: repoRoot,
+ encoding: "utf-8",
+ stdio: "pipe",
+ });
+ return;
+ } catch {
+ // Fall through to raw git
+ }
+ }
+
+ // Fall back to raw git
let cmd = `git worktree remove "${worktree.path}"`;
if (options.force) {
cmd += " --force";
}
- // Execute command
try {
execSync(cmd, {
cwd: repoRoot,
encoding: "utf-8",
- stdio: "pipe", // Capture output instead of inheriting
+ stdio: "pipe",
});
} catch (error) {
throw new Error(`Failed to remove worktree: ${error}`);
@@ -220,7 +290,7 @@ export function pruneWorktrees(cwd: string = process.cwd()): void {
execSync("git worktree prune", {
cwd: repoRoot,
encoding: "utf-8",
- stdio: "pipe", // Capture output instead of inheriting
+ stdio: "pipe",
});
} catch (error) {
throw new Error(`Failed to prune worktrees: ${error}`);
@@ -234,6 +304,6 @@ export function pruneWorktrees(cwd: string = process.cwd()): void {
export function getCurrentWorktreeInfo(cwd: string = process.cwd()): WorktreeInfo | null {
const worktrees = listWorktrees(cwd);
const currentPath = path.resolve(cwd);
-
+
return worktrees.find((wt) => path.resolve(wt.path) === currentPath) ?? null;
}
home/common/dev/lazyworktree.nix
@@ -6,7 +6,7 @@
programs.lazyworktree = {
enable = true;
enableZshIntegration = true;
- package = pkgs.master.lazyworktree;
+ package = pkgs.lazyworktree;
# Explicit aliases for commonly used repositories
aliases = {
flake.lock
@@ -128,11 +128,11 @@
]
},
"locked": {
- "lastModified": 1771565661,
- "narHash": "sha256-RP+biroKgfJdmCzol02gMglC8fyMAIjV27k9xMoGne4=",
+ "lastModified": 1771838063,
+ "narHash": "sha256-BviEpCy+RUEperWTlnSWFBFdv3wOPDr/yV/L+6IZjio=",
"owner": "vdemeester",
"repo": "chick-group",
- "rev": "20e381b00673a9d97a7819d3ec832d030aeb0a06",
+ "rev": "428104bedcafedad45a58544ed73ed368aff8408",
"type": "github"
},
"original": {