[devscripts] 02/02: debpatch: rewrite to use debian.changelog python module

Ximin Luo infinity0 at debian.org
Tue Apr 18 18:49:50 UTC 2017


This is an automated email from the git hooks/post-receive script.

infinity0 pushed a commit to branch pu/debpatch
in repository devscripts.

commit 290b7ffbe10e50a35b3e369dd4db7a101ae8e9cd
Author: Ximin Luo <infinity0 at debian.org>
Date:   Tue Apr 18 20:49:21 2017 +0200

    debpatch: rewrite to use debian.changelog python module
    
    This allows us to avoid dpkg-parsechangelog and mostly dch.
    
    Also tidy up some other things based on reviewer feedback.
---
 debian/copyright   |  20 +-------
 scripts/debpatch   | 139 ++++++++++++++++++++---------------------------------
 scripts/debpatch.1 |   4 ++
 3 files changed, 59 insertions(+), 104 deletions(-)

diff --git a/debian/copyright b/debian/copyright
index 438c48e..f3f40dc 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -21,8 +21,8 @@ the GPL, version 2 or later.
 - annotate-output, debdiff and nmudiff are released under version 2
   (only) of the GPL.
 
-- debsnap, diff2patches and git-deborig are released under version 3
-  or later of the GPL
+- debpatch, debsnap, diff2patches and git-deborig are released under
+  version 3 or later of the GPL
 
 - deb-reversion is under the Artistic License version 2.0.
 
@@ -82,22 +82,6 @@ License: GPL-3+
  On Debian systems, the complete text of the GNU General Public License
  version 3 can be found in the /usr/share/common-licenses/GPL-3 file.
 
