[devscripts] 01/01: Merge remote-tracking branch 'origin/pu/debpatch'
James McCoy
jamessan at debian.org
Thu Jun 29 13:20:24 UTC 2017
This is an automated email from the git hooks/post-receive script.
jamessan pushed a commit to branch master
in repository devscripts.
commit 6361402c9f6b9fbe469fa7a064b48c37c47a15c7
Merge: bd3b2ce c60bcf3
Author: James McCoy <jamessan at debian.org>
Date: Thu Jun 29 08:50:56 2017 -0400
Merge remote-tracking branch 'origin/pu/debpatch'
Add new debpatch script from Ximin Luo.
* Fix flake8 various errors
README | 7 +
conf.default.in | 4 +
debian/changelog | 4 +
debian/control | 4 +
debian/copyright | 4 +-
po4a/devscripts-po4a.conf | 2 +
scripts/Makefile | 2 +-
scripts/debdiff.1 | 1 +
scripts/debpatch | 319 ++++++++++++++++++++++++++++++++++++++++++++++
scripts/debpatch.1 | 112 ++++++++++++++++
10 files changed, 456 insertions(+), 3 deletions(-)
diff --cc scripts/debpatch
index 0000000,09b7d53..5e0e5f3
mode 000000,100755..100755
--- a/scripts/debpatch
+++ b/scripts/debpatch
@@@ -1,0 -1,289 +1,319 @@@
+ #!/usr/bin/python3
+ # This program is free software; you can redistribute it and/or
+ # modify it under the terms of the GNU General Public License
+ # as published by the Free Software Foundation; either version 3
+ # of the License, or (at your option) any later version.
+ #
+ # This program is distributed in the hope that it will be useful,
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ # GNU General Public License for more details.
+ #
+ # See file /usr/share/common-licenses/GPL-3 for more details.
+ #
+ """
+ Apply a debdiff to a Debian source package.
+
+ It handles d/changelog hunks specially, to avoid conflicts.
+
+ Depends on dpkg-dev, devscripts, python3-unidiff, quilt.
+ """
+
+ import argparse
+ import email.utils
+ import hashlib
-import io
+ import logging
+ import os
-import random
+ import unidiff
+ import shutil
+ import subprocess
+ import sys
+ import tempfile
+ import time
+
+ from debian.changelog import Changelog, ChangeBlock
+
+ 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"
++DCH_DUMMY_TAIL = "\n -- debpatch dummy tool <infinity0 at debian.org> " \
++ "Thu, 01 Jan 1970 00:00:00 +0000\n\n"
+ CHBLOCK_DUMMY_PACKAGE = "debpatch PLACEHOLDER"
+ TRY_ENCODINGS = ["utf-8", "latin-1"]
+ DISTRIBUTION_DEFAULT = "experimental"
+
++
+ def workaround_dpkg_865430(dscfile, origdir, stdout):
+ f = subprocess.check_output(["dcmd", "--tar", "echo", dscfile]).rstrip()
+ if not os.path.exists(os.path.join(origdir.encode("utf-8"), os.path.basename(f))):
+ C(["dcmd", "--tar", "cp", dscfile, origdir], stdout=stdout)
+
++
+ def is_dch(path):
+ return (basename(path) == 'changelog'
- and basename(dirname(path)) == 'debian'
- and dirname(dirname(dirname(path))) == '')
++ and basename(dirname(path)) == 'debian'
++ and dirname(dirname(dirname(path))) == '')
++
+
+ def hunk_lines_to_str(hunk_lines):
+ return "".join(map(lambda x: str(x)[1:], hunk_lines))
+
++
+ def read_dch_patch(dch_patch):
+ if len(dch_patch) > 1:
- raise ValueError("don't know how to deal with d/changelog patch that has more than one hunk")
++ raise ValueError("don't know how to deal with debian/changelog patch "
++ "that has more than one hunk")
+ hunk = dch_patch[0]
+ source_str = hunk_lines_to_str(hunk.source_lines()) + DCH_DUMMY_TAIL
+ target_str = hunk_lines_to_str(hunk.target_lines())
+ # here we assume the debdiff has enough context to see the previous version
+ # this should be true all the time in practice
+ source_version = str(Changelog(source_str, 1)[0].version)
+ target = Changelog(target_str, 1)[0]
+ return source_version, target
+
++
+ def apply_dch_patch(source_file, current, patch_name, old_version, target, dry_run):
+ target_version = str(target.version)
- dch_args = []
- dch_env = dict(os.environ)
+
+ if not old_version or not target_version.startswith(old_version):
+ logging.warn("don't know how to rebase version-change (%s => %s) onto %s" %
- (old_version, target_version, old_version))
++ (old_version, target_version, old_version))
+ newlog = subprocess.getoutput("EDITOR=cat dch -n 2>/dev/null").rstrip()
+ version = str(Changelog(newlog, 1)[0].version)
+ logging.warn("using version %s based on `dch -n`; feel free to make me smarter", version)
+ else:
+ version_suffix = target_version[len(old_version):]
+ version = str(current[0].version) + version_suffix
+ logging.info("using version %s based on suffix %s", version, version_suffix)
+
+ if dry_run:
+ return version
+
+ current._blocks.insert(0, target)
+ current.set_version(version)
+
+ shutil.copy(source_file, source_file + ".new")
+ try:
+ with open(source_file + ".new", "w") as fp:
+ current.write_to_open_file(fp)
+ os.rename(source_file + ".new", source_file)
+ except:
+ logging.warn("failed to patch %s", source_file)
+ logging.warn("half-applied changes in %s", source_file + ".new")
+ logging.warn("current working directory is %s", os.getcwd())
+ raise
+
++
+ def call_patch(patch_str, *args, check=True, **kwargs):
+ return subprocess.run(
+ ["patch", "-p1"] + list(args),
+ input=patch_str,
+ universal_newlines=True,
+ check=check,
+ **kwargs)
+
++
+ def check_patch(patch_str, *args, **kwargs):
+ return call_patch(patch_str,
- "--dry-run", "-f", "--silent",
- *args,
- check=False,
- stdout=subprocess.DEVNULL,
- stderr=subprocess.DEVNULL,
- **kwargs).returncode == 0
++ "--dry-run", "-f", "--silent",
++ *args,
++ check=False,
++ stdout=subprocess.DEVNULL,
++ stderr=subprocess.DEVNULL,
++ **kwargs).returncode == 0
++
+
+ def debpatch(patch, patch_name, args):
+ # don't change anything if...
+ dry_run = args.target_version or args.source_version
+
+ changelog = list(filter(lambda x: is_dch(x.path), patch))
+ if not changelog:
+ logging.info("no debian/changelog in patch: %s" % args.patch_file)
+ old_version = None
+ target = ChangeBlock(
- package = CHBLOCK_DUMMY_PACKAGE,
- author = "%s <%s>" % (os.getenv("DEBFULLNAME"), os.getenv("DEBEMAIL")),
- date = email.utils.formatdate(time.time(), localtime=True),
- version = None,
- distributions = args.distribution,
- urgency = "low",
- changes = ["", " * Rebase patch %s." % patch_name, ""],
++ package=CHBLOCK_DUMMY_PACKAGE,
++ author="%s <%s>" % (os.getenv("DEBFULLNAME"), os.getenv("DEBEMAIL")),
++ date=email.utils.formatdate(time.time(), localtime=True),
++ version=None,
++ distributions=args.distribution,
++ urgency="low",
++ changes=["", " * Rebase patch %s." % patch_name, ""],
+ )
+ target.add_trailing_line("")
+ elif len(changelog) > 1:
+ raise ValueError("more than one debian/changelog patch???")
+ else:
+ patch.remove(changelog[0])
+ old_version, target = read_dch_patch(changelog[0])
+
+ if args.source_version:
+ if old_version:
+ print(old_version)
+ return False
+
+ # read this here so --source-version can work even without a d/changelog
+ with open(args.changelog) as fp:
+ current = Changelog(fp.read())
+ if target.package == CHBLOCK_DUMMY_PACKAGE:
+ target.package = current[0].package
+
+ if not dry_run:
+ patch_str = str(patch)
+ if check_patch(patch_str, "-N"):
+ call_patch(patch_str)
+ logging.info("patch %s applies!", patch_name)
+ elif check_patch(patch_str, "-R"):
+ logging.warn("patch %s already applied", patch_name)
+ return False
+ else:
+ call_patch(patch_str, "--dry-run", "-f")
+ raise ValueError("patch %s doesn't apply!", patch_name)
+
+ # only apply d/changelog patch if the rest of the patch applied
- new_version = apply_dch_patch(args.changelog, current, patch_name, old_version, target, dry_run)
++ new_version = apply_dch_patch(args.changelog, current, patch_name,
++ old_version, target, dry_run)
+ if args.target_version:
+ print(new_version)
+ return False
+
+ if args.repl:
+ import code
+ code.interact(local=locals())
+
+ return True
+
++
+ def main(args):
+ parser = argparse.ArgumentParser(
+ description='Apply a debdiff to a Debian source package')
- 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('-D', '--distribution', default='experimental',
- help='Distribution to use, if the patch doesn\'t already contain a '
- 'changelog; default: %(default)s')
- parser.add_argument('--repl', 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",
++ 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(
++ '-D', '--distribution', default='experimental',
++ help='Distribution to use, if the patch doesn\'t already '
++ 'contain a changelog; default: %(default)s',
++ )
++ parser.add_argument(
++ '--repl', 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, as specified by the other arguments.")
- parser.add_argument('orig_dsc_or_dir', nargs='?', default=".",
++ "the given target package, as 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",
++ "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")
++ "Default: %(default)s",
++ )
+ group1 = parser.add_argument_group('Options for .dsc patch targets')
- group1.add_argument('--no-clean', action="store_true",
++ 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",
++ "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.")
- group1.add_argument('-d', '--directory', default=None,
++ "patches using quilt(1) then try building it again.",
++ )
++ group1.add_argument(
++ '-d', '--directory', default=None,
+ help="Extract the .dsc into this directory, which won't be cleaned up "
+ "after debpatch(1) exits. If not given, then it will be extracted to a "
- "temporary directory.")
++ "temporary directory.",
++ )
+ 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
++ 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):
+ dscfile = args.orig_dsc_or_dir
+ parts = os.path.splitext(os.path.basename(dscfile))
+ if parts[1] != ".dsc":
+ raise ValueError("unrecognised patch target: %s" % dscfile)
+ extractdir = args.directory if args.directory else tempfile.mkdtemp()
+ if not os.path.isdir(extractdir):
+ os.makedirs(extractdir)
+ try:
- builddir = os.path.join(extractdir, parts[0]) # dpkg-source doesn't like existing dirs
++ builddir = os.path.join(extractdir, parts[0]) # dpkg-source doesn't like existing dirs
+ C(["dpkg-source", "-x", "--skip-patches", dscfile, builddir], stdout=stdout)
+ origdir = os.getcwd()
+ workaround_dpkg_865430(dscfile, origdir, stdout)
+ os.chdir(builddir)
+ did_patch = debpatch(patch, patch_name, args)
+ if dry_run or not did_patch:
+ 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)
++ ], cwd=builddir)
+ C(["dpkg-source", "-b", builddir])
+ else:
+ raise
+ finally:
+ cleandir = builddir if args.directory else extractdir
+ if args.no_clean:
+ logging.warn("you should clean up temp files in %s", cleandir)
+ else:
+ shutil.rmtree(cleandir)
+
++
+ if __name__ == "__main__":
+ sys.exit(main(sys.argv[1:]))
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/collab-maint/devscripts.git
More information about the devscripts-devel
mailing list