multi-workspace 3.0.5__tar.gz → 3.2.2__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 (59) hide show
  1. {multi_workspace-3.0.5 → multi_workspace-3.2.2}/.gitignore +8 -1
  2. multi_workspace-3.2.2/PKG-INFO +121 -0
  3. multi_workspace-3.2.2/README.md +94 -0
  4. multi_workspace-3.2.2/multi/_version.py +24 -0
  5. multi_workspace-3.2.2/multi/add.py +116 -0
  6. multi_workspace-3.2.2/multi/api.py +17 -0
  7. multi_workspace-3.2.2/multi/app_api.py +561 -0
  8. multi_workspace-3.2.2/multi/bootstrap.py +63 -0
  9. {multi_workspace-3.0.5 → multi_workspace-3.2.2}/multi/cli.py +16 -0
  10. {multi_workspace-3.0.5 → multi_workspace-3.2.2}/multi/cli_helpers.py +34 -8
  11. multi_workspace-3.2.2/multi/collaborator.py +369 -0
  12. multi_workspace-3.2.2/multi/convert_monorepo.py +75 -0
  13. multi_workspace-3.2.2/multi/doctor.py +374 -0
  14. multi_workspace-3.2.2/multi/errors.py +10 -0
  15. {multi_workspace-3.0.5 → multi_workspace-3.2.2}/multi/git_helpers.py +27 -9
  16. {multi_workspace-3.0.5 → multi_workspace-3.2.2}/multi/git_run.py +10 -4
  17. {multi_workspace-3.0.5 → multi_workspace-3.2.2}/multi/git_set_branch.py +8 -1
  18. multi_workspace-3.2.2/multi/ignore_files.py +165 -0
  19. multi_workspace-3.2.2/multi/init.py +328 -0
  20. multi_workspace-3.2.2/multi/managed_blocks.py +102 -0
  21. multi_workspace-3.2.2/multi/open.py +12 -0
  22. {multi_workspace-3.0.5 → multi_workspace-3.2.2}/multi/paths.py +18 -5
  23. multi_workspace-3.2.2/multi/registry.py +124 -0
  24. multi_workspace-3.2.2/multi/remove.py +92 -0
  25. multi_workspace-3.2.2/multi/repo_urls.py +63 -0
  26. multi_workspace-3.2.2/multi/repos.py +140 -0
  27. multi_workspace-3.2.2/multi/resources/init_readme.md +11 -0
  28. multi_workspace-3.2.2/multi/service.py +115 -0
  29. {multi_workspace-3.0.5 → multi_workspace-3.2.2}/multi/settings.py +11 -0
  30. multi_workspace-3.2.2/multi/sync.py +225 -0
  31. multi_workspace-3.2.2/multi/sync_agents.py +147 -0
  32. multi_workspace-3.2.2/multi/sync_github.py +211 -0
  33. {multi_workspace-3.0.5 → multi_workspace-3.2.2}/multi/sync_vscode.py +14 -7
  34. multi_workspace-3.2.2/multi/sync_vscode_devcontainer.py +71 -0
  35. {multi_workspace-3.0.5 → multi_workspace-3.2.2}/multi/sync_vscode_extensions.py +4 -3
  36. {multi_workspace-3.0.5 → multi_workspace-3.2.2}/multi/sync_vscode_helpers.py +80 -8
  37. {multi_workspace-3.0.5 → multi_workspace-3.2.2}/multi/sync_vscode_launch.py +58 -11
  38. multi_workspace-3.2.2/multi/sync_vscode_settings.py +159 -0
  39. {multi_workspace-3.0.5 → multi_workspace-3.2.2}/multi/sync_vscode_tasks.py +38 -4
  40. {multi_workspace-3.0.5 → multi_workspace-3.2.2}/multi/utils.py +128 -38
  41. multi_workspace-3.2.2/multi/worktree.py +235 -0
  42. {multi_workspace-3.0.5 → multi_workspace-3.2.2}/pyproject.toml +5 -4
  43. multi_workspace-3.0.5/PKG-INFO +0 -57
  44. multi_workspace-3.0.5/README.md +0 -32
  45. multi_workspace-3.0.5/multi/_version.py +0 -34
  46. multi_workspace-3.0.5/multi/errors.py +0 -22
  47. multi_workspace-3.0.5/multi/ignore_files.py +0 -104
  48. multi_workspace-3.0.5/multi/init.py +0 -193
  49. multi_workspace-3.0.5/multi/repos.py +0 -92
  50. multi_workspace-3.0.5/multi/resources/init_readme.md +0 -11
  51. multi_workspace-3.0.5/multi/rules.py +0 -100
  52. multi_workspace-3.0.5/multi/sync.py +0 -89
  53. multi_workspace-3.0.5/multi/sync_claude.py +0 -88
  54. multi_workspace-3.0.5/multi/sync_ruff.py +0 -85
  55. multi_workspace-3.0.5/multi/sync_vscode_settings.py +0 -100
  56. {multi_workspace-3.0.5 → multi_workspace-3.2.2}/LICENSE +0 -0
  57. {multi_workspace-3.0.5 → multi_workspace-3.2.2}/multi/__init__.py +0 -0
  58. {multi_workspace-3.0.5 → multi_workspace-3.2.2}/multi/__main__.py +0 -0
  59. {multi_workspace-3.0.5 → multi_workspace-3.2.2}/multi/logging.py +0 -0
