@akiojin/gwt 2.9.0 → 2.10.0

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.
package/src/worktree.ts CHANGED
@@ -100,11 +100,9 @@ async function listWorktrees(): Promise<WorktreeInfo[]> {
100
100
  try {
101
101
  const { getRepositoryRoot } = await import("./git.js");
102
102
  const repoRoot = await getRepositoryRoot();
103
- const { stdout } = await execa(
104
- "git",
105
- ["worktree", "list", "--porcelain"],
106
- { cwd: repoRoot },
107
- );
103
+ const { stdout } = await execa("git", ["worktree", "list", "--porcelain"], {
104
+ cwd: repoRoot,
105
+ });
108
106
  const worktrees: WorktreeInfo[] = [];
109
107
  const lines = stdout.split("\n");
110
108
 
@@ -486,16 +484,14 @@ async function getOrphanedLocalBranches({
486
484
  reasons.push("merged-pr");
487
485
  }
488
486
 
489
- if (!hasUnpushed) {
490
- const hasUniqueCommits = await branchHasUniqueCommitsComparedToBase(
491
- localBranch.name,
492
- baseBranch,
493
- repoRoot,
494
- );
487
+ const hasUniqueCommits = await branchHasUniqueCommitsComparedToBase(
488
+ localBranch.name,
489
+ baseBranch,
490
+ repoRoot,
491
+ );
495
492
 
496
- if (!hasUniqueCommits) {
497
- reasons.push("no-diff-with-base");
498
- }
493
+ if (!hasUniqueCommits) {
494
+ reasons.push("no-diff-with-base");
499
495
  }
500
496
 
501
497
  if (process.env.DEBUG_CLEANUP) {
@@ -506,14 +502,18 @@ async function getOrphanedLocalBranches({
506
502
  );
507
503
  }
508
504
 
509
- if (reasons.length > 0) {
510
- let hasRemoteBranch = false;
511
- try {
512
- hasRemoteBranch = await checkRemoteBranchExists(localBranch.name);
513
- } catch {
514
- hasRemoteBranch = false;
515
- }
505
+ let hasRemoteBranch = false;
506
+ try {
507
+ hasRemoteBranch = await checkRemoteBranchExists(localBranch.name);
508
+ } catch {
509
+ hasRemoteBranch = false;
510
+ }
511
+
512
+ if (!hasUnpushed && hasRemoteBranch && hasUniqueCommits) {
513
+ reasons.push("remote-synced");
514
+ }
516
515
 
516
+ if (reasons.length > 0) {
517
517
  cleanupTargets.push({
518
518
  worktreePath: null, // worktreeは存在しない
519
519
  branch: localBranch.name,
@@ -627,13 +627,19 @@ export async function getMergedPRWorktrees(): Promise<CleanupTarget[]> {
627
627
 
628
628
  const cleanupReasons: CleanupReason[] = [];
629
629
 
630
- if (mergedPR) {
631
- cleanupReasons.push("merged-pr");
632
- }
633
-
634
630
  // worktreeパスの存在を確認
635
631
  const fs = await import("node:fs");
636
- const isAccessible = fs.existsSync(worktree.worktreePath);
632
+ // Some test environments mock node:fs without existsSync on the module root.
633
+ const existsSync =
634
+ typeof fs.existsSync === "function"
635
+ ? fs.existsSync
636
+ : typeof (fs as { default?: { existsSync?: unknown } }).default
637
+ ?.existsSync === "function"
638
+ ? (fs as { default: { existsSync: (p: string) => boolean } }).default
639
+ .existsSync
640
+ : null;
641
+
642
+ const isAccessible = existsSync ? existsSync(worktree.worktreePath) : false;
637
643
 
638
644
  let hasUncommitted = false;
639
645
  let hasUnpushed = false;
@@ -657,16 +663,29 @@ export async function getMergedPRWorktrees(): Promise<CleanupTarget[]> {
657
663
  }
658
664
  }
659
665
 
660
- if (!hasUnpushed) {
661
- const hasUniqueCommits = await branchHasUniqueCommitsComparedToBase(
662
- worktree.branch,
663
- baseBranch,
664
- repoRoot,
665
- );
666
+ let hasRemoteBranch = false;
667
+ try {
668
+ hasRemoteBranch = await checkRemoteBranchExists(worktree.branch);
669
+ } catch {
670
+ hasRemoteBranch = false;
671
+ }
666
672
 
667
- if (!hasUniqueCommits) {
668
- cleanupReasons.push("no-diff-with-base");
669
- }
673
+ if (mergedPR) {
674
+ cleanupReasons.push("merged-pr");
675
+ }
676
+
677
+ const hasUniqueCommits = await branchHasUniqueCommitsComparedToBase(
678
+ worktree.branch,
679
+ baseBranch,
680
+ repoRoot,
681
+ );
682
+
683
+ // 差分がない場合はベース同等としてクリーンアップ候補
684
+ if (!hasUniqueCommits) {
685
+ cleanupReasons.push("no-diff-with-base");
686
+ } else if (!hasUncommitted && !hasUnpushed && hasRemoteBranch) {
687
+ // 未マージでも、ローカルに未コミット/未プッシュがなくリモートが最新ならローカルのみクリーンアップ許可
688
+ cleanupReasons.push("remote-synced");
670
689
  }
671
690
 
672
691
  if (process.env.DEBUG_CLEANUP) {
@@ -681,8 +700,6 @@ export async function getMergedPRWorktrees(): Promise<CleanupTarget[]> {
681
700
  continue;
682
701
  }
683
702
 
684
- const hasRemoteBranch = await checkRemoteBranchExists(worktree.branch);
685
-
686
703
  const target: CleanupTarget = {
687
704
  worktreePath: worktree.worktreePath,
688
705
  branch: worktree.branch,