odoo-workspaces 2.0.1__tar.gz → 2.2.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 (127) hide show
  1. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/.github/workflows/release.yml +1 -1
  2. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/.gitignore +2 -7
  3. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/.vscode/launch.json +5 -5
  4. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/AGENTS.md +11 -1
  5. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/PKG-INFO +6 -1
  6. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/README.md +2 -0
  7. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/docs/commands.md +80 -0
  8. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/mise.toml +0 -2
  9. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/pyproject.toml +5 -1
  10. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/odoo_workspaces.egg-info/PKG-INFO +6 -1
  11. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/odoo_workspaces.egg-info/SOURCES.txt +23 -4
  12. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/odoo_workspaces.egg-info/requires.txt +4 -0
  13. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/odoo_workspaces.egg-info/scm_file_list.json +23 -4
  14. odoo_workspaces-2.2.0/src/odoo_workspaces.egg-info/scm_version.json +8 -0
  15. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/__main__.py +119 -8
  16. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/_version.py +3 -3
  17. odoo_workspaces-2.2.0/src/ow/askpass.py +41 -0
  18. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/commands/__init__.py +17 -2
  19. odoo_workspaces-2.2.0/src/ow/commands/archive.py +170 -0
  20. odoo_workspaces-2.2.0/src/ow/commands/cd.py +13 -0
  21. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/commands/init.py +9 -3
  22. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/commands/ls.py +21 -24
  23. odoo_workspaces-2.2.0/src/ow/commands/mv.py +100 -0
  24. odoo_workspaces-2.2.0/src/ow/commands/open.py +36 -0
  25. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/commands/prune.py +129 -17
  26. odoo_workspaces-2.2.0/src/ow/commands/pull.py +230 -0
  27. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/commands/rebase.py +13 -15
  28. odoo_workspaces-2.2.0/src/ow/commands/reset.py +274 -0
  29. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/commands/rm.py +31 -29
  30. odoo_workspaces-2.2.0/src/ow/commands/shell_init.py +52 -0
  31. odoo_workspaces-2.2.0/src/ow/utils/askpass.py +180 -0
  32. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/utils/config.py +21 -5
  33. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/utils/display.py +26 -0
  34. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/utils/git.py +30 -1
  35. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/utils/paths.py +12 -0
  36. odoo_workspaces-2.2.0/src/ow/utils/pull_plan.py +104 -0
  37. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/utils/refs.py +46 -17
  38. odoo_workspaces-2.2.0/src/ow/utils/relocate.py +56 -0
  39. odoo_workspaces-2.2.0/src/ow/utils/reset_plan.py +94 -0
  40. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/utils/templates.py +52 -9
  41. odoo_workspaces-2.2.0/src/tests/commands/test_archive.py +254 -0
  42. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/commands/test_init.py +3 -3
  43. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/commands/test_init_extended.py +54 -1
  44. odoo_workspaces-2.2.0/src/tests/commands/test_mv.py +237 -0
  45. odoo_workspaces-2.2.0/src/tests/commands/test_open.py +86 -0
  46. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/commands/test_prune.py +163 -0
  47. odoo_workspaces-2.2.0/src/tests/commands/test_pull.py +294 -0
  48. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/commands/test_rebase.py +62 -0
  49. odoo_workspaces-2.2.0/src/tests/commands/test_reset.py +301 -0
  50. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/commands/test_rm.py +68 -0
  51. odoo_workspaces-2.2.0/src/tests/commands/test_shell_init.py +74 -0
  52. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/test_cli.py +55 -6
  53. odoo_workspaces-2.2.0/src/tests/utils/test_askpass.py +129 -0
  54. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/utils/test_config.py +26 -0
  55. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/utils/test_git_primitives.py +20 -0
  56. odoo_workspaces-2.2.0/src/tests/utils/test_pull_plan.py +135 -0
  57. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/utils/test_refs.py +66 -0
  58. odoo_workspaces-2.2.0/src/tests/utils/test_reset_plan.py +127 -0
  59. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/utils/test_templates.py +120 -0
  60. odoo_workspaces-2.0.1/scripts/migrate-to-1.0.0.py +0 -167
  61. odoo_workspaces-2.0.1/scripts/tests/test_migrate.py +0 -371
  62. odoo_workspaces-2.0.1/src/odoo_workspaces.egg-info/scm_version.json +0 -8
  63. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/.vscode/settings.json +0 -0
  64. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/LICENSE +0 -0
  65. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/RELEASE.md +0 -0
  66. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/docs/configuration.md +0 -0
  67. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/docs/migrating-to-2.0.md +0 -0
  68. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/docs/sandboxing.md +0 -0
  69. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/docs/services.md +0 -0
  70. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/docs/templates.md +0 -0
  71. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0/scripts}/bwrap-claude +0 -0
  72. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0/scripts}/bwrap-opencode +0 -0
  73. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/scripts/migrate-to-2.0.py +0 -0
  74. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/scripts/tests/__init__.py +0 -0
  75. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/scripts/tests/test_migrate_2.py +0 -0
  76. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/setup.cfg +0 -0
  77. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/odoo_workspaces.egg-info/dependency_links.txt +0 -0
  78. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/odoo_workspaces.egg-info/entry_points.txt +0 -0
  79. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/odoo_workspaces.egg-info/top_level.txt +0 -0
  80. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/__init__.py +0 -0
  81. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/_static/services/compose.yml.j2 +0 -0
  82. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/_static/templates/bwrap/bwrap-claude.j2 +0 -0
  83. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/_static/templates/bwrap/bwrap-opencode.j2 +0 -0
  84. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/_static/templates/common/mise.toml.j2 +0 -0
  85. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/_static/templates/common/odools.toml.j2 +0 -0
  86. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/_static/templates/common/odoorc.j2 +0 -0
  87. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/_static/templates/common/pyrightconfig.json.j2 +0 -0
  88. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/_static/templates/common/requirements-dev.txt +0 -0
  89. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/_static/templates/vscode/.vscode/launch.json.j2 +0 -0
  90. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/_static/templates/vscode/.vscode/settings.json.j2 +0 -0
  91. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/_static/templates/zed/.zed/debug.json.j2 +0 -0
  92. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/_static/templates/zed/.zed/settings.json.j2 +0 -0
  93. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/commands/apply.py +0 -0
  94. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/commands/status.py +0 -0
  95. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/commands/templates.py +0 -0
  96. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/utils/drift.py +0 -0
  97. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/utils/index.py +0 -0
  98. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/utils/legacy.py +0 -0
  99. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/utils/rebase_plan.py +0 -0
  100. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/ow/utils/resolver.py +0 -0
  101. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/__init__.py +0 -0
  102. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/commands/__init__.py +0 -0
  103. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/commands/test_apply.py +0 -0
  104. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/commands/test_ls.py +0 -0
  105. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/commands/test_misc_extended.py +0 -0
  106. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/commands/test_rebase_scenarios.py +0 -0
  107. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/commands/test_status.py +0 -0
  108. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/commands/test_status_extended.py +0 -0
  109. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/commands/test_status_extended2.py +0 -0
  110. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/commands/test_templates_cmd.py +0 -0
  111. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/conftest.py +0 -0
  112. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/test_main_extended.py +0 -0
  113. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/utils/__init__.py +0 -0
  114. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/utils/test_display.py +0 -0
  115. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/utils/test_drift.py +0 -0
  116. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/utils/test_git.py +0 -0
  117. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/utils/test_index.py +0 -0
  118. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/utils/test_interrupt.py +0 -0
  119. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/utils/test_legacy.py +0 -0
  120. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/utils/test_misc.py +0 -0
  121. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/utils/test_paths.py +0 -0
  122. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/utils/test_progress.py +0 -0
  123. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/utils/test_rebase_plan.py +0 -0
  124. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/utils/test_resolve_template_files.py +0 -0
  125. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/utils/test_resolver.py +0 -0
  126. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/utils/test_templates_extended.py +0 -0
  127. {odoo_workspaces-2.0.1 → odoo_workspaces-2.2.0}/src/tests/utils/test_templates_extended2.py +0 -0
