[debrepatch] 01/02: Make the interface to debpatch(1) more like patch(1)
Ximin Luo
infinity0 at debian.org
Thu Nov 17 18:01:47 UTC 2016
This is an automated email from the git hooks/post-receive script.
infinity0 pushed a commit to branch master
in repository debrepatch.
commit df44cf6820ca4c68e28526581a8418a114340eb8
Author: Ximin Luo <infinity0 at debian.org>
Date: Thu Nov 17 19:00:56 2016 +0100
Make the interface to debpatch(1) more like patch(1)
---
debpatch | 167 ++++++++++++++++++++++++++++++++++++++++++++-----------------
debrepatch | 46 ++---------------
2 files changed, 125 insertions(+), 88 deletions(-)
diff --git a/debpatch b/debpatch
index a34b7b8..3aedb53 100755
--- a/debpatch
+++ b/debpatch
@@ -3,22 +3,26 @@
Apply a debdiff, but more smartly to avoid conflicts
Specifically, it's smarter in how it applies updates to d/changelog.
-Depends on dpkg-dev, devscripts, python3-unidiff.
+Depends on dpkg-dev, devscripts, python3-unidiff, quilt.
"""
import argparse
+import hashlib
import io
import logging
import os
import random
import unidiff
+import shutil
import subprocess
import sys
+import tempfile
dirname = os.path.dirname
basename = os.path.basename
C = subprocess.check_call
+# this can be any valid value, it doesn't appear in the final output
DCH_DUMMY_TAIL = "\n -- debpatch dummy tool <infinity0 at debian.org> Thu, 01 Jan 1970 00:00:00 +0000\n\n"
TRY_ENCODINGS = ["utf-8", "latin-1"]
DISTRIBUTION_DEFAULT = "experimental"
@@ -57,10 +61,13 @@ def read_dch_patch(dch_patch):
target = read_dch(target_str)
return source_version, target
-def apply_dch_patch(source_file, current, patch_name, old_version, target):
- if patch_name in current["Changes"]:
+def apply_dch_patch(source_file, current, patch_name, old_version, target, dry_run):
+ # Do not change this text, unless you also add logic to detect markers from
+ # previously-released versions.
+ marker = "Patch '%s' applied by debpatch(1)" % patch_name
+ if marker in current["Changes"]:
logging.info("patch %s already applied to d/changelog", patch_name)
- return
+ return target["Version"]
dch_args = []
dch_env = dict(os.environ)
@@ -77,12 +84,17 @@ def apply_dch_patch(source_file, current, patch_name, old_version, target):
(old_version, target["Version"]))
logging.warn("will give -n to `dch` instead of trying to be smart; feel free to make me smarter")
dch_args.append("-n")
+ version = None
else:
version_suffix = target["Version"][len(old_version):]
version = current["Version"] + version_suffix
logging.info("using version %s based on suffix %s", version, version_suffix)
dch_args.extend(["-v", version])
+ if dry_run:
+ return version if version else subprocess.check_output(["sh", "-c",
+ "EDITOR=cat dch -n 2>/dev/null | dpkg-parsechangelog -l- -SVersion"]).decode("utf-8").rstrip()
+
dch_args += ["--force-distribution", "-D", target["Distribution"]]
dch_args += ["-u", target["Urgency"]]
if "Maintainer" in target:
@@ -96,7 +108,7 @@ def apply_dch_patch(source_file, current, patch_name, old_version, target):
# TODO: make this a bit more atomic; if any of the C()s fail we should rewind
token = "DEBPATCH PLACEHOLDER %s DELETEME" % random.randint(0, 2**64)
C(["dch", "-c", source_file] + dch_args +
- ["Patch %s automatically applied by debpatch(1)" % patch_name])
+ [marker])
C(["dch", "-c", source_file, "-a", token], env=dch_env)
C(["sed", "-e", "/%s/c\\\n%s" % (token, changes.replace("\n", "\\\n")), "-i", source_file])
@@ -127,45 +139,13 @@ def apply_patch_str(patch_name, patch_str):
call_patch(patch_str, "--dry-run")
raise ValueError("patch %s doesn't apply!", patch_name)
-def main(args):
- parser = argparse.ArgumentParser(
- description='Apply a debdiff, but more smartly to avoid conflicts.')
- parser.add_argument('-v', '--verbose', action="store_true",
- help='Output more information')
- parser.add_argument('-c', '--changelog', default='debian/changelog',
- help='Path to debian/changelog; default: %(default)s')
- parser.add_argument('-C', '--directory',
- help="Apply patches from this directory; should be the top-level source dir")
- parser.add_argument('-i', '--interactive', action="store_true",
- help="Run the python REPL after processing.")
- parser.add_argument('--source-version', action="store_true",
- help="Don't apply the patch; instead print out the version of the Debian "
- "package that it is supposed to be applied to, or nothing if this can't be "
- "read from the debian/changelog hunk.")
- parser.add_argument('patch_file')
- args = parser.parse_args(args)
-
- if args.verbose:
- logging.getLogger().setLevel(logging.DEBUG)
-
- patch_name = basename(args.patch_file)
+def debpatch(patch, patch_name, args):
if len(patch_name) > 60:
# this messes with our dch "already applied" logic detection, sorry
raise ValueError("pick a shorter patch name; sorry")
- for enc in TRY_ENCODINGS:
- try:
- patch = unidiff.PatchSet.from_filename(args.patch_file, encoding=enc)
- break
- except:
- if enc == TRY_ENCODINGS[-1]:
- raise
- else:
- continue
-
- # change directory before applying patches
- if args.directory:
- os.chdir(args.directory)
+ # don't change anything if...
+ dry_run = args.target_version
changelog = list(filter(lambda x: is_dch(x.path), patch))
if not changelog:
@@ -188,19 +168,114 @@ def main(args):
print(old_version)
return
- # TODO: if we patched any patch in d/patches, then we should refresh *that*
- # patch here. this is hard, but it would help us to avoid refreshing *all*
- # patches in ./debrepatch, hopefully ending up with a smaller debdiff.
- apply_patch_str(patch_name, str(patch))
+ if not dry_run:
+ apply_patch_str(patch_name, str(patch))
- # only apply d/changelog patch if the rest of the patch applies
+ # only apply d/changelog patch if the rest of the patch applied
with open(args.changelog) as fp:
current = read_dch(fp.read())
- apply_dch_patch(args.changelog, current, patch_name, old_version, target)
+ new_version = apply_dch_patch(args.changelog, current, patch_name, old_version, target, dry_run)
+ if args.target_version:
+ print(new_version)
+ return
if args.interactive:
import code
code.interact(local=locals())
+def main(args):
+ parser = argparse.ArgumentParser(
+ description='Apply a debdiff, but more smartly to avoid conflicts.')
+ parser.add_argument('-v', '--verbose', action="store_true",
+ help='Output more information')
+ parser.add_argument('-c', '--changelog', default='debian/changelog',
+ help='Path to debian/changelog; default: %(default)s')
+ parser.add_argument('-i', '--interactive', action="store_true",
+ help="Run the python REPL after processing.")
+ parser.add_argument('--source-version', action="store_true",
+ help="Don't apply the patch; instead print out the version of the "
+ "package that it is supposed to be applied to, or nothing if the patch "
+ "does not specify a source version.")
+ parser.add_argument('--target-version', action="store_true",
+ help="Don't apply the patch; instead print out the new version of the "
+ "package debpatch(1) would generate, when the patch is applied to the "
+ "the given target package specified by the other arguments.")
+ parser.add_argument('orig_dsc_or_dir', nargs='?', default=".",
+ help="Target to apply the patch to. This can either be an unpacked "
+ "source tree, or a .dsc file. In the former case, the directory is "
+ "modified in-place; in the latter case, a second .dsc is created. "
+ "Default: %(default)s")
+ parser.add_argument('patch_file', nargs='?', default="/dev/stdin",
+ help="Patch file to apply, in the format output by debdiff(1). "
+ "Default: %(default)s")
+ group1 = parser.add_argument_group('Options for .dsc patch targets')
+ group1.add_argument('--no-clean', action="store_true",
+ help="Don't clean temporary directories after a failure, so you can "
+ "examine what failed.")
+ group1.add_argument('--quilt-refresh', action="store_true",
+ help="If the building of the new source package fails, try to refresh "
+ "patches using quilt(1) then try building it again.")
+ args = parser.parse_args(args)
+ #print(args)
+
+ if args.verbose:
+ logging.getLogger().setLevel(logging.DEBUG)
+
+ with open(args.patch_file, 'rb') as fp:
+ data = fp.read()
+ for enc in TRY_ENCODINGS:
+ try:
+ patch = unidiff.PatchSet(data.splitlines(keepends=True), encoding=enc)
+ break
+ except:
+ if enc == TRY_ENCODINGS[-1]:
+ raise
+ else:
+ continue
+
+ patch_name = '%s:%s' % (
+ basename(args.patch_file),
+ hashlib.sha256(data).hexdigest()[:20 if args.patch_file == '/dev/stdin' else 8])
+ quiet = args.source_version or args.target_version
+ dry_run = args.source_version or args.target_version
+ stdout = subprocess.DEVNULL if quiet else None # user can redirect stderr themselves
+
+ # change directory before applying patches
+ if os.path.isdir(args.orig_dsc_or_dir):
+ os.chdir(args.orig_dsc_or_dir)
+ debpatch(patch, patch_name, args)
+ elif os.path.isfile(args.orig_dsc_or_dir):
+ parts = os.path.splitext(os.path.basename(args.orig_dsc_or_dir))
+ if parts[1] != ".dsc":
+ raise ValueError("unrecognised patch target: %s" % args.orig_dsc_or_dir)
+ temp_dir = tempfile.mkdtemp()
+ try:
+ builddir = os.path.join(temp_dir, parts[0]) # dpkg-source doesn't like existing dirs
+ C(["dpkg-source", "-x", "--skip-patches", args.orig_dsc_or_dir, builddir], stdout=stdout)
+ origdir = os.getcwd()
+ os.chdir(builddir)
+ debpatch(patch, patch_name, args)
+ if dry_run:
+ return
+ os.chdir(origdir)
+ try:
+ C(["dpkg-source", "-b", builddir])
+ except subprocess.CalledProcessError:
+ if args.quilt_refresh:
+ C(["sh", "-c", """
+set -ex
+export QUILT_PATCHES=debian/patches
+while quilt push; do quilt refresh; done
+"""
+ ], cwd=builddir)
+ C(["dpkg-source", "-b", builddir])
+ else:
+ raise
+ finally:
+ if args.no_clean:
+ logging.warn("you should clean up temp files in %s", temp_dir)
+ else:
+ shutil.rmtree(temp_dir)
+
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
diff --git a/debrepatch b/debrepatch
index 2ac605f..23f358a 100755
--- a/debrepatch
+++ b/debrepatch
@@ -1,21 +1,19 @@
#!/bin/sh
# Apply a debdiff to the latest version of a package, and maybe run a command.
#
-# Depends: dpkg-dev, quilt
+# Depends: dpkg-dev
#
scriptdir="$(readlink -f "$(dirname "$0")")"
debpatch="$scriptdir/debpatch"
set -e
USAGE="Usage: $0 [-hyuf] [-p dir] [-b dir] SOURCE_PACKAGE [post_patch_command [args ...]]"
-force_yes=false
auto_update=false
force_postpatch=false
patchdir="./patches"
builddir="./build"
while getopts 'hyufb:p:' o; do
case $o in
- y ) force_yes=true;;
u ) auto_update=true;;
f ) force_postpatch=true;;
p ) patchdir="$OPTARG";;
@@ -29,8 +27,6 @@ mentioned in d/changelog from the debdiff, then run a command from inside the
unpacked and patched package source directory.
-h This help text.
- -y Answer "yes" to all questions, e.g. automatically trying to
- refresh d/patches when a naive source-build fails.
-u Run "apt-get update" first; requires sudo.
-f Run post_patch_command even if there was no version change,
i.e. the current version is the same as what is mentioned in
@@ -48,11 +44,6 @@ log() {
# TODO: add some colours based on $1
echo >&2 "$0: $2"
}
-maybe_ask() { read -p "$1 (press enter to continue) " x >&2; }
-if $force_yes; then
- maybe_ask() { log I "$1"; }
-fi
-export QUILT_PATCHES=debian/patches
test -n "$1" || { log E "$USAGE"; exit 2; }
srcpkg="$1"
@@ -82,44 +73,15 @@ oldver="$(LC_ALL="en_US.utf8" apt-get source -s "$srcpkg" | sed -n -e "s/.*versi
srcdir="$srcpkg-$(echo "$oldver" | sed -re 's/([^:]*:)?(.*)-.*/\2/')"
apt-get source "$srcpkg"
olddsc="$(ls -1 *.dsc)" # TODO: probably more correct to use $oldver
-cd "$srcdir"
-"$debpatch" -v "$patchfile"
-
-build_dsc() {
- # we give "-nc" because:
- #
- # (1) some packages use build-dependencies in the "clean" rule and we'd
- # otherwise have to install them.
- # (2) this might interfere with our ability to retry build_dsc in case it
- # fails - e.g. because "clean" might remove source files or whatever.
- #
- # Since we did nothing to the package, "-nc" is fine and we can ignore
- # dpkg-buildpackage's warning about "might contain undesired files".
- dpkg-buildpackage -d -nc -uc -us -S
-}
-
-refreshed_patches=false
-if ! build_dsc; then
- maybe_ask "*** Failed to create source package, will refresh patches and try again."
- { quilt pop -afq || true; while quilt push -q; do quilt refresh; done; } >/dev/null
- refreshed_patches=true
-
- build_dsc
-fi
-
-version="$(dpkg-parsechangelog -c1 -SVersion | sed -e 's/^[0-9]*://')"
-newdsc="${srcpkg}_${version}.dsc"
-cd ..
+rm -rf "$srcdir"
+"$debpatch" --verbose --no-clean "$olddsc" "$patchfile"
+newdsc="$(ls -1 *.dsc | grep -v -F "$olddsc")"
log I "$srcpkg patched successfully"
ls -l "$olddsc" "$newdsc"
debdiff "$olddsc" "$newdsc" > "$patchdir/$srcpkg.patch.new" || true
log I "updated patch written to $patchdir/$srcpkg.patch.new"
-if $refreshed_patches; then
- log W "however, we had to refresh d/patches in order to build $newdsc;"
- log W "you should review the updated patch for unnecessary cruft before re-submitting it."
-fi
if [ -z "$*" ]; then exit 0; fi
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/reproducible/debrepatch.git
More information about the Reproducible-commits
mailing list