summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--gbp/scripts/common/pq.py213
-rwxr-xr-xgbp/scripts/pq.py40
-rw-r--r--tests/13_test_gbp_pq.py15
3 files changed, 106 insertions, 162 deletions
diff --git a/gbp/scripts/common/pq.py b/gbp/scripts/common/pq.py
index 7e6e210..7bd855e 100644
--- a/gbp/scripts/common/pq.py
+++ b/gbp/scripts/common/pq.py
@@ -20,8 +20,9 @@
import re
import os
-import shutil
import subprocess
+import textwrap
+
from gbp.git import GitRepositoryError, GitModifier
from gbp.errors import GbpError
import gbp.log
@@ -65,146 +66,76 @@ def pq_branch_base(pq_branch):
return pq_branch[len(PQ_BRANCH_PREFIX):]
-def patch_read_header(src):
- """
- Read a patches header and split it into single lines. We
- assume the header ends at the first line starting with
- "diff ..."
- """
- header = []
-
- for line in src:
- if line.startswith('diff '):
- break
- else:
- header.append(line)
- else:
- raise GbpError("Failed to find patch header in %s" % src.name)
- return header
-
-
-def patch_header_parse_topic(header):
- """
- Parse the topic from the patch header removing the corresponding
- line. This mangles the header in place.
-
- @param header: patch header
- @type header: C{list} of C{str}
-
- >>> h = ['foo', 'gbp-pq-topic: bar']
- >>> patch_header_parse_topic(h)
- 'bar'
- >>> h
- ['foo']
- """
- topic = None
- index = -1
-
- for line in header:
- if line.lower().startswith("gbp-pq-topic: "):
- index = header.index(line)
- break
- if index != -1:
- topic = header[index].split(" ", 1)[1].strip()
- del header[index]
- return topic
-
-
-def patch_header_mangle_newline(header):
- """
- Look for the diff stat separator and remove
- trailing new lines before it. This mangles
- the header in place.
-
- @param header: patch header
- @type header: C{list} of C{str}
-
- >>> h = ['foo bar\\n', '\\n', 'bar', '\\n', '\\n', '\\n', '---\\n', '\\n']
- >>> patch_header_mangle_newline(h)
- >>> h
- ['foo bar\\n', '\\n', 'bar', '\\n', '---\\n', '\\n']
- """
- while True:
- try:
- index = header.index('---\n')
- except ValueError:
- return
- try:
- # Remove trailing newlines until we have at
- # at most one left
- if header[index-1] == header[index-2] == '\n':
- del header[index-2]
- else:
- return
- except IndexError:
- return
-
-
-def patch_write_header(srcname, dstname):
- """
- Write out the patch header doing any necessary processing such
- as detecting and removing a given topic, dropping trailing
- new lines and skipping the first line containing the sha1.
- """
- topic = None
-
- with open(srcname) as src:
- header = patch_read_header(src)
- header_len = len(''.join(header))
-
- topic = patch_header_parse_topic(header)
- patch_header_mangle_newline(header)
-
- with open(dstname, 'w') as dst:
- dst.write(''.join(header[1:]))
-
- return (header_len, topic)
-
-
-def patch_write_content(srcname, dstname, header_len):
- """
- Write out the patch body skipping the header
- """
- with open(srcname) as src:
- src.seek(header_len, 0)
- with open(dstname, 'a') as dst:
- dst.write(src.read())
-
-
-def write_patch(patch, patch_dir, options):
- """Write the patch exported by 'git-format-patch' to it's final location
- (as specified in the commit)"""
- oldname = os.path.basename(patch)
- tmpname = patch + ".gbp"
- topic = None
-
- header_len, topic = patch_write_header(patch, tmpname)
- patch_write_content(patch, tmpname, header_len)
-
- if options.patch_numbers:
- newname = oldname
- else:
- patch_re = re.compile("[0-9]+-(?P<name>.+)")
- m = patch_re.match(oldname)
- if m:
- newname = m.group('name')
- else:
- raise GbpError("Can't get patch name from '%s'" % oldname)
-
- if topic:
- dstdir = os.path.join(patch_dir, topic)
- else:
- dstdir = patch_dir
-
- if not os.path.isdir(dstdir):
- os.makedirs(dstdir, 0755)
-
- os.unlink(patch)
- dstname = os.path.join(dstdir, newname)
- gbp.log.debug("Moving %s to %s" % (tmpname, dstname))
- shutil.move(tmpname, dstname)
-
- return dstname
+def write_patch_file(filename, commit_info, diff):
+ """Write patch file"""
+ if not diff:
+ gbp.log.debug("I won't generate empty diff %s" % filename)
+ return None
+ try:
+ with open(filename, 'w') as patch:
+ name = commit_info['author']['name']
+ email = commit_info['author']['email']
+ # Put name in quotes if special characters found
+ if re.search("[,.@()\[\]\\\:;]", name):
+ name = '"%s"' % name
+ patch.write('From: %s <%s>\n' % (name, email))
+ date = commit_info['author'].datetime
+ patch.write('Date: %s\n' %
+ date.strftime('%a, %-d %b %Y %H:%M:%S %z'))
+ subj_lines = textwrap.wrap('Subject: ' + commit_info['subject'],
+ 77, subsequent_indent=' ',
+ break_long_words=False,
+ break_on_hyphens=False)
+ patch.write('\n'.join(subj_lines) + '\n\n')
+ patch.writelines(commit_info['body'])
+ patch.write('---\n')
+ patch.write(diff)
+ except IOError as err:
+ raise GbpError('Unable to create patch file: %s' % err)
+ return filename
+
+
+def format_patch(outdir, repo, commit_info, series, numbered=True,
+ topic_regex=None):
+ """Create patch of a single commit"""
+ commit = commit_info['id']
+
+ # Parse and filter commit message body
+ topic = ""
+ mangled_body = ""
+ for line in commit_info['body'].splitlines():
+ if topic_regex:
+ match = re.match(topic_regex, line, flags=re.I)
+ if match:
+ topic = match.group('topic')
+ gbp.log.debug("Topic %s found for %s" % (topic, commit))
+ continue
+ mangled_body += line + '\n'
+ commit_info['body'] = mangled_body
+
+ # Determine filename and path
+ outdir = os.path.join(outdir, topic)
+ if not os.path.exists(outdir):
+ os.makedirs(outdir)
+ num_prefix = '%04d-' % (len(series) + 1)
+ suffix = '.patch'
+ base_maxlen = 63 - len(num_prefix) - len(suffix)
+ base = commit_info['patchname'][:base_maxlen]
+ filename = (num_prefix if numbered else '') + base + suffix
+ filepath = os.path.join(outdir, filename)
+ # Make sure that we don't overwrite existing patches in the series
+ if filepath in series:
+ presuffix = '-%d' % len(series)
+ base = base[:base_maxlen-len(presuffix)] + presuffix
+ filename = (num_prefix if numbered else '') + base + suffix
+ filepath = os.path.join(outdir, filename)
+
+ # Finally, create the patch
+ diff = repo.diff('%s^!' % commit, stat=80, summary=True, text=True)
+ patch = write_patch_file(filepath, commit_info, diff)
+ if patch:
+ series.append(patch)
+ return patch
def get_maintainer_from_control(repo):
diff --git a/gbp/scripts/pq.py b/gbp/scripts/pq.py
index 59be383..c0f0946 100755
--- a/gbp/scripts/pq.py
+++ b/gbp/scripts/pq.py
@@ -22,6 +22,7 @@ import os
import shutil
import sys
import tempfile
+import re
from gbp.config import GbpOptionParserDebian
from gbp.git import (GitRepositoryError, GitRepository)
from gbp.command_wrappers import (GitCommand, CommandExecFailed)
@@ -29,7 +30,7 @@ from gbp.errors import GbpError
import gbp.log
from gbp.patch_series import (PatchSeries, Patch)
from gbp.scripts.common.pq import (is_pq_branch, pq_branch_name, pq_branch_base,
- write_patch, switch_to_pq_branch,
+ format_patch, switch_to_pq_branch,
apply_single_patch, apply_and_commit_patch,
drop_pq, get_maintainer_from_control)
@@ -37,6 +38,27 @@ PATCH_DIR = "debian/patches/"
SERIES_FILE = os.path.join(PATCH_DIR,"series")
+def generate_patches(repo, start, end, outdir, options):
+ """
+ Generate patch files from git
+ """
+ gbp.log.info("Generating patches from git (%s..%s)" % (start, end))
+ patches = []
+ for treeish in [start, end]:
+ if not repo.has_treeish(treeish):
+ raise GbpError('%s not a valid tree-ish' % treeish)
+
+ # Generate patches
+ rev_list = reversed(repo.get_commits(start, end))
+ topic_regex = 'gbp-pq-topic:\s*(?P<topic>\S.*)'
+ for commit in rev_list:
+ info = repo.get_commit_info(commit)
+ format_patch(outdir, repo, info, patches, options.patch_numbers,
+ topic_regex=topic_regex)
+
+ return patches
+
+
def export_patches(repo, branch, options):
"""Export patches from the pq branch into a patch series"""
if is_pq_branch(branch):
@@ -54,18 +76,12 @@ def export_patches(repo, branch, options):
else:
gbp.log.debug("%s does not exist." % PATCH_DIR)
- patches = repo.format_patches(branch,
- pq_branch, PATCH_DIR,
- signature=False,
- symmetric=False)
- if patches:
- f = open(SERIES_FILE, 'w')
- gbp.log.info("Regenerating patch queue in '%s'." % PATCH_DIR)
- for patch in patches:
- filename = write_patch(patch, PATCH_DIR, options)
- f.write(filename[len(PATCH_DIR):] + '\n')
+ patches = generate_patches(repo, branch, pq_branch, PATCH_DIR, options)
- f.close()
+ if patches:
+ with open(SERIES_FILE, 'w') as seriesfd:
+ for patch in patches:
+ seriesfd.write(os.path.relpath(patch, PATCH_DIR) + '\n')
GitCommand('status')(['--', PATCH_DIR])
else:
gbp.log.info("No patches on '%s' - nothing to do." % pq_branch)
diff --git a/tests/13_test_gbp_pq.py b/tests/13_test_gbp_pq.py
index cebea25..753143d 100644
--- a/tests/13_test_gbp_pq.py
+++ b/tests/13_test_gbp_pq.py
@@ -21,6 +21,7 @@ import os
import logging
import unittest
+from gbp.scripts.pq import generate_patches
import gbp.scripts.common.pq as pq
import gbp.patch_series
import tests.testutils as testutils
@@ -103,8 +104,8 @@ class TestWritePatch(testutils.DebianGitTestRepo):
def tearDown(self):
context.teardown()
- def test_write_patch(self):
- """Test moving a patch to it's final location"""
+ def test_generate_patches(self):
+ """Test generation of patches"""
class opts: patch_numbers = False
@@ -115,13 +116,9 @@ class TestWritePatch(testutils.DebianGitTestRepo):
# Write it out as patch and check it's existence
d = context.new_tmpdir(__name__)
- patchfile = self.repo.format_patches('HEAD^', 'HEAD', str(d))[0]
- expected = os.path.join(str(d), '0001-added-foo.patch')
- self.assertEqual(expected, patchfile)
- pq.write_patch(patchfile, self.repo.path, opts)
- expected = os.path.join(self.repo.path,
- 'gbptest',
- 'added-foo.patch')
+ patchfile = generate_patches(self.repo, 'HEAD^', 'HEAD', str(d),
+ opts)[0]
+ expected = os.path.join(str(d), 'gbptest', 'added-foo.patch')
self.assertTrue(os.path.exists(expected))
logging.debug(file(expected).read())