Package: release.debian.org
Severity: normal
User: release.debian.org@packages.debian.org
Usertags: unblock
X-Debbugs-Cc: devscripts@packages.debian.org
Control: affects -1 + src:devscripts
Please unblock package devscripts
[ Reason ]
2.23.3 is only bugfix, including one RC.
Also, it fixes the description generation.
[ Tests ]
most of the changes include tests, and autopkgtest passed great.
[ Risks ]
the changes are quite trivial by themselves.
[ Checklist ]
[x] all changes are documented in the d/changelog
[x] I reviewed all changes and I approve them
[x] attach debdiff against the package in testing
unblock devscripts/2.23.3
--
regards,
Mattia Rizzolo
GPG Key: 66AE 2B4A FCCF 3F52 DA18 4D18 4B04 3FCD B944 4540 .''`.
More about me: https://mapreri.org : :' :
Launchpad user: https://launchpad.net/~mapreri `. `'`
Debian QA page: https://qa.debian.org/developer.php?login=mattia `-
diffstat for devscripts-2.23.2 devscripts-2.23.3
README | 2
debian/changelog | 29
debian/control | 5
debian/rules | 2
lib/Devscripts/Salsa/Hooks.pm | 246 +------
lib/Devscripts/Salsa/check_repo.pm | 16
po4a/po/de.po | 5
po4a/po/devscripts.pot | 5
po4a/po/fr.po | 8
po4a/po/pt.po | 7
scripts/bts.pl | 2
scripts/deb-janitor | 6
scripts/debootsnap | 37 -
scripts/debootsnap.py | 694 ++++++++++++++++++++++
scripts/devscripts/test/test_debootsnap.py | 56 +
scripts/devscripts/test/test_suspicious_source.py | 41 +
scripts/edit-patch.sh | 12
scripts/sadt | 2
scripts/suspicious-source | 2
19 files changed, 929 insertions(+), 248 deletions(-)
diff -Nru devscripts-2.23.2/debian/changelog devscripts-2.23.3/debian/changelog
--- devscripts-2.23.2/debian/changelog 2023-02-19 00:56:21.000000000 +0100
+++ devscripts-2.23.3/debian/changelog 2023-03-15 23:52:52.000000000 +0100
@@ -1,3 +1,32 @@
+devscripts (2.23.3) unstable; urgency=medium
+
+ [ Samuel Henrique ]
+ * Fix generation of the extended description (Closes: #1032337)
+
+ [ Benjamin Drung ]
+ * Fix complaints from pylint 2.16.2
+ * suspicious-source: Fix MIME type name for Python code
+ * Add myself to uploaders
+
+ [ Zixing Liu ]
+ * Salsa/check_repo: avoid dependency on Digest::MD5::File (LP: #2007279)
+ * Salsa/Hooks: using if-elsif chains to avoid Switch which is a deprecated
+ package (LP: #2007279)
+
+ [ Johannes Schauer Marin Rodrigues ]
+ * debootsnap:
+ - check to make sure that equivs-build, apt-ftparchive, mmdebstrap,
+ apt-get and dpkg-name exist
+ - allow reading package list from a file
+
+ [ Rémy Martin ]
+ * edit-patch: Fix failure on creating new patch (LP: #1222364)
+
+ [ Paul Wise ]
+ * bts: Fix mangled UTF-8 name
+
+ -- Benjamin Drung <bdrung@debian.org> Wed, 15 Mar 2023 23:52:52 +0100
+
devscripts (2.23.2) unstable; urgency=medium
* Team upload.
diff -Nru devscripts-2.23.2/debian/control devscripts-2.23.3/debian/control
--- devscripts-2.23.2/debian/control 2023-02-05 01:14:44.000000000 +0100
+++ devscripts-2.23.3/debian/control 2023-03-15 23:36:26.000000000 +0100
@@ -4,6 +4,7 @@
Maintainer: Devscripts Maintainers <devscripts@packages.debian.org>
Uploaders:
Mattia Rizzolo <mattia@debian.org>,
+ Benjamin Drung <bdrung@debian.org>,
Build-Depends:
autodep8 <!nocheck>,
bash-completion,
@@ -18,7 +19,6 @@
gnupg <!nocheck> | gnupg2 <!nocheck>,
help2man,
isort <!nocheck>,
- libdigest-md5-file-perl <!nocheck>,
libdistro-info-perl <!nocheck>,
libdpkg-perl <!nocheck>,
libfile-desktopentry-perl <!nocheck>,
@@ -32,7 +32,6 @@
liblist-compare-perl <!nocheck>,
libmoo-perl <!nocheck>,
libstring-shellquote-perl <!nocheck>,
- libswitch-perl <!nocheck>,
libtest-output-perl <!nocheck>,
libtimedate-perl <!nocheck>,
libtry-tiny-perl <!nocheck>,
@@ -78,14 +77,12 @@
file,
gnupg | gnupg2,
gpgv | gpgv2,
- libdigest-md5-file-perl,
libfile-dirlist-perl,
libfile-homedir-perl,
libfile-touch-perl,
libfile-which-perl,
libipc-run-perl,
libmoo-perl,
- libswitch-perl,
libwww-perl,
patchutils,
sensible-utils,
diff -Nru devscripts-2.23.2/debian/rules devscripts-2.23.3/debian/rules
--- devscripts-2.23.2/debian/rules 2023-02-10 09:57:47.000000000 +0100
+++ devscripts-2.23.3/debian/rules 2023-02-27 16:46:00.000000000 +0100
@@ -14,4 +14,4 @@
override_dh_gencontrol:
dh_gencontrol -- $(SUBSTVARS) \
- -V"devscripts:LongDesc=$$(cat README| awk '/^- annotate-output/,/^ exim script for sorting//'|sed -e '/^[[:space:]]*$$/d' -e 's/^/ /g')"
+ -V"devscripts:LongDesc=$$(cat README | awk '/^- annotate-output/,/^ exim script for sorting/' | sed -e '/^[[:space:]]*$$/d' -e 's/^/ /g')"
diff -Nru devscripts-2.23.2/lib/Devscripts/Salsa/check_repo.pm devscripts-2.23.3/lib/Devscripts/Salsa/check_repo.pm
--- devscripts-2.23.2/lib/Devscripts/Salsa/check_repo.pm 2023-02-05 00:33:58.000000000 +0100
+++ devscripts-2.23.3/lib/Devscripts/Salsa/check_repo.pm 2023-03-15 16:51:50.000000000 +0100
@@ -3,7 +3,9 @@
use strict;
use Devscripts::Output;
-use Digest::MD5::File qw(file_md5_hex url_md5_hex);
+use Digest::MD5 qw(md5_hex);
+use Digest::file qw(digest_file_hex);
+use LWP::UserAgent;
use Moo::Role;
with "Devscripts::Salsa::Repo";
@@ -14,6 +16,14 @@
return $res;
}
+sub _url_md5_hex {
+ my $res = LWP::UserAgent->new->get(shift());
+ if (!$res->is_success) {
+ return undef;
+ }
+ return Digest::MD5::md5_hex($res->content);
+}
+
sub _check_repo {
my ($self, @reponames) = @_;
my $res = 0;
@@ -101,11 +111,11 @@
my ($md5_file, $md5_url) = "";
if ($prms_multipart{avatar}) {
ds_verbose "Calculating local checksum";
- $md5_file = file_md5_hex($prms_multipart{avatar})
+ $md5_file = digest_file_hex($prms_multipart{avatar}, "MD5")
or die "$prms_multipart{avatar} failed md5: $!";
if ($project->{avatar_url}) {
ds_verbose "Calculating remote checksum";
- $md5_url = url_md5_hex($project->{avatar_url})
+ $md5_url = _url_md5_hex($project->{avatar_url})
or die "$project->{avatar_url} failed md5: $!";
}
push @err, "Will set the avatar to be: $prms_multipart{avatar}"
diff -Nru devscripts-2.23.2/lib/Devscripts/Salsa/Hooks.pm devscripts-2.23.3/lib/Devscripts/Salsa/Hooks.pm
--- devscripts-2.23.2/lib/Devscripts/Salsa/Hooks.pm 2023-02-05 00:33:58.000000000 +0100
+++ devscripts-2.23.3/lib/Devscripts/Salsa/Hooks.pm 2023-03-15 16:51:50.000000000 +0100
@@ -4,7 +4,6 @@
use strict;
use Devscripts::Output;
use Moo::Role;
-use Switch;
sub add_hooks {
my ($self, $repo_id, $repo) = @_;
@@ -187,6 +186,23 @@
return $res;
}
+sub _check_config {
+ my ($config, $key_name, $config_name, $can_be_private, $res_ref) = @_;
+ if (!$config) { return undef; }
+ for ($config) {
+ if ($can_be_private && ($_ eq "private")) { push @$res_ref, $key_name => "private"; }
+ elsif (qr/y(es)?|true|enabled?/) {
+ push @$res_ref, $key_name => "enabled";
+ }
+ elsif (qr/no?|false|disabled?/) {
+ push @$res_ref, $key_name => "disabled";
+ }
+ else {
+ print "error with SALSA_$config_name";
+ }
+ }
+}
+
sub desc {
my ($self, $repo) = @_;
my @res = ();
@@ -200,217 +216,25 @@
if ($self->config->build_timeout) {
push @res, build_timeout => $self->config->build_timeout;
}
- if ($self->config->issues) {
- switch ($self->config->issues) {
- case "private" { push @res, issues_access_level => "private"; }
- case qr/y(es)?|true|enabled?/ {
- push @res, issues_access_level => "enabled";
- }
- case qr/no?|false|disabled?/ {
- push @res, issues_access_level => "disabled";
- } else {
- print "error with SALSA_ENABLE_ISSUES";
- }
- }
- }
- if ($self->config->repo) {
- switch ($self->config->repo) {
- case "private" { push @res, repository_access_level => "private"; }
- case qr/y(es)?|true|enabled?/ {
- push @res, repository_access_level => "enabled";
- }
- case qr/no?|false|disabled?/ {
- push @res, repository_access_level => "disabled";
- } else {
- print "error with SALSA_ENABLE_REPO";
- }
- }
- }
- if ($self->config->mr) {
- switch ($self->config->mr) {
- case "private" {
- push @res, merge_requests_access_level => "private";
- }
- case qr/y(es)?|true|enabled?/ {
- push @res, merge_requests_access_level => "enabled";
- }
- case qr/no?|false|disabled?/ {
- push @res, merge_requests_access_level => "disabled";
- } else {
- print "error with SALSA_ENABLE_MR";
- }
- }
- }
- if ($self->config->forks) {
- switch ($self->config->forks) {
- case "private" { push @res, forking_access_level => "private"; }
- case qr/y(es)?|true|enabled?/ {
- push @res, forking_access_level => "enabled";
- }
- case qr/no?|false|disabled?/ {
- push @res, forking_access_level => "disabled";
- } else {
- print "error with SALSA_ENABLE_FORKS";
- }
- }
- }
- if ($self->config->lfs) {
- switch ($self->config->lfs) {
- case qr/y(es)?|true|enabled?/ {
- push @res, lfs_enabled => "enabled";
- }
- case qr/no?|false|disabled?/ {
- push @res, lfs_enabled => "disabled";
- } else {
- print "error with SALSA_ENABLE_LFS";
- }
- }
- }
- if ($self->config->packages) {
- switch ($self->config->packages) {
- case qr/y(es)?|true|enabled?/ {
- push @res, packages_enabled => "enabled";
- }
- case qr/no?|false|disabled?/ {
- push @res, packages_enabled => "disabled";
- } else {
- print "error with SALSA_ENABLE_PACKAGES";
- }
- }
- }
- if ($self->config->jobs) {
- switch ($self->config->jobs) {
- case "private" { push @res, builds_access_level => "private"; }
- case qr/y(es)?|true|enabled?/ {
- push @res, builds_access_level => "enabled";
- }
- case qr/no?|false|disabled?/ {
- push @res, builds_access_level => "disabled";
- } else {
- print "error with SALSA_ENABLE_JOBS";
- }
- }
- }
- if ($self->config->container) {
- switch ($self->config->container) {
- case "private" {
- push @res, container_registry_access_level => "private";
- }
- case qr/y(es)?|true|enabled?/ {
- push @res, container_registry_access_level => "enabled";
- }
- case qr/no?|false|disabled?/ {
- push @res, container_registry_access_level => "disabled";
- } else {
- print "error with SALSA_ENABLE_CONTAINER";
- }
- }
- }
- if ($self->config->analytics) {
- switch ($self->config->analytics) {
- case "private" { push @res, analytics_access_level => "private"; }
- case qr/y(es)?|true|enabled?/ {
- push @res, analytics_access_level => "enabled";
- }
- case qr/no?|false|disabled?/ {
- push @res, analytics_access_level => "disabled";
- } else {
- print "error with SALSA_ENABLE_ANALYTICS";
- }
- }
- }
- if ($self->config->requirements) {
- switch ($self->config->requirements) {
- case "private" {
- push @res, requirements_access_level => "private";
- }
- case qr/y(es)?|true|enabled?/ {
- push @res, requirements_access_level => "enabled";
- }
- case qr/no?|false|disabled?/ {
- push @res, requirements_access_level => "disabled";
- } else {
- print "error with SALSA_ENABLE_REQUIREMENTS";
- }
- }
- }
- if ($self->config->wiki) {
- switch ($self->config->wiki) {
- case "private" { push @res, wiki_access_level => "private"; }
- case qr/y(es)?|true|enabled?/ {
- push @res, wiki_access_level => "enabled";
- }
- case qr/no?|false|disabled?/ {
- push @res, wiki_access_level => "disabled";
- } else {
- print "error with SALSA_ENABLE_WIKI";
- }
- }
- }
- if ($self->config->snippets) {
- switch ($self->config->snippets) {
- case "private" { push @res, snippets_access_level => "private"; }
- case qr/y(es)?|true|enabled?/ {
- push @res, snippets_access_level => "enabled";
- }
- case qr/no?|false|disabled?/ {
- push @res, snippets_access_level => "disabled";
- } else {
- print "error with SALSA_ENABLE_SNIPPETS";
- }
- }
- }
- if ($self->config->pages) {
- switch ($self->config->pages) {
- case "private" { push @res, pages_access_level => "private"; }
- case qr/y(es)?|true|enabled?/ {
- push @res, pages_access_level => "enabled";
- }
- case qr/no?|false|disabled?/ {
- push @res, pages_access_level => "disabled";
- } else {
- print "error with SALSA_ENABLE_PAGES";
- }
- }
- }
- if ($self->config->releases) {
- switch ($self->config->releases) {
- case "private" { push @res, releases_access_level => "private"; }
- case qr/y(es)?|true|enabled?/ {
- push @res, releases_access_level => "enabled";
- }
- case qr/no?|false|disabled?/ {
- push @res, releases_access_level => "disabled";
- } else {
- print "error with SALSA_ENABLE_RELEASES";
- }
- }
- }
- if ($self->config->auto_devops) {
- switch ($self->config->auto_devops) {
- case qr/y(es)?|true|enabled?/ {
- push @res, auto_devops_enabled => "enabled";
- }
- case qr/no?|false|disabled?/ {
- push @res, auto_devops_enabled => "disabled";
- } else {
- print "error with SALSA_ENABLE_AUTO_DEVOPS";
- }
- }
- }
- if ($self->config->request_acc) {
- switch ($self->config->request_acc) {
- case qr/y(es)?|true|enabled?/ {
- push @res, request_access_enabled => "enabled";
- }
- case qr/no?|false|disabled?/ {
- push @res, request_access_enabled => "disabled";
- } else {
- print "error with SALSA_ENABLE_REQUEST_ACC";
- }
- }
- }
+ # config value key name config name has private
+ _check_config( $self->config->issues, "issues_access_level", "ENABLE_ISSUES", 1, \@res );
+ _check_config( $self->config->repo, "repository_access_level", "ENABLE_REPO", 1, \@res );
+ _check_config( $self->config->mr, "merge_requests_access_level", "ENABLE_MR", 1, \@res );
+ _check_config( $self->config->forks, "forking_access_level", "ENABLE_FORKS", 1, \@res );
+ _check_config( $self->config->lfs, "lfs_enabled", "ENABLE_LFS", 0, \@res );
+ _check_config( $self->config->packages, "packages_enabled", "ENABLE_PACKAGES", 0, \@res );
+ _check_config( $self->config->jobs, "builds_access_level", "ENABLE_JOBS", 1, \@res );
+ _check_config( $self->config->container, "container_registry_access_level", "ENABLE_CONTAINER", 1, \@res );
+ _check_config( $self->config->analytics, "analytics_access_level", "ENABLE_ANALYTICS", 1, \@res );
+ _check_config( $self->config->requirements, "requirements_access_level", "ENABLE_REQUIREMENTS", 1, \@res );
+ _check_config( $self->config->wiki, "wiki_access_level", "ENABLE_WIKI", 1, \@res );
+ _check_config( $self->config->snippets, "snippets_access_level", "ENABLE_SNIPPETS", 1, \@res );
+ _check_config( $self->config->pages, "pages_access_level", "ENABLE_PAGES", 1, \@res );
+ _check_config( $self->config->releases, "releases_access_level", "ENABLE_RELEASES", 1, \@res );
+ _check_config( $self->config->auto_devops, "auto_devops_enabled", "ENABLE_AUTO_DEVOPS", 0, \@res );
+ _check_config( $self->config->request_acc, "request_access_enabled", "ENABLE_REQUEST_ACC", 0, \@res );
+
if ($self->config->disable_remove_branch) {
push @res, remove_source_branch_after_merge => 0;
} elsif ($self->config->enable_remove_branch) {
diff -Nru devscripts-2.23.2/po4a/po/de.po devscripts-2.23.3/po4a/po/de.po
--- devscripts-2.23.2/po4a/po/de.po 2023-02-19 00:55:44.000000000 +0100
+++ devscripts-2.23.3/po4a/po/de.po 2023-03-15 23:52:50.000000000 +0100
@@ -7,7 +7,7 @@
msgstr ""
"Project-Id-Version: devscripts 2.18.9\n"
"Report-Msgid-Bugs-To: devscripts@packages.debian.org\n"
-"POT-Creation-Date: 2023-02-19 00:34+0100\n"
+"POT-Creation-Date: 2023-03-15 23:43+0100\n"
"PO-Revision-Date: 2020-04-25 23:04+0200\n"
"Last-Translator: Chris Leick <c.leick@vollbio.de>\n"
"Language-Team: de <debian-l10n-german@lists.debian.org>\n"
@@ -14068,7 +14068,8 @@
"exactly the requested selection of packages. This can be used to re-create a "
"chroot from the past, for example to reproduce a bug. The tool is also used "
"by debrebuild to build a package in a chroot with build dependencies in the "
-"same version as recorded in the buildinfo file. [python3-pycurl, mmdebstrap]"
+"same version as recorded in the buildinfo file. [apt-utils, dpkg-dev, "
+"equivs, mmdebstrap, python3-pycurl]"
msgstr ""
#. type: IP
diff -Nru devscripts-2.23.2/po4a/po/devscripts.pot devscripts-2.23.3/po4a/po/devscripts.pot
--- devscripts-2.23.2/po4a/po/devscripts.pot 2023-02-19 00:56:21.000000000 +0100
+++ devscripts-2.23.3/po4a/po/devscripts.pot 2023-03-15 23:52:52.000000000 +0100
@@ -7,7 +7,7 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
-"POT-Creation-Date: 2023-02-19 00:34+0100\n"
+"POT-Creation-Date: 2023-03-15 23:43+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -11228,7 +11228,8 @@
"exactly the requested selection of packages. This can be used to re-create a "
"chroot from the past, for example to reproduce a bug. The tool is also used "
"by debrebuild to build a package in a chroot with build dependencies in the "
-"same version as recorded in the buildinfo file. [python3-pycurl, mmdebstrap]"
+"same version as recorded in the buildinfo file. [apt-utils, dpkg-dev, "
+"equivs, mmdebstrap, python3-pycurl]"
msgstr ""
#. type: IP
diff -Nru devscripts-2.23.2/po4a/po/fr.po devscripts-2.23.3/po4a/po/fr.po
--- devscripts-2.23.2/po4a/po/fr.po 2023-02-19 00:55:44.000000000 +0100
+++ devscripts-2.23.3/po4a/po/fr.po 2023-03-15 23:52:50.000000000 +0100
@@ -14,7 +14,7 @@
msgid ""
msgstr ""
"Project-Id-Version: devscripts\n"
-"POT-Creation-Date: 2023-02-19 00:34+0100\n"
+"POT-Creation-Date: 2023-03-15 23:43+0100\n"
"PO-Revision-Date: 2023-02-10 18:09+0400\n"
"Last-Translator: Xavier Guimard <yadd@debian.org>\n"
"Language-Team: French <debian-l10n-french@lists.debian.org>\n"
@@ -14052,14 +14052,16 @@
"exactly the requested selection of packages. This can be used to re-create a "
"chroot from the past, for example to reproduce a bug. The tool is also used "
"by debrebuild to build a package in a chroot with build dependencies in the "
-"same version as recorded in the buildinfo file. [python3-pycurl, mmdebstrap]"
+"same version as recorded in the buildinfo file. [apt-utils, dpkg-dev, "
+"equivs, mmdebstrap, python3-pycurl]"
msgstr ""
"Combine debootstrap and snapshot.debian.org pour créer un B<chroot> "
"contenant exactement la sélection de paquets demandés. Ceci peut être "
"utilisé pour recréer un chroot passé, par exemple pour reproduire un bogue. "
"Cet outil est également utilisé par B<debrebuild> pour construire un paquet "
"dont les dépendances de construction sont les mêmes que celles enregistrées "
-"dans le fichier buildinfo. [python3-pycurl, mmdebstrap]"
+"dans le fichier buildinfo. [apt-utils, dpkg-dev, equivs, mmdebstrap, "
+"python3-pycurl]"
#. type: IP
#: ../doc/devscripts.1:75
diff -Nru devscripts-2.23.2/po4a/po/pt.po devscripts-2.23.3/po4a/po/pt.po
--- devscripts-2.23.2/po4a/po/pt.po 2023-02-19 00:55:44.000000000 +0100
+++ devscripts-2.23.3/po4a/po/pt.po 2023-03-15 23:52:50.000000000 +0100
@@ -6,7 +6,7 @@
msgid ""
msgstr ""
"Project-Id-Version: devscripts 2.22.2\n"
-"POT-Creation-Date: 2023-02-19 00:34+0100\n"
+"POT-Creation-Date: 2023-03-15 23:43+0100\n"
"PO-Revision-Date: 2022-09-04 23:47+0100\n"
"Last-Translator: Américo Monteiro <a_monteiro@gmx.com>\n"
"Language-Team: Portuguese <>\n"
@@ -13819,14 +13819,15 @@
"exactly the requested selection of packages. This can be used to re-create a "
"chroot from the past, for example to reproduce a bug. The tool is also used "
"by debrebuild to build a package in a chroot with build dependencies in the "
-"same version as recorded in the buildinfo file. [python3-pycurl, mmdebstrap]"
+"same version as recorded in the buildinfo file. [apt-utils, dpkg-dev, "
+"equivs, mmdebstrap, python3-pycurl]"
msgstr ""
"Combina debootstrap e snapshot.debian.org para criar uma chroot que contém "
"exactamente a selecção de pacotes requerida. Isto pode ser usado para re-"
"criar uma chroot do passado, por exemplo para reproduzir um bug. A "
"ferramenta é também usada pelo debrebuild para compilar um pacote numa "
"chroot com dependências de compilação na mesma versão como registado no "
-"ficheiro buildinfo. [python3-pycurl, mmdebstrap]"
+"ficheiro buildinfo. [apt-utils, dpkg-dev, equivs, mmdebstrap, python3-pycurl]"
#. type: IP
#: ../doc/devscripts.1:75
diff -Nru devscripts-2.23.2/README devscripts-2.23.3/README
--- devscripts-2.23.2/README 2023-02-05 00:33:58.000000000 +0100
+++ devscripts-2.23.3/README 2023-03-15 23:35:37.000000000 +0100
@@ -113,7 +113,7 @@
to re-create a chroot from the past, for example to reproduce a bug. The
tool is also used by debrebuild to build a package in a chroot with build
dependencies in the same version as recorded in the buildinfo file.
- [python3-pycurl, mmdebstrap]
+ [apt-utils, dpkg-dev, equivs, mmdebstrap, python3-pycurl]
- debpkg: A wrapper for dpkg used by debi to allow convenient testing
of packages. For debpkg to work, it needs to be made setuid root,
diff -Nru devscripts-2.23.2/scripts/bts.pl devscripts-2.23.3/scripts/bts.pl
--- devscripts-2.23.2/scripts/bts.pl 2023-02-05 00:33:58.000000000 +0100
+++ devscripts-2.23.3/scripts/bts.pl 2023-03-15 23:42:05.000000000 +0100
@@ -3000,7 +3000,7 @@
# The following routines are taken from a patched version of MIME::Words
# posted at http://mail.nl.linux.org/linux-utf8/2002-01/msg00242.html
-# by Richard =?utf-8?B?xIxlcGFz?= (Chepas) <rch@richard.eu.org>
+# by Richard Čepas (Chepas) <rch@richard.eu.org>
sub MIME_encode_B {
my $str = shift;
diff -Nru devscripts-2.23.2/scripts/deb-janitor devscripts-2.23.3/scripts/deb-janitor
--- devscripts-2.23.2/scripts/deb-janitor 2023-02-05 00:33:58.000000000 +0100
+++ devscripts-2.23.3/scripts/deb-janitor 2023-03-01 00:23:53.000000000 +0100
@@ -102,8 +102,7 @@
if err.code == 404:
raise MissingDiffError(err.read().decode()) from err
raise err
- else:
- return data
+ return data
def merge(
@@ -299,8 +298,7 @@
except MissingDiffError as err:
logging.fatal("%s", err.args[0])
return 1
- else:
- return 0
+ return 0
if args.subcommand == "merge":
source = _get_local_source()
return merge(source, args.campaign, api_url=args.api_url)
diff -Nru devscripts-2.23.2/scripts/debootsnap devscripts-2.23.3/scripts/debootsnap
--- devscripts-2.23.2/scripts/debootsnap 2023-02-18 23:51:19.000000000 +0100
+++ devscripts-2.23.3/scripts/debootsnap 2023-03-15 23:35:37.000000000 +0100
@@ -28,6 +28,7 @@
import dataclasses
import http.server
import os
+import pathlib
import re
import shutil
import socketserver
@@ -59,6 +60,10 @@
pass
+class RetryCountExceeded(Exception):
+ pass
+
+
# pylint: disable=c-extension-no-member
class Proxy(http.server.SimpleHTTPRequestHandler):
last_request = None
@@ -189,7 +194,7 @@
# restart from the beginning or otherwise, the result might
# include a varnish cache error message
else:
- raise Exception("failed too often...")
+ raise RetryCountExceeded("failed too often...")
@dataclasses.dataclass
@@ -225,6 +230,12 @@
def parse_pkgs(val):
if val == "-":
val = sys.stdin.read()
+ if val.startswith("./") or val.startswith("/"):
+ val = pathlib.Path(val)
+ if not val.exists():
+ print(f"{val} does not exist", file=sys.stderr)
+ sys.exit(1)
+ val = val.read_text(encoding="utf8")
pkgs = []
pattern = re.compile(
r"""
@@ -248,7 +259,7 @@
return [pkgs]
-def parse_args():
+def parse_args(args: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(
formatter_class=argparse.RawDescriptionHelpFormatter,
description="""\
@@ -310,7 +321,8 @@
type=parse_pkgs,
help="list of packages, optional architecture and version, separated "
"by comma or linebreak. Read list from standard input if value is "
- '"-". The option can be specified multiple times. Package name, '
+ '"-". Read list from a file if value starts with "./" or "/". The '
+ "option can be specified multiple times. Package name, "
"version and architecture are separated by one or more characters "
"that are not legal in the respective adjacent field. Leading and "
"trailing illegal characters are allowed. Example: "
@@ -325,7 +337,7 @@
parser.add_argument(
"output", nargs="?", default="-", help="path to output chroot tarball"
)
- return parser.parse_args()
+ return parser.parse_args(args)
def query_metasnap(pkgsleft, archive, nativearch):
@@ -612,8 +624,8 @@
shutil.move(tmpdir2 + "/" + debs[0], tmpdirname + "/cache")
-def main():
- args = parse_args()
+def main(arguments: list[str]) -> None:
+ args = parse_args(arguments)
if args.packages:
pkgs = [v for sublist in args.packages for v in sublist]
if args.architecture is None:
@@ -646,6 +658,17 @@
if a != nativearch:
foreignarches.add(a)
+ for tool in [
+ "equivs-build",
+ "apt-ftparchive",
+ "mmdebstrap",
+ "apt-get",
+ "dpkg-name",
+ ]:
+ if shutil.which(tool) is None:
+ print(f"{tool} is required but not installed", file=sys.stderr)
+ sys.exit(1)
+
sources = compute_sources(pkgs, nativearch, args.ignore_notfound)
if args.sources_list_only:
@@ -668,4 +691,4 @@
if __name__ == "__main__":
- main()
+ main(sys.argv[1:])
diff -Nru devscripts-2.23.2/scripts/debootsnap.py devscripts-2.23.3/scripts/debootsnap.py
--- devscripts-2.23.2/scripts/debootsnap.py 1970-01-01 01:00:00.000000000 +0100
+++ devscripts-2.23.3/scripts/debootsnap.py 2023-03-15 23:35:37.000000000 +0100
@@ -0,0 +1,694 @@
+#!/usr/bin/env python3
+#
+# Copyright 2021 Johannes Schauer Marin Rodrigues <josch@debian.org>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+
+# This tool is similar to debootstrap but is able to recreate a chroot
+# containing precisely the given package and version selection. The package
+# list is expected on standard input and may be of the format produced by:
+#
+# dpkg-query --showformat '${binary:Package}=${Version}\n' --show
+
+# The name was suggested by Adrian Bunk as a portmanteau of debootstrap and
+# snapshot.debian.org.
+
+# TODO: Adress invalid names
+# pylint: disable=invalid-name
+
+import argparse
+import dataclasses
+import http.server
+import os
+import pathlib
+import re
+import shutil
+import socketserver
+import subprocess
+import sys
+import tempfile
+import threading
+import time
+from collections import defaultdict
+from contextlib import contextmanager
+from functools import partial
+from http import HTTPStatus
+from operator import itemgetter
+
+import pycurl
+import requests
+from debian.deb822 import BuildInfo
+
+
+class MyHTTPException(Exception):
+ pass
+
+
+class MyHTTP404Exception(Exception):
+ pass
+
+
+class MyHTTPTimeoutException(Exception):
+ pass
+
+
+class RetryCountExceeded(Exception):
+ pass
+
+
+# pylint: disable=c-extension-no-member
+class Proxy(http.server.SimpleHTTPRequestHandler):
+ last_request = None
+ maxretries = 10
+
+ def do_GET(self): # pylint: disable=too-many-branches,too-many-statements
+ # check validity and extract the timestamp
+ url = "http://snapshot.debian.org/" + self.path
+ start = None
+ state = ""
+ written = 0
+ for retrynum in range(self.maxretries):
+ try:
+ c = pycurl.Curl()
+ c.setopt(c.URL, url)
+ # even 100 kB/s is too much sometimes
+ c.setopt(c.MAX_RECV_SPEED_LARGE, 1000 * 1024) # bytes per second
+ c.setopt(c.CONNECTTIMEOUT, 30) # the default is 300
+ # sometimes, curl stalls forever and even ctrl+c doesn't work
+ start = time.time()
+
+ def progress(*_):
+ # a download must not last more than 10 minutes
+ # with 100 kB/s this means files cannot be larger than 62MB
+ if time.time() - start > 10 * 60:
+ print("transfer took too long")
+ # the code will not see this exception but instead get a
+ # pycurl.error
+ raise MyHTTPTimeoutException(url)
+
+ c.setopt(pycurl.NOPROGRESS, 0)
+ c.setopt(pycurl.XFERINFOFUNCTION, progress)
+ # $ host snapshot.debian.org
+ # snapshot.debian.org has address 185.17.185.185
+ # snapshot.debian.org has address 193.62.202.27
+ # c.setopt(c.RESOLVE, ["snapshot.debian.org:80:185.17.185.185"])
+ if written > 0:
+ c.setopt(pycurl.RESUME_FROM, written)
+
+ def writer_cb(data):
+ assert state == "headers sent", state
+ nonlocal written
+ written += len(data)
+ return self.wfile.write(data)
+
+ c.setopt(c.WRITEFUNCTION, writer_cb)
+
+ # using a header callback allows us to send headers of our own
+ # with the correct content-length value out without having to
+ # wait for perform() to finish
+ def header_cb(line):
+ nonlocal state
+ # if this is a retry, then the headers have already been
+ # sent and there is nothing to do
+ if state == "headers sent":
+ return
+ # HTTP standard specifies that headers are encoded in iso-8859-1
+ line = line.decode("iso-8859-1").rstrip()
+ # the first try must be a http 200
+ if line == "HTTP/1.1 200 OK":
+ assert state == ""
+ self.send_response(HTTPStatus.OK)
+ state = "http200 sent"
+ return
+ # the header is done
+ if line == "":
+ assert state == "length sent"
+ self.end_headers()
+ state = "headers sent"
+ return
+ field, value = line.split(":", 1)
+ field = field.strip().lower()
+ value = value.strip()
+ # we are only interested in content-length
+ if field != "content-length":
+ return
+ assert state == "http200 sent"
+ self.send_header("Content-Length", value)
+ state = "length sent"
+
+ c.setopt(c.HEADERFUNCTION, header_cb)
+ c.perform()
+ if c.getinfo(c.RESPONSE_CODE) == 404:
+ raise MyHTTP404Exception(f"got HTTP 404 for {url}")
+ if c.getinfo(c.RESPONSE_CODE) not in [200, 206]:
+ raise MyHTTPException(
+ f"got HTTP {c.getinfo(c.RESPONSE_CODE)} for {url}"
+ )
+ c.close()
+ # if the requests finished too quickly, sleep the remaining time
+ # s/r r/h
+ # 3 1020
+ # 2.5 1384
+ # 2.4 1408
+ # 2 1466
+ # 1.5 2267
+ seconds_per_request = 1.5
+ if self.last_request is not None:
+ sleep_time = seconds_per_request - (time.time() - self.last_request)
+ if sleep_time > 0:
+ time.sleep(sleep_time)
+ self.last_request = time.time()
+ break
+ except pycurl.error as e:
+ code, _ = e.args
+ if code in [
+ pycurl.E_PARTIAL_FILE,
+ pycurl.E_COULDNT_CONNECT,
+ pycurl.E_ABORTED_BY_CALLBACK,
+ ]:
+ if retrynum == self.maxretries - 1:
+ break
+ if code == pycurl.E_ABORTED_BY_CALLBACK:
+ # callback was aborted due to timeout
+ pass
+ sleep_time = 4 ** (retrynum + 1)
+ print(f"retrying after {sleep_time} s...")
+ time.sleep(sleep_time)
+ continue
+ raise
+ except MyHTTPException as e:
+ print("got HTTP error:", repr(e))
+ if retrynum == self.maxretries - 1:
+ break
+ sleep_time = 4 ** (retrynum + 1)
+ print(f"retrying after {sleep_time} s...")
+ time.sleep(sleep_time)
+ # restart from the beginning or otherwise, the result might
+ # include a varnish cache error message
+ else:
+ raise RetryCountExceeded("failed too often...")
+
+
+@dataclasses.dataclass
+class Source:
+ archive: str
+ timestamp: str
+ suite: str
+ components: list[str]
+
+ def deb_line(self, host: str = "snapshot.debian.org") -> str:
+ return (
+ f"deb [check-valid-until=no] http://{host}/archive/{self.archive}"
+ f"/{self.timestamp}/ {self.suite} {' '.join(self.components)}\n"
+ )
+
+
+def parse_buildinfo(val):
+ with open(val, encoding="utf8") as f:
+ buildinfo = BuildInfo(f)
+ pkgs = []
+ for dep in buildinfo.relations["installed-build-depends"]:
+ assert len(dep) == 1
+ dep = dep[0]
+ assert dep["arch"] is None
+ assert dep["restrictions"] is None
+ assert len(dep["version"]) == 2
+ rel, version = dep["version"]
+ assert rel == "="
+ pkgs.append((dep["name"], dep["archqual"], version))
+ return pkgs, buildinfo.get("Build-Architecture")
+
+
+def parse_pkgs(val):
+ if val == "-":
+ val = sys.stdin.read()
+ if val.startswith("./") or val.startswith("/"):
+ val = pathlib.Path(val)
+ if not val.exists():
+ print(f"{val} does not exist", file=sys.stderr)
+ sys.exit(1)
+ val = val.read_text(encoding="utf8")
+ pkgs = []
+ pattern = re.compile(
+ r"""
+ ^[^a-z0-9]* # garbage at the beginning
+ ([a-z0-9][a-z0-9+.-]+) # package name
+ (?:[^a-z0-9+.-]+([a-z0-9-]+))? # optional version
+ [^A-Za-z0-9.+~:-]+ # optional garbage
+ ([A-Za-z0-9.+~:-]+) # version
+ [^A-Za-z0-9.+~:-]*$ # garbage at the end
+ """,
+ re.VERBOSE,
+ )
+ for line in re.split(r"[,\r\n]+", val):
+ if not line:
+ continue
+ match = pattern.fullmatch(line)
+ if match is None:
+ print(f"cannot parse: {line}", file=sys.stderr)
+ sys.exit(1)
+ pkgs.append(match.groups())
+ return [pkgs]
+
+
+def parse_args(args: list[str]) -> argparse.Namespace:
+ parser = argparse.ArgumentParser(
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ description="""\
+
+Combines debootstrap and snapshot.debian.org to create a chroot with exact
+package versions from the past either to reproduce bugs or to test source
+package reproducibility.
+
+To obtain a list of packages run the following command on one machine:
+
+ $ dpkg-query --showformat '${binary:Package}=${Version}\\n' --show
+
+And pass the output to debootsnap with the --packages argument. The result
+will be a chroot tarball with precisely the package versions as they were
+found on the system that ran dpkg-query.
+""",
+ epilog="""\
+
+*EXAMPLES*
+
+On one system run:
+
+ $ dpkg-query --showformat '${binary:Package}=${Version}\\n' --show > pkglist
+
+Then copy over "pkglist" and on another system run:
+
+ $ debootsnap --pkgs=./pkglist chroot.tar
+
+Or use a buildinfo file as input:
+
+ $ debootsnap --buildinfo=./package.buildinfo chroot.tar
+
+""",
+ )
+ parser.add_argument(
+ "--architecture",
+ "--nativearch",
+ help="native architecture of the chroot. Ignored if --buildinfo is"
+ " used. Foreign architectures are inferred from the package list."
+ " Not required if packages are architecture qualified.",
+ )
+ parser.add_argument(
+ "--ignore-notfound",
+ action="store_true",
+ help="only warn about packages that cannot be found on "
+ "snapshot.debian.org instead of exiting",
+ )
+ group = parser.add_mutually_exclusive_group(required=True)
+ group.add_argument(
+ "--buildinfo",
+ type=parse_buildinfo,
+ help="use packages from a buildinfo file. Read buildinfo file from "
+ 'standard input if value is "-".',
+ )
+ group.add_argument(
+ "--packages",
+ "--pkgs",
+ action="extend",
+ type=parse_pkgs,
+ help="list of packages, optional architecture and version, separated "
+ "by comma or linebreak. Read list from standard input if value is "
+ '"-". Read list from a file if value starts with "./" or "/". The '
+ "option can be specified multiple times. Package name, "
+ "version and architecture are separated by one or more characters "
+ "that are not legal in the respective adjacent field. Leading and "
+ "trailing illegal characters are allowed. Example: "
+ "pkg1:arch=ver1,pkg2:arch=ver2",
+ )
+ parser.add_argument(
+ "--sources-list-only",
+ action="store_true",
+ help="only query metasnap.debian.net and print the sources.list "
+ "needed to create chroot and exit",
+ )
+ parser.add_argument(
+ "output", nargs="?", default="-", help="path to output chroot tarball"
+ )
+ return parser.parse_args(args)
+
+
+def query_metasnap(pkgsleft, archive, nativearch):
+ handled_pkgs = set(pkgsleft)
+ r = requests.post(
+ "http://metasnap.debian.net/cgi-bin/api",
+ files={
+ "archive": archive,
+ "arch": nativearch,
+ "pkgs": ",".join([n + ":" + a + "=" + v for n, a, v in handled_pkgs]),
+ },
+ timeout=60,
+ )
+ if r.status_code == 404:
+ for line in r.text.splitlines():
+ n, a, v = line.split()
+ handled_pkgs.remove((n, a, v))
+ r = requests.post(
+ "http://metasnap.debian.net/cgi-bin/api",
+ files={
+ "archive": archive,
+ "arch": nativearch,
+ "pkgs": ",".join([n + ":" + a + "=" + v for n, a, v in handled_pkgs]),
+ },
+ timeout=60,
+ )
+ assert r.status_code == 200, r.text
+
+ suite2pkgs = defaultdict(set)
+ pkg2range = {}
+ for line in r.text.splitlines():
+ n, a, v, s, c, b, e = line.split()
+ assert (n, a, v) in handled_pkgs
+ suite2pkgs[s].add((n, a, v))
+ # this will only keep one range of packages with multiple
+ # ranges but we don't care because we only need one
+ pkg2range[((n, a, v), s)] = (c, b, e)
+
+ return handled_pkgs, suite2pkgs, pkg2range
+
+
+def comp_ts(ranges):
+ last = "19700101T000000Z" # impossibly early date
+ res = []
+ for c, b, e in ranges:
+ if last >= b:
+ # add the component the current timestamp needs
+ res[-1][1].add(c)
+ continue
+ # add new timestamp with initial component
+ last = e
+ res.append((last, set([c])))
+ return res
+
+
+def compute_sources(pkgs, nativearch, ignore_notfound) -> list[Source]:
+ sources = []
+ pkgsleft = set(pkgs)
+ for archive in [
+ "debian",
+ "debian-debug",
+ "debian-security",
+ "debian-ports",
+ "debian-volatile",
+ "debian-backports",
+ ]:
+ if len(pkgsleft) == 0:
+ break
+
+ handled_pkgs, suite2pkgs, pkg2range = query_metasnap(
+ pkgsleft, archive, nativearch
+ )
+
+ # greedy algorithm:
+ # pick the suite covering most packages first
+ while len(handled_pkgs) > 0:
+ bestsuite = sorted(suite2pkgs.items(), key=lambda v: len(v[1]))[-1][0]
+ ranges = [pkg2range[nav, bestsuite] for nav in suite2pkgs[bestsuite]]
+ # sort by end-time
+ ranges.sort(key=itemgetter(2))
+
+ for ts, comps in comp_ts(ranges):
+ sources.append(Source(archive, ts, bestsuite, comps))
+
+ for nav in suite2pkgs[bestsuite]:
+ handled_pkgs.remove(nav)
+ pkgsleft.remove(nav)
+ for suite in suite2pkgs:
+ if suite == bestsuite:
+ continue
+ if nav in suite2pkgs[suite]:
+ suite2pkgs[suite].remove(nav)
+ del suite2pkgs[bestsuite]
+ if pkgsleft:
+ print("cannot find:", file=sys.stderr)
+ print(
+ "\n".join([f"{pkg[0]}:{pkg[1]}={pkg[2]}" for pkg in pkgsleft]),
+ file=sys.stderr,
+ )
+ if not ignore_notfound:
+ sys.exit(1)
+
+ return sources
+
+
+def create_repo(tmpdirname, pkgs):
+ with open(tmpdirname + "/control", "w", encoding="utf8") as f:
+
+ def pkg2name(n, a, v):
+ if a is None:
+ return f"{n} (= {v})"
+ return f"{n}:{a} (= {v})"
+
+ f.write("Package: debootsnap-dummy\n")
+ f.write(f"Depends: {', '.join([pkg2name(*pkg) for pkg in pkgs])}\n")
+ subprocess.check_call(
+ ["equivs-build", tmpdirname + "/control"], cwd=tmpdirname + "/cache"
+ )
+
+ packages_content = subprocess.check_output(
+ ["apt-ftparchive", "packages", "."], cwd=tmpdirname + "/cache"
+ )
+ with open(tmpdirname + "/cache/Packages", "wb") as f:
+ f.write(packages_content)
+ release_content = subprocess.check_output(
+ [
+ "apt-ftparchive",
+ "release",
+ "-oAPT::FTPArchive::Release::Suite=dummysuite",
+ ".",
+ ],
+ cwd=tmpdirname + "/cache",
+ )
+ with open(tmpdirname + "/cache/Release", "wb") as f:
+ f.write(release_content)
+
+
+@contextmanager
+def serve_repo(tmpdirname):
+ httpd = http.server.HTTPServer(
+ ("localhost", 0),
+ partial(http.server.SimpleHTTPRequestHandler, directory=tmpdirname + "/cache"),
+ )
+ # run server in a new thread
+ server_thread = threading.Thread(target=httpd.serve_forever)
+ server_thread.daemon = True
+ # start thread
+ server_thread.start()
+ # retrieve port (in case it was generated automatically)
+ _, port = httpd.server_address
+ try:
+ yield port
+ finally:
+ httpd.shutdown()
+ httpd.server_close()
+ server_thread.join()
+
+
+def run_mmdebstrap(
+ tmpdirname, sources: list[Source], nativearch, foreignarches, output
+):
+ with open(tmpdirname + "/sources.list", "w", encoding="utf8") as f:
+ for source in sources:
+ f.write(source.deb_line())
+ # we serve the directory via http instead of using a copy:// mirror
+ # because the temporary directory is not accessible to the unshared
+ # user
+ with serve_repo(tmpdirname) as port:
+ cmd = [
+ "mmdebstrap",
+ f"--architectures={','.join([nativearch] + list(foreignarches))}",
+ "--variant=essential",
+ "--include=debootsnap-dummy",
+ '--aptopt=Apt::Key::gpgvcommand "/usr/libexec/mmdebstrap/gpgvnoexpkeysig"',
+ '--customize-hook=chroot "$1" dpkg -r debootsnap-dummy',
+ '--customize-hook=chroot "$1" dpkg-query --showformat '
+ "'${binary:Package}=${Version}\\n' --show > \"$1/pkglist\"",
+ "--customize-hook=download /pkglist ./pkglist",
+ '--customize-hook=rm "$1/pkglist"',
+ "--customize-hook=upload sources.list /etc/apt/sources.list",
+ "dummysuite",
+ output,
+ f"deb [trusted=yes] http://localhost:{port}/ ./",
+ ]
+ subprocess.check_call(cmd, cwd=tmpdirname)
+
+ newpkgs = set()
+ with open(tmpdirname + "/pkglist", encoding="utf8") as f:
+ for line in f:
+ line = line.rstrip()
+ n, v = line.split("=")
+ a = nativearch
+ if ":" in n:
+ n, a = n.split(":")
+ newpkgs.add((n, a, v))
+
+ return newpkgs
+
+
+@contextmanager
+def proxy_snapshot(tmpdirname):
+ httpd = socketserver.TCPServer(
+ # the default address family for socketserver is AF_INET so we
+ # explicitly bind to ipv4 localhost
+ ("localhost", 0),
+ partial(Proxy, directory=tmpdirname + "/cache"),
+ )
+ # run server in a new thread
+ server_thread = threading.Thread(target=httpd.serve_forever)
+ server_thread.daemon = True
+ # start thread
+ server_thread.start()
+ # retrieve port (in case it was generated automatically)
+ _, port = httpd.server_address
+ try:
+ yield port
+ finally:
+ httpd.shutdown()
+ httpd.server_close()
+ server_thread.join()
+
+
+def download_packages(
+ tmpdirname, sources: list[Source], pkgs, nativearch, foreignarches
+):
+ for d in [
+ "/etc/apt/apt.conf.d",
+ "/etc/apt/sources.list.d",
+ "/etc/apt/preferences.d",
+ "/var/cache/apt",
+ "/var/lib/apt/lists/partial",
+ "/var/lib/dpkg",
+ ]:
+ os.makedirs(tmpdirname + "/" + d)
+ # apt-get update requires /var/lib/dpkg/status
+ with open(tmpdirname + "/var/lib/dpkg/status", "w", encoding="utf8") as f:
+ pass
+ with open(tmpdirname + "/apt.conf", "w", encoding="utf8") as f:
+ f.write(f'Apt::Architecture "{nativearch}";\n')
+ f.write("Apt::Architectures { " + f'"{nativearch}"; ')
+ for a in foreignarches:
+ f.write(f'"{a}"; ')
+ f.write("};\n")
+ f.write('Dir "' + tmpdirname + '";\n')
+ f.write('Dir::Etc::Trusted "/etc/apt/trusted.gpg";\n')
+ f.write('Dir::Etc::TrustedParts "/usr/share/keyrings/";\n')
+ f.write('Acquire::Languages "none";\n')
+ # f.write("Acquire::http::Dl-Limit \"1000\";\n")
+ # f.write("Acquire::https::Dl-Limit \"1000\";\n")
+ f.write('Acquire::Retries "5";\n')
+ # ignore expired signatures
+ f.write('Apt::Key::gpgvcommand "/usr/libexec/mmdebstrap/gpgvnoexpkeysig";\n')
+
+ os.makedirs(tmpdirname + "/cache")
+
+ with proxy_snapshot(tmpdirname) as port:
+ with open(tmpdirname + "/etc/apt/sources.list", "w", encoding="utf8") as f:
+ for source in sources:
+ f.write(source.deb_line(f"localhost:{port}"))
+ subprocess.check_call(
+ ["apt-get", "update", "--error-on=any"],
+ env={"APT_CONFIG": tmpdirname + "/apt.conf"},
+ )
+ for i, nav in enumerate(pkgs):
+ print(f"{i + 1} of {len(pkgs)}")
+ with tempfile.TemporaryDirectory() as tmpdir2:
+ subprocess.check_call(
+ ["apt-get", "download", "--yes", f"{nav[0]}:{nav[1]}={nav[2]}"],
+ cwd=tmpdir2,
+ env={"APT_CONFIG": tmpdirname + "/apt.conf"},
+ )
+ debs = os.listdir(tmpdir2)
+ assert len(debs) == 1
+ # Normalize the package name to how it appears in the archive.
+ # Mainly this removes the epoch from the filename, see
+ # https://bugs.debian.org/645895
+ # This avoids apt bugs connected with a percent sign in the
+ # filename as they occasionally appear, for example as
+ # introduced in apt 2.1.15 and later fixed by DonKult:
+ # https://salsa.debian.org/apt-team/apt/-/merge_requests/175
+ subprocess.check_call(["dpkg-name", tmpdir2 + "/" + debs[0]])
+ debs = os.listdir(tmpdir2)
+ assert len(debs) == 1
+ shutil.move(tmpdir2 + "/" + debs[0], tmpdirname + "/cache")
+
+
+def main(arguments: list[str]) -> None:
+ args = parse_args(arguments)
+ if args.packages:
+ pkgs = [v for sublist in args.packages for v in sublist]
+ if args.architecture is None:
+ arches = {a for _, a, _ in pkgs if a is not None}
+ if len(arches) == 0:
+ print("packages are not architecture qualified", file=sys.stderr)
+ print(
+ "use --architecture to set the native architecture", file=sys.stderr
+ )
+ sys.exit(1)
+ elif len(arches) > 1:
+ print("more than one architecture in the package list", file=sys.stderr)
+ print(
+ "use --architecture to set the native architecture", file=sys.stderr
+ )
+ sys.exit(1)
+ nativearch = arches.pop()
+ assert arches == set()
+ else:
+ nativearch = args.architecture
+ else:
+ pkgs, nativearch = args.buildinfo
+ # unknown architectures are the native architecture
+ pkgs = [(n, a if a is not None else nativearch, v) for n, a, v in pkgs]
+ # make package list unique
+ pkgs = list(set(pkgs))
+ # compute foreign architectures
+ foreignarches = set()
+ for _, a, _ in pkgs:
+ if a != nativearch:
+ foreignarches.add(a)
+
+ for tool in [
+ "equivs-build",
+ "apt-ftparchive",
+ "mmdebstrap",
+ "apt-get",
+ "dpkg-name",
+ ]:
+ if shutil.which(tool) is None:
+ print(f"{tool} is required but not installed", file=sys.stderr)
+ sys.exit(1)
+
+ sources = compute_sources(pkgs, nativearch, args.ignore_notfound)
+
+ if args.sources_list_only:
+ for source in sources:
+ print(source.deb_line(), end="")
+ sys.exit(0)
+
+ with tempfile.TemporaryDirectory() as tmpdirname:
+ download_packages(tmpdirname, sources, pkgs, nativearch, foreignarches)
+
+ create_repo(tmpdirname, pkgs)
+
+ newpkgs = run_mmdebstrap(
+ tmpdirname, sources, nativearch, foreignarches, args.output
+ )
+
+ # make sure that the installed packages match the requested package
+ # list
+ assert set(newpkgs) == set(pkgs)
+
+
+if __name__ == "__main__":
+ main(sys.argv[1:])
diff -Nru devscripts-2.23.2/scripts/devscripts/test/test_debootsnap.py devscripts-2.23.3/scripts/devscripts/test/test_debootsnap.py
--- devscripts-2.23.2/scripts/devscripts/test/test_debootsnap.py 1970-01-01 01:00:00.000000000 +0100
+++ devscripts-2.23.3/scripts/devscripts/test/test_debootsnap.py 2023-03-15 23:35:37.000000000 +0100
@@ -0,0 +1,56 @@
+# Copyright (C) 2023, Benjamin Drung <bdrung@debian.org>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+"""Test debootsnap script."""
+
+import contextlib
+import io
+import tempfile
+import unittest
+import unittest.mock
+
+from debootsnap import main, parse_pkgs
+
+
+class TestDebootsnap(unittest.TestCase):
+ """Test debootsnap script."""
+
+ @unittest.mock.patch("shutil.which")
+ def test_missing_tools(self, which_mock) -> None:
+ """Test debootsnap fails cleanly if required binaries are missing."""
+ which_mock.return_value = None
+ stderr = io.StringIO()
+ with contextlib.redirect_stderr(stderr):
+ with self.assertRaisesRegex(SystemExit, "1"):
+ main(["--packages=pkg1:arch=ver1", "chroot.tar"])
+ self.assertEqual(
+ stderr.getvalue(), "equivs-build is required but not installed\n"
+ )
+ which_mock.assert_called_once_with("equivs-build")
+
+ def test_parse_pkgs_from_file(self) -> None:
+ """Test parse_pkgs() for a given file name."""
+ with tempfile.NamedTemporaryFile(mode="w", prefix="devscripts-") as pkgfile:
+ pkgfile.write("pkg1:arch=ver1\npkg2:arch=ver2\n")
+ pkgfile.flush()
+ pkgs = parse_pkgs(pkgfile.name)
+ self.assertEqual(pkgs, [[("pkg1", "arch", "ver1"), ("pkg2", "arch", "ver2")]])
+
+ def test_parse_pkgs_missing_file(self) -> None:
+ """Test parse_pkgs() for a missing file name."""
+ stderr = io.StringIO()
+ with contextlib.redirect_stderr(stderr):
+ with self.assertRaisesRegex(SystemExit, "1"):
+ parse_pkgs("/non-existing/pkgfile")
+ self.assertEqual(stderr.getvalue(), "/non-existing/pkgfile does not exist\n")
diff -Nru devscripts-2.23.2/scripts/devscripts/test/test_suspicious_source.py devscripts-2.23.3/scripts/devscripts/test/test_suspicious_source.py
--- devscripts-2.23.2/scripts/devscripts/test/test_suspicious_source.py 1970-01-01 01:00:00.000000000 +0100
+++ devscripts-2.23.3/scripts/devscripts/test/test_suspicious_source.py 2023-03-02 15:33:09.000000000 +0100
@@ -0,0 +1,41 @@
+# Copyright (C) 2023, Benjamin Drung <bdrung@debian.org>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+"""Test suspicious-source script."""
+
+import pathlib
+import subprocess
+import tempfile
+import unittest
+
+
+class TestSuspiciousSource(unittest.TestCase):
+ """Test suspicious-source script."""
+
+ @staticmethod
+ def _run_suspicious_source(directory: str) -> str:
+ suspicious_source = subprocess.run(
+ ["./suspicious-source", "-d", directory],
+ check=True,
+ stdout=subprocess.PIPE,
+ text=True,
+ )
+ return suspicious_source.stdout.strip()
+
+ def test_python_sript(self) -> None:
+ """Test not complaining about Python code."""
+ with tempfile.TemporaryDirectory(prefix="devscripts-") as tmpdir:
+ python_file = pathlib.Path(tmpdir) / "example.py"
+ python_file.write_text("#!/usr/bin/python3\nprint('hello world')\n")
+ self.assertEqual(self._run_suspicious_source(tmpdir), "")
diff -Nru devscripts-2.23.2/scripts/edit-patch.sh devscripts-2.23.3/scripts/edit-patch.sh
--- devscripts-2.23.2/scripts/edit-patch.sh 2023-02-05 00:33:58.000000000 +0100
+++ devscripts-2.23.3/scripts/edit-patch.sh 2023-03-15 23:37:22.000000000 +0100
@@ -122,8 +122,10 @@
edit_patch_quilt() {
export QUILT_PATCHES=debian/patches
- top_patch=$(quilt top)
- echo "Top patch: $top_patch"
+ if [ -e $QUILT_PATCHES ]; then
+ top_patch=$(quilt top)
+ echo "Top patch: $top_patch"
+ fi
if [ -e $PREFIX/$1 ]; then
# if it's an existing patch and we are at the end of the stack,
# go back at the beginning
@@ -141,8 +143,10 @@
# use a sub-shell
quilt shell
quilt refresh
- echo "Reverting quilt back to $top_patch"
- quilt pop $top_patch
+ if [ -n $top_patch ]; then
+ echo "Reverting quilt back to $top_patch"
+ quilt pop $top_patch
+ fi
vcs_add $PREFIX/$1 $PREFIX/series
}
diff -Nru devscripts-2.23.2/scripts/sadt devscripts-2.23.3/scripts/sadt
--- devscripts-2.23.2/scripts/sadt 2023-02-18 23:50:46.000000000 +0100
+++ devscripts-2.23.3/scripts/sadt 2023-03-01 00:23:53.000000000 +0100
@@ -346,7 +346,7 @@
for package in packages:
or_clauses += [
stripped_or_clause
- + [dict(name=package, version=None, arch=None)]
+ + [{"name": package, "version": None, "arch": None}]
]
else:
or_clauses += [or_clause]
diff -Nru devscripts-2.23.2/scripts/suspicious-source devscripts-2.23.3/scripts/suspicious-source
--- devscripts-2.23.2/scripts/suspicious-source 2023-02-05 00:33:58.000000000 +0100
+++ devscripts-2.23.3/scripts/suspicious-source 2023-03-02 15:33:09.000000000 +0100
@@ -75,8 +75,8 @@
"text/x-perl",
"text/x-php",
"text/x-po",
- "text/x-python",
"text/x-ruby",
+ "text/x-script.python",
"text/x-shellscript",
"text/x-tex",
"text/x-texinfo",
Attachment:
signature.asc
Description: PGP signature