odoo-workspaces 1.1.0__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 (51) hide show
  1. odoo_workspaces-1.1.0/.bare-git-repos/.gitkeep +4 -0
  2. odoo_workspaces-1.1.0/.github/workflows/release.yml +36 -0
  3. odoo_workspaces-1.1.0/.gitignore +24 -0
  4. odoo_workspaces-1.1.0/.vscode/launch.json +65 -0
  5. odoo_workspaces-1.1.0/.vscode/settings.json +14 -0
  6. odoo_workspaces-1.1.0/AGENTS.md +95 -0
  7. odoo_workspaces-1.1.0/LICENSE +21 -0
  8. odoo_workspaces-1.1.0/PKG-INFO +267 -0
  9. odoo_workspaces-1.1.0/README.md +242 -0
  10. odoo_workspaces-1.1.0/RELEASE.md +29 -0
  11. odoo_workspaces-1.1.0/bwrap-claude +176 -0
  12. odoo_workspaces-1.1.0/bwrap-opencode +148 -0
  13. odoo_workspaces-1.1.0/mise.toml +12 -0
  14. odoo_workspaces-1.1.0/odoo_workspaces.egg-info/PKG-INFO +267 -0
  15. odoo_workspaces-1.1.0/odoo_workspaces.egg-info/SOURCES.txt +49 -0
  16. odoo_workspaces-1.1.0/odoo_workspaces.egg-info/dependency_links.txt +1 -0
  17. odoo_workspaces-1.1.0/odoo_workspaces.egg-info/entry_points.txt +2 -0
  18. odoo_workspaces-1.1.0/odoo_workspaces.egg-info/requires.txt +4 -0
  19. odoo_workspaces-1.1.0/odoo_workspaces.egg-info/top_level.txt +1 -0
  20. odoo_workspaces-1.1.0/ow/__init__.py +0 -0
  21. odoo_workspaces-1.1.0/ow/__main__.py +199 -0
  22. odoo_workspaces-1.1.0/ow/_version.py +24 -0
  23. odoo_workspaces-1.1.0/ow/config.py +129 -0
  24. odoo_workspaces-1.1.0/ow/git.py +469 -0
  25. odoo_workspaces-1.1.0/ow/services/compose.yml +31 -0
  26. odoo_workspaces-1.1.0/ow/services/volumes/.gitkeep +4 -0
  27. odoo_workspaces-1.1.0/ow/templates/bwrap/bwrap-claude.j2 +174 -0
  28. odoo_workspaces-1.1.0/ow/templates/bwrap/bwrap-opencode.j2 +146 -0
  29. odoo_workspaces-1.1.0/ow/templates/common/mise.toml.j2 +14 -0
  30. odoo_workspaces-1.1.0/ow/templates/common/odools.toml.j2 +9 -0
  31. odoo_workspaces-1.1.0/ow/templates/common/odoorc.j2 +12 -0
  32. odoo_workspaces-1.1.0/ow/templates/common/pyrightconfig.json.j2 +12 -0
  33. odoo_workspaces-1.1.0/ow/templates/common/requirements-dev.txt +1 -0
  34. odoo_workspaces-1.1.0/ow/templates/vscode/.vscode/launch.json.j2 +37 -0
  35. odoo_workspaces-1.1.0/ow/templates/vscode/.vscode/settings.json.j2 +3 -0
  36. odoo_workspaces-1.1.0/ow/templates/zed/.zed/debug.json.j2 +40 -0
  37. odoo_workspaces-1.1.0/ow/templates/zed/.zed/settings.json.j2 +11 -0
  38. odoo_workspaces-1.1.0/ow/tests/__init__.py +0 -0
  39. odoo_workspaces-1.1.0/ow/tests/conftest.py +100 -0
  40. odoo_workspaces-1.1.0/ow/tests/test_cli.py +403 -0
  41. odoo_workspaces-1.1.0/ow/tests/test_commands.py +638 -0
  42. odoo_workspaces-1.1.0/ow/tests/test_config.py +279 -0
  43. odoo_workspaces-1.1.0/ow/tests/test_git.py +1517 -0
  44. odoo_workspaces-1.1.0/ow/tests/test_resolve_workspace.py +142 -0
  45. odoo_workspaces-1.1.0/ow/tests/test_workspace.py +1130 -0
  46. odoo_workspaces-1.1.0/ow/workspace.py +1588 -0
  47. odoo_workspaces-1.1.0/pyproject.toml +39 -0
  48. odoo_workspaces-1.1.0/scripts/migrate-to-1.0.0.py +169 -0
  49. odoo_workspaces-1.1.0/scripts/tests/__init__.py +0 -0
  50. odoo_workspaces-1.1.0/scripts/tests/test_migrate.py +373 -0
  51. odoo_workspaces-1.1.0/setup.cfg +4 -0
