Inspect every bucket under `` /projects/`` and decide which ones belong to a worktree that should be folded into a canonical sibling. A bucket becomes a migration candidate when: 1. Its ``unmangle`` back to a path still exists on disk (we have the file system available to p
(home: Path | None = None)
| 156 | |
| 157 | |
| 158 | def plan_migration(home: Path | None = None) -> MigrationPlan: |
| 159 | """Inspect every bucket under ``<home>/projects/`` and decide which ones |
| 160 | belong to a worktree that should be folded into a canonical sibling. |
| 161 | |
| 162 | A bucket becomes a migration candidate when: |
| 163 | |
| 164 | 1. Its ``unmangle`` back to a path still exists on disk (we have the file |
| 165 | system available to probe git state). |
| 166 | 2. That path is inside a git worktree. |
| 167 | 3. The worktree's canonical identity differs from its own path — i.e. the |
| 168 | bucket is not the main repo worktree. |
| 169 | |
| 170 | Buckets that skip any of the above show up in ``plan.skipped`` with a |
| 171 | human-readable reason, so dry-run output stays transparent. |
| 172 | """ |
| 173 | home = home or get_home_dir() |
| 174 | projects_dir = home / PROJECTS_SUBDIR |
| 175 | plan = MigrationPlan(home=home) |
| 176 | if not projects_dir.is_dir(): |
| 177 | return plan |
| 178 | |
| 179 | for bucket_path in sorted(projects_dir.iterdir()): |
| 180 | if not bucket_path.is_dir(): |
| 181 | continue |
| 182 | name = bucket_path.name |
| 183 | if is_archived_bucket(name): |
| 184 | plan.skipped.append(BucketSkip(bucket=name, reason="already archived")) |
| 185 | continue |
| 186 | |
| 187 | candidate_cwd = _resolve_bucket_cwd(bucket_path, name) |
| 188 | if not candidate_cwd.exists(): |
| 189 | plan.skipped.append( |
| 190 | BucketSkip(bucket=name, reason="original cwd no longer exists on disk") |
| 191 | ) |
| 192 | continue |
| 193 | |
| 194 | identity = detect_repo_identity(candidate_cwd) |
| 195 | if identity is None: |
| 196 | plan.skipped.append( |
| 197 | BucketSkip(bucket=name, reason="not a git worktree (or git unavailable)") |
| 198 | ) |
| 199 | continue |
| 200 | |
| 201 | if identity == candidate_cwd: |
| 202 | plan.skipped.append( |
| 203 | BucketSkip(bucket=name, reason="already the canonical worktree") |
| 204 | ) |
| 205 | continue |
| 206 | |
| 207 | target_bucket_name = mangle_project_path(identity) |
| 208 | target_path = projects_dir / target_bucket_name |
| 209 | plan.plans.append( |
| 210 | BucketPlan( |
| 211 | source_bucket=name, |
| 212 | source_path=bucket_path, |
| 213 | source_cwd=candidate_cwd, |
| 214 | target_bucket=target_bucket_name, |
| 215 | target_path=target_path, |