odoo-dev 1.2.0__tar.gz → 1.2.1__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.
- odoo_dev-1.2.1/.release-please-manifest.json +3 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/CHANGELOG.md +10 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/PKG-INFO +1 -1
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/pyproject.toml +1 -1
- odoo_dev-1.2.1/src/odoo_dev/__init__.py +3 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/commands/run.py +37 -8
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/commands/vendor.py +9 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/vendor/develop.py +15 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/vendor/edit.py +9 -2
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/vendor/migrate.py +11 -12
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/vendor/update.py +26 -7
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/tests/test_vendor_develop.py +37 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/tests/test_vendor_edit.py +33 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/tests/test_vendor_migrate.py +45 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/tests/test_vendor_update.py +25 -0
- odoo_dev-1.2.0/.release-please-manifest.json +0 -3
- odoo_dev-1.2.0/src/odoo_dev/__init__.py +0 -3
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/.github/workflows/release-please.yml +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/.gitignore +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/.gitmodules +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/.python-version +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/README.md +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/release-please-config.json +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/cli.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/commands/__init__.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/commands/bump.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/commands/db.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/commands/docker.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/commands/setup.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/config.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/preflight.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/templates/__init__.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/templates/docker/Dockerfile +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/templates/docker/__init__.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/templates/docker/docker-entrypoint.sh +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/templates/vscode/__init__.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/templates/vscode/launch.json +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/templates/vscode/settings.json +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/templates/vscode/tasks.json +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/utils/__init__.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/utils/console.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/utils/manifest.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/vendor/__init__.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/vendor/lock.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/vendor/materialize.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/vendor/sources.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/vendor/sync.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/src/odoo_dev/vendor/verify.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/tests/__init__.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/tests/fixtures/__init__.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/tests/test_bump.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/tests/test_cli.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/tests/test_config.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/tests/test_db.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/tests/test_db_config.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/tests/test_drop_database.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/tests/test_integration.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/tests/test_preflight.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/tests/test_run.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/tests/test_setup.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/tests/test_setup_db.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/tests/test_vendor_lock.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/tests/test_vendor_materialize.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/tests/test_vendor_verify.py +0 -0
- {odoo_dev-1.2.0 → odoo_dev-1.2.1}/uv.lock +0 -0
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.2.1](https://github.com/bemade/odoo-dev/compare/v1.2.0...v1.2.1) (2026-07-14)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* **test:** make full-suite runs work under a dev odoo.conf ([2a1b752](https://github.com/bemade/odoo-dev/commit/2a1b7527d29b3bf8bb776e41ce739b4864543b73))
|
|
9
|
+
* **vendor:** bump clears stale version when pinning to a commit ([19544ba](https://github.com/bemade/odoo-dev/commit/19544bac55a3dded4b912cc9f2ac7226f5d72d61))
|
|
10
|
+
* **vendor:** constrain `vendor update` to the pin's own major series ([5b58e26](https://github.com/bemade/odoo-dev/commit/5b58e2676de6c1b63a678d1dd365efca9e4c8b99))
|
|
11
|
+
* **vendor:** migrate sets version only when the tag resolves to the pin ([ea59b1d](https://github.com/bemade/odoo-dev/commit/ea59b1d62c9844fb831925d9973b4ec179b803e6))
|
|
12
|
+
|
|
3
13
|
## [1.2.0](https://github.com/bemade/odoo-dev/compare/v1.1.0...v1.2.0) (2026-07-07)
|
|
4
14
|
|
|
5
15
|
|
|
@@ -235,17 +235,18 @@ def test(
|
|
|
235
235
|
break
|
|
236
236
|
|
|
237
237
|
# Auto-load exclusions from repo_deps.yaml when not explicitly provided
|
|
238
|
-
|
|
238
|
+
no_ci_modules: list[str] = []
|
|
239
|
+
if repo_deps_file is not None:
|
|
239
240
|
try:
|
|
240
241
|
import yaml
|
|
241
242
|
|
|
242
243
|
data = yaml.safe_load(repo_deps_file.read_text()) or {}
|
|
243
|
-
|
|
244
|
-
if no_ci:
|
|
245
|
-
exclude = ",".join(no_ci)
|
|
246
|
-
info(f"Excluding (from {repo_deps_file} no_ci): {exclude}")
|
|
244
|
+
no_ci_modules = list(data.get("no_ci", []) or [])
|
|
247
245
|
except Exception:
|
|
248
|
-
|
|
246
|
+
no_ci_modules = []
|
|
247
|
+
if exclude is None and no_ci_modules:
|
|
248
|
+
exclude = ",".join(no_ci_modules)
|
|
249
|
+
info(f"Excluding (from {repo_deps_file} no_ci): {exclude}")
|
|
249
250
|
|
|
250
251
|
# Verify setup
|
|
251
252
|
if not cfg.venv_path.exists():
|
|
@@ -376,6 +377,9 @@ def test(
|
|
|
376
377
|
"--stop-after-init",
|
|
377
378
|
"--http-port",
|
|
378
379
|
str(http_port),
|
|
380
|
+
# No need to fork prefork workers for a stop-after-init install.
|
|
381
|
+
"--workers",
|
|
382
|
+
"0",
|
|
379
383
|
],
|
|
380
384
|
)
|
|
381
385
|
|
|
@@ -422,11 +426,36 @@ def test(
|
|
|
422
426
|
"--stop-after-init",
|
|
423
427
|
"--http-port",
|
|
424
428
|
str(http_port),
|
|
429
|
+
# HttpCase needs the threaded server (ThreadedServer.httpd);
|
|
430
|
+
# a prefork/multi-worker conf raises
|
|
431
|
+
# "'PreforkServer' object has no attribute 'httpd'" in
|
|
432
|
+
# setUpClass. Force single-process regardless of odoo.conf.
|
|
433
|
+
"--workers",
|
|
434
|
+
"0",
|
|
435
|
+
# A restrictive dbfilter in odoo.conf (e.g. `dbfilter = ^rwi$`)
|
|
436
|
+
# never matches the ephemeral test DB, so HttpCase requests get
|
|
437
|
+
# "dbfilter rejects it; logging session out" -> auth='user'
|
|
438
|
+
# routes 404 and JSON endpoints return non-JSON. Scope the
|
|
439
|
+
# filter to this run's test DB so the test server serves it.
|
|
440
|
+
f"--db-filter=^{db_name}$",
|
|
425
441
|
]
|
|
426
442
|
)
|
|
427
443
|
|
|
428
|
-
|
|
429
|
-
|
|
444
|
+
# Excluding no_ci modules from the tested-module *list* is not enough:
|
|
445
|
+
# they still install as transitive deps, and Odoo's button_upgrade
|
|
446
|
+
# cascades 'to upgrade' to downstream dependents of any upgraded target
|
|
447
|
+
# module (`-u modules`). A no_ci module that depends on a tested module
|
|
448
|
+
# thus gets re-upgraded and its post_install tests run. Exclude their
|
|
449
|
+
# tests explicitly via Odoo test-tags. The TagsSelector auto-adds
|
|
450
|
+
# '+standard' when only '-' filters are present, so this keeps running
|
|
451
|
+
# every standard test except those belonging to a no_ci module.
|
|
452
|
+
effective_test_tags = test_tags
|
|
453
|
+
if effective_test_tags is None and no_ci_modules:
|
|
454
|
+
effective_test_tags = ",".join(f"-/{m}" for m in no_ci_modules)
|
|
455
|
+
info(f"Excluding no_ci module tests via --test-tags: {effective_test_tags}")
|
|
456
|
+
|
|
457
|
+
if effective_test_tags:
|
|
458
|
+
test_cmd.extend(["--test-tags", effective_test_tags])
|
|
430
459
|
|
|
431
460
|
result = subprocess.run(test_cmd)
|
|
432
461
|
test_exit_code = result.returncode
|
|
@@ -18,6 +18,7 @@ from odoo_dev.utils.console import error, info, success
|
|
|
18
18
|
from odoo_dev.vendor.develop import (
|
|
19
19
|
DevelopError,
|
|
20
20
|
develop_state,
|
|
21
|
+
ensure_addons_path,
|
|
21
22
|
start_develop,
|
|
22
23
|
stop_develop,
|
|
23
24
|
)
|
|
@@ -159,6 +160,14 @@ def migrate_cmd(
|
|
|
159
160
|
|
|
160
161
|
lock, unused = migrate_repo(cfg.project_dir, addons=addon or None, deinit=deinit)
|
|
161
162
|
success(f"Migrated {len(lock.entries)} addon(s) to vendored/ (see addons.lock).")
|
|
163
|
+
|
|
164
|
+
# Wire vendored/ into the local conf's addons_path. A fresh `setup` adds it,
|
|
165
|
+
# but `setup` won't overwrite an existing conf, so a repo vendored for the
|
|
166
|
+
# first time would otherwise leave Odoo unable to find the new addons
|
|
167
|
+
# ("invalid module names, ignored"). Local conf only — never Docker/CI.
|
|
168
|
+
if cfg.config_file.exists() and cfg.vendored_dir.is_dir():
|
|
169
|
+
if ensure_addons_path(cfg.config_file, str(cfg.vendored_dir)):
|
|
170
|
+
info(f"Added {cfg.vendored_dir} to {cfg.config_file} addons_path.")
|
|
162
171
|
if unused:
|
|
163
172
|
info("")
|
|
164
173
|
if deinit:
|
|
@@ -167,6 +167,21 @@ def remove_addons_path(conf_file: Path, path: str) -> None:
|
|
|
167
167
|
_write_addons_path(conf_file, [p for p in read_addons_path(conf_file) if p != path])
|
|
168
168
|
|
|
169
169
|
|
|
170
|
+
def ensure_addons_path(conf_file: Path, path: str) -> bool:
|
|
171
|
+
"""Append ``path`` to the conf's addons_path if it isn't already listed.
|
|
172
|
+
|
|
173
|
+
Idempotent; returns True only when it actually added the entry. Used by
|
|
174
|
+
``vendor migrate`` to wire ``vendored/`` into a conf that predates vendoring
|
|
175
|
+
(``setup`` adds it for fresh confs but never overwrites an existing one).
|
|
176
|
+
"""
|
|
177
|
+
paths = read_addons_path(conf_file)
|
|
178
|
+
if path in paths:
|
|
179
|
+
return False
|
|
180
|
+
paths.append(path)
|
|
181
|
+
_write_addons_path(conf_file, paths)
|
|
182
|
+
return True
|
|
183
|
+
|
|
184
|
+
|
|
170
185
|
# --- overlay / gitignore / state ------------------------------------------------
|
|
171
186
|
|
|
172
187
|
|
|
@@ -76,8 +76,15 @@ def bump_addon(
|
|
|
76
76
|
entry = lock.entries[name]
|
|
77
77
|
sha = _resolve_pin(name, entry.source, version, commit, entry.branch, cache_dir)
|
|
78
78
|
entry.commit = sha
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
# ``version`` is the tag-intent metadata, and ``vendor check``'s moved-tag
|
|
80
|
+
# tripwire (verify.py) requires ``<name>/<version>`` to still resolve to
|
|
81
|
+
# ``commit``. A ``--commit`` / branch-HEAD bump moves the pin off the tagged
|
|
82
|
+
# commit, so the old ``version`` is now stale and would (correctly) fail the
|
|
83
|
+
# tripwire. Set it to whatever the bump was keyed on: the new tag for a
|
|
84
|
+
# ``--version`` bump, or ``None`` for a raw ``--commit`` / branch bump — a
|
|
85
|
+
# commit pin carries no version metadata (matching how ``migrate`` renders
|
|
86
|
+
# untagged pins).
|
|
87
|
+
entry.version = version
|
|
81
88
|
lock.entries[name] = entry
|
|
82
89
|
lock.dump(project_dir / "addons.lock")
|
|
83
90
|
sync_addons(project_dir, lock, cache_dir=cache_dir, names=[name])
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Reads the ``addons/<addon>`` symlinks that point into ``.repos/*`` submodules,
|
|
4
4
|
derives a per-addon pin (submodule remote URL = source, submodule HEAD = commit,
|
|
5
|
-
manifest version -> version when
|
|
5
|
+
manifest version -> version ONLY when the ``<addon>/<version>`` tag resolves to
|
|
6
|
+
the pinned commit; a commit that isn't the tagged release (a pin behind or ahead
|
|
7
|
+
of the tag, or any intermediate commit) is left commit-only, no version),
|
|
6
8
|
writes ``addons.lock``, materializes ``vendored/<addon>/`` from the submodule,
|
|
7
9
|
and removes the now-redundant symlink. Client-private real dirs under ``addons/``
|
|
8
10
|
are left untouched; only symlinks into submodules are migrated. Submodule removal
|
|
@@ -19,6 +21,7 @@ from typing import Iterable, Optional
|
|
|
19
21
|
|
|
20
22
|
from odoo_dev.utils.manifest import manifest_path, read_version
|
|
21
23
|
from odoo_dev.vendor.lock import LockEntry, Lockfile
|
|
24
|
+
from odoo_dev.vendor.sources import tag_resolves_to
|
|
22
25
|
from odoo_dev.vendor.sync import sync_addons
|
|
23
26
|
|
|
24
27
|
|
|
@@ -63,16 +66,6 @@ def _submodule_head(project_dir: Path, sub_path: str) -> str:
|
|
|
63
66
|
).stdout.strip()
|
|
64
67
|
|
|
65
68
|
|
|
66
|
-
def _tag_exists(repo: Path, tag: str) -> bool:
|
|
67
|
-
return (
|
|
68
|
-
subprocess.run(
|
|
69
|
-
["git", "-C", str(repo), "rev-parse", f"refs/tags/{tag}"],
|
|
70
|
-
capture_output=True,
|
|
71
|
-
).returncode
|
|
72
|
-
== 0
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
|
|
76
69
|
def plan_migration(project_dir: Path, addons: Optional[Iterable[str]] = None) -> list:
|
|
77
70
|
"""Return a list of planned pins without changing anything.
|
|
78
71
|
|
|
@@ -118,7 +111,13 @@ def plan_migration(project_dir: Path, addons: Optional[Iterable[str]] = None) ->
|
|
|
118
111
|
mf = manifest_path(real)
|
|
119
112
|
if mf is not None:
|
|
120
113
|
v = read_version(mf.read_text())
|
|
121
|
-
|
|
114
|
+
# Only claim a ``version`` when the tag actually points at the pinned
|
|
115
|
+
# commit. Checking mere tag EXISTENCE was wrong: the same manifest
|
|
116
|
+
# version can live at several commits (the tag is cut at one of
|
|
117
|
+
# them), so a pin at any other commit with that version would carry a
|
|
118
|
+
# ``version`` whose tag resolves elsewhere — tripping ``vendor
|
|
119
|
+
# check``'s moved-tag guard. A non-release pin is commit-only.
|
|
120
|
+
if v is not None and tag_resolves_to(sub_root, f"{name}/{v}", commit):
|
|
122
121
|
version = v
|
|
123
122
|
plans.append(
|
|
124
123
|
dict(
|
|
@@ -24,27 +24,46 @@ def _version_key(v: str) -> tuple:
|
|
|
24
24
|
return tuple(int(p) if p.isdigit() else 0 for p in v.replace("-", ".").split("."))
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
def _series(v: str) -> str:
|
|
28
|
+
"""The major-series segment of a version — the cross-series guard.
|
|
29
|
+
|
|
30
|
+
An Odoo addon carries both ``18.0.*`` and ``19.0.*`` version tags in its source;
|
|
31
|
+
version-tracking must stay within the pin's own series (``18`` vs ``19``), since
|
|
32
|
+
moving across Odoo series is a deliberate migration, never an auto-update.
|
|
33
|
+
"""
|
|
34
|
+
return v.split(".", 1)[0]
|
|
35
|
+
|
|
36
|
+
|
|
27
37
|
def list_addon_versions(
|
|
28
|
-
source: str,
|
|
38
|
+
source: str,
|
|
39
|
+
addon: str,
|
|
40
|
+
cache_dir: Optional[Path] = None,
|
|
41
|
+
series: Optional[str] = None,
|
|
29
42
|
) -> list[str]:
|
|
30
|
-
"""
|
|
43
|
+
"""``<addon>/<version>`` tag versions in ``source``, optionally same-series only."""
|
|
31
44
|
repo = _ensure_clone(source, cache_dir)
|
|
32
45
|
r = subprocess.run(
|
|
33
46
|
["git", "-C", str(repo), "tag", "-l", f"{addon}/*"],
|
|
34
47
|
capture_output=True,
|
|
35
48
|
text=True,
|
|
36
49
|
)
|
|
37
|
-
|
|
50
|
+
versions = [
|
|
38
51
|
t.split("/", 1)[1]
|
|
39
52
|
for t in r.stdout.split()
|
|
40
53
|
if t.startswith(f"{addon}/") and "/" in t
|
|
41
54
|
]
|
|
55
|
+
if series is not None:
|
|
56
|
+
versions = [v for v in versions if _series(v) == series]
|
|
57
|
+
return versions
|
|
42
58
|
|
|
43
59
|
|
|
44
60
|
def latest_version(
|
|
45
|
-
source: str,
|
|
61
|
+
source: str,
|
|
62
|
+
addon: str,
|
|
63
|
+
cache_dir: Optional[Path] = None,
|
|
64
|
+
series: Optional[str] = None,
|
|
46
65
|
) -> Optional[str]:
|
|
47
|
-
versions = list_addon_versions(source, addon, cache_dir)
|
|
66
|
+
versions = list_addon_versions(source, addon, cache_dir, series)
|
|
48
67
|
return max(versions, key=_version_key) if versions else None
|
|
49
68
|
|
|
50
69
|
|
|
@@ -65,8 +84,8 @@ def find_updates(
|
|
|
65
84
|
for name, e in lock.entries.items():
|
|
66
85
|
if only is not None and name not in only:
|
|
67
86
|
continue
|
|
68
|
-
if e.version: # version-tracked
|
|
69
|
-
latest = latest_version(e.source, name, cache_dir)
|
|
87
|
+
if e.version: # version-tracked (within the pin's own major series)
|
|
88
|
+
latest = latest_version(e.source, name, cache_dir, series=_series(e.version))
|
|
70
89
|
if latest and _version_key(latest) > _version_key(e.version):
|
|
71
90
|
updates.append(
|
|
72
91
|
{
|
|
@@ -14,6 +14,7 @@ import pytest
|
|
|
14
14
|
from odoo_dev.vendor.develop import (
|
|
15
15
|
DevelopError,
|
|
16
16
|
develop_state,
|
|
17
|
+
ensure_addons_path,
|
|
17
18
|
read_addons_path,
|
|
18
19
|
start_develop,
|
|
19
20
|
stop_develop,
|
|
@@ -187,3 +188,39 @@ def test_two_addons_share_overlay(tmp_path):
|
|
|
187
188
|
stop_develop(proj, "shared_addon", conf_file=conf)
|
|
188
189
|
assert str(dev / "overlay") in read_addons_path(conf)
|
|
189
190
|
assert [p.name for p in (dev / "overlay").iterdir()] == ["other_addon"]
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# --- ensure_addons_path: wiring vendored/ into a pre-existing conf --------------
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def test_ensure_addons_path_appends_when_missing(tmp_path: Path):
|
|
197
|
+
"""A conf that predates vendoring gets vendored/ appended (returns True)."""
|
|
198
|
+
conf = tmp_path / "odoo.conf"
|
|
199
|
+
conf.write_text("[options]\naddons_path = /p/odoo/addons,/p/addons\n")
|
|
200
|
+
vendored = str(tmp_path / "vendored")
|
|
201
|
+
|
|
202
|
+
assert ensure_addons_path(conf, vendored) is True
|
|
203
|
+
paths = read_addons_path(conf)
|
|
204
|
+
assert paths[-1] == vendored
|
|
205
|
+
assert paths[:2] == ["/p/odoo/addons", "/p/addons"]
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def test_ensure_addons_path_is_idempotent(tmp_path: Path):
|
|
209
|
+
"""A second call is a no-op (returns False, no duplicate entry)."""
|
|
210
|
+
conf = tmp_path / "odoo.conf"
|
|
211
|
+
conf.write_text("[options]\naddons_path = /p/addons\n")
|
|
212
|
+
vendored = str(tmp_path / "vendored")
|
|
213
|
+
|
|
214
|
+
assert ensure_addons_path(conf, vendored) is True
|
|
215
|
+
assert ensure_addons_path(conf, vendored) is False
|
|
216
|
+
assert read_addons_path(conf).count(vendored) == 1
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def test_ensure_addons_path_inserts_line_when_absent(tmp_path: Path):
|
|
220
|
+
"""A conf with no addons_path line gets one created under [options]."""
|
|
221
|
+
conf = tmp_path / "odoo.conf"
|
|
222
|
+
conf.write_text("[options]\nadmin_passwd = x\n")
|
|
223
|
+
vendored = str(tmp_path / "vendored")
|
|
224
|
+
|
|
225
|
+
assert ensure_addons_path(conf, vendored) is True
|
|
226
|
+
assert read_addons_path(conf) == [vendored]
|
|
@@ -99,6 +99,39 @@ def test_bump_moves_pin_and_rematerializes(tmp_path):
|
|
|
99
99
|
assert verify(proj, lock, cache_dir=tmp_path / "c") == []
|
|
100
100
|
|
|
101
101
|
|
|
102
|
+
def _untagged_commit(repo: Path) -> str:
|
|
103
|
+
"""A commit that is NOT a version tag (a between-releases pin target)."""
|
|
104
|
+
(repo / "shared_addon" / "m.py").write_text("v = 3\n")
|
|
105
|
+
_git(repo, "add", "-A")
|
|
106
|
+
_git(repo, "commit", "-qm", "v3-untagged")
|
|
107
|
+
return _git(repo, "rev-parse", "HEAD")
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_bump_by_commit_clears_stale_version(tmp_path):
|
|
111
|
+
"""A ``--commit`` bump moves the pin off the tagged commit, so it must clear
|
|
112
|
+
the now-stale ``version`` — otherwise ``vendor check``'s moved-tag tripwire
|
|
113
|
+
(correctly) fails because ``<name>/<old-version>`` no longer resolves to the
|
|
114
|
+
new pin. Regression for the bug where ``bump`` left ``version`` untouched.
|
|
115
|
+
"""
|
|
116
|
+
repo = _source_repo(tmp_path)
|
|
117
|
+
proj = tmp_path / "client"
|
|
118
|
+
proj.mkdir()
|
|
119
|
+
add_addon(
|
|
120
|
+
proj, "shared_addon", str(repo), version="18.0.1.0.0", cache_dir=tmp_path / "c"
|
|
121
|
+
)
|
|
122
|
+
sha = _untagged_commit(repo)
|
|
123
|
+
|
|
124
|
+
entry = bump_addon(proj, "shared_addon", commit=sha, cache_dir=tmp_path / "c")
|
|
125
|
+
|
|
126
|
+
assert entry.commit == sha
|
|
127
|
+
assert entry.version is None, "commit bump must clear the stale version tag"
|
|
128
|
+
assert (proj / "vendored" / "shared_addon" / "m.py").read_text() == "v = 3\n"
|
|
129
|
+
lock = Lockfile.load(proj / "addons.lock")
|
|
130
|
+
assert "version" not in (lock.entries["shared_addon"].to_dict())
|
|
131
|
+
# The moved-tag tripwire must NOT fire (it would if version stayed stale).
|
|
132
|
+
assert verify(proj, lock, cache_dir=tmp_path / "c") == []
|
|
133
|
+
|
|
134
|
+
|
|
102
135
|
def test_bump_unknown_addon_raises(tmp_path):
|
|
103
136
|
proj = tmp_path / "client"
|
|
104
137
|
proj.mkdir()
|
|
@@ -92,6 +92,51 @@ def test_plan_leaves_version_none_when_no_tag(tmp_path):
|
|
|
92
92
|
assert plans[0]["version"] is None
|
|
93
93
|
|
|
94
94
|
|
|
95
|
+
def _source_repo_tag_behind_head(tmp_path: Path) -> Path:
|
|
96
|
+
"""Source where the ``<addon>/<version>`` tag sits at an OLDER commit than
|
|
97
|
+
HEAD, and HEAD's manifest still carries that same version (no bump between).
|
|
98
|
+
Mirrors the real case: a pin at an intermediate commit that no release tag
|
|
99
|
+
points at, even though the manifest version string matches a tag.
|
|
100
|
+
"""
|
|
101
|
+
repo = tmp_path / "sale-workflow"
|
|
102
|
+
(repo / "shared_addon" / "models").mkdir(parents=True)
|
|
103
|
+
(repo / "shared_addon" / "__manifest__.py").write_text(
|
|
104
|
+
"{'name': 'shared', 'version': '18.0.2.0.0'}\n"
|
|
105
|
+
)
|
|
106
|
+
(repo / "shared_addon" / "models" / "m.py").write_text("z = 3\n")
|
|
107
|
+
_git(repo, "init", "-q")
|
|
108
|
+
_git(repo, "config", "user.email", "t@t")
|
|
109
|
+
_git(repo, "config", "user.name", "t")
|
|
110
|
+
_git(repo, "add", "-A")
|
|
111
|
+
_git(repo, "commit", "-qm", "init")
|
|
112
|
+
c1 = _git(repo, "rev-parse", "HEAD")
|
|
113
|
+
_git(repo, "tag", "shared_addon/18.0.2.0.0", c1) # tag at the OLDER commit
|
|
114
|
+
# A later commit with the SAME manifest version — the pin target.
|
|
115
|
+
(repo / "shared_addon" / "models" / "m.py").write_text("z = 4\n")
|
|
116
|
+
_git(repo, "add", "-A")
|
|
117
|
+
_git(repo, "commit", "-qm", "later, same version")
|
|
118
|
+
return repo
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_plan_omits_version_when_tag_points_elsewhere(tmp_path):
|
|
122
|
+
"""Regression: a pin that is NOT the tagged commit (same manifest version at
|
|
123
|
+
several commits) must be commit-only. Checking mere tag EXISTENCE wrote a
|
|
124
|
+
``version`` whose tag resolved to a different commit, tripping ``vendor
|
|
125
|
+
check``'s moved-tag guard. The full migrate+verify must be clean.
|
|
126
|
+
"""
|
|
127
|
+
source = _source_repo_tag_behind_head(tmp_path)
|
|
128
|
+
head = _git(source, "rev-parse", "HEAD")
|
|
129
|
+
client = _client_repo(tmp_path, source) # submodule pins source HEAD
|
|
130
|
+
plans = plan_migration(client)
|
|
131
|
+
assert plans[0]["commit"] == head
|
|
132
|
+
assert plans[0]["version"] is None # tag resolves to the older commit, not HEAD
|
|
133
|
+
|
|
134
|
+
migrate_repo(client, deinit=False)
|
|
135
|
+
lock = Lockfile.load(client / "addons.lock")
|
|
136
|
+
assert lock.entries["shared_addon"].version is None
|
|
137
|
+
assert verify(client, lock) == [] # no moved-tag failure
|
|
138
|
+
|
|
139
|
+
|
|
95
140
|
def test_migrate_materializes_removes_symlink_and_verifies(tmp_path):
|
|
96
141
|
source, sha = _source_repo(tmp_path, tag="shared_addon/18.0.2.0.0")
|
|
97
142
|
client = _client_repo(tmp_path, source)
|
|
@@ -121,3 +121,28 @@ def test_find_updates_skips_bare_commit_pin(tmp_path):
|
|
|
121
121
|
_cut_version(repo, "18.0.1.1.0", 2)
|
|
122
122
|
# A bare-commit pin (no version, no branch) is deliberately fixed.
|
|
123
123
|
assert find_updates(proj, Lockfile.load(proj / "addons.lock"), cache_dir=tmp_path / "c2") == []
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_find_updates_stays_in_series(tmp_path):
|
|
127
|
+
repo = _source(tmp_path)
|
|
128
|
+
proj = tmp_path / "client"
|
|
129
|
+
proj.mkdir()
|
|
130
|
+
add_addon(proj, "shared", str(repo), version="18.0.1.0.0", cache_dir=tmp_path / "c")
|
|
131
|
+
# A newer 18.0 release AND a cross-series 19.0 tag both exist upstream
|
|
132
|
+
# (as after baselining both branches). Must NOT jump 18.0 -> 19.0.
|
|
133
|
+
_cut_version(repo, "18.0.1.1.0", 2)
|
|
134
|
+
_git(repo, "tag", "shared/19.0.0.1.0")
|
|
135
|
+
updates = find_updates(
|
|
136
|
+
proj, Lockfile.load(proj / "addons.lock"), cache_dir=tmp_path / "c2"
|
|
137
|
+
)
|
|
138
|
+
assert updates == [
|
|
139
|
+
{"name": "shared", "kind": "version", "current": "18.0.1.0.0", "latest": "18.0.1.1.0"}
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_latest_version_series_filter(tmp_path):
|
|
144
|
+
repo = _source(tmp_path)
|
|
145
|
+
_cut_version(repo, "18.0.1.1.0", 2)
|
|
146
|
+
_git(repo, "tag", "shared/19.0.5.0.0")
|
|
147
|
+
assert latest_version(str(repo), "shared", tmp_path / "c", series="18") == "18.0.1.1.0"
|
|
148
|
+
assert latest_version(str(repo), "shared", tmp_path / "c", series="19") == "19.0.5.0.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|