multi-workspace 3.2.16__tar.gz → 3.2.18__tar.gz

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 (47) hide show
  1. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/PKG-INFO +1 -1
  2. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/_version.py +2 -2
  3. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/app_api.py +22 -1
  4. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/worktree.py +31 -9
  5. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/.gitignore +0 -0
  6. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/LICENSE +0 -0
  7. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/README.md +0 -0
  8. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/__init__.py +0 -0
  9. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/__main__.py +0 -0
  10. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/add.py +0 -0
  11. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/api.py +0 -0
  12. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/bootstrap.py +0 -0
  13. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/branch.py +0 -0
  14. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/cli.py +0 -0
  15. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/cli_helpers.py +0 -0
  16. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/collaborator.py +0 -0
  17. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/convert_monorepo.py +0 -0
  18. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/doctor.py +0 -0
  19. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/errors.py +0 -0
  20. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/git_helpers.py +0 -0
  21. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/git_run.py +0 -0
  22. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/git_set_branch.py +0 -0
  23. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/ignore_files.py +0 -0
  24. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/init.py +0 -0
  25. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/logging.py +0 -0
  26. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/managed_blocks.py +0 -0
  27. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/open.py +0 -0
  28. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/paths.py +0 -0
  29. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/registry.py +0 -0
  30. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/remove.py +0 -0
  31. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/repo_urls.py +0 -0
  32. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/repos.py +0 -0
  33. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/resources/init_readme.md +0 -0
  34. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/service.py +0 -0
  35. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/settings.py +0 -0
  36. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/sync.py +0 -0
  37. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/sync_agents.py +0 -0
  38. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/sync_github.py +0 -0
  39. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/sync_vscode.py +0 -0
  40. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/sync_vscode_devcontainer.py +0 -0
  41. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/sync_vscode_extensions.py +0 -0
  42. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/sync_vscode_helpers.py +0 -0
  43. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/sync_vscode_launch.py +0 -0
  44. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/sync_vscode_settings.py +0 -0
  45. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/sync_vscode_tasks.py +0 -0
  46. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/multi/utils.py +0 -0
  47. {multi_workspace-3.2.16 → multi_workspace-3.2.18}/pyproject.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: multi-workspace
3
- Version: 3.2.16
3
+ Version: 3.2.18
4
4
  Summary: Multi
5
5
  Project-URL: Repository, https://github.com/gabemontague/multi
6
6
  Project-URL: Issues, https://github.com/gabemontague/multi/issues
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
18
18
  commit_id: str | None
19
19
  __commit_id__: str | None
20
20
 
21
- __version__ = version = '3.2.16'
22
- __version_tuple__ = version_tuple = (3, 2, 16)
21
+ __version__ = version = '3.2.18'
22
+ __version_tuple__ = version_tuple = (3, 2, 18)
23
23
 
24
24
  __commit_id__ = commit_id = None
@@ -126,6 +126,21 @@ def _subrepo_payloads(repo_path: Path) -> list[SubRepoPayload] | None:
126
126
  ]
127
127
 
128
128
 
129
+ def get_project_subrepos(repo_path: str | Path) -> list[SubRepoPayload]:
130
+ """Public multi.json sub-repo listing for library consumers ([] when not a workspace)."""
131
+ return _subrepo_payloads(Path(repo_path)) or []
132
+
133
+
134
+ def list_project_repo_names(repo_path: str | Path) -> list[str]:
135
+ """Names of repos declared in repo_path's own multi.json ([] when absent/invalid).
136
+
137
+ Only reads a multi.json directly inside repo_path — never searches parent
138
+ directories — so callers can safely probe arbitrary directories.
139
+ """
140
+ subrepos = _load_subrepos(Path(repo_path))
141
+ return [repo.name for repo in subrepos] if subrepos else []
142
+
143
+
129
144
  def _describe_git_head(repo_path: Path) -> str | None:
130
145
  if not repo_path.exists():
131
146
  return None
@@ -212,7 +227,13 @@ def _get_git_status_internal(repo_path: Path, ignore_untracked: bool) -> RepoSta
212
227
  if ignore_untracked:
213
228
  status_args.append("--untracked-files=no")
214
229
 
215
- porcelain = _run_git(status_args, cwd=repo_path, timeout=10).stdout
230
+ try:
231
+ porcelain = _run_git(status_args, cwd=repo_path, timeout=10).stdout
232
+ except (OSError, subprocess.SubprocessError):
233
+ # A directory with a .git entry that git itself rejects (corrupt or
234
+ # locked repo) is neither clean nor dirty; report it as unknown
235
+ # instead of surfacing an exception to status callers.
236
+ return "unknown"
216
237
  if porcelain.strip():
217
238
  return "dirty"
218
239
 