-Files: scripts/debpatch*
-Copyright: 2016 Ximin Luo <infinity0 at debian.org>
-License: GPL-3+
- 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.
- .
- On Debian systems, the complete text of the GNU General Public License
- version 3 can be found in the /usr/share/common-licenses/GPL-3 file.
-
 Files: doc/suspicious-source.1
        doc/wrap-and-sort.1
        scripts/devscripts/*
diff --git a/scripts/debpatch b/scripts/debpatch
index fd861e3..578392a 100755
--- a/scripts/debpatch
+++ b/scripts/debpatch
@@ -20,6 +20,7 @@ Depends on dpkg-dev, devscripts, python3-unidiff, quilt.
 """
 
 import argparse
+import email.utils
 import hashlib
 import io
 import logging
@@ -30,6 +31,9 @@ import shutil
 import subprocess
 import sys
 import tempfile
+import time
+
+from debian.changelog import Changelog, ChangeBlock
 
 dirname = os.path.dirname
 basename = os.path.basename
@@ -40,22 +44,6 @@ DCH_DUMMY_TAIL = "\n -- debpatch dummy tool <infinity0 at debian.org>  Thu, 01 Jan
 TRY_ENCODINGS = ["utf-8", "latin-1"]
 DISTRIBUTION_DEFAULT = "experimental"
 
-def parse_dch(dch_str, *args):
-    return subprocess.run(
-        ["dpkg-parsechangelog", "-l-", "-c1"] + list(args),
-        input=dch_str,
-        check=True,
-        universal_newlines=True,
-        stdout=subprocess.PIPE,
-        ).stdout.rstrip()
-
-def read_dch(dch_str):
-    dch = {}
-    for i in ("Version", "Distribution", "Urgency", "Maintainer"):
-        dch[i] = parse_dch(dch_str, "-S"+i)
-    dch["Changes"] = "".join(parse_dch(dch_str, "-SChanges").splitlines(True)[3:])
-    return dch
-
 def is_dch(path):
     return (basename(path) == 'changelog'
         and basename(dirname(path)) == 'debian'
@@ -72,68 +60,42 @@ def read_dch_patch(dch_patch):
     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 = parse_dch(source_str, "-SVersion")
-    target = read_dch(target_str)
+    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):
-    # 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 target["Version"]
-
+    target_version = str(target.version)
     dch_args = []
     dch_env = dict(os.environ)
 
-    if target["Distribution"] == "UNRELEASED":
-        # UNRELEASED causes hard-to-reason-about behaviours in dch, let's avoid that
-        newdist = current["Distribution"] if current["Distribution"] != "UNRELEASED" else DISTRIBUTION_DEFAULT
-        logging.info("using distribution '%s' instead of 'UNRELEASED'", newdist)
-        target["Distribution"] = newdist
-
-    if not old_version or not target["Version"].startswith(old_version):
-        logging.warn("don't know how to reapply version-change %s to %s" %
-            (old_version, target["Version"]))
-        version = subprocess.check_output(["sh", "-c",
-            "EDITOR=cat dch -n 2>/dev/null | dpkg-parsechangelog -l- -SVersion"
-            ]).decode("utf-8").rstrip()
+    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))
+        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 = current["Version"] + version_suffix
+        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
 
-    dch_args += ["-v", version]
-    dch_args += ["--force-distribution", "-D", target["Distribution"]]
-    dch_args += ["-u", target["Urgency"]]
-    if "Maintainer" in target:
-        dch_env["DEBEMAIL"] = target["Maintainer"]
-        del dch_env["DEBFULLNAME"]
-
-    changes = target["Changes"]
-    if changes.lstrip().startswith("["):
-        changes = "\n" + changes
+    current._blocks.insert(0, target)
+    current.set_version(version)
 
-    token = "DEBPATCH PLACEHOLDER %s DELETEME" % random.randint(0, 2**64)
-    shutil.copy(source_file, source_file + ".debpatch.bak")
+    shutil.copy(source_file, source_file + ".new")
     try:
-        C(["dch", "-c", source_file] + dch_args + [token])
-        C(["dch", "-c", source_file, "-a", marker], )
-        C(["sed", "-e", "/%s/c\\\n%s" % (token, changes.replace("\n", "\\\n")), "-i", source_file])
+        with open(source_file + ".new", "w") as fp:
+            current.write_to_open_file(fp)
+        os.rename(source_file + ".new", source_file)
     except:
-        os.rename(source_file, source_file + ".debpatch.err")
         logging.warn("failed to patch %s", source_file)
-        logging.warn("half-applied changes in %s", source_file + ".debpatch.err")
+        logging.warn("half-applied changes in %s", source_file + ".new")
         logging.warn("current working directory is %s", os.getcwd())
-        os.rename(source_file + ".debpatch.bak", source_file)
         raise
-    else:
-        os.unlink(source_file + ".debpatch.bak")
 
 def call_patch(patch_str, *args, check=True, **kwargs):
     return subprocess.run(
@@ -152,34 +114,27 @@ def check_patch(patch_str, *args, **kwargs):
         stderr=subprocess.DEVNULL,
         **kwargs).returncode == 0
 
-def apply_patch_str(patch_name, patch_str):
-    if check_patch(patch_str, "-N"):
-        call_patch(patch_str)
-        logging.info("patch %s applies!", patch_name)
-    elif check_patch(patch_str, "-R"):
-        logging.info("patch %s already applied", patch_name)
-    else:
-        call_patch(patch_str, "--dry-run")
-        raise ValueError("patch %s doesn't apply!", patch_name)
-
 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")
-
     # don't change anything if...
     dry_run = args.target_version
 
+    with open(args.changelog) as fp:
+        current = Changelog(fp.read())
+
     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 = {
-            "Version": None,
-            "Distribution": DISTRIBUTION_DEFAULT,
-            "Urgency": "low",
-            "Changes": "  * Rebase patch %s." % patch_name,
-        }
+        target = ChangeBlock(
+            package = current[0].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:
@@ -189,23 +144,32 @@ def debpatch(patch, patch_name, args):
     if args.source_version:
         if old_version:
             print(old_version)
-        return
+        return False
 
     if not dry_run:
-        apply_patch_str(patch_name, str(patch))
+        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
-    with open(args.changelog) as fp:
-        current = read_dch(fp.read())
     new_version = apply_dch_patch(args.changelog, current, patch_name, old_version, target, dry_run)
     if args.target_version:
         print(new_version)
-        return
+        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')
@@ -213,6 +177,9 @@ def main(args):
         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",
@@ -283,8 +250,8 @@ def main(args):
             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:
+            did_patch = debpatch(patch, patch_name, args)
+            if dry_run or not did_patch:
                 return
             os.chdir(origdir)
             try:
diff --git a/scripts/debpatch.1 b/scripts/debpatch.1
index 8af6fb7..e94f55c 100644
--- a/scripts/debpatch.1
+++ b/scripts/debpatch.1
@@ -69,6 +69,10 @@ Output more information
 \fB\-c\fR CHANGELOG, \fB\-\-changelog\fR CHANGELOG
 Path to debian/changelog; default: debian/changelog
 .TP
+\fB\-D\fR DISTRIBUTION, \fB\-\-distribution\fR DISTRIBUTION
+Distribution to use, if the patch doesn't already contain a changelog; default:
+experimental
+.TP
 \fB\-\-repl\fR
 Run the python REPL after processing.
 .TP

-- 
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