##// END OF EJS Templates
Remove temporary git patch files
Brendan Cully -
r3056:6848528f default
parent child Browse files
Show More
@@ -1,539 +1,539 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 errno os re shutil sys tempfile")
13 13
14 14 # helper functions
15 15
16 16 def copyfile(src, dst, basedir=None):
17 17 if not basedir:
18 18 basedir = os.getcwd()
19 19
20 20 abssrc, absdst = [os.path.join(basedir, n) for n in (src, dst)]
21 21 if os.path.exists(absdst):
22 22 raise util.Abort(_("cannot create %s: destination already exists") %
23 23 dst)
24 24
25 25 targetdir = os.path.dirname(absdst)
26 26 if not os.path.isdir(targetdir):
27 27 os.makedirs(targetdir)
28 28 try:
29 29 shutil.copyfile(abssrc, absdst)
30 30 shutil.copymode(abssrc, absdst)
31 31 except shutil.Error, inst:
32 32 raise util.Abort(str(inst))
33 33
34 34 # public functions
35 35
36 36 def extract(ui, fileobj):
37 37 '''extract patch from data read from fileobj.
38 38
39 39 patch can be normal patch or contained in email message.
40 40
41 41 return tuple (filename, message, user, date). any item in returned
42 42 tuple can be None. if filename is None, fileobj did not contain
43 43 patch. caller must unlink filename when done.'''
44 44
45 45 # attempt to detect the start of a patch
46 46 # (this heuristic is borrowed from quilt)
47 47 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' +
48 48 'retrieving revision [0-9]+(\.[0-9]+)*$|' +
49 49 '(---|\*\*\*)[ \t])', re.MULTILINE)
50 50
51 51 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
52 52 tmpfp = os.fdopen(fd, 'w')
53 53 try:
54 54 hgpatch = False
55 55
56 56 msg = email.Parser.Parser().parse(fileobj)
57 57
58 58 message = msg['Subject']
59 59 user = msg['From']
60 60 # should try to parse msg['Date']
61 61 date = None
62 62
63 63 if message:
64 64 message = message.replace('\n\t', ' ')
65 65 ui.debug('Subject: %s\n' % message)
66 66 if user:
67 67 ui.debug('From: %s\n' % user)
68 68 diffs_seen = 0
69 69 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch')
70 70
71 71 for part in msg.walk():
72 72 content_type = part.get_content_type()
73 73 ui.debug('Content-Type: %s\n' % content_type)
74 74 if content_type not in ok_types:
75 75 continue
76 76 payload = part.get_payload(decode=True)
77 77 m = diffre.search(payload)
78 78 if m:
79 79 ui.debug(_('found patch at byte %d\n') % m.start(0))
80 80 diffs_seen += 1
81 81 cfp = cStringIO.StringIO()
82 82 if message:
83 83 cfp.write(message)
84 84 cfp.write('\n')
85 85 for line in payload[:m.start(0)].splitlines():
86 86 if line.startswith('# HG changeset patch'):
87 87 ui.debug(_('patch generated by hg export\n'))
88 88 hgpatch = True
89 89 # drop earlier commit message content
90 90 cfp.seek(0)
91 91 cfp.truncate()
92 92 elif hgpatch:
93 93 if line.startswith('# User '):
94 94 user = line[7:]
95 95 ui.debug('From: %s\n' % user)
96 96 elif line.startswith("# Date "):
97 97 date = line[7:]
98 98 if not line.startswith('# '):
99 99 cfp.write(line)
100 100 cfp.write('\n')
101 101 message = cfp.getvalue()
102 102 if tmpfp:
103 103 tmpfp.write(payload)
104 104 if not payload.endswith('\n'):
105 105 tmpfp.write('\n')
106 106 elif not diffs_seen and message and content_type == 'text/plain':
107 107 message += '\n' + payload
108 108 except:
109 109 tmpfp.close()
110 110 os.unlink(tmpname)
111 111 raise
112 112
113 113 tmpfp.close()
114 114 if not diffs_seen:
115 115 os.unlink(tmpname)
116 116 return None, message, user, date
117 117 return tmpname, message, user, date
118 118
119 119 def readgitpatch(patchname):
120 120 """extract git-style metadata about patches from <patchname>"""
121 121 class gitpatch:
122 122 "op is one of ADD, DELETE, RENAME, MODIFY or COPY"
123 123 def __init__(self, path):
124 124 self.path = path
125 125 self.oldpath = None
126 126 self.mode = None
127 127 self.op = 'MODIFY'
128 128 self.copymod = False
129 129 self.lineno = 0
130 130
131 131 # Filter patch for git information
132 132 gitre = re.compile('diff --git a/(.*) b/(.*)')
133 133 pf = file(patchname)
134 134 gp = None
135 135 gitpatches = []
136 136 # Can have a git patch with only metadata, causing patch to complain
137 137 dopatch = False
138 138
139 139 lineno = 0
140 140 for line in pf:
141 141 lineno += 1
142 142 if line.startswith('diff --git'):
143 143 m = gitre.match(line)
144 144 if m:
145 145 if gp:
146 146 gitpatches.append(gp)
147 147 src, dst = m.group(1,2)
148 148 gp = gitpatch(dst)
149 149 gp.lineno = lineno
150 150 elif gp:
151 151 if line.startswith('--- '):
152 152 if gp.op in ('COPY', 'RENAME'):
153 153 gp.copymod = True
154 154 dopatch = 'filter'
155 155 gitpatches.append(gp)
156 156 gp = None
157 157 if not dopatch:
158 158 dopatch = True
159 159 continue
160 160 if line.startswith('rename from '):
161 161 gp.op = 'RENAME'
162 162 gp.oldpath = line[12:].rstrip()
163 163 elif line.startswith('rename to '):
164 164 gp.path = line[10:].rstrip()
165 165 elif line.startswith('copy from '):
166 166 gp.op = 'COPY'
167 167 gp.oldpath = line[10:].rstrip()
168 168 elif line.startswith('copy to '):
169 169 gp.path = line[8:].rstrip()
170 170 elif line.startswith('deleted file'):
171 171 gp.op = 'DELETE'
172 172 elif line.startswith('new file mode '):
173 173 gp.op = 'ADD'
174 174 gp.mode = int(line.rstrip()[-3:], 8)
175 175 elif line.startswith('new mode '):
176 176 gp.mode = int(line.rstrip()[-3:], 8)
177 177 if gp:
178 178 gitpatches.append(gp)
179 179
180 180 if not gitpatches:
181 181 dopatch = True
182 182
183 183 return (dopatch, gitpatches)
184 184
185 185 def dogitpatch(patchname, gitpatches, cwd=None):
186 186 """Preprocess git patch so that vanilla patch can handle it"""
187 187 pf = file(patchname)
188 188 pfline = 1
189 189
190 190 fd, patchname = tempfile.mkstemp(prefix='hg-patch-')
191 191 tmpfp = os.fdopen(fd, 'w')
192 192
193 193 try:
194 194 for i in range(len(gitpatches)):
195 195 p = gitpatches[i]
196 196 if not p.copymod:
197 197 continue
198 198
199 199 copyfile(p.oldpath, p.path, basedir=cwd)
200 200
201 201 # rewrite patch hunk
202 202 while pfline < p.lineno:
203 203 tmpfp.write(pf.readline())
204 204 pfline += 1
205 205 tmpfp.write('diff --git a/%s b/%s\n' % (p.path, p.path))
206 206 line = pf.readline()
207 207 pfline += 1
208 208 while not line.startswith('--- a/'):
209 209 tmpfp.write(line)
210 210 line = pf.readline()
211 211 pfline += 1
212 212 tmpfp.write('--- a/%s\n' % p.path)
213 213
214 214 line = pf.readline()
215 215 while line:
216 216 tmpfp.write(line)
217 217 line = pf.readline()
218 218 except:
219 219 tmpfp.close()
220 220 os.unlink(patchname)
221 221 raise
222 222
223 223 tmpfp.close()
224 224 return patchname
225 225
226 226 def patch(patchname, ui, strip=1, cwd=None):
227 227 """apply the patch <patchname> to the working directory.
228 228 a list of patched files is returned"""
229 229
230 230 (dopatch, gitpatches) = readgitpatch(patchname)
231 231
232 232 files = {}
233 233 fuzz = False
234 234 if dopatch:
235 235 if dopatch == 'filter':
236 236 patchname = dogitpatch(patchname, gitpatches, cwd=cwd)
237 237 patcher = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch')
238 238 args = []
239 239 if cwd:
240 240 args.append('-d %s' % util.shellquote(cwd))
241 241 fp = os.popen('%s %s -p%d < %s' % (patcher, ' '.join(args), strip,
242 242 util.shellquote(patchname)))
243 243
244 if dopatch == 'filter':
245 False and os.unlink(patchname)
246
247 244 for line in fp:
248 245 line = line.rstrip()
249 246 ui.note(line + '\n')
250 247 if line.startswith('patching file '):
251 248 pf = util.parse_patch_output(line)
252 249 printed_file = False
253 250 files.setdefault(pf, (None, None))
254 251 elif line.find('with fuzz') >= 0:
255 252 fuzz = True
256 253 if not printed_file:
257 254 ui.warn(pf + '\n')
258 255 printed_file = True
259 256 ui.warn(line + '\n')
260 257 elif line.find('saving rejects to file') >= 0:
261 258 ui.warn(line + '\n')
262 259 elif line.find('FAILED') >= 0:
263 260 if not printed_file:
264 261 ui.warn(pf + '\n')
265 262 printed_file = True
266 263 ui.warn(line + '\n')
267 264
265 if dopatch == 'filter':
266 os.unlink(patchname)
267
268 268 code = fp.close()
269 269 if code:
270 270 raise util.Abort(_("patch command failed: %s") %
271 271 util.explain_exit(code)[0])
272 272
273 273 for gp in gitpatches:
274 274 files[gp.path] = (gp.op, gp)
275 275
276 276 return (files, fuzz)
277 277
278 278 def diffopts(ui, opts={}):
279 279 return mdiff.diffopts(
280 280 text=opts.get('text'),
281 281 git=(opts.get('git') or
282 282 ui.configbool('diff', 'git', None)),
283 283 showfunc=(opts.get('show_function') or
284 284 ui.configbool('diff', 'showfunc', None)),
285 285 ignorews=(opts.get('ignore_all_space') or
286 286 ui.configbool('diff', 'ignorews', None)),
287 287 ignorewsamount=(opts.get('ignore_space_change') or
288 288 ui.configbool('diff', 'ignorewsamount', None)),
289 289 ignoreblanklines=(opts.get('ignore_blank_lines') or
290 290 ui.configbool('diff', 'ignoreblanklines', None)))
291 291
292 292 def updatedir(ui, repo, patches, wlock=None):
293 293 '''Update dirstate after patch application according to metadata'''
294 294 if not patches:
295 295 return
296 296 copies = []
297 297 removes = []
298 298 cfiles = patches.keys()
299 299 copts = {'after': False, 'force': False}
300 300 cwd = repo.getcwd()
301 301 if cwd:
302 302 cfiles = [util.pathto(cwd, f) for f in patches.keys()]
303 303 for f in patches:
304 304 ctype, gp = patches[f]
305 305 if ctype == 'RENAME':
306 306 copies.append((gp.oldpath, gp.path, gp.copymod))
307 307 removes.append(gp.oldpath)
308 308 elif ctype == 'COPY':
309 309 copies.append((gp.oldpath, gp.path, gp.copymod))
310 310 elif ctype == 'DELETE':
311 311 removes.append(gp.path)
312 312 for src, dst, after in copies:
313 313 if not after:
314 314 copyfile(src, dst, repo.root)
315 315 repo.copy(src, dst, wlock=wlock)
316 316 if removes:
317 317 repo.remove(removes, True, wlock=wlock)
318 318 for f in patches:
319 319 ctype, gp = patches[f]
320 320 if gp and gp.mode:
321 321 x = gp.mode & 0100 != 0
322 322 dst = os.path.join(repo.root, gp.path)
323 323 util.set_exec(dst, x)
324 324 cmdutil.addremove(repo, cfiles, wlock=wlock)
325 325 files = patches.keys()
326 326 files.extend([r for r in removes if r not in files])
327 327 files.sort()
328 328
329 329 return files
330 330
331 331 def diff(repo, node1=None, node2=None, files=None, match=util.always,
332 332 fp=None, changes=None, opts=None):
333 333 '''print diff of changes to files between two nodes, or node and
334 334 working directory.
335 335
336 336 if node1 is None, use first dirstate parent instead.
337 337 if node2 is None, compare node1 with working directory.'''
338 338
339 339 if opts is None:
340 340 opts = mdiff.defaultopts
341 341 if fp is None:
342 342 fp = repo.ui
343 343
344 344 if not node1:
345 345 node1 = repo.dirstate.parents()[0]
346 346
347 347 clcache = {}
348 348 def getchangelog(n):
349 349 if n not in clcache:
350 350 clcache[n] = repo.changelog.read(n)
351 351 return clcache[n]
352 352 mcache = {}
353 353 def getmanifest(n):
354 354 if n not in mcache:
355 355 mcache[n] = repo.manifest.read(n)
356 356 return mcache[n]
357 357 fcache = {}
358 358 def getfile(f):
359 359 if f not in fcache:
360 360 fcache[f] = repo.file(f)
361 361 return fcache[f]
362 362
363 363 # reading the data for node1 early allows it to play nicely
364 364 # with repo.status and the revlog cache.
365 365 change = getchangelog(node1)
366 366 mmap = getmanifest(change[0])
367 367 date1 = util.datestr(change[2])
368 368
369 369 if not changes:
370 370 changes = repo.status(node1, node2, files, match=match)[:5]
371 371 modified, added, removed, deleted, unknown = changes
372 372 if files:
373 373 def filterfiles(filters):
374 374 l = [x for x in filters if x in files]
375 375
376 376 for t in files:
377 377 if not t.endswith("/"):
378 378 t += "/"
379 379 l += [x for x in filters if x.startswith(t)]
380 380 return l
381 381
382 382 modified, added, removed = map(filterfiles, (modified, added, removed))
383 383
384 384 if not modified and not added and not removed:
385 385 return
386 386
387 387 def renamedbetween(f, n1, n2):
388 388 r1, r2 = map(repo.changelog.rev, (n1, n2))
389 389 src = None
390 390 while r2 > r1:
391 391 cl = getchangelog(n2)[0]
392 392 m = getmanifest(cl)
393 393 try:
394 394 src = getfile(f).renamed(m[f])
395 395 except KeyError:
396 396 return None
397 397 if src:
398 398 f = src[0]
399 399 n2 = repo.changelog.parents(n2)[0]
400 400 r2 = repo.changelog.rev(n2)
401 401 return src
402 402
403 403 if node2:
404 404 change = getchangelog(node2)
405 405 mmap2 = getmanifest(change[0])
406 406 _date2 = util.datestr(change[2])
407 407 def date2(f):
408 408 return _date2
409 409 def read(f):
410 410 return getfile(f).read(mmap2[f])
411 411 def renamed(f):
412 412 return renamedbetween(f, node1, node2)
413 413 else:
414 414 tz = util.makedate()[1]
415 415 _date2 = util.datestr()
416 416 def date2(f):
417 417 try:
418 418 return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz))
419 419 except OSError, err:
420 420 if err.errno != errno.ENOENT: raise
421 421 return _date2
422 422 def read(f):
423 423 return repo.wread(f)
424 424 def renamed(f):
425 425 src = repo.dirstate.copies.get(f)
426 426 parent = repo.dirstate.parents()[0]
427 427 if src:
428 428 f = src[0]
429 429 of = renamedbetween(f, node1, parent)
430 430 if of:
431 431 return of
432 432 elif src:
433 433 cl = getchangelog(parent)[0]
434 434 return (src, getmanifest(cl)[src])
435 435 else:
436 436 return None
437 437
438 438 if repo.ui.quiet:
439 439 r = None
440 440 else:
441 441 hexfunc = repo.ui.verbose and hex or short
442 442 r = [hexfunc(node) for node in [node1, node2] if node]
443 443
444 444 if opts.git:
445 445 copied = {}
446 446 for f in added:
447 447 src = renamed(f)
448 448 if src:
449 449 copied[f] = src
450 450 srcs = [x[1][0] for x in copied.items()]
451 451
452 452 all = modified + added + removed
453 453 all.sort()
454 454 for f in all:
455 455 to = None
456 456 tn = None
457 457 dodiff = True
458 458 if f in mmap:
459 459 to = getfile(f).read(mmap[f])
460 460 if f not in removed:
461 461 tn = read(f)
462 462 if opts.git:
463 463 def gitmode(x):
464 464 return x and '100755' or '100644'
465 465 def addmodehdr(header, omode, nmode):
466 466 if omode != nmode:
467 467 header.append('old mode %s\n' % omode)
468 468 header.append('new mode %s\n' % nmode)
469 469
470 470 a, b = f, f
471 471 header = []
472 472 if f in added:
473 473 if node2:
474 474 mode = gitmode(mmap2.execf(f))
475 475 else:
476 476 mode = gitmode(util.is_exec(repo.wjoin(f), None))
477 477 if f in copied:
478 478 a, arev = copied[f]
479 479 omode = gitmode(mmap.execf(a))
480 480 addmodehdr(header, omode, mode)
481 481 op = a in removed and 'rename' or 'copy'
482 482 header.append('%s from %s\n' % (op, a))
483 483 header.append('%s to %s\n' % (op, f))
484 484 to = getfile(a).read(arev)
485 485 else:
486 486 header.append('new file mode %s\n' % mode)
487 487 elif f in removed:
488 488 if f in srcs:
489 489 dodiff = False
490 490 else:
491 491 mode = gitmode(mmap.execf(f))
492 492 header.append('deleted file mode %s\n' % mode)
493 493 else:
494 494 omode = gitmode(mmap.execf(f))
495 495 nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f)))
496 496 addmodehdr(header, omode, nmode)
497 497 r = None
498 498 if dodiff:
499 499 header.insert(0, 'diff --git a/%s b/%s\n' % (a, b))
500 500 fp.write(''.join(header))
501 501 if dodiff:
502 502 fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts))
503 503
504 504 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
505 505 opts=None):
506 506 '''export changesets as hg patches.'''
507 507
508 508 total = len(revs)
509 509 revwidth = max(map(len, revs))
510 510
511 511 def single(node, seqno, fp):
512 512 parents = [p for p in repo.changelog.parents(node) if p != nullid]
513 513 if switch_parent:
514 514 parents.reverse()
515 515 prev = (parents and parents[0]) or nullid
516 516 change = repo.changelog.read(node)
517 517
518 518 if not fp:
519 519 fp = cmdutil.make_file(repo, template, node, total=total,
520 520 seqno=seqno, revwidth=revwidth)
521 521 if fp not in (sys.stdout, repo.ui):
522 522 repo.ui.note("%s\n" % fp.name)
523 523
524 524 fp.write("# HG changeset patch\n")
525 525 fp.write("# User %s\n" % change[1])
526 526 fp.write("# Date %d %d\n" % change[2])
527 527 fp.write("# Node ID %s\n" % hex(node))
528 528 fp.write("# Parent %s\n" % hex(prev))
529 529 if len(parents) > 1:
530 530 fp.write("# Parent %s\n" % hex(parents[1]))
531 531 fp.write(change[4].rstrip())
532 532 fp.write("\n\n")
533 533
534 534 diff(repo, prev, node, fp=fp, opts=opts)
535 535 if fp not in (sys.stdout, repo.ui):
536 536 fp.close()
537 537
538 538 for seqno, cset in enumerate(revs):
539 539 single(cset, seqno, fp)
General Comments 0
You need to be logged in to leave comments. Login now