aboutsummaryrefslogtreecommitdiffhomepage
path: root/gbp/scripts/common/pq.py
diff options
context:
space:
mode:
authorMarkus Lehtonen <markus.lehtonen@linux.intel.com>2013-05-02 12:57:10 +0300
committerGuido Günther <agx@sigxcpu.org>2013-10-31 19:17:20 +0100
commitc661c7153783c79c45fbe63d828b03ea0a788215 (patch)
treefd2190d631e47c1f8d9b99f4949da237392eaacc /gbp/scripts/common/pq.py
parent3092623a8841a6aa9e12902436779c8dea80598c (diff)
pq: rewrite patch export functionality
Use our own function for constructing the patch files instead of using the format-patch command of git. This way, we get the desired output format directly, without the need for the error-prone "format-patch, parse patch files, mangle and re-write patch files" cycle. Also, fix patch naming in patch generation when '--no-patch-numbers' is used. Previously, multiple commits with the same subject resulted in multiple patches having the same filename. This lead into broken series with missing patches as patch files were overwritten by the topmost commit. Signed-off-by: Markus Lehtonen <markus.lehtonen@linux.intel.com>
Diffstat (limited to 'gbp/scripts/common/pq.py')
-rw-r--r--gbp/scripts/common/pq.py213
1 files changed, 72 insertions, 141 deletions
diff --git a/gbp/scripts/common/pq.py b/gbp/scripts/common/pq.py
index 7e6e210b..7bd855e6 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):