aboutsummaryrefslogtreecommitdiffhomepage
path: root/gbp/patch_series.py
diff options
context:
space:
mode:
authorMaximiliano Curia <maxy@debian.org>2016-09-11 16:23:11 +0200
committerGuido Günther <agx@sigxcpu.org>2017-12-24 19:47:46 +0100
commit17a471d1fc07935dd85c31d3a7c4ae3ea5c39208 (patch)
tree4ef3b83af5078774ffb0c315f9ededaf3e707938 /gbp/patch_series.py
parent4312e54b6ed154f4149ddcfd1b88a40cc1b4caad (diff)
pq: Parse DEP3 headers
Currently the patch headers in DEP3 format are partially supported, as git's mailinfo only reads the From and Subject fields from the first paragraph. But the default in dep3 patches is Description and Author, that are ignored by git. Even worse, when this fields are in the first paragraph (again the default) git mailinfo drops all the contained information. This patch parses the dep3 headers if git's mailinfo couldn't obtain any useful information, any header other than Subject|Description and Author|From is appended to the patch message. The description field is splitted in first line for the short description and the rest is prepended to the patch message. Closes: #785274
Diffstat (limited to 'gbp/patch_series.py')
-rw-r--r--gbp/patch_series.py154
1 files changed, 152 insertions, 2 deletions
diff --git a/gbp/patch_series.py b/gbp/patch_series.py
index ccb12fe8..ec606379 100644
--- a/gbp/patch_series.py
+++ b/gbp/patch_series.py
@@ -16,12 +16,15 @@
# <http://www.gnu.org/licenses/>
"""Handle Patches and Patch Series"""
+import collections
import os
import re
import subprocess
import tempfile
from gbp.errors import GbpError
+VALID_DEP3_ENDS = re.compile(r'(?:---|\*\*\*|Index:)[ \t][^ \t]|^diff -|^---')
+
class Patch(object):
"""
@@ -56,6 +59,9 @@ class Patch(object):
return repr
def _read_info(self):
+ self._read_git_mailinfo()
+
+ def _read_git_mailinfo(self):
"""
Read patch information into a structured form
@@ -127,7 +133,7 @@ class Patch(object):
@param key: key to fetch
@type key: C{str}
@param get_val: alternate value if key is not in info dict
- @type get_val: C{str}
+ @type get_val: C{()->str}
"""
if self.info is None:
self._read_info()
@@ -160,6 +166,150 @@ class Patch(object):
return self._get_info_field('date')
+class Dep3Patch(Patch):
+ def _read_info(self):
+ self._read_git_mailinfo()
+ if not self.info:
+ self._check_dep3()
+
+ def _dep3_get_value(self, lines):
+ value = []
+ for line in lines:
+ if line.startswith(' '):
+ line = line[1:]
+ if line == '.\n':
+ line = line[1:]
+ else:
+ line = line.split(':', 1)[1].lstrip()
+ value.append(line)
+ return ''.join(value)
+
+ def _dep3_to_info(self, headers):
+ """
+ Process the ordered dict generated by check_dep3 and add the
+ information to self.info
+ """
+
+ def add_author(lines):
+ value = self._dep3_get_value(lines).strip()
+ m = re.match('(.*)<([^<>]+)>', value)
+ if m:
+ value = m.group(1).strip()
+ self.info['email'] = m.group(2)
+ self.info['author'] = value
+ return 1
+
+ def add_subject(lines, long_desc, changes):
+ value = self._dep3_get_value(lines).lstrip()
+ if '\n' in value:
+ value, description = value.split('\n', 1)
+ # prepend the continuation lines
+ long_desc = description + long_desc
+ self.info['subject'] = value
+ return long_desc, changes + 1
+
+ changes = 0
+ long_desc = self._dep3_get_value(headers.get('long_desc', list()))
+
+ for k, v in headers.items():
+ if k in ('author', 'from'):
+ changes += add_author(v)
+ elif k in ('subject', 'description'):
+ long_desc, changes = add_subject(v, long_desc, changes)
+ elif k == 'long_desc':
+ pass
+ else:
+ long_desc += ''.join(v)
+ changes += 1
+ if changes:
+ self.long_desc = long_desc + self.long_desc
+
+ def _check_dep3(self):
+ """
+ Read DEP3 patch information into a structured form
+ """
+ if not os.path.exists(self.path):
+ return
+
+ # patch_header logic from quilt plus any line starting with ---
+ # which is the dep3 stop processing and the git separation between the
+ # header and diff stat
+ headers = collections.OrderedDict()
+ current = 'long_desc'
+ with open(self.path) as file:
+ for line in file:
+ if VALID_DEP3_ENDS.search(line):
+ break
+
+ if line.startswith(' '):
+ # continuation
+ headers.setdefault(current, list()).append(line)
+ elif ':' in line:
+ current = line.split(':', 1)[0].lower()
+ headers.setdefault(current, list()).append(line)
+ else:
+ # end of paragraph or not a header, read_info already left
+ # everything else in the long_desc, nothing else to do
+ break
+ self._dep3_to_info(headers)
+
+ def _get_subject_from_filename(self):
+ """
+ Determine the patch's subject based on the its filename
+
+ >>> p = Patch('debian/patches/foo.patch')
+ >>> p._get_subject_from_filename()
+ 'foo'
+ >>> Patch('foo.patch')._get_subject_from_filename()
+ 'foo'
+ >>> Patch('debian/patches/foo.bar')._get_subject_from_filename()
+ 'foo.bar'
+ >>> p = Patch('debian/patches/foo')
+ >>> p._get_subject_from_filename()
+ 'foo'
+ >>> Patch('0123-foo.patch')._get_subject_from_filename()
+ 'foo'
+ >>> Patch('0123.patch')._get_subject_from_filename()
+ '0123'
+ >>> Patch('0123-foo-0123.patch')._get_subject_from_filename()
+ 'foo-0123'
+
+ @return: the patch's subject
+ @rtype: C{str}
+ """
+ subject = os.path.basename(self.path)
+ # Strip of .diff or .patch from patch name
+ try:
+ base, ext = subject.rsplit('.', 1)
+ if ext in self.patch_exts:
+ subject = base
+ except ValueError:
+ pass # No ext so keep subject as is
+ return subject.lstrip('0123456789-') or subject
+
+ def _get_info_field(self, key, get_val=None):
+ """
+ Return the key I{key} from the info C{dict}
+ or use val if I{key} is not a valid key.
+
+ Fill self.info if not already done.
+
+ @param key: key to fetch
+ @type key: C{str}
+ @param get_val: alternate value if key is not in info dict
+ @type get_val: C{str}
+ """
+ if self.info is None:
+ self._read_info()
+ if not self.info:
+ self._check_dep3()
+
+ if key in self.info:
+ return self.info[key]
+ else:
+ return get_val() if get_val else None
+
+
class PatchSeries(list):
"""
A series of L{Patch}es as read from a quilt series file).
@@ -281,4 +431,4 @@ class PatchSeries(list):
line = cls._strip_comment(line.rstrip())
topic = cls._get_topic(line)
(patch, split) = cls._split_strip(line)
- return Patch(os.path.join(patch_dir, patch), topic, split)
+ return Dep3Patch(os.path.join(patch_dir, patch), topic, split)