@@ -1,4 +1,5 @@
1
1
  CLAUDE.md
2
+ AGENTS.md
2
3
  .data/
3
4
 
4
5
  # Byte-compiled / optimized / DLL files
@@ -182,4 +183,10 @@ Thumbs.db
182
183
  staticfiles
183
184
 
184
185
  .env
185
- _version.py
186
+ _version.py
187
+
188
+ # BEGIN multi-managed: generated-files
189
+ # These files are generated by multi.
190
+ CLAUDE.md
191
+ AGENTS.md
192
+ # END multi-managed: generated-files
@@ -0,0 +1,121 @@
1
+ Metadata-Version: 2.4
2
+ Name: multi-workspace
3
+ Version: 3.2.2
4
+ Summary: Multi
5
+ Project-URL: Repository, https://github.com/gabemontague/multi
6
+ Project-URL: Issues, https://github.com/gabemontague/multi/issues
7
+ Author-email: Gabe Montague <gabemontague@outlook.com>
8
+ License-File: LICENSE
9
+ Classifier: Environment :: Console
10
+ Classifier: Intended Audience :: Developers
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Software Development :: Version Control :: Git
15
+ Requires-Python: >=3.12
16
+ Requires-Dist: click>=8.3.1
17
+ Requires-Dist: gitpython>=3.1.0
18
+ Requires-Dist: pyyaml>=6.0.2
19
+ Provides-Extra: dev
20
+ Requires-Dist: pyinstaller>=6.9.0; extra == 'dev'
21
+ Requires-Dist: pytest-cov>=6.1.1; extra == 'dev'
22
+ Requires-Dist: pytest-mock>=3.14.0; extra == 'dev'
23
+ Requires-Dist: pytest>=8.3.5; extra == 'dev'
24
+ Requires-Dist: ruff>=0.11.10; extra == 'dev'
25
+ Requires-Dist: zensical; extra == 'dev'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # multi
29
+
30
+ `multi` is a better way to work with VS Code/Cursor on multiple Git repos at once. It is an alternative to [multi-root workspaces](https://code.visualstudio.com/docs/editing/workspaces/multi-root-workspaces) that offers more flexibility and control. With `multi`, you can gain control over how tasks, debug runnables, and various IDE and linter settings are combined from multiple project repos ("sub-repos") located within a root workspace folder.
31
+
32
+ **[Documentation](https://multi.bighelp.ai/)**
33
+
34
+ Features:
35
+
36
+ - Generates files in your root `.vscode` folder from sub-repo `launch.json`, `tasks.json`, and `settings.json` files.
37
+ - Supports optional `installSets` so installer scripts can sync only a public/runtime subset of repos.
38
+ - Optionally generates `CLAUDE.md` and `AGENTS.md` files from tracked `AGENTS.parts/*.md` files.
39
+ - In monorepo mode, syncs sub-repo GitHub workflows into root `.github/workflows`.
40
+
41
+ ## Installation
42
+
43
+ ### Using `pipx`:
44
+
45
+ - Install [pipx](https://github.com/pypa/pipx)
46
+ - Run `pipx install multi-workspace`
47
+
48
+ ### Using `uv`
49
+
50
+ - Install [uv](https://docs.astral.sh/uv/getting-started/installation/)
51
+ - Run `uv tool install multi-workspace`
52
+
53
+ ## Getting started
54
+
55
+ To get started, create a new workspace directory that will house all your related repos and run:
56
+
57
+ ```
58
+ multi init
59
+ ```
60
+
61
+ When prompted, paste in the URLs of all the repositories you want to have in your workspace. You can optionally specify descriptions of what they do, which can be included in generated agent instruction files when that feature is enabled.
62
+
63
+ For scripts, you can also initialize non-interactively:
64
+
65
+ ```bash
66
+ multi init \
67
+ --repo https://github.com/org/backend \
68
+ --repo-description "Backend API" \
69
+ --repo https://github.com/org/frontend \
70
+ --repo-description "Frontend app"
71
+ ```
72
+
73
+ To create GitHub repositories first, add `--github-repo` entries. This uses `gh repo create` and defaults to private visibility:
74
+
75
+ ```bash
76
+ multi init \
77
+ --github-repo org/backend \
78
+ --github-description "Backend API" \
79
+ --github-repo org/frontend \
80
+ --github-description "Frontend app"
81
+ ```
82
+
83
+ For `--github-repo`, the GitHub CLI (`gh`) must already be installed and authenticated.
84
+
85
+ When repository slugs are product-prefixed, `multi init` automatically writes short local directory names into `multi.json` when the slug matches the workspace name prefix. For example, in a `t-ide/` workspace, `t-ide-cli` becomes local folder `cli`.
86
+
87
+ The same naming rule applies to `multi add`, so adding `https://github.com/org/t-ide-cli` inside a `t-ide/` workspace will also default to local folder `cli`.
88
+
89
+ For public installer workflows, add `installSets` to repo entries and run a filtered sync:
90
+
91
+ ```json
92
+ {
93
+ "repos": [
94
+ { "name": "cli", "url": "https://github.com/org/product-cli", "installSets": ["default", "dev"] },
95
+ { "name": "ios", "url": "git@github.com:org/product-ios.git", "installSets": ["dev"] }
96
+ ]
97
+ }
98
+ ```
99
+
100
+ ```bash
101
+ multi sync --install-set default
102
+ ```
103
+
104
+ Running `multi sync` without `--install-set` still includes every repo. Repos without `installSets` are included in every named set.
105
+
106
+ To grant or remove GitHub collaborator access across every GitHub repo in a workspace, use:
107
+
108
+ ```bash
109
+ multi collaborator add octocat --permission maintain --yes
110
+ multi collaborator add --yes
111
+ multi collaborator remove octocat --yes
112
+ multi collaborator recent-users
113
+ ```
114
+
115
+ When the workspace root repository has a GitHub `origin`, `multi collaborator` applies the change there too.
116
+
117
+ Recent collaborator usernames are saved under `~/.multi`, and `multi collaborator add` can prompt from that list when no username is supplied.
118
+
119
+ If one repo fails, `multi collaborator` continues through the rest of the workspace and reports the failures at the end.
120
+
121
+ It is recommended you also install the [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=montaguegabe.multi-workspace) that automatically keeps your project synced when edits are made to synced files. To manually sync, you can run `multi sync`.
@@ -0,0 +1,94 @@
1
+ # multi
2
+
3
+ `multi` is a better way to work with VS Code/Cursor on multiple Git repos at once. It is an alternative to [multi-root workspaces](https://code.visualstudio.com/docs/editing/workspaces/multi-root-workspaces) that offers more flexibility and control. With `multi`, you can gain control over how tasks, debug runnables, and various IDE and linter settings are combined from multiple project repos ("sub-repos") located within a root workspace folder.
4
+
5
+ **[Documentation](https://multi.bighelp.ai/)**
6
+
7
+ Features:
8
+
9
+ - Generates files in your root `.vscode` folder from sub-repo `launch.json`, `tasks.json`, and `settings.json` files.
10
+ - Supports optional `installSets` so installer scripts can sync only a public/runtime subset of repos.
11
+ - Optionally generates `CLAUDE.md` and `AGENTS.md` files from tracked `AGENTS.parts/*.md` files.
12
+ - In monorepo mode, syncs sub-repo GitHub workflows into root `.github/workflows`.
13
+
14
+ ## Installation
15
+
16
+ ### Using `pipx`:
17
+
18
+ - Install [pipx](https://github.com/pypa/pipx)
19
+ - Run `pipx install multi-workspace`
20
+
21
+ ### Using `uv`
22
+
23
+ - Install [uv](https://docs.astral.sh/uv/getting-started/installation/)
24
+ - Run `uv tool install multi-workspace`
25
+
26
+ ## Getting started
27
+
28
+ To get started, create a new workspace directory that will house all your related repos and run:
29
+
30
+ ```
31
+ multi init
32
+ ```
33
+
34
+ When prompted, paste in the URLs of all the repositories you want to have in your workspace. You can optionally specify descriptions of what they do, which can be included in generated agent instruction files when that feature is enabled.
35
+
36
+ For scripts, you can also initialize non-interactively:
37
+
38
+ ```bash
39
+ multi init \
40
+ --repo https://github.com/org/backend \
41
+ --repo-description "Backend API" \
42
+ --repo https://github.com/org/frontend \
43
+ --repo-description "Frontend app"
44
+ ```
45
+
46
+ To create GitHub repositories first, add `--github-repo` entries. This uses `gh repo create` and defaults to private visibility:
47
+
48
+ ```bash
49
+ multi init \
50
+ --github-repo org/backend \
51
+ --github-description "Backend API" \
52
+ --github-repo org/frontend \
53
+ --github-description "Frontend app"
54
+ ```
55
+
56
+ For `--github-repo`, the GitHub CLI (`gh`) must already be installed and authenticated.
57
+
58
+ When repository slugs are product-prefixed, `multi init` automatically writes short local directory names into `multi.json` when the slug matches the workspace name prefix. For example, in a `t-ide/` workspace, `t-ide-cli` becomes local folder `cli`.
59
+
60
+ The same naming rule applies to `multi add`, so adding `https://github.com/org/t-ide-cli` inside a `t-ide/` workspace will also default to local folder `cli`.
61
+
62
+ For public installer workflows, add `installSets` to repo entries and run a filtered sync:
63
+
64
+ ```json
65
+ {
66
+ "repos": [
67
+ { "name": "cli", "url": "https://github.com/org/product-cli", "installSets": ["default", "dev"] },
68
+ { "name": "ios", "url": "git@github.com:org/product-ios.git", "installSets": ["dev"] }
69
+ ]
70
+ }
71
+ ```
72
+
73
+ ```bash
74
+ multi sync --install-set default
75
+ ```
76
+
77
+ Running `multi sync` without `--install-set` still includes every repo. Repos without `installSets` are included in every named set.
78
+
79
+ To grant or remove GitHub collaborator access across every GitHub repo in a workspace, use:
80
+
81
+ ```bash
82
+ multi collaborator add octocat --permission maintain --yes
83
+ multi collaborator add --yes
84
+ multi collaborator remove octocat --yes
85
+ multi collaborator recent-users
86
+ ```
87
+
88
+ When the workspace root repository has a GitHub `origin`, `multi collaborator` applies the change there too.
89
+
90
+ Recent collaborator usernames are saved under `~/.multi`, and `multi collaborator add` can prompt from that list when no username is supplied.
91
+
92
+ If one repo fails, `multi collaborator` continues through the rest of the workspace and reports the failures at the end.
93
+
94
+ It is recommended you also install the [VS Code Extension](https://marketplace.visualstudio.com/items?itemName=montaguegabe.multi-workspace) that automatically keeps your project synced when edits are made to synced files. To manually sync, you can run `multi sync`.
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '3.2.2'
22
+ __version_tuple__ = version_tuple = (3, 2, 2)
23
+
24
+ __commit_id__ = commit_id = None
@@ -0,0 +1,116 @@
1
+ import json
2
+ import logging
3
+ import shutil
4
+ from pathlib import Path
5
+
6
+ import click
7
+ import git
8
+
9
+ from multi.paths import Paths
10
+ from multi.repo_urls import (
11
+ derive_explicit_local_name,
12
+ derive_repo_slug_from_url,
13
+ normalize_repo_url,
14
+ )
15
+ from multi.sync import sync
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ @click.command(name="add")
21
+ @click.argument("repo_url")
22
+ @click.option("--name", default=None, help="Override directory name derived from URL")
23
+ def add_cmd(repo_url: str, name: str | None):
24
+ """Add a repository to the workspace.
25
+
26
+ Appends the repo to multi.json and runs sync to clone it
27
+ and merge all configurations. In monorepo mode, the repo's
28
+ .git directory is removed after cloning so it becomes part
29
+ of the root repository.
30
+ """
31
+ paths = Paths(Path.cwd())
32
+ is_monorepo = paths.settings.is_monorepo()
33
+
34
+ # Read multi.json as raw JSON
35
+ with paths.multi_json_path.open("r") as f:
36
+ config = json.load(f)
37
+
38
+ repos_list = config.get("repos", [])
39
+
40
+ # Check for duplicate URL
41
+ normalized_new = normalize_repo_url(repo_url)
42
+ for entry in repos_list:
43
+ existing_url = entry.get("url", "")
44
+ if normalize_repo_url(existing_url) == normalized_new:
45
+ raise click.ClickException(
46
+ f"A repo with URL '{existing_url}' already exists in multi.json."
47
+ )
48
+
49
+ # Derive or validate name
50
+ auto_name = derive_explicit_local_name(
51
+ repo_url,
52
+ paths.root_dir.name,
53
+ )
54
+ slug_name = derive_repo_slug_from_url(repo_url)
55
+
56
+ existing_names = {
57
+ entry.get("name") or derive_repo_slug_from_url(entry.get("url", ""))
58
+ for entry in repos_list
59
+ }
60
+
61
+ if (
62
+ not name
63
+ and auto_name
64
+ and (
65
+ auto_name in existing_names
66
+ or (paths.root_dir / auto_name).exists()
67
+ )
68
+ ):
69
+ auto_name = None
70
+
71
+ repo_name = name if name else (auto_name or slug_name)
72
+
73
+ # Check for duplicate name
74
+ if repo_name in existing_names:
75
+ raise click.ClickException(
76
+ f"A repo with name '{repo_name}' already exists in multi.json."
77
+ )
78
+
79
+ # Check target directory doesn't already exist
80
+ target_dir = paths.root_dir / repo_name
81
+ if target_dir.exists():
82
+ raise click.ClickException(f"Directory '{repo_name}' already exists on disk.")
83
+
84
+ # In monorepo mode, clone and remove .git before writing multi.json,
85
+ # since sync() skips clone_repos() in monorepo mode.
86
+ if is_monorepo:
87
+ logger.info(f"Cloning {repo_name}...")
88
+ git.Repo.clone_from(repo_url, target_dir)
89
+ git_dir = target_dir / ".git"
90
+ if git_dir.is_dir():
91
+ shutil.rmtree(git_dir)
92
+ elif git_dir.exists() or git_dir.is_symlink():
93
+ git_dir.unlink()
94
+ logger.info(f"Cloned {repo_name} and removed .git for monorepo mode")
95
+
96
+ # Build new entry
97
+ new_entry: dict = {"url": repo_url}
98
+ if name:
99
+ new_entry["name"] = name
100
+ elif auto_name:
101
+ new_entry["name"] = auto_name
102
+
103
+ repos_list.append(new_entry)
104
+ config["repos"] = repos_list
105
+
106
+ # Write multi.json
107
+ with paths.multi_json_path.open("w") as f:
108
+ json.dump(config, f, indent=2)
109
+ f.write("\n")
110
+
111
+ logger.info(f"Added '{repo_name}' to multi.json")
112
+
113
+ # Run full sync to clone (non-monorepo) and merge configs
114
+ sync(root_dir=paths.root_dir)
115
+
116
+ logger.info(f"✅ Successfully added {repo_name}")
@@ -0,0 +1,17 @@
1
+ from pathlib import Path
2
+
3
+ from multi.sync import sync
4
+
5
+
6
+ def sync_workspace(
7
+ root_dir: str | Path,
8
+ *,
9
+ install_set: str | None = None,
10
+ ensure_on_same_branch: bool = True,
11
+ ) -> None:
12
+ """Sync a Multi workspace from Python code."""
13
+ sync(
14
+ root_dir=Path(root_dir),
15
+ ensure_on_same_branch=ensure_on_same_branch,
16
+ install_set=install_set,
17
+ )