@@ -11,7 +11,6 @@ from multi.cli_helpers import common_command_wrapper
11
11
  from multi.errors import GitError
12
12
  from multi.git_helpers import (
13
13
  check_all_on_same_branch,
14
- check_all_repos_are_clean,
15
14
  check_branch_existence,
16
15
  expected_branch_for_repo,
17
16
  repo_uses_fixed_branch,
@@ -93,14 +92,23 @@ def _resolve_relative_path(entry: str) -> Path:
93
92
  return relative_path
94
93
 
95
94
 
95
+ def _worktrees_dir(root_dir: Path) -> Path:
96
+ """Return the sibling directory that holds all worktrees for a workspace.
97
+
98
+ For a workspace at ``<parent>/my-workspace``, worktrees live under
99
+ ``<parent>/my-workspace-worktrees/``.
100
+ """
101
+ return root_dir.parent / f"{root_dir.name}-worktrees"
102
+
103
+
96
104
  def _destination_for_name(root_dir: Path, name: str) -> Path:
97
105
  name_path = Path(name)
98
106
  if name_path.is_absolute() or len(name_path.parts) != 1 or name in {"", ".", ".."}:
99
107
  raise GitError(
100
- "Worktree NAME must be a single sibling directory name. "
108
+ "Worktree NAME must be a single directory name. "
101
109
  "Use --branch for branch names that contain path separators."
102
110
  )
103
- return root_dir.parent / name
111
+ return _worktrees_dir(root_dir) / name
104
112
 
105
113
 
106
114
  def _repo_for_path(paths: Paths, relative_path: Path) -> tuple[Path, Path]:
@@ -197,7 +205,9 @@ def _checkout_subrepos(
197
205
  try:
198
206
  _run_git(repo.path, ["fetch", str(source_repo.path), base_ref])
199
207
  _run_git(repo.path, ["checkout", "-b", repo_branch_name, "FETCH_HEAD"])
200
- logger.info(f"✅ Switched to branch '{repo_branch_name}' in {repo.path}")
208
+ logger.info(
209
+ f"✅ Switched to branch '{repo_branch_name}' in {repo.path}"
210
+ )
201
211
  continue
202
212
  except GitError as exc:
203
213
  logger.warning(
@@ -234,7 +244,8 @@ def add_worktree(
234
244
  if destination.exists():
235
245
  raise GitError(f"Worktree destination already exists: {destination}")
236
246
 
237
- check_all_repos_are_clean(paths=paths, raise_error=True)
247
+ # No clean-working-tree requirement: worktree creation branches from a
248
+ # commit (HEAD or --base-ref) and never mutates the source working trees.
238
249
  all_on_same_branch = check_all_on_same_branch(paths=paths, raise_error=False)
239
250
  if not all_on_same_branch:
240
251
  logger.warning(
@@ -242,8 +253,12 @@ def add_worktree(
242
253
  "already exist for all repos, this command will fix the situation."
243
254
  )
244
255
 
256
+ destination.parent.mkdir(parents=True, exist_ok=True)
245
257
  _create_root_worktree(paths.root_dir, destination, branch_name, base_ref)
246
- sync(root_dir=destination, install_set=install_set)
258
+ # Skip branch matching during the initial sync: the worktree branch is
259
+ # created in each sub-repo right after, so warning about it being missing
260
+ # here would only be confusing.
261
+ sync(root_dir=destination, ensure_on_same_branch=False, install_set=install_set)
247
262
 
248
263
  destination_paths = Paths(destination, install_set=install_set)
249
264
  _checkout_subrepos(
@@ -259,7 +274,11 @@ def add_worktree(
259
274
 
260
275
  @click.group(name="worktree")
261
276
  def worktree_cmd() -> None:
262
- """Manage sibling git worktrees for a multi workspace."""
277
+ """Manage git worktrees for a multi workspace.
278
+
279
+ Worktrees live in a sibling directory named after the workspace with
280
+ '-worktrees' appended (e.g. my-workspace-worktrees/).
281
+ """
263
282
 
264
283
 
265
284
  @click.command(name="add")
@@ -288,9 +307,12 @@ def worktree_add_cmd(
288
307
  install_set: str | None = None,
289
308
  base_ref: str = "HEAD",
290
309
  ) -> None:
291
- """Create a sibling worktree.
310
+ """Create an isolated worktree of the whole workspace.
311
+
312
+ The worktree is created at <parent>/<workspace-dirname>-worktrees/NAME
313
+ (the -worktrees directory is created if missing).
292
314
 
293
- NAME: Directory name for the sibling worktree. Used as the branch name when
315
+ NAME: Directory name for the worktree. Used as the branch name when
294
316
  --branch is omitted.
295
317
  """
296
318
  add_worktree(