git-worktree-organize 1.0.12 → 1.0.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +93 -12
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -113,7 +113,7 @@ async function listWorktrees(repoPath) {
113
113
  }
114
114
 
115
115
  // src/migrate.ts
116
- import { mkdirSync, writeFileSync, readFileSync as readFileSync2, existsSync as existsSync2, renameSync, statSync as statSync2 } from "node:fs";
116
+ import { mkdirSync, writeFileSync, readFileSync as readFileSync2, existsSync as existsSync2, renameSync, statSync as statSync2, readdirSync } from "node:fs";
117
117
  import { join as join2, dirname, basename, resolve as resolve2 } from "node:path";
118
118
 
119
119
  // src/git.ts
@@ -158,23 +158,74 @@ function isPartialMigration(dest) {
158
158
  const gitFile = join2(dest, ".git");
159
159
  return existsSync2(join2(dest, ".bare")) && existsSync2(gitFile) && statSync2(gitFile).isFile();
160
160
  }
161
+ function findHub(startPath) {
162
+ let current = resolve2(startPath);
163
+ while (true) {
164
+ if (isPartialMigration(current))
165
+ return current;
166
+ const parent = dirname(current);
167
+ if (parent === current)
168
+ return null;
169
+ current = parent;
170
+ }
171
+ }
172
+ async function repairHub(dest, log = console.log) {
173
+ const adminBase = join2(dest, ".bare", "worktrees");
174
+ if (!existsSync2(adminBase))
175
+ return;
176
+ for (const adminName of readdirSync(adminBase)) {
177
+ const adminDir = join2(adminBase, adminName);
178
+ if (!statSync2(adminDir).isDirectory())
179
+ continue;
180
+ const gitdirFile = join2(adminDir, "gitdir");
181
+ if (!existsSync2(gitdirFile))
182
+ continue;
183
+ const registeredGitFile = readFileSync2(gitdirFile, "utf8").trim();
184
+ const worktreePath = dirname(registeredGitFile);
185
+ if (!worktreePath.startsWith(dest + "/"))
186
+ continue;
187
+ if (!existsSync2(registeredGitFile) || !statSync2(registeredGitFile).isFile())
188
+ continue;
189
+ const content = readFileSync2(registeredGitFile, "utf8");
190
+ const match = content.match(/^gitdir:\s*(.+)/m);
191
+ if (!match)
192
+ continue;
193
+ if (match[1].trim() === adminDir)
194
+ continue;
195
+ log(`Repairing .git for [${basename(worktreePath)}]`);
196
+ writeFileSync(registeredGitFile, `gitdir: ${adminDir}
197
+ `);
198
+ }
199
+ }
161
200
  async function resumeMigrate(dest, log = console.log) {
162
201
  const destBare = join2(dest, ".bare");
163
202
  const hubWorktrees = await listWorktrees(dest);
164
- const pending = hubWorktrees.filter((wt) => !wt.isBare && !wt.path.startsWith(dest + "/"));
203
+ const pending = hubWorktrees.filter((wt) => {
204
+ if (wt.isBare)
205
+ return false;
206
+ const branch = wt.branch ?? `detached-${wt.head.slice(0, 8)}`;
207
+ return wt.path !== join2(dest, sanitizeBranch(branch));
208
+ });
165
209
  if (pending.length === 0) {
166
210
  log("Nothing to resume — all worktrees are already in place.");
167
- return dest;
168
- }
169
- for (const wt of pending) {
170
- const branch = wt.branch ?? `detached-${wt.head.slice(0, 8)}`;
171
- if (!existsSync2(wt.path)) {
172
- log(`warn: Skipping [${branch}] — path no longer exists: ${wt.path}`);
173
- continue;
211
+ } else {
212
+ for (const wt of pending) {
213
+ const branch = wt.branch ?? `detached-${wt.head.slice(0, 8)}`;
214
+ const expectedPath = join2(dest, sanitizeBranch(branch));
215
+ let wtPath = wt.path;
216
+ if (!existsSync2(wtPath)) {
217
+ if (existsSync2(expectedPath)) {
218
+ wtPath = expectedPath;
219
+ } else {
220
+ log(`warn: Skipping [${branch}] — path no longer exists: ${wt.path}`);
221
+ continue;
222
+ }
223
+ }
224
+ log(`Moving [${branch}] → ${expectedPath}`);
225
+ await processLinkedWorktree({ ...wt, path: wtPath }, dest, destBare);
174
226
  }
175
- log(`Moving [${branch}] → ${join2(dest, sanitizeBranch(branch))}`);
176
- await processLinkedWorktree(wt, dest, destBare);
177
227
  }
228
+ await repairHub(dest, log);
178
229
  return dest;
179
230
  }
180
231
  async function migrate(config, options) {
@@ -307,9 +358,39 @@ async function main() {
307
358
  const destArg = args[1];
308
359
  const source = resolve3(sourcePath);
309
360
  const dest = isPartialMigration(source) ? source : destArg ? resolve3(destArg) : join3(dirname2(source), basename2(source) + "-bare");
361
+ if (!isPartialMigration(source) && !destArg) {
362
+ const ancestorHub = findHub(dirname2(source));
363
+ if (ancestorHub) {
364
+ console.log(`
365
+ ${yellow("warn:")} ${bold(source)} is inside an existing hub at ${bold(ancestorHub)}`);
366
+ console.log(`
367
+ This looks like manually-placed worktrees with stale .git files.`);
368
+ console.log(`Running repair will fix all worktree .git connections in the hub.
369
+ `);
370
+ process.stdout.write(`Repair hub at ${ancestorHub}? [y/N] `);
371
+ const repairAns = await prompt();
372
+ process.stdin.destroy();
373
+ if (!/^[Yy]$/.test(repairAns)) {
374
+ console.log("Aborted.");
375
+ process.exit(0);
376
+ }
377
+ console.log();
378
+ await repairHub(ancestorHub, (msg) => console.log(`${green("==>")} ${msg}`));
379
+ console.log();
380
+ console.log(`${green("==>")} Verifying with git worktree list...`);
381
+ console.log(run("git", ["-C", ancestorHub, "worktree", "list"]).stdout);
382
+ console.log(`Done! Hub: ${ancestorHub}`);
383
+ process.exit(0);
384
+ }
385
+ }
310
386
  if (isPartialMigration(dest)) {
311
387
  const hubWorktrees = await listWorktrees(dest);
312
- const pending = hubWorktrees.filter((wt) => !wt.isBare && !wt.path.startsWith(dest + "/"));
388
+ const pending = hubWorktrees.filter((wt) => {
389
+ if (wt.isBare)
390
+ return false;
391
+ const branch = wt.branch ?? `detached-${wt.head.slice(0, 8)}`;
392
+ return wt.path !== join3(dest, sanitizeBranch(branch));
393
+ });
313
394
  console.log(`
314
395
  ${yellow("warn:")} Partial migration detected at ${bold(dest)}`);
315
396
  if (pending.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "git-worktree-organize",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "description": "Convert any git repo into the canonical bare-hub worktree layout",
5
5
  "type": "module",
6
6
  "bin": {