@@ -30,7 +30,7 @@ jobs:
30
30
  password: ${{ secrets.PYPI_API_TOKEN }}
31
31
 
32
32
  - name: Create GitHub Release
33
- uses: softprops/action-gh-release@v1
33
+ uses: softprops/action-gh-release@v2
34
34
  with:
35
35
  files: dist/*
36
36
  generate_release_notes: true
@@ -1,6 +1,7 @@
1
1
  # Hidden files
2
2
  /.*
3
3
  !.github
4
+ !.vscode
4
5
 
5
6
  # Python
6
7
  *.egg-info
@@ -9,11 +10,5 @@ __pycache__
9
10
  # OW version
10
11
  src/ow/_version.py
11
12
 
12
- # OW working directories
13
- .bare-git-repos/
14
- mise.toml
13
+ # OW local
15
14
  mise.local.toml
16
- ow.toml
17
- services/*
18
- templates/*
19
- workspaces/*
@@ -32,21 +32,21 @@
32
32
  "cwd": "${workspaceFolder}"
33
33
  },
34
34
  {
35
- "name": "Debug OW: create",
35
+ "name": "Debug OW: init",
36
36
  "type": "debugpy",
37
37
  "request": "launch",
38
38
  "module": "ow",
39
- "args": ["create", "test-ws", "community:master", "templates=common"],
39
+ "args": ["init", "test-ws", "-r", "community:master", "-t", "common"],
40
40
  "console": "integratedTerminal",
41
41
  "justMyCode": false,
42
42
  "cwd": "${workspaceFolder}"
43
43
  },
44
44
  {
45
- "name": "Debug OW: remove",
45
+ "name": "Debug OW: rm",
46
46
  "type": "debugpy",
47
47
  "request": "launch",
48
48
  "module": "ow",
49
- "args": ["remove", "test-ws"],
49
+ "args": ["rm", "test-ws", "-y"],
50
50
  "console": "integratedTerminal",
51
51
  "justMyCode": false,
52
52
  "cwd": "${workspaceFolder}"
@@ -56,7 +56,7 @@
56
56
  "type": "debugpy",
57
57
  "request": "launch",
58
58
  "module": "pytest",
59
- "args": ["-v", "ow/tests/"],
59
+ "args": ["-v", "src/tests/"],
60
60
  "console": "integratedTerminal",
61
61
  "justMyCode": false,
62
62
  "cwd": "${workspaceFolder}"
@@ -12,6 +12,8 @@ src/
12
12
  │ │ ├── apply.py # cmd_apply — make the tree match .ow/config.toml (--only narrows the git work, never the rendering)
13
13
  │ │ ├── status.py # cmd_status + display helpers
14
14
  │ │ ├── rebase.py # cmd_rebase + fact gathering, display, execution
15
+ │ │ ├── pull.py # cmd_pull + fact gathering, display, execution (fast-forward only)
16
+ │ │ ├── reset.py # cmd_reset + fact gathering, display, execution (git reset, per repo)
15
17
  │ │ ├── prune.py # cmd_prune
16
18
  │ │ ├── rm.py # cmd_rm — remove a workspace: worktrees, local branches, directory, index entry
17
19
  │ │ └── templates.py # cmd_templates — list/take/diff template files; outdated_templates() used by cmd_apply
@@ -25,6 +27,9 @@ src/
25
27
  │ │ ├── paths.py # ow's locations, resolved from the XDG base directories
26
28
  │ │ ├── refs.py # fetch_workspace_refs
27
29
  │ │ ├── rebase_plan.py # RepoFacts, GitStep, RebasePlan, plan_for (pure)
30
+ │ │ ├── pull_plan.py # PullFacts, PullPlan, plan_pull (pure)
31
+ │ │ ├── reset_plan.py # ResetFacts, ResetPlan, plan_reset (pure)
32
+ │ │ ├── askpass.py # SSH_ASKPASS broker: git children ask ow, ow asks the terminal
28
33
  │ │ ├── resolver.py # resolve_workspace
29
34
  │ │ └── templates.py # file generators, template resolution, application, materialization
30
35
  │ └── _static/
@@ -66,9 +71,12 @@ AGENTS.md
66
71
  - `index.py` — remembers where workspaces live (for name-based resolution and duplicate-branch checks), without owning the truth — the workspace's own `.ow/config.toml` is that.
67
72
  - `display.py` — `console`, `err_console`, `counts`, `print_git_result`.
68
73
  - `drift.py` — `DriftResult`, `check_drift`, `warn_if_drifted`. Detect when worktree branch state doesn't match config. Commands call `warn_if_drifted` to display warnings but proceed anyway.
69
- - `git.py` — worktree operations: `create_worktree`, `attach_worktree`, `detach_worktree`, `worktree_exists`, `worktree_is_detached`, `get_worktree_branch`, `get_worktree_head`. Bare repo operations: `ensure_bare_repo`, `ensure_ref`. Ref/branch queries: `resolve_spec`, `resolve_spec_local`, `get_all_remote_refs`, `get_remote_ref_for_branch`, `get_remote_url`, `get_upstream`, `set_branch_upstream`, `is_branch_pushed`. Analysis: `rev_parse`, `is_ancestor`, `merge_base`, `count_commits`, `count_new_patches`, `count_unpushed`, `in_progress_operation`, `dirty_files`. Execution: `git`, `git_fetch`, `parallel_per_repo`, `run_cmd`.
74
+ - `git.py` — worktree operations: `create_worktree`, `attach_worktree`, `detach_worktree`, `worktree_exists`, `worktree_is_detached`, `get_worktree_branch`, `get_worktree_head`. Bare repo operations: `ensure_bare_repo`, `ensure_ref`. Ref/branch queries: `resolve_spec`, `resolve_spec_local`, `get_all_remote_refs`, `get_remote_ref_for_branch`, `get_remote_url`, `get_upstream`, `set_branch_upstream`, `is_branch_pushed`. Analysis: `rev_parse`, `is_ancestor`, `merge_base`, `count_commits`, `count_new_patches`, `count_unpushed`, `count_unbacked_commits`, `in_progress_operation`, `dirty_files`. Execution: `git`, `git_fetch`, `parallel_per_repo`, `run_cmd`.
70
75
  - `refs.py` — `fetch_workspace_refs` — three-phase pipeline for fetching workspace refs.
71
76
  - `rebase_plan.py` — `RepoFacts`, `GitStep`, `RebasePlan`, `plan_for` — pure analysis functions for rebase planning.
77
+ - `pull_plan.py` — `PullFacts`, `PullPlan`, `plan_pull` — pure analysis functions for fast-forward planning.
78
+ - `reset_plan.py` — `ResetFacts`, `ResetPlan`, `plan_reset` — pure analysis functions for reset planning.
79
+ - `askpass.py` — `broker()`, `child_env()`. Every git child runs in its own session and so has no controlling terminal; ssh therefore falls back to `SSH_ASKPASS`. The broker points it at a generated shim that forwards the prompt over a Unix socket to ow, which still owns the terminal. Prompts are serialised and answers cached per run. Entered once, in `__main__.main()`.
72
80
  - `resolver.py` — `resolve_workspace(name=None)` resolves a workspace: an explicit path, an explicit name (looked up via the index), the `OW_WORKSPACE` env var, or a cwd walk-up for `.ow/config.toml`. One rule, four branches, no fallback between them.
73
81
  - `templates.py` — `is_odoo_main_repo`, `find_addon_paths`, `build_template_context`, `apply_templates`, `ensure_workspace_materialized`, `available_templates`, `packaged_files`, `resolve_template_files` (local overrides win per file, packaged sibling still reachable via Jinja include/extends).
74
82
 
@@ -80,6 +88,8 @@ AGENTS.md
80
88
  | `ow apply` | `cmd_apply(config, workspace=None, *, only=None)` | Materialize worktrees + re-render templates |
81
89
  | `ow status` | `cmd_status(config, workspace=None)` | Show workspace branch status |
82
90
  | `ow rebase` | `cmd_rebase(config, workspace=None, *, only=None, autostash=False, dry_run=False, yes=False)` | Fetch + rebase workspace branches |
91
+ | `ow pull` | `cmd_pull(config, workspace=None, *, only=None, dry_run=False)` | Fetch, then fast-forward each repo — or replay it on its own upstream, `git pull --rebase` style. Never moves a repo off its base ref; that stays `ow rebase` |
92
+ | `ow reset` | `cmd_reset(config, workspace=None, *, only=None, hard=False, fetch=False, dry_run=False, yes=False)` | Put each repo back on the ref it follows — its branch's upstream, or the base ref when there is none. Plain form moves HEAD and leaves the working tree, so nothing on disk is lost; `--hard` discards it too. No fetch unless `-f`; skips a repo that is not on the branch the config names |
83
93
  | `ow prune` | `cmd_prune(config)` | Clean up stale worktree references, orphaned branches, dead index entries |
84
94
  | `ow rm` | `cmd_rm(name, *, yes=False)` | Remove a workspace: worktrees, local branches, directory, index entry |
85
95
  | `ow templates` | `cmd_templates(take=None, show_diff=False)` | List template files with their state, take one, or diff the stale ones |
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: odoo-workspaces
3
- Version: 2.0.1
3
+ Version: 2.2.0
4
4
  Summary: Odoo workspace manager
5
5
  Author-email: Bruno BOI <boi@odoo.com>
6
6
  License: MIT
@@ -18,16 +18,21 @@ Requires-Python: >=3.11
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
20
  Requires-Dist: typer>=0.12
21
+ Requires-Dist: click>=8.5.0
21
22
  Requires-Dist: rich>=13.0
22
23
  Requires-Dist: jinja2>=3.1
23
24
  Requires-Dist: questionary>=2.0
24
25
  Requires-Dist: tomli-w>=1.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8.0; extra == "dev"
25
28
  Dynamic: license-file
26
29
 
27
30
  # ow — Odoo Workspaces
28
31
 
29
32
  CLI tool that turns interactive prompts into ready-to-code Odoo workspaces using git worktrees.
30
33
 
34
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/brboi/ow)
35
+
31
36
  ## What it is
32
37
 
33
38
  `ow` manages Odoo development workspaces. Each workspace is a directory holding git worktrees for
@@ -2,6 +2,8 @@
2
2
 
3
3
  CLI tool that turns interactive prompts into ready-to-code Odoo workspaces using git worktrees.
4
4
 
5
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/brboi/ow)
6
+
5
7
  ## What it is
6
8
 
7
9
  `ow` manages Odoo development workspaces. Each workspace is a directory holding git worktrees for
@@ -8,6 +8,9 @@
8
8
  | `ow apply` | `[workspace]` | Re-render templates and materialize worktrees |
9
9
  | `ow status` | `[workspace]`, `-f/--fetch` | Show branch status with behind/ahead counts |
10
10
  | `ow rebase` | `[workspace]`, `--only`, `--autostash`, `--dry-run`, `-y/--yes` | Fetch and rebase repos in a workspace |
11
+ | `ow pull` | `[workspace]`, `--only`, `--dry-run` | Fetch and fast-forward repos in a workspace |
12
+ | `ow reset` | `[workspace]`, `--only`, `--hard`, `-f/--fetch`, `--dry-run`, `-y/--yes` | Put repos back on the refs they follow |
13
+ | `ow prune` | `--dry-run`, `-y/--yes` | Clean up stale worktree references, orphaned branches, and dead index entries |
11
14
  | `ow rm` | `<name>`, `-y/--yes` | Remove a workspace: worktrees, local branches, directory, and index entry |
12
15
  | `ow ls` | — | List every known workspace, its path, and its repos |
13
16
  | `ow templates` | `--take`, `--diff` | List template files and their state, take one, or diff the stale ones |
@@ -110,6 +113,83 @@ ever pushed: the `git push --force-with-lease` stays yours.
110
113
  `--dry-run` fetches refs to show you what would happen, but runs no command that
111
114
  touches your worktrees.
112
115
 
116
+ ## `ow pull`
117
+
118
+ Brings every repo of a workspace up to date without moving any of them off the
119
+ base branch they are configured on. `ow rebase` is the command that does move
120
+ them.
121
+
122
+ ```sh
123
+ ow pull # every repo of the current workspace
124
+ ow pull parrot --only community # one repo of a named workspace
125
+ ow pull --dry-run # fetch, then print the plan — no worktree touched
126
+ ```
127
+
128
+ Each repo follows its upstream when it has one — its own branch as pushed
129
+ elsewhere — and its base ref otherwise.
130
+
131
+ - already there: left alone
132
+ - detached: re-detaches onto the fetched ref
133
+ - behind: `git merge --ff-only`, which git refuses if the move would clobber a
134
+ local modification
135
+ - diverged **from its upstream**: `git rebase <upstream>`, exactly what
136
+ `git pull --rebase` does — same branch, same base, nobody else's commits
137
+ - diverged **from its base ref**, with no upstream: reported and left alone.
138
+ Carrying local work over to a moved base needs force-push detection and a
139
+ replay floor, and is `ow rebase`'s job
140
+
141
+ A repo is also left alone, and the run exits non-zero, when a git operation is
142
+ already in progress, when a replay would be needed but the worktree is dirty
143
+ (the message names the files), when the worktree is missing, or when the fetch
144
+ failed — moving onto the stale cached ref would look like success.
145
+
146
+ On conflict during a replay, resolve, `git rebase --continue`, then re-run
147
+ `ow pull --only <alias>`. Nothing is ever pushed.
148
+
149
+ ## `ow reset`
150
+
151
+ Puts every repo back on the ref it follows — `git reset`, one repo at a time.
152
+ Use it when a workspace has wandered, or when an upstream was force-pushed and
153
+ you want the remote's version of the branch, whatever yours has become.
154
+
155
+ ```sh
156
+ ow reset # every repo of the current workspace
157
+ ow reset parrot --only enterprise # one repo of a named workspace
158
+ ow reset --hard # discard the working tree too
159
+ ow reset --hard -f # ... against freshly fetched refs
160
+ ow reset --dry-run # print the plan, touch nothing
161
+ ```
162
+
163
+ **The ref each repo is reset to** is the one `git reset @{u}` would have used:
164
+ the branch's upstream — its own copy on a remote — or, for a branch nobody has
165
+ pushed and for a detached repo, the base ref it was cut from. An attached branch
166
+ is never reset onto its base: that would not put the repo back, it would throw
167
+ the whole branch away.
168
+
169
+ The two forms differ exactly as git's do:
170
+
171
+ - `ow reset` moves HEAD and leaves the working tree alone. The commits are gone
172
+ from the branch, but their content is still on disk as unstaged changes —
173
+ **nothing on disk is lost**.
174
+ - `ow reset --hard` discards the working tree as well. Untracked files are left
175
+ alone, exactly as `git reset --hard` leaves them; `git clean` is its own
176
+ command.
177
+
178
+ No fetch by default: it resets to the refs already in the bare repos, the way
179
+ `git reset origin/master` does. `-f/--fetch` refreshes them first, which is what
180
+ an upstream that was force-pushed needs — otherwise you land on the copy the
181
+ last fetch happened to cache.
182
+
183
+ The summary counts what each repo loses before anything runs, and flags the
184
+ commits no remote carries — the only ones a reset makes unrecoverable outside
185
+ the reflog. The confirmation defaults to no; `-y/--yes` skips it.
186
+
187
+ A repo is skipped, and the run exits non-zero, when a git operation is already
188
+ in progress, when the worktree is missing, when its refs will not resolve
189
+ locally, or when it is not on the branch the config names. That last one
190
+ matters: resetting whatever else happens to be checked out would throw away
191
+ work ow was never told about, and realigning is `ow apply`'s job.
192
+
113
193
  ## `ow prune`
114
194
 
115
195
  Cleans up stale worktree references and orphaned local branches from every bare repo, and drops
@@ -1,5 +1,4 @@
1
1
  [tools]
2
- node = { version = "latest", postinstall = "npm install -g rtlcss" }
3
2
  python = "3.12"
4
3
 
5
4
  [settings]
@@ -7,7 +6,6 @@ experimental = true
7
6
 
8
7
  [env]
9
8
  _.path = ["{{config_root}}"]
10
- COMPOSE_FILE = "{{config_root}}/services/compose.yml"
11
9
 
12
10
  [hooks]
13
11
  postinstall = "pip install -e {{config_root}} -q"
@@ -9,7 +9,7 @@ authors = [{ name = "Bruno BOI", email = "boi@odoo.com" }]
9
9
  license = { text = "MIT" }
10
10
  readme = "README.md"
11
11
  requires-python = ">=3.11"
12
- dependencies = ["typer>=0.12", "rich>=13.0", "jinja2>=3.1", "questionary>=2.0", "tomli-w>=1.0"]
12
+ dependencies = ["typer>=0.12", "click>=8.5.0", "rich>=13.0", "jinja2>=3.1", "questionary>=2.0", "tomli-w>=1.0"]
13
13
  dynamic = ["version"]
14
14
  classifiers = [
15
15
  "Development Status :: 5 - Production/Stable",
@@ -26,6 +26,10 @@ classifiers = [
26
26
  Homepage = "https://github.com/brboi/ow"
27
27
  Repository = "https://github.com/brboi/ow"
28
28
 
29
+
30
+ [project.optional-dependencies]
31
+ dev = ["pytest>=8.0"]
32
+
29
33
  [project.scripts]
30
34
  ow = "ow.__main__:main"
31
35
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: odoo-workspaces
3
- Version: 2.0.1
3
+ Version: 2.2.0
4
4
  Summary: Odoo workspace manager
5
5
  Author-email: Bruno BOI <boi@odoo.com>
6
6
  License: MIT
@@ -18,16 +18,21 @@ Requires-Python: >=3.11
18
18
  Description-Content-Type: text/markdown
19
19
  License-File: LICENSE
20
20
  Requires-Dist: typer>=0.12
21
+ Requires-Dist: click>=8.5.0
21
22
  Requires-Dist: rich>=13.0
22
23
  Requires-Dist: jinja2>=3.1
23
24
  Requires-Dist: questionary>=2.0
24
25
  Requires-Dist: tomli-w>=1.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8.0; extra == "dev"
25
28
  Dynamic: license-file
26
29
 
27
30
  # ow — Odoo Workspaces
28
31
 
29
32
  CLI tool that turns interactive prompts into ready-to-code Odoo workspaces using git worktrees.
30
33
 
34
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/brboi/ow)
35
+
31
36
  ## What it is
32
37
 
33
38
  `ow` manages Odoo development workspaces. Each workspace is a directory holding git worktrees for
@@ -3,8 +3,6 @@ AGENTS.md
3
3
  LICENSE
4
4
  README.md
5
5
  RELEASE.md
6
- bwrap-claude
7
- bwrap-opencode
8
6
  mise.toml
9
7
  pyproject.toml
10
8
  .github/workflows/release.yml
@@ -16,10 +14,10 @@ docs/migrating-to-2.0.md
16
14
  docs/sandboxing.md
17
15
  docs/services.md
18
16
  docs/templates.md
19
- scripts/migrate-to-1.0.0.py
17
+ scripts/bwrap-claude
18
+ scripts/bwrap-opencode
20
19
  scripts/migrate-to-2.0.py
21
20
  scripts/tests/__init__.py
22
- scripts/tests/test_migrate.py
23
21
  scripts/tests/test_migrate_2.py
24
22
  src/odoo_workspaces.egg-info/PKG-INFO
25
23
  src/odoo_workspaces.egg-info/SOURCES.txt
@@ -32,6 +30,7 @@ src/odoo_workspaces.egg-info/top_level.txt
32
30
  src/ow/__init__.py
33
31
  src/ow/__main__.py
34
32
  src/ow/_version.py
33
+ src/ow/askpass.py
35
34
  src/ow/_static/services/compose.yml.j2
36
35
  src/ow/_static/templates/bwrap/bwrap-claude.j2
37
36
  src/ow/_static/templates/bwrap/bwrap-opencode.j2
@@ -46,13 +45,21 @@ src/ow/_static/templates/zed/.zed/debug.json.j2
46
45
  src/ow/_static/templates/zed/.zed/settings.json.j2
47
46
  src/ow/commands/__init__.py
48
47
  src/ow/commands/apply.py
48
+ src/ow/commands/archive.py
49
+ src/ow/commands/cd.py
49
50
  src/ow/commands/init.py
50
51
  src/ow/commands/ls.py
52
+ src/ow/commands/mv.py
53
+ src/ow/commands/open.py
51
54
  src/ow/commands/prune.py
55
+ src/ow/commands/pull.py
52
56
  src/ow/commands/rebase.py
57
+ src/ow/commands/reset.py
53
58
  src/ow/commands/rm.py
59
+ src/ow/commands/shell_init.py
54
60
  src/ow/commands/status.py
55
61
  src/ow/commands/templates.py
62
+ src/ow/utils/askpass.py
56
63
  src/ow/utils/config.py
57
64
  src/ow/utils/display.py
58
65
  src/ow/utils/drift.py
@@ -60,8 +67,11 @@ src/ow/utils/git.py
60
67
  src/ow/utils/index.py
61
68
  src/ow/utils/legacy.py
62
69
  src/ow/utils/paths.py
70
+ src/ow/utils/pull_plan.py
63
71
  src/ow/utils/rebase_plan.py
64
72
  src/ow/utils/refs.py
73
+ src/ow/utils/relocate.py
74
+ src/ow/utils/reset_plan.py
65
75
  src/ow/utils/resolver.py
66
76
  src/ow/utils/templates.py
67
77
  src/tests/__init__.py
@@ -70,19 +80,26 @@ src/tests/test_cli.py
70
80
  src/tests/test_main_extended.py
71
81
  src/tests/commands/__init__.py
72
82
  src/tests/commands/test_apply.py
83
+ src/tests/commands/test_archive.py
73
84
  src/tests/commands/test_init.py
74
85
  src/tests/commands/test_init_extended.py
75
86
  src/tests/commands/test_ls.py
76
87
  src/tests/commands/test_misc_extended.py
88
+ src/tests/commands/test_mv.py
89
+ src/tests/commands/test_open.py
77
90
  src/tests/commands/test_prune.py
91
+ src/tests/commands/test_pull.py
78
92
  src/tests/commands/test_rebase.py
79
93
  src/tests/commands/test_rebase_scenarios.py
94
+ src/tests/commands/test_reset.py
80
95
  src/tests/commands/test_rm.py
96
+ src/tests/commands/test_shell_init.py
81
97
  src/tests/commands/test_status.py
82
98
  src/tests/commands/test_status_extended.py
83
99
  src/tests/commands/test_status_extended2.py
84
100
  src/tests/commands/test_templates_cmd.py
85
101
  src/tests/utils/__init__.py
102
+ src/tests/utils/test_askpass.py
86
103
  src/tests/utils/test_config.py
87
104
  src/tests/utils/test_display.py
88
105
  src/tests/utils/test_drift.py
@@ -94,8 +111,10 @@ src/tests/utils/test_legacy.py
94
111
  src/tests/utils/test_misc.py
95
112
  src/tests/utils/test_paths.py
96
113
  src/tests/utils/test_progress.py
114
+ src/tests/utils/test_pull_plan.py
97
115
  src/tests/utils/test_rebase_plan.py
98
116
  src/tests/utils/test_refs.py
117
+ src/tests/utils/test_reset_plan.py
99
118
  src/tests/utils/test_resolve_template_files.py
100
119
  src/tests/utils/test_resolver.py
101
120
  src/tests/utils/test_templates.py
@@ -1,5 +1,9 @@
1
1
  typer>=0.12
2
+ click>=8.5.0
2
3
  rich>=13.0
3
4
  jinja2>=3.1
4
5
  questionary>=2.0
5
6
  tomli-w>=1.0
7
+
8
+ [dev]
9
+ pytest>=8.0
@@ -8,8 +8,6 @@
8
8
  "LICENSE",
9
9
  "README.md",
10
10
  "RELEASE.md",
11
- "bwrap-claude",
12
- "bwrap-opencode",
13
11
  "docs/commands.md",
14
12
  "docs/configuration.md",
15
13
  "docs/migrating-to-2.0.md",
@@ -18,10 +16,10 @@
18
16
  "docs/templates.md",
19
17
  "mise.toml",
20
18
  "pyproject.toml",
21
- "scripts/migrate-to-1.0.0.py",
19
+ "scripts/bwrap-claude",
20
+ "scripts/bwrap-opencode",
22
21
  "scripts/migrate-to-2.0.py",
23
22
  "scripts/tests/__init__.py",
24
- "scripts/tests/test_migrate.py",
25
23
  "scripts/tests/test_migrate_2.py",
26
24
  "src/ow/__init__.py",
27
25
  "src/ow/__main__.py",
@@ -37,15 +35,24 @@
37
35
  "src/ow/_static/templates/vscode/.vscode/settings.json.j2",
38
36
  "src/ow/_static/templates/zed/.zed/debug.json.j2",
39
37
  "src/ow/_static/templates/zed/.zed/settings.json.j2",
38
+ "src/ow/askpass.py",
40
39
  "src/ow/commands/__init__.py",
41
40
  "src/ow/commands/apply.py",
41
+ "src/ow/commands/archive.py",
42
+ "src/ow/commands/cd.py",
42
43
  "src/ow/commands/init.py",
43
44
  "src/ow/commands/ls.py",
45
+ "src/ow/commands/mv.py",
46
+ "src/ow/commands/open.py",
44
47
  "src/ow/commands/prune.py",
48
+ "src/ow/commands/pull.py",
45
49
  "src/ow/commands/rebase.py",
50
+ "src/ow/commands/reset.py",
46
51
  "src/ow/commands/rm.py",
52
+ "src/ow/commands/shell_init.py",
47
53
  "src/ow/commands/status.py",
48
54
  "src/ow/commands/templates.py",
55
+ "src/ow/utils/askpass.py",
49
56
  "src/ow/utils/config.py",
50
57
  "src/ow/utils/display.py",
51
58
  "src/ow/utils/drift.py",
@@ -53,21 +60,30 @@
53
60
  "src/ow/utils/index.py",
54
61
  "src/ow/utils/legacy.py",
55
62
  "src/ow/utils/paths.py",
63
+ "src/ow/utils/pull_plan.py",
56
64
  "src/ow/utils/rebase_plan.py",
57
65
  "src/ow/utils/refs.py",
66
+ "src/ow/utils/relocate.py",
67
+ "src/ow/utils/reset_plan.py",
58
68
  "src/ow/utils/resolver.py",
59
69
  "src/ow/utils/templates.py",
60
70
  "src/tests/__init__.py",
61
71
  "src/tests/commands/__init__.py",
62
72
  "src/tests/commands/test_apply.py",
73
+ "src/tests/commands/test_archive.py",
63
74
  "src/tests/commands/test_init.py",
64
75
  "src/tests/commands/test_init_extended.py",
65
76
  "src/tests/commands/test_ls.py",
66
77
  "src/tests/commands/test_misc_extended.py",
78
+ "src/tests/commands/test_mv.py",
79
+ "src/tests/commands/test_open.py",
67
80
  "src/tests/commands/test_prune.py",
81
+ "src/tests/commands/test_pull.py",
68
82
  "src/tests/commands/test_rebase.py",
69
83
  "src/tests/commands/test_rebase_scenarios.py",
84
+ "src/tests/commands/test_reset.py",
70
85
  "src/tests/commands/test_rm.py",
86
+ "src/tests/commands/test_shell_init.py",
71
87
  "src/tests/commands/test_status.py",
72
88
  "src/tests/commands/test_status_extended.py",
73
89
  "src/tests/commands/test_status_extended2.py",
@@ -76,6 +92,7 @@
76
92
  "src/tests/test_cli.py",
77
93
  "src/tests/test_main_extended.py",
78
94
  "src/tests/utils/__init__.py",
95
+ "src/tests/utils/test_askpass.py",
79
96
  "src/tests/utils/test_config.py",
80
97
  "src/tests/utils/test_display.py",
81
98
  "src/tests/utils/test_drift.py",
@@ -87,8 +104,10 @@
87
104
  "src/tests/utils/test_misc.py",
88
105
  "src/tests/utils/test_paths.py",
89
106
  "src/tests/utils/test_progress.py",
107
+ "src/tests/utils/test_pull_plan.py",
90
108
  "src/tests/utils/test_rebase_plan.py",
91
109
  "src/tests/utils/test_refs.py",
110
+ "src/tests/utils/test_reset_plan.py",
92
111
  "src/tests/utils/test_resolve_template_files.py",
93
112
  "src/tests/utils/test_resolver.py",
94
113
  "src/tests/utils/test_templates.py",
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "2.2.0",
3
+ "distance": 0,
4
+ "node": "g2cc2fc2b1e998c35d547d3da26e3f905919335c1",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-09-11"
8
+ }