/usr/share/pyshared/gbp/patch_series.py is in git-buildpackage 0.6.0~git20120601.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 | # vim: set fileencoding=utf-8 :
#
# (C) 2011 Guido Guenther <agx@sigxcpu.org>
# 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 2 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.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
"""Handle Patches and Patch Series"""
import os
import re
import subprocess
import tempfile
from gbp.errors import GbpError
class Patch(object):
"""
A patch in a L{PatchSeries}
@ivar path: path to the patch
@type path: string
@ivar topic: the topic of the patch (the directory component)
@type topic: string
@ivar strip: path components to strip (think patch -p<strip>)
@type strip: integer
@ivar info: Information retrieved from a RFC822 style patch header
@type info: C{dict} with C{str} keys and values
@ivar long_desc: the long description of the patch
"""
patch_exts = ['diff', 'patch']
def __init__(self, path, topic=None, strip=None):
self.path = path
self.topic = topic
self.strip = strip
self.info = None
self.long_desc = None
def __repr__(self):
repr = "<gbp.patch_series.Patch path='%s' " % self.path
if self.topic:
repr += "topic='%s' " % self.topic
if self.strip != None:
repr += "strip=%d " % self.strip
repr += ">"
return repr
def _read_info(self):
"""
Read patch information into a structured form
using I{git mailinfo}
"""
self.info = {}
body = tempfile.NamedTemporaryFile(prefix='gbp_')
pipe = subprocess.Popen("git mailinfo '%s' /dev/null < '%s'" %
(body.name, self.path),
shell=True,
stdout=subprocess.PIPE).stdout
for line in pipe:
if ':' in line:
rfc_header, value = line.split(" ", 1)
header = rfc_header[:-1].lower()
self.info[header] = value.strip()
try:
self.long_desc = "".join([ line for line in body ])
body.close()
except IOError as msg:
raise GbpError("Failed to read patch header of '%s': %s" %
(self.patch, msg))
finally:
if os.path.exists(body.name):
os.unlink(body.name)
def _get_subject_from_filename(self):
"""
Determine the patch's subject based on the it's 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'
@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
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 == None:
self._read_info()
if self.info.has_key(key):
return self.info[key]
else:
return get_val() if get_val else None
@property
def subject(self):
"""
The patch's subject, either from the patch header or from the filename.
"""
return self._get_info_field('subject', self._get_subject_from_filename)
@property
def author(self):
"""The patch's author"""
return self._get_info_field('author')
@property
def email(self):
"""The patch author's email address"""
return self._get_info_field('email')
@property
def date(self):
"""The patch's modification time"""
return self._get_info_field('date')
class PatchSeries(list):
"""
A series of L{Patch}es as read from a quilt series file).
"""
@classmethod
def read_series_file(klass, seriesfile):
"""Read a series file into L{Patch} objects"""
patch_dir = os.path.dirname(seriesfile)
if not os.path.exists(seriesfile):
return []
try:
s = file(seriesfile)
except Exception, err:
raise GbpError("Cannot open series file: %s" % err)
queue = klass._read_series(s, patch_dir)
s.close()
return queue
@classmethod
def _read_series(klass, series, patch_dir):
"""
Read patch series
>>> PatchSeries._read_series(['a/b', \
'a -p1', \
'a/b -p2'], '.') # doctest:+NORMALIZE_WHITESPACE
[<gbp.patch_series.Patch path='./a/b' topic='a' >,
<gbp.patch_series.Patch path='./a' strip=1 >,
<gbp.patch_series.Patch path='./a/b' topic='a' strip=2 >]
>>> PatchSeries._read_series(['# foo', 'a/b', '', '# bar'], '.')
[<gbp.patch_series.Patch path='./a/b' topic='a' >]
@param series: series of patches in quilt format
@type series: iterable of strings
@param patch_dir: path prefix to prepend to each patch path
@type patch_dir: string
"""
queue = PatchSeries()
for line in series:
try:
if line[0] in [ '\n', '#' ]:
continue
except IndexError:
continue # ignore empty lines
queue.append(klass._parse_line(line, patch_dir))
return queue
@staticmethod
def _get_topic(line):
"""
Get the topic from the patch's path
>>> PatchSeries._get_topic("a/b c")
'a'
>>> PatchSeries._get_topic("asdf")
>>> PatchSeries._get_topic("/asdf")
"""
topic = os.path.dirname(line)
if topic in [ '', '/' ]:
topic = None
return topic
@staticmethod
def _split_strip(line):
"""
Separate the -p<num> option from the patch name
>>> PatchSeries._split_strip("asdf -p1")
('asdf', 1)
>>> PatchSeries._split_strip("a/nice/patch")
('a/nice/patch', None)
>>> PatchSeries._split_strip("asdf foo")
('asdf foo', None)
"""
patch = line
strip = None
split = line.rsplit(None, 1)
if len(split) > 1:
m = re.match('-p(?P<level>[0-9]+)', split[1])
if m:
patch = split[0]
strip = int(m.group('level'))
return (patch, strip)
@classmethod
def _parse_line(klass, line, patch_dir):
"""
Parse a single line from a series file
>>> PatchSeries._parse_line("a/b -p1", '/tmp/patches')
<gbp.patch_series.Patch path='/tmp/patches/a/b' topic='a' strip=1 >
>>> PatchSeries._parse_line("a/b", '.')
<gbp.patch_series.Patch path='./a/b' topic='a' >
"""
line = line.rstrip()
topic = klass._get_topic(line)
(patch, split) = klass._split_strip(line)
return Patch(os.path.join(patch_dir, patch), topic, split)
|