@@ -0,0 +1,4 @@
1
+ This is where the Odoo workspaces bare repositories will live.
2
+ This file is here to ensure that the directory exists,
3
+ as git does not track empty directories and more than that:
4
+ this directory is gitignore'd, so it won't be tracked even if it had content.
@@ -0,0 +1,36 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v*'
7
+
8
+ jobs:
9
+ release:
10
+ runs-on: ubuntu-latest
11
+ environment: pypi
12
+ permissions:
13
+ contents: write
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - name: Set up Python
18
+ uses: actions/setup-python@v5
19
+ with:
20
+ python-version: '3.12'
21
+
22
+ - name: Build package
23
+ run: |
24
+ pip install build
25
+ python -m build
26
+
27
+ - name: Publish to PyPI
28
+ uses: pypa/gh-action-pypi-publish@release/v1
29
+ with:
30
+ password: ${{ secrets.PYPI_API_TOKEN }}
31
+
32
+ - name: Create GitHub Release
33
+ uses: softprops/action-gh-release@v1
34
+ with:
35
+ files: dist/*
36
+ generate_release_notes: true
@@ -0,0 +1,24 @@
1
+ .bare-git-repos/
2
+ .claude/
3
+ ow/services/volumes/
4
+ ow/services/compose.local.yml
5
+ workspaces/*
6
+ .ow/
7
+ mise.local.toml
8
+ ow.toml
9
+ .ow.toml.archived-workspaces
10
+ templates/*
11
+ ow/templates/*
12
+ !ow/templates/common
13
+ !ow/templates/common/**
14
+ !ow/templates/vscode
15
+ !ow/templates/vscode/**
16
+ !ow/templates/zed
17
+ !ow/templates/zed/**
18
+ !ow/templates/bwrap
19
+ !ow/templates/bwrap/**
20
+ docs/superpowers/
21
+ *.egg-info
22
+ .pytest_cache
23
+ __pycache__
24
+ ow/_version.py
@@ -0,0 +1,65 @@
1
+ {
2
+ "version": "0.2.0",
3
+ "configurations": [
4
+ {
5
+ "name": "Debug OW: apply",
6
+ "type": "debugpy",
7
+ "request": "launch",
8
+ "module": "ow",
9
+ "args": ["apply"],
10
+ "console": "integratedTerminal",
11
+ "justMyCode": false,
12
+ "cwd": "${workspaceFolder}"
13
+ },
14
+ {
15
+ "name": "Debug OW: status",
16
+ "type": "debugpy",
17
+ "request": "launch",
18
+ "module": "ow",
19
+ "args": ["status"],
20
+ "console": "integratedTerminal",
21
+ "justMyCode": false,
22
+ "cwd": "${workspaceFolder}"
23
+ },
24
+ {
25
+ "name": "Debug OW: rebase",
26
+ "type": "debugpy",
27
+ "request": "launch",
28
+ "module": "ow",
29
+ "args": ["rebase"],
30
+ "console": "integratedTerminal",
31
+ "justMyCode": false,
32
+ "cwd": "${workspaceFolder}"
33
+ },
34
+ {
35
+ "name": "Debug OW: create",
36
+ "type": "debugpy",
37
+ "request": "launch",
38
+ "module": "ow",
39
+ "args": ["create", "test-ws", "community:master", "templates=common"],
40
+ "console": "integratedTerminal",
41
+ "justMyCode": false,
42
+ "cwd": "${workspaceFolder}"
43
+ },
44
+ {
45
+ "name": "Debug OW: remove",
46
+ "type": "debugpy",
47
+ "request": "launch",
48
+ "module": "ow",
49
+ "args": ["remove", "test-ws"],
50
+ "console": "integratedTerminal",
51
+ "justMyCode": false,
52
+ "cwd": "${workspaceFolder}"
53
+ },
54
+ {
55
+ "name": "Debug Tests",
56
+ "type": "debugpy",
57
+ "request": "launch",
58
+ "module": "pytest",
59
+ "args": ["-v", "ow/tests/"],
60
+ "console": "integratedTerminal",
61
+ "justMyCode": false,
62
+ "cwd": "${workspaceFolder}"
63
+ }
64
+ ]
65
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "python.testing.pytestEnabled": true,
3
+ "python.testing.unittestEnabled": false,
4
+ "python.testing.cwd": "${workspaceFolder}",
5
+ "python.testing.autoTestDiscoverOnSaveEnabled": true,
6
+ "files.exclude": {
7
+ "**/__pycache__": true,
8
+ "**/*.pyc": true,
9
+ "**/.pytest_cache": true,
10
+ "**/*.egg-info": true
11
+ },
12
+ "editor.formatOnSave": true,
13
+ "editor.rulers": [88]
14
+ }
@@ -0,0 +1,95 @@
1
+ # ow — Module Structure
2
+
3
+ ```
4
+ ow/
5
+ ├── __init__.py # empty
6
+ ├── __main__.py # CLI entry point (argparse + argcomplete)
7
+ ├── config.py # Config dataclasses, TOML loading/writing
8
+ ├── git.py # All git operations via subprocess
9
+ ├── workspace.py # File generators + command implementations + drift detection
10
+ ├── tests/
11
+ │ ├── __init__.py
12
+ │ ├── test_config.py # parse_branch_spec, load_config, load_workspace_config
13
+ │ ├── test_git.py # git functions (subprocess mocked)
14
+ │ ├── test_workspace.py # file generators, template rendering, cache/drift, DriftResult
15
+ │ └── test_commands.py # cmd_status, cmd_rebase integration tests
16
+ ├── bwrap-opencode # Opencode sandbox wrapper (for developing on ow)
17
+ ├── bwrap-claude # Claude Code sandbox wrapper (for developing on ow)
18
+ ├── scripts/
19
+ │ └── migrate-to-1.0.0.py # One-time migration script for v1.0.0
20
+ └── templates/
21
+ ├── bwrap/ # Sandbox scripts template for workspaces
22
+ │ ├── bwrap-opencode.j2
23
+ │ └── bwrap-claude.j2
24
+ ```
25
+
26
+ `ow/__main__.py` is the CLI entry point. `init` is handled before the config-loading block (it doesn't require an existing `ow.toml`) and returns early; all other commands go through `find_root()` + config loading.
27
+
28
+ ## Key abstractions
29
+
30
+ - **`BranchSpec`** — represents `"master"` / `"master..feature"` / `"dev/master-phoenix..fix"`. Knows remote, branch, local_branch, detached vs attached.
31
+ - **`Config`** — parsed from `ow.toml`. `Config.vars` holds global template variable defaults. Merged at render time: `{**config.vars, **ws.vars}`.
32
+ - **`WorkspaceConfig`** — parsed from `.ow/config` inside each workspace. `WorkspaceConfig.vars` holds per-workspace overrides.
33
+ - **`ow/git.py`** — stateless git helpers. All bare-repo operations run with `git -C <bare_repo>`, worktree operations with `git -C <worktree>`. `get_remote_ref_for_branch` scans ow.toml-configured remotes for a pushed local branch (non-base remotes checked first). `get_remote_url` falls back to `git remote get-url` for remotes not in ow.toml.
34
+ - `run_cmd(args, quiet=False)` wraps `subprocess.run`, printing `$ cmd` to stderr unless `quiet=True`. Action functions (clone, fetch, worktree add/remove, switch, rebase) use `run_cmd`; probes (rev-parse, symbolic-ref, rev-list) use `subprocess.run` directly.
35
+ - `git(repo, *args)` — central wrapper that adds `-C` automatically. Use this for all git commands.
36
+ - `git_fetch(repo, remote, refspec, *, force=False)` — fetch with optional force (+refspec).
37
+ - `git_switch(worktree, ref, *, detach=False, create=False)` — unified switch with detach/create options.
38
+ - `git_rebase(worktree, onto)` — rebase onto ref, returns CompletedProcess for caller to check.
39
+ - `git_merge_base_fork_point(worktree, upstream, branch)` — find fork-point between branch and upstream; returns None if upstream was rewritten.
40
+ - `git_rev_list(repo, commit_range, *, reverse=False)` — return list of commit hashes in range.
41
+ - `git_log_oneline(repo, commit)` — return one-line log for a commit: 'hash message'.
42
+ - `git_cherry_pick(worktree, commit)` — cherry-pick a commit, returns CompletedProcess.
43
+ - `git_reset_hard(worktree, ref)` — reset worktree to ref with --hard.
44
+ - `ordered_remotes(alias_remotes)` returns remote names with `origin` first, then alphabetical. Used in `resolve_spec`, `resolve_spec_local`, `get_remote_ref_for_branch`, `ensure_bare_repo`.
45
+ - `get_worktree_branch(worktree_path)` returns the current branch name or `None` if detached (uses `rev-parse --abbrev-ref HEAD`).
46
+ - **`ow/workspace.py`** — file generators + command functions (`cmd_*`). Commands call git helpers then render Jinja2 templates. Also owns cache/drift helpers and worktree drift detection (see below).
47
+ - `_copy_packaged_templates(dest)` — copies all bundled template directories from the installed package to `dest`.
48
+ - `_copy_ow_services(dest)` — copies the bundled `services/` files from the installed package to `dest`.
49
+ - `resolve_workspace(path, config)` — resolves a workspace from explicit path, `OW_WORKSPACE` env var, or cwd walk-up for `.ow/config`.
50
+ - `DriftResult` / `check_drift` / `warn_if_drifted` — detect when worktree branch state doesn't match config. `cmd_status`, `cmd_rebase`, `cmd_update` call `warn_if_drifted` to display warnings but proceed anyway.
51
+ - `RebasePlan` / `_analyze_repo_for_rebase` — analyze the rebase situation for a single repo: track ref, upstream, fork_point, commits_to_reapply, local commits, unpushed commits, whether upstream was rewritten.
52
+ - `_recover_with_cherry_pick(worktree, upstream, commits)` — reset hard to upstream and cherry-pick commits sequentially. Returns None on success, or the failing commit hash on conflict.
53
+ - `cmd_rebase` handles four cases: (1) detached worktree → switch to track ref, (2) upstream rewritten with fork-point → recovery via reset + cherry-pick, (3) upstream rewritten without fork-point → skip with manual recovery instructions, (4) normal rebase → two-step rebase onto upstream then track ref. Displays summary and asks for confirmation before proceeding.
54
+
55
+ ## Commands
56
+
57
+ | Command | Signature | Purpose |
58
+ |---------|-----------|---------|
59
+ | `ow init` | `cmd_init(path, *, force, with_backup)` | Initialize new project directory (no `ow.toml` required) |
60
+ | `ow create` | `cmd_create(config, ...)` | Interactive form → create workspace + `.ow/config` |
61
+ | `ow update` | `cmd_update(config)` | Re-render templates + materialize worktrees |
62
+ | `ow status` | `cmd_status(config)` | Show workspace branch status |
63
+ | `ow rebase` | `cmd_rebase(config)` | Fetch + rebase workspace branches |
64
+ | `ow prune` | `cmd_prune(config)` | Clean up stale worktree references from bare repos |
65
+
66
+ All commands resolve the current workspace via `OW_WORKSPACE` env var or cwd walk-up for `.ow/config`.
67
+
68
+ ## Template system
69
+
70
+ Workspace files are generated from `templates/` at the project root. Each subdirectory is a template bundle. Workspaces declare templates via the `templates` field (required array). Templates are applied in order — later templates can override files from earlier ones.
71
+
72
+ Bundled templates (git-tracked):
73
+ - `templates/common/` — core files: mise.toml, odoorc, odools.toml, pyrightconfig.json, requirements-dev.txt
74
+ - `templates/vscode/.vscode/` — VSCode settings and debug config
75
+ - `templates/zed/.zed/` — Zed settings and debug config
76
+ - `templates/bwrap/` — sandbox scripts for AI coding assistants (bwrap-opencode, bwrap-claude)
77
+
78
+ Templates are Jinja2 (`.j2` extension); static files are copied as-is.
79
+
80
+ Template context keys:
81
+ - `ws_name` — workspace name
82
+ - `vars` — merged dict of `config.vars` and `ws.vars` (use `{{ vars.key | default(fallback) }}`)
83
+ - `addons_paths` — ordered list of absolute addon paths
84
+ - `odools_path_items` — relative paths for odools.toml
85
+ - `repos` — list of repo aliases
86
+ - `main_repo_alias` — alias of the Odoo core repo (has `odoo-bin`), or `None`
87
+
88
+ ## Conventions
89
+
90
+ - Bare repos live in `.bare-git-repos/<alias>.git`.
91
+ - Workspace dirs are `workspaces/<name>/`, with subdirs matching repo aliases.
92
+ - `community` is always the Odoo core repo; its addons are at `community/addons` and `community/odoo/addons`. Note that `community` is the default proposed alias for the main `odoo/odoo` repository, but it can be changed. There is `is_odoo_main_repo` that is made to discover which repo is the main.
93
+ - `ow.toml` is user-local (gitignored). Contains only `[vars]` and `[remotes]`. No workspace declarations.
94
+ - Each workspace has its own `.ow/config` file (gitignored) that stores its config: name, templates, repos, vars.
95
+ - `templates/` contains git-tracked template bundles; subdirectories like `common/`, `vscode/`, `zed/` are whitelisted in `.gitignore`.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Bruno BOI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,267 @@
1
+ Metadata-Version: 2.4
2
+ Name: odoo-workspaces
3
+ Version: 1.1.0
4
+ Summary: Odoo workspace manager
5
+ Author-email: Bruno BOI <boi@odoo.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/brboi/ow
8
+ Project-URL: Repository, https://github.com/brboi/ow
9
+ Classifier: Development Status :: 4 - Beta
10
+ Classifier: Environment :: Console
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Software Development :: Version Control :: Git
17
+ Requires-Python: >=3.11
18
+ Description-Content-Type: text/markdown
19
+ License-File: LICENSE
20
+ Requires-Dist: argcomplete
21
+ Requires-Dist: jinja2
22
+ Requires-Dist: questionary
23
+ Requires-Dist: tomli-w
24
+ Dynamic: license-file
25
+
26
+ # ow — Odoo Workspaces
27
+
28
+ CLI tool that turns interactive prompts into ready-to-code Odoo workspaces using git worktrees.
29
+
30
+ ## Overview
31
+
32
+ - **Git Optimized Commands** — Clone Odoo repos in minutes using shared bare repos
33
+ - **Workspace generation** — each workspace is a folder with git worktrees and IDE configs, ready to open in VSCode or Zed
34
+ - **Interactive setup** — `ow create` guides you through name, templates, repos, and variables
35
+ - **Branch spec syntax** — concise `base..feature` notation to control detached vs attached worktrees
36
+ - **Shared bare repos** — all workspaces share the same `.bare-git-repos/`, so fetching once updates refs for all
37
+ - **Jinja2 template system** — generates `mise.toml`, `odoorc`, `odools.toml`, `pyrightconfig.json`, and IDE configs from customizable templates
38
+ - **Per-workspace variables** — global `[vars]` with per-workspace overrides for ports, DB credentials, etc.
39
+ - **Smart rebase** — two-step rebase (upstream then base), with conflict reporting and instructions
40
+ - **Rich status** — behind/ahead counts with color-coded output
41
+ - **Optional services** — Docker Compose stack with PostgreSQL, pgweb, and mailpit for local development
42
+ - **Tab completion** — fish, bash, zsh via `argcomplete`
43
+ - **Full transparency** — git commands that change your trees are printed to your terminal before execution
44
+
45
+ ## Prerequisites
46
+
47
+ - **[mise](https://mise.jdx.dev/)** — manages Python, virtualenvs, and dependencies in generated workspaces
48
+ - **Odoo system dependencies** — see [Odoo source install docs](https://www.odoo.com/documentation/master/administration/on_premise/source.html#dependencies) (includes wkhtmltopdf, PostgreSQL client libs, etc.)
49
+ - **SSH** — configured for access to Odoo repositories
50
+ - **Docker or Podman** (optional) — to run services like postgres, pgweb, mailpit (see `services/`)
51
+
52
+ ## Installation
53
+
54
+ ```sh
55
+ pipx install odoo-workspaces # recommended
56
+ pip install odoo-workspaces # or in an active venv
57
+ ```
58
+
59
+ ## Quick Start
60
+
61
+ ```sh
62
+ ow init # initialize project (ow.toml, templates/, services/)
63
+ # optionally edit ow.toml to add enterprise or dev remotes
64
+ ow create # interactive form: name, templates, repos, vars
65
+ cd workspaces/my_work && mise install
66
+ code workspaces/my_work # open in your IDE and enjoy
67
+ ```
68
+
69
+ ## Commands
70
+
71
+ | Command | Flags | Description |
72
+ |---------|-------|-------------|
73
+ | `ow init` | `--force`, `--force-with-backup` | Initialize a new ow project |
74
+ | `ow create` | `-n/--name`, `-t/--template`, `-r/--repo`, `-c/--configuration` | Create a workspace interactively |
75
+ | `ow update` | `[workspace]` | Re-render templates and materialize worktrees |
76
+ | `ow status` | `[workspace]` | Show branch status with behind/ahead counts |
77
+ | `ow rebase` | `[workspace]` | Fetch and rebase all repos in a workspace |
78
+ | `ow prune` | — | Clean up stale worktree references from bare repos |
79
+
80
+ `ow` walks up from the current directory to find `ow.toml` (the project root). Commands that need a workspace resolve it from the `OW_WORKSPACE` environment variable (set automatically by `mise`), or by walking up for `.ow/config`.
81
+
82
+ ### `ow init`
83
+
84
+ Initializes a new ow project in the current directory:
85
+
86
+ - `ow.toml` — minimal config with the Odoo community remote
87
+ - `workspaces/` — empty directory for future workspaces
88
+ - `templates/` — copy of the bundled templates (customize freely)
89
+ - `mise.toml` — Python + Node tooling for ow itself
90
+ - `services/` — Docker Compose stack (postgres, pgweb, mailpit)
91
+
92
+ Use `--force` to overwrite existing files, or `--force-with-backup` to back them up first (`.bak` suffix).
93
+
94
+ ### `ow create`
95
+
96
+ Interactive form: workspace name → template selection → repo aliases + branch specs → variable defaults. After confirmation:
97
+
98
+ 1. Clones bare repos if needed
99
+ 2. Fetches required refs
100
+ 3. Creates worktrees
101
+ 4. Applies templates in order
102
+ 5. Writes `workspaces/<name>/.ow/config`
103
+ 6. Trusts `mise.toml` and prints a reminder to run `mise install`
104
+
105
+ ### `ow update`
106
+
107
+ Re-renders templates and materializes worktrees for the current workspace. Also creates any missing worktrees and merges new vars from `ow.toml`. Useful after template changes or to regenerate workspace files.
108
+
109
+ ### `ow status`
110
+
111
+ Fetches latest refs and displays branch status with color-coded behind/ahead counts:
112
+
113
+ ```
114
+ [canary]
115
+ branches
116
+ community: dev/master-canary ↓0 ↑0 (origin/master ↓34 ↑0)
117
+ enterprise: dev/master-canary ↓1 ↑1 (origin/master ↓12 ↑0)
118
+ ```
119
+
120
+ ### `ow rebase`
121
+
122
+ Fetches and rebases all repos in a workspace. Before executing, displays a summary of the rebase situation for each repo and asks for confirmation. Handles detached worktrees, force-pushed upstreams (with or without a recoverable fork-point), and normal two-step rebases.
123
+
124
+ ### `ow prune`
125
+
126
+ Cleans up stale worktree references from all bare repos. Run after manually removing a workspace directory:
127
+
128
+ ```sh
129
+ rm -rf workspaces/my-workspace
130
+ ow prune
131
+ ```
132
+
133
+ ## Configuration (`ow.toml`)
134
+
135
+ ### Remotes
136
+
137
+ ```toml
138
+ [remotes]
139
+ community.origin.url = "git@github.com:odoo/odoo.git"
140
+ community.dev.url = "git@github.com:odoo-dev/odoo.git"
141
+ community.dev.pushurl = "git@github.com:odoo-dev/odoo.git"
142
+ community.dev.fetch = "+refs/heads/*:refs/remotes/dev/*"
143
+
144
+ enterprise.origin.url = "git@github.com:odoo/enterprise.git"
145
+ enterprise.dev.url = "git@github.com:odoo-dev/enterprise.git"
146
+ ```
147
+
148
+ Each remote supports `url`, `pushurl` (optional), and `fetch` (optional refspec).
149
+
150
+ ### Variables
151
+
152
+ ```toml
153
+ [vars]
154
+ http_port = 8069
155
+ db_host = "localhost"
156
+ db_port = 5432
157
+ db_user = "odoo"
158
+ db_password = "odoo"
159
+ ```
160
+
161
+ Templates use `{{ vars.key | default(fallback) }}` so undefined variables get safe defaults.
162
+
163
+ ### Branch Spec Syntax
164
+
165
+ | Spec | Worktree mode |
166
+ |------|---------------|
167
+ | `master` | Detached HEAD at `origin/master` |
168
+ | `origin/master` | Detached HEAD at `origin/master` |
169
+ | `dev/master-phoenix` | Detached HEAD at `dev/master-phoenix` |
170
+ | `master..master-feature` | Attached local branch `master-feature` tracking `origin/master` |
171
+ | `dev/master-phoenix..fix` | Attached local branch `fix` tracking `dev/master-phoenix` |
172
+
173
+ Without `..`, the worktree is detached (read-only tracking). With `..`, a local branch is created — this is what you want for feature development.
174
+
175
+ ## Template System
176
+
177
+ Templates live in `templates/` at the project root. Each subdirectory is a bundle that can be applied to workspaces during `ow create`. Bundles are applied in order — later ones override files from earlier ones.
178
+
179
+ | Bundle | Contents |
180
+ |--------|----------|
181
+ | `common/` | `mise.toml`, `odoorc`, `odools.toml`, `pyrightconfig.json`, `requirements-dev.txt` |
182
+ | `vscode/.vscode/` | `settings.json`, `launch.json` |
183
+ | `zed/.zed/` | `settings.json`, `debug.json` |
184
+ | `bwrap/` | Sandbox scripts for AI coding assistants |
185
+
186
+ Templates are Jinja2 (`.j2` extension); static files are copied as-is. `ow init` seeds `templates/` with the bundled defaults so you can customize them.
187
+
188
+ To create a custom bundle:
189
+
190
+ ```sh
191
+ mkdir -p templates/my-setup
192
+ cp templates/common/odoorc.j2 templates/my-setup/
193
+ # edit templates/my-setup/odoorc.j2
194
+ ```
195
+
196
+ Then select it during `ow create` or add it to an existing workspace's `.ow/config`.
197
+
198
+ ## Services
199
+
200
+ Optional containerized services for local development:
201
+
202
+ ```sh
203
+ docker compose -f services/compose.yml up -d
204
+ ```
205
+
206
+ | Service | Port | Description |
207
+ |---------|------|-------------|
208
+ | postgres | 5432 | PostgreSQL 17 with pgvector |
209
+ | pgweb | 8081 | Web-based PostgreSQL browser |
210
+ | mailpit | 8025 / 1025 | Email testing (web UI / SMTP) |
211
+
212
+ Configure your workspaces to use them via `[vars]` in `ow.toml`:
213
+
214
+ ```toml
215
+ [vars]
216
+ db_host = "localhost"
217
+ db_port = 5432
218
+ smtp_server = "localhost"
219
+ smtp_port = 1025
220
+ ```
221
+
222
+ ## Tab Completion
223
+
224
+ Fish (one-time setup):
225
+ ```sh
226
+ register-python-argcomplete --shell fish ow > ~/.config/fish/completions/ow.fish
227
+ ```
228
+
229
+ Bash/Zsh:
230
+ ```sh
231
+ activate-global-python-argcomplete
232
+ ```
233
+
234
+ ## Sandboxing AI Coding Assistants
235
+
236
+ `ow` includes sandbox scripts for running AI coding assistants (Opencode, Claude Code) with filesystem isolation using [bubblewrap](https://github.com/containers/bubblewrap).
237
+
238
+ Install bubblewrap:
239
+
240
+ ```sh
241
+ sudo apt install bubblewrap # Debian/Ubuntu
242
+ sudo dnf install bubblewrap # Fedora
243
+ sudo pacman -S bubblewrap # Arch
244
+ ```
245
+
246
+ Add `bwrap` to your workspace templates during `ow create`. The scripts are automatically added to PATH via `mise`:
247
+
248
+ ```sh
249
+ bwrap-opencode # Launch Opencode sandboxed
250
+ bwrap-claude # Launch Claude Code sandboxed
251
+ bwrap-opencode --add-dir ~/src/my-addon # grant access to an extra directory
252
+ ```
253
+
254
+ To work on `ow` itself, use the scripts at the project root:
255
+
256
+ ```sh
257
+ ./bwrap-opencode # Launch Opencode sandboxed in ow directory
258
+ ./bwrap-claude # Launch Claude Code sandboxed in ow directory
259
+ ```
260
+
261
+ ## Disclaimer
262
+
263
+ This is a small personal project built with the help of [Claude](https://claude.ai). It scratches a very specific itch — managing multiple Odoo worktrees side by side — and comes with no warranty. Use at your own risk, and expect rough edges.
264
+
265
+ ## Want to contribute?
266
+
267
+ Contributions are welcome! If something is broken, confusing, or missing — open an issue. If you have a fix or improvement in mind, PRs are appreciated.