Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions dput.d/hooks/ppa-version-string.json
Original file line number Diff line number Diff line change
@@ -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
}
5 changes: 5 additions & 0 deletions dput.d/profiles/ppa.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"+hooks": [
"ppa-version-string"
]
}
1 change: 1 addition & 0 deletions dput.d/profiles/ubuntu.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
154 changes: 154 additions & 0 deletions tests/test_linters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 <ubuntu-devel-discuss@lists.ubuntu.com>
Changed-By: John Doe <john.doe@example.com>
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 <sanvila@debian.org>
""")

basic_changes_sru = deb822.Changes("""
Format: 1.8
Date: Wed, 11 Mar 2026 16:01:41 -0400
Expand Down Expand Up @@ -103,6 +136,107 @@
Original-Maintainer: Santiago Vila <sanvila@debian.org>
""")

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(
Expand Down Expand Up @@ -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")

Expand Down
2 changes: 2 additions & 0 deletions ubuntu_lint/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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",
Expand Down
3 changes: 3 additions & 0 deletions ubuntu_lint/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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",
Expand All @@ -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",
Expand Down
13 changes: 13 additions & 0 deletions ubuntu_lint/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Context:
def __init__(
self,
changes: str | deb822.Changes | None = None,
profile: dict | None = None,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, I am not sure it makes sense to add this to Context. It's mean to wrap generic things about the package itself. But this check is really a pure dput hook; it doesn't make sense to run this check except when uploading, because it depends on the upload target.

It would be different than the other checks so far, but I think it might make more sense to write this as a pure hook in dput.py.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree it doesn't feel great to add this to context for essentially a single hook, but adding this as a pure hook in dput.py also breaks with the existing architecture of this repo.

I would argue enlarging the context object makes more sense than essentially bypassing half your work by adding a pure hook, since the upload target is context and arguably could be used by other hooks, but I'll leave it up to you which way you prefer the implementation.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's go with the pure dput hook for now. It doesn't really feel like it's bypassing anything substantial to me.

Also, the dput hooks may ultimately be upstreamed to dput-ng anyways. That's part of why they are separated like this

debian_changelog: str | changelog.Changelog | None = None,
launchpad_handle: Launchpad | None = None,
source_dir: str | None = None,
Expand All @@ -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:
Expand All @@ -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")
Expand Down
16 changes: 15 additions & 1 deletion ubuntu_lint/dput.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
):
Expand Down
14 changes: 14 additions & 0 deletions ubuntu_lint/linters.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down