diff --git a/dput.d/hooks/ppa-version-string.json b/dput.d/hooks/ppa-version-string.json new file mode 100644 index 0000000..8a3c079 --- /dev/null +++ b/dput.d/hooks/ppa-version-string.json @@ -0,0 +1,5 @@ +{ + "description": "check that ppa is not present in the version string for uploads to the archive or vice versa", + "path": "ubuntu_lint.dput.dput_ppa_version_string", + "pre": true +} diff --git a/dput.d/profiles/ppa.json b/dput.d/profiles/ppa.json new file mode 100644 index 0000000..3041092 --- /dev/null +++ b/dput.d/profiles/ppa.json @@ -0,0 +1,5 @@ +{ + "+hooks": [ + "ppa-version-string" + ] +} diff --git a/dput.d/profiles/ubuntu.json b/dput.d/profiles/ubuntu.json index c45d65d..c710394 100644 --- a/dput.d/profiles/ubuntu.json +++ b/dput.d/profiles/ubuntu.json @@ -4,6 +4,7 @@ "missing-launchpad-bugs-fixed", "missing-pending-changelog-entry", "missing-ubuntu-maintainer", + "ppa-version-string", "sru-bug-missing-release-tasks", "sru-bug-missing-template", "sru-version-string-breaks-upgrades" diff --git a/tests/test_linters.py b/tests/test_linters.py index cb16e21..f7d209c 100644 --- a/tests/test_linters.py +++ b/tests/test_linters.py @@ -72,6 +72,39 @@ Vcs-Git-Ref: refs/heads/testing """) +basic_changes_ppa = deb822.Changes(""" +Format: 1.8 +Date: Thu, 19 Mar 2026 19:53:17 -0700 +Source: hello +Built-For-Profiles: derivative.ubuntu noudeb +Architecture: source +Version: 2.10-5ubuntu1~ppa1 +Distribution: resolute +Urgency: medium +Maintainer: Ubuntu Developers +Changed-By: John Doe +Changes: + hello (2.10-5ubuntu1~ppa1) resolute; urgency=medium + . + * Testing +Checksums-Sha1: + 93d36eb50575b7520ef1c83b9bb710e47b173485 1090 hello_2.10-5ubuntu1~ppa1.dsc + f7bebf6f9c62a2295e889f66e05ce9bfaed9ace3 725946 hello_2.10.orig.tar.gz + 68e569d38607b4a6c49855157a7e088e7cbd29b5 13280 hello_2.10-5ubuntu1~ppa1.debian.tar.xz + 710b6fda80d0db1662bb1be900e16d36778e4e0c 6488 hello_2.10-5ubuntu1~ppa1_source.buildinfo +Checksums-Sha256: + b13071d9b7f4e7d5940cec73b21efe640894d135d8b959dac6bc77fff878728b 1090 hello_2.10-5ubuntu1~ppa1.dsc + 31e066137a962676e89f69d1b65382de95a7ef7d914b8cb956f41ea72e0f516b 725946 hello_2.10.orig.tar.gz + 8b1e3f6da54c3fc390e49cb5ee2e8079793496b0488465822754abbc91f0af27 13280 hello_2.10-5ubuntu1~ppa1.debian.tar.xz + ef2fd9b53c0058c85c0b6c35752e6df068f4fa2267622a93ef922275a045ad0e 6488 hello_2.10-5ubuntu1~ppa1_source.buildinfo +Files: + 3f9815521e1d422171f47ae52122bf81 1090 devel optional hello_2.10-5ubuntu1~ppa1.dsc + 6cd0ffea3884a4e79330338dcc2987d6 725946 devel optional hello_2.10.orig.tar.gz + bf2ddaa879860206e8d7f93a08e148a5 13280 devel optional hello_2.10-5ubuntu1~ppa1.debian.tar.xz + e7210718902f4ef005852d3b484e4eeb 6488 devel optional hello_2.10-5ubuntu1~ppa1_source.buildinfo +Original-Maintainer: Santiago Vila +""") + basic_changes_sru = deb822.Changes(""" Format: 1.8 Date: Wed, 11 Mar 2026 16:01:41 -0400 @@ -103,6 +136,107 @@ Original-Maintainer: Santiago Vila """) +archive_upload_profile = { + "name": "ubuntu", + "allow_dcut": False, + "allow_unsigned_uploads": False, + "allowed_distributions": "(?!UNRELEASED)", + "default_host_main": "ssh-ubuntu", + "full_upload_log": False, + "hash": "md5", + "interface": "cli", + "login": "anonymous", + "meta": "ubuntu", + "method": "ftp", + "passive_ftp": True, + "post_upload_command": "", + "pre_upload_command": "", + "run_lintian": False, + "scp_compress": False, + "allowed-distribution": {}, + "codenames": "ubuntu", + "hooks": [ + "badauthor", + "updatemaintainer", + "ppaforppaonly", + "badcontent", + "nobug", + "sure", + "gitubuntu", + "placeholderbug", + "checksum", + "suite-mismatch", + "releasemismatch", + "supported-distribution", + "required-fields", + "check-debs", + "gpg", + ], + "run_dinstall": False, + "check_version": False, + "progress_indicator": "2", + "fqdn": "upload.ubuntu.com", + "incoming": "/ubuntu", + "supported-distribution": { + "allowed": ["release", "proposed", "backports", "security"], + "known": ["release", "proposed", "updates", "backports", "security"], + }, + "check-debs": {"enforce": "source", "skip": False}, + "required-fields": { + "skip": False, + "fields": ["Launchpad-Bugs-Fixed"], + "suites": ["any-stable"], + }, + "valid_commands": [], +} + +ppa_upload_profile = { + "name": "ppa", + "allow_dcut": False, + "allow_unsigned_uploads": False, + "allowed_distributions": "(?!UNRELEASED)", + "default_host_main": "ssh-ubuntu", + "full_upload_log": False, + "hash": "md5", + "interface": "cli", + "login": "anonymous", + "meta": "ubuntu", + "method": "ftp", + "passive_ftp": True, + "post_upload_command": "", + "pre_upload_command": "", + "run_lintian": False, + "scp_compress": False, + "allowed-distribution": {}, + "codenames": "ubuntu", + "hooks": [ + "check-debs", + "nobug", + "badcontent", + "checksum", + "required-fields", + "ppaforppaonly", + "suite-mismatch", + "badauthor", + "placeholderbug", + "gpg", + "releasemismatch", + ], + "run_dinstall": False, + "check_version": False, + "progress_indicator": "2", + "fqdn": "ppa.launchpad.net", + "incoming": "~johndoe/testing", + "required-fields": {"skip": True}, + "supported-distribution": { + "allowed": ["release"], + "known": ["release", "proposed", "updates", "backports", "security"], + }, + "check-debs": {"enforce": "source", "skip": False}, + "valid_commands": [], + "ppa": "johndoe/testing", +} + def test_check_missing_ubuntu_maintainer(): ubuntu_lint.check_missing_ubuntu_maintainer( @@ -221,6 +355,26 @@ def test_check_distribution_invalid(): ) +def test_check_ppa_version_string(): + ubuntu_lint.check_ppa_version_string( + context=ubuntu_lint.Context(changes=basic_changes_ubuntu_delta, profile=archive_upload_profile) + ) + + with pytest.raises(ubuntu_lint.LintFailure): + ubuntu_lint.check_ppa_version_string( + context=ubuntu_lint.Context(changes=basic_changes_ubuntu_delta, profile=ppa_upload_profile) + ) + + ubuntu_lint.check_ppa_version_string( + context=ubuntu_lint.Context(changes=basic_changes_ppa, profile=ppa_upload_profile) + ) + + with pytest.raises(ubuntu_lint.LintFailure): + ubuntu_lint.check_ppa_version_string( + context=ubuntu_lint.Context(changes=basic_changes_ppa, profile=archive_upload_profile) + ) + + def test_check_sru_version_string_breaks_upgrades(requests_mock): package = basic_changes_sru.get("Source") diff --git a/ubuntu_lint/__init__.py b/ubuntu_lint/__init__.py index 4bbe6aa..ae3bcb8 100644 --- a/ubuntu_lint/__init__.py +++ b/ubuntu_lint/__init__.py @@ -13,6 +13,7 @@ check_distribution_invalid, check_missing_git_ubuntu_references, check_missing_pending_changelog_entry, + check_ppa_version_string, check_sru_bug_missing_template, check_sru_bug_missing_release_tasks, check_sru_version_string_breaks_upgrades, @@ -29,6 +30,7 @@ "check_distribution_invalid", "check_missing_git_ubuntu_references", "check_missing_pending_changelog_entry", + "check_ppa_version_string", "check_sru_bug_missing_template", "check_sru_bug_missing_release_tasks", "check_sru_version_string_breaks_upgrades", diff --git a/ubuntu_lint/cli.py b/ubuntu_lint/cli.py index b460000..17eb6e9 100644 --- a/ubuntu_lint/cli.py +++ b/ubuntu_lint/cli.py @@ -16,6 +16,7 @@ class Runner: "missing-launchpad-bugs-fixed": ubuntu_lint.check_missing_launchpad_bugs_fixed, "missing-pending-changelog-entry": ubuntu_lint.check_missing_pending_changelog_entry, "missing-ubuntu-maintainer": ubuntu_lint.check_missing_ubuntu_maintainer, + "ppa-version-string": ubuntu_lint.check_ppa_version_string, "sru-bug-missing-template": ubuntu_lint.check_sru_bug_missing_template, "sru-bug-missing-release-tasks": ubuntu_lint.check_sru_bug_missing_release_tasks, "sru-version-string-breaks-upgrades": ubuntu_lint.check_sru_version_string_breaks_upgrades, @@ -30,6 +31,7 @@ class Runner: "missing-launchpad-bugs-fixed": "warn", "missing-pending-changelog-entry": "warn", "missing-ubuntu-maintainer": "fail", + "ppa-version-string": "fail", "sru-bug-missing-template": "off", "sru-bug-missing-release-tasks": "off", "sru-version-string-breaks-upgrades": "off", @@ -44,6 +46,7 @@ class Runner: "missing-launchpad-bugs-fixed": "fail", "missing-pending-changelog-entry": "fail", "missing-ubuntu-maintainer": "fail", + "ppa-version-string": "fail", "sru-bug-missing-template": "warn", "sru-bug-missing-release-tasks": "warn", "sru-version-string-breaks-upgrades": "warn", diff --git a/ubuntu_lint/context.py b/ubuntu_lint/context.py index ab11792..4837189 100644 --- a/ubuntu_lint/context.py +++ b/ubuntu_lint/context.py @@ -36,6 +36,7 @@ class Context: def __init__( self, changes: str | deb822.Changes | None = None, + profile: dict | None = None, debian_changelog: str | changelog.Changelog | None = None, launchpad_handle: Launchpad | None = None, source_dir: str | None = None, @@ -58,6 +59,10 @@ def __init__( elif changes is not None: raise ValueError("invalid type for changes") + self._profile: dict | None = None + if profile is not None: + self._profile = profile + self._changelog: changelog.Changelog | None = None if isinstance(debian_changelog, str): with open(debian_changelog, "r") as f: @@ -84,6 +89,14 @@ def changes(self) -> deb822.Changes: return self._changes + @property + def profile(self) -> dict: + if not self._profile: + raise MissingContextException("missing context for upload profile") + assert self._profile is not None + + return self._profile + def changelog_entry_by_index(self, index: int) -> changelog.ChangeBlock: if not self._changelog: raise MissingContextException("missing context for changelog entry") diff --git a/ubuntu_lint/dput.py b/ubuntu_lint/dput.py index 937d350..69f91c7 100644 --- a/ubuntu_lint/dput.py +++ b/ubuntu_lint/dput.py @@ -16,7 +16,7 @@ def call_lint_as_hook( interface: CLInterface, can_ignore: bool = False, ): - context = ubuntu_lint.Context(changes=changes.get_raw_changes()) + context = ubuntu_lint.Context(changes=changes.get_raw_changes(), profile=profile) try: lint(context) except ubuntu_lint.LintFailure as e: @@ -26,6 +26,20 @@ def call_lint_as_hook( raise HookException(f"ERROR: {msg}") +def dput_ppa_version_string( + changes: Changes, profile: dict, interface: CLInterface +): + """ + Hook wrapper around ubuntu_lint.check_ppa_version_string. + """ + call_lint_as_hook( + ubuntu_lint.check_ppa_version_string, + changes, + profile, + interface, + ) + + def dput_missing_launchpad_bugs_fixed( changes: Changes, profile: dict, interface: CLInterface ): diff --git a/ubuntu_lint/linters.py b/ubuntu_lint/linters.py index db8a2e9..6ce786d 100644 --- a/ubuntu_lint/linters.py +++ b/ubuntu_lint/linters.py @@ -133,6 +133,20 @@ def check_missing_pending_changelog_entry(context: Context): ) +def check_ppa_version_string(context: Context): + """ + For any upload to the archive, check that ~ppa is not present in the + version string. For uploads to PPAs, check that ~ppa is present. + """ + version = context.changes.get_as_string("Version") + target = context.profile.get("name") + + if target == "ppa" and "~ppa" not in version: + context.lint_fail("upload to ppa does not include ~ppa in version string") + elif target == "ubuntu" and "~ppa" in version: + context.lint_fail("upload to archive includes ~ppa in version string") + + def check_sru_bug_missing_template(context: Context): """ For uploads to stable releases, checks that bugs referenced in the changes