##// END OF EJS Templates
fix patch.patch.filterfiles....
Vadim Gelfer -
r2881:eab07a7b default
parent child Browse files
Show More
@@ -1,366 +1,365 b''
1 1 # patch.py - patch file parsing routines
2 2 #
3 3 # Copyright 2006 Brendan Cully <brendan@kublai.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from demandload import demandload
9 9 from i18n import gettext as _
10 10 from node import *
11 11 demandload(globals(), "cmdutil mdiff util")
12 12 demandload(globals(), "cStringIO email.Parser os re shutil sys tempfile")
13 13
14 14 def extract(ui, fileobj):
15 15 '''extract patch from data read from fileobj.
16 16
17 17 patch can be normal patch or contained in email message.
18 18
19 19 return tuple (filename, message, user, date). any item in returned
20 20 tuple can be None. if filename is None, fileobj did not contain
21 21 patch. caller must unlink filename when done.'''
22 22
23 23 # attempt to detect the start of a patch
24 24 # (this heuristic is borrowed from quilt)
25 25 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
26 26 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
27 27 '(---|\*\*\*)[ \t])', re.MULTILINE)
28 28
29 29 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
30 30 tmpfp = os.fdopen(fd, 'w')
31 31 try:
32 32 hgpatch = False
33 33
34 34 msg = email.Parser.Parser().parse(fileobj)
35 35
36 36 message = msg['Subject']
37 37 user = msg['From']
38 38 # should try to parse msg['Date']
39 39 date = None
40 40
41 41 if message:
42 42 message = message.replace('\n\t', ' ')
43 43 ui.debug('Subject: %s\n' % message)
44 44 if user:
45 45 ui.debug('From: %s\n' % user)
46 46 diffs_seen = 0
47 47 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
48 48
49 49 for part in msg.walk():
50 50 content_type = part.get_content_type()
51 51 ui.debug('Content-Type: %s\n' % content_type)
52 52 if content_type not in ok_types:
53 53 continue
54 54 payload = part.get_payload(decode=True)
55 55 m = diffre.search(payload)
56 56 if m:
57 57 ui.debug(_('found patch at byte %d\n') % m.start(0))
58 58 diffs_seen += 1
59 59 cfp = cStringIO.StringIO()
60 60 if message:
61 61 cfp.write(message)
62 62 cfp.write('\n')
63 63 for line in payload[:m.start(0)].splitlines():
64 64 if line.startswith('# HG changeset patch'):
65 65 ui.debug(_('patch generated by hg export\n'))
66 66 hgpatch = True
67 67 # drop earlier commit message content
68 68 cfp.seek(0)
69 69 cfp.truncate()
70 70 elif hgpatch:
71 71 if line.startswith('# User '):
72 72 user = line[7:]
73 73 ui.debug('From: %s\n' % user)
74 74 elif line.startswith("# Date "):
75 75 date = line[7:]
76 76 if not line.startswith('# '):
77 77 cfp.write(line)
78 78 cfp.write('\n')
79 79 message = cfp.getvalue()
80 80 if tmpfp:
81 81 tmpfp.write(payload)
82 82 if not payload.endswith('\n'):
83 83 tmpfp.write('\n')
84 84 elif not diffs_seen and message and content_type == 'text/plain':
85 85 message += '\n' + payload
86 86 except:
87 87 tmpfp.close()
88 88 os.unlink(tmpname)
89 89 raise
90 90
91 91 tmpfp.close()
92 92 if not diffs_seen:
93 93 os.unlink(tmpname)
94 94 return None, message, user, date
95 95 return tmpname, message, user, date
96 96
97 97 def readgitpatch(patchname):
98 98 """extract git-style metadata about patches from <patchname>"""
99 99 class gitpatch:
100 100 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
101 101 def __init__(self, path):
102 102 self.path = path
103 103 self.oldpath = None
104 104 self.mode = None
105 105 self.op = 'MODIFY'
106 106 self.copymod = False
107 107 self.lineno = 0
108 108
109 109 # Filter patch for git information
110 110 gitre = re.compile('diff --git a/(.*) b/(.*)')
111 111 pf = file(patchname)
112 112 gp = None
113 113 gitpatches = []
114 114 # Can have a git patch with only metadata, causing patch to complain
115 115 dopatch = False
116 116
117 117 lineno = 0
118 118 for line in pf:
119 119 lineno += 1
120 120 if line.startswith('diff --git'):
121 121 m = gitre.match(line)
122 122 if m:
123 123 if gp:
124 124 gitpatches.append(gp)
125 125 src, dst = m.group(1,2)
126 126 gp = gitpatch(dst)
127 127 gp.lineno = lineno
128 128 elif gp:
129 129 if line.startswith('--- '):
130 130 if gp.op in ('COPY', 'RENAME'):
131 131 gp.copymod = True
132 132 dopatch = 'filter'
133 133 gitpatches.append(gp)
134 134 gp = None
135 135 if not dopatch:
136 136 dopatch = True
137 137 continue
138 138 if line.startswith('rename from '):
139 139 gp.op = 'RENAME'
140 140 gp.oldpath = line[12:].rstrip()
141 141 elif line.startswith('rename to '):
142 142 gp.path = line[10:].rstrip()
143 143 elif line.startswith('copy from '):
144 144 gp.op = 'COPY'
145 145 gp.oldpath = line[10:].rstrip()
146 146 elif line.startswith('copy to '):
147 147 gp.path = line[8:].rstrip()
148 148 elif line.startswith('deleted file'):
149 149 gp.op = 'DELETE'
150 150 elif line.startswith('new file mode '):
151 151 gp.op = 'ADD'
152 152 gp.mode = int(line.rstrip()[-3:], 8)
153 153 elif line.startswith('new mode '):
154 154 gp.mode = int(line.rstrip()[-3:], 8)
155 155 if gp:
156 156 gitpatches.append(gp)
157 157
158 158 if not gitpatches:
159 159 dopatch = True
160 160
161 161 return (dopatch, gitpatches)
162 162
163 163 def dogitpatch(patchname, gitpatches):
164 164 """Preprocess git patch so that vanilla patch can handle it"""
165 165 pf = file(patchname)
166 166 pfline = 1
167 167
168 168 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
169 169 tmpfp = os.fdopen(fd, 'w')
170 170
171 171 try:
172 172 for i in range(len(gitpatches)):
173 173 p = gitpatches[i]
174 174 if not p.copymod:
175 175 continue
176 176
177 177 if os.path.exists(p.path):
178 178 raise util.Abort(_("cannot create %s: destination already exists") %
179 179 p.path)
180 180
181 181 (src, dst) = [os.path.join(os.getcwd(), n)
182 182 for n in (p.oldpath, p.path)]
183 183
184 184 targetdir = os.path.dirname(dst)
185 185 if not os.path.isdir(targetdir):
186 186 os.makedirs(targetdir)
187 187 try:
188 188 shutil.copyfile(src, dst)
189 189 shutil.copymode(src, dst)
190 190 except shutil.Error, inst:
191 191 raise util.Abort(str(inst))
192 192
193 193 # rewrite patch hunk
194 194 while pfline < p.lineno:
195 195 tmpfp.write(pf.readline())
196 196 pfline += 1
197 197 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
198 198 line = pf.readline()
199 199 pfline += 1
200 200 while not line.startswith('--- a/'):
201 201 tmpfp.write(line)
202 202 line = pf.readline()
203 203 pfline += 1
204 204 tmpfp.write('--- a/%s\n' % p.path)
205 205
206 206 line = pf.readline()
207 207 while line:
208 208 tmpfp.write(line)
209 209 line = pf.readline()
210 210 except:
211 211 tmpfp.close()
212 212 os.unlink(patchname)
213 213 raise
214 214
215 215 tmpfp.close()
216 216 return patchname
217 217
218 218 def patch(strip, patchname, ui, cwd=None):
219 219 """apply the patch <patchname> to the working directory.
220 220 a list of patched files is returned"""
221 221
222 222 (dopatch, gitpatches) = readgitpatch(patchname)
223 223
224 224 files = {}
225 225 if dopatch:
226 226 if dopatch == 'filter':
227 227 patchname = dogitpatch(patchname, gitpatches)
228 228 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
229 229 args = []
230 230 if cwd:
231 231 args.append('-d %s' % util.shellquote(cwd))
232 232 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
233 233 util.shellquote(patchname)))
234 234
235 235 if dopatch == 'filter':
236 236 False and os.unlink(patchname)
237 237
238 238 for line in fp:
239 239 line = line.rstrip()
240 240 ui.status("%s\n" % line)
241 241 if line.startswith('patching file '):
242 242 pf = util.parse_patch_output(line)
243 243 files.setdefault(pf, (None, None))
244 244 code = fp.close()
245 245 if code:
246 246 raise util.Abort(_("patch command failed: %s") %
247 247 util.explain_exit(code)[0])
248 248
249 249 for gp in gitpatches:
250 250 files[gp.path] = (gp.op, gp)
251 251
252 252 return files
253 253
254 254 def diff(repo, node1=None, node2=None, files=None, match=util.always,
255 255 fp=None, changes=None, opts=None):
256 256 '''print diff of changes to files between two nodes, or node and
257 257 working directory.
258 258
259 259 if node1 is None, use first dirstate parent instead.
260 260 if node2 is None, compare node1 with working directory.'''
261 261
262 262 if opts is None:
263 263 opts = mdiff.defaultopts
264 264 if fp is None:
265 265 fp = repo.ui
266 266
267 267 if not node1:
268 268 node1 = repo.dirstate.parents()[0]
269 269 # reading the data for node1 early allows it to play nicely
270 270 # with repo.status and the revlog cache.
271 271 change = repo.changelog.read(node1)
272 272 mmap = repo.manifest.read(change[0])
273 273 date1 = util.datestr(change[2])
274 274
275 275 if not changes:
276 276 changes = repo.status(node1, node2, files, match=match)[:5]
277 277 modified, added, removed, deleted, unknown = changes
278 278 if files:
279 279 def filterfiles(filters):
280 l = [x for x in files if x in filters]
280 l = [x for x in filters if x in files]
281 281
282 for t in filters:
283 if t and t[-1] != "/":
282 for t in files:
283 if not t.endswith("/"):
284 284 t += "/"
285 l += [x for x in files if x.startswith(t)]
285 l += [x for x in filters if x.startswith(t)]
286 286 return l
287 287
288 modified, added, removed = map(lambda x: filterfiles(x),
289 (modified, added, removed))
288 modified, added, removed = map(filterfiles, (modified, added, removed))
290 289
291 290 if not modified and not added and not removed:
292 291 return
293 292
294 293 if node2:
295 294 change = repo.changelog.read(node2)
296 295 mmap2 = repo.manifest.read(change[0])
297 296 _date2 = util.datestr(change[2])
298 297 def date2(f):
299 298 return _date2
300 299 def read(f):
301 300 return repo.file(f).read(mmap2[f])
302 301 else:
303 302 tz = util.makedate()[1]
304 303 _date2 = util.datestr()
305 304 def date2(f):
306 305 try:
307 306 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
308 307 except OSError, err:
309 308 if err.errno != errno.ENOENT: raise
310 309 return _date2
311 310 def read(f):
312 311 return repo.wread(f)
313 312
314 313 if repo.ui.quiet:
315 314 r = None
316 315 else:
317 316 hexfunc = repo.ui.verbose and hex or short
318 317 r = [hexfunc(node) for node in [node1, node2] if node]
319 318
320 319 all = modified + added + removed
321 320 all.sort()
322 321 for f in all:
323 322 to = None
324 323 tn = None
325 324 if f in mmap:
326 325 to = repo.file(f).read(mmap[f])
327 326 if f not in removed:
328 327 tn = read(f)
329 328 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts))
330 329
331 330 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
332 331 opts=None):
333 332 '''export changesets as hg patches.'''
334 333
335 334 total = len(revs)
336 335 revwidth = max(map(len, revs))
337 336
338 337 def single(node, seqno, fp):
339 338 parents = [p for p in repo.changelog.parents(node) if p != nullid]
340 339 if switch_parent:
341 340 parents.reverse()
342 341 prev = (parents and parents[0]) or nullid
343 342 change = repo.changelog.read(node)
344 343
345 344 if not fp:
346 345 fp = cmdutil.make_file(repo, template, node, total=total,
347 346 seqno=seqno, revwidth=revwidth)
348 347 if fp not in (sys.stdout, repo.ui):
349 348 repo.ui.note("%s\n" % fp.name)
350 349
351 350 fp.write("# HG changeset patch\n")
352 351 fp.write("# User %s\n" % change[1])
353 352 fp.write("# Date %d %d\n" % change[2])
354 353 fp.write("# Node ID %s\n" % hex(node))
355 354 fp.write("# Parent %s\n" % hex(prev))
356 355 if len(parents) > 1:
357 356 fp.write("# Parent %s\n" % hex(parents[1]))
358 357 fp.write(change[4].rstrip())
359 358 fp.write("\n\n")
360 359
361 360 diff(repo, prev, node, fp=fp, opts=opts)
362 361 if fp not in (sys.stdout, repo.ui):
363 362 fp.close()
364 363
365 364 for seqno, cset in enumerate(revs):
366 365 single(cset, seqno, fp)
General Comments 0
You need to be logged in to leave comments. Login now