##// END OF EJS Templates
patch: use temporary files to handle intermediate copies...
Patrick Mezard -
r14452:ee574cfd default
parent child Browse files
Show More
@@ -595,11 +595,12 b' def reposetup(ui, repo):'
595 wlock.release()
595 wlock.release()
596
596
597 # monkeypatches
597 # monkeypatches
598 def kwpatchfile_init(orig, self, ui, fname, backend, mode, create, remove,
598 def kwpatchfile_init(orig, self, ui, fname, backend, store, mode, create,
599 missing=False, eolmode=None):
599 remove, eolmode=None, copysource=None):
600 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
600 '''Monkeypatch/wrap patch.patchfile.__init__ to avoid
601 rejects or conflicts due to expanded keywords in working dir.'''
601 rejects or conflicts due to expanded keywords in working dir.'''
602 orig(self, ui, fname, backend, mode, create, remove, missing, eolmode)
602 orig(self, ui, fname, backend, store, mode, create, remove,
603 eolmode, copysource)
603 # shrink keywords read from working dir
604 # shrink keywords read from working dir
604 self.lines = kwt.shrinklines(self.fname, self.lines)
605 self.lines = kwt.shrinklines(self.fname, self.lines)
605
606
@@ -7,7 +7,7 b''
7 # GNU General Public License version 2 or any later version.
7 # GNU General Public License version 2 or any later version.
8
8
9 import cStringIO, email.Parser, os, errno, re
9 import cStringIO, email.Parser, os, errno, re
10 import tempfile, zlib
10 import tempfile, zlib, shutil
11
11
12 from i18n import _
12 from i18n import _
13 from node import hex, nullid, short
13 from node import hex, nullid, short
@@ -362,10 +362,11 b' class abstractbackend(object):'
362 """
362 """
363 raise NotImplementedError
363 raise NotImplementedError
364
364
365 def setfile(self, fname, data, mode):
365 def setfile(self, fname, data, mode, copysource):
366 """Write data to target file fname and set its mode. mode is a
366 """Write data to target file fname and set its mode. mode is a
367 (islink, isexec) tuple. If data is None, the file content should
367 (islink, isexec) tuple. If data is None, the file content should
368 be left unchanged.
368 be left unchanged. If the file is modified after being copied,
369 copysource is set to the original file name.
369 """
370 """
370 raise NotImplementedError
371 raise NotImplementedError
371
372
@@ -380,13 +381,6 b' class abstractbackend(object):'
380 """
381 """
381 pass
382 pass
382
383
383 def copy(self, src, dst):
384 """Copy src file into dst file. Create intermediate directories if
385 necessary. Files are specified relatively to the patching base
386 directory.
387 """
388 raise NotImplementedError
389
390 def exists(self, fname):
384 def exists(self, fname):
391 raise NotImplementedError
385 raise NotImplementedError
392
386
@@ -411,7 +405,7 b' class fsbackend(abstractbackend):'
411 raise
405 raise
412 return (self.opener.read(fname), (islink, isexec))
406 return (self.opener.read(fname), (islink, isexec))
413
407
414 def setfile(self, fname, data, mode):
408 def setfile(self, fname, data, mode, copysource):
415 islink, isexec = mode
409 islink, isexec = mode
416 if data is None:
410 if data is None:
417 util.setflags(self._join(fname), islink, isexec)
411 util.setflags(self._join(fname), islink, isexec)
@@ -439,23 +433,6 b' class fsbackend(abstractbackend):'
439 fp.writelines(lines)
433 fp.writelines(lines)
440 fp.close()
434 fp.close()
441
435
442 def copy(self, src, dst):
443 basedir = self.opener.base
444 abssrc, absdst = [scmutil.canonpath(basedir, basedir, x)
445 for x in [src, dst]]
446 if os.path.lexists(absdst):
447 raise util.Abort(_("cannot create %s: destination already exists")
448 % dst)
449 dstdir = os.path.dirname(absdst)
450 if dstdir and not os.path.isdir(dstdir):
451 try:
452 os.makedirs(dstdir)
453 except IOError:
454 raise util.Abort(
455 _("cannot create %s: unable to create destination directory")
456 % dst)
457 util.copyfile(abssrc, absdst)
458
459 def exists(self, fname):
436 def exists(self, fname):
460 return os.path.lexists(self._join(fname))
437 return os.path.lexists(self._join(fname))
461
438
@@ -468,8 +445,10 b' class workingbackend(fsbackend):'
468 self.changed = set()
445 self.changed = set()
469 self.copied = []
446 self.copied = []
470
447
471 def setfile(self, fname, data, mode):
448 def setfile(self, fname, data, mode, copysource):
472 super(workingbackend, self).setfile(fname, data, mode)
449 super(workingbackend, self).setfile(fname, data, mode, copysource)
450 if copysource is not None:
451 self.copied.append((copysource, fname))
473 self.changed.add(fname)
452 self.changed.add(fname)
474
453
475 def unlink(self, fname):
454 def unlink(self, fname):
@@ -477,11 +456,6 b' class workingbackend(fsbackend):'
477 self.removed.add(fname)
456 self.removed.add(fname)
478 self.changed.add(fname)
457 self.changed.add(fname)
479
458
480 def copy(self, src, dst):
481 super(workingbackend, self).copy(src, dst)
482 self.copied.append((src, dst))
483 self.changed.add(dst)
484
485 def close(self):
459 def close(self):
486 wctx = self.repo[None]
460 wctx = self.repo[None]
487 addremoved = set(self.changed)
461 addremoved = set(self.changed)
@@ -498,14 +472,40 b' class workingbackend(fsbackend):'
498 scmutil.addremove(self.repo, addremoved, similarity=self.similarity)
472 scmutil.addremove(self.repo, addremoved, similarity=self.similarity)
499 return sorted(self.changed)
473 return sorted(self.changed)
500
474
475 class filestore(object):
476 def __init__(self):
477 self.opener = None
478 self.files = {}
479 self.created = 0
480
481 def setfile(self, fname, data, mode):
482 if self.opener is None:
483 root = tempfile.mkdtemp(prefix='hg-patch-')
484 self.opener = scmutil.opener(root)
485 # Avoid filename issues with these simple names
486 fn = str(self.created)
487 self.opener.write(fn, data)
488 self.created += 1
489 self.files[fname] = (fn, mode)
490
491 def getfile(self, fname):
492 if fname not in self.files:
493 raise IOError()
494 fn, mode = self.files[fname]
495 return self.opener.read(fn), mode
496
497 def close(self):
498 if self.opener:
499 shutil.rmtree(self.opener.base)
500
501 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
501 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1
502 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
502 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@')
503 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
503 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)')
504 eolmodes = ['strict', 'crlf', 'lf', 'auto']
504 eolmodes = ['strict', 'crlf', 'lf', 'auto']
505
505
506 class patchfile(object):
506 class patchfile(object):
507 def __init__(self, ui, fname, backend, mode, create, remove, missing=False,
507 def __init__(self, ui, fname, backend, store, mode, create, remove,
508 eolmode='strict'):
508 eolmode='strict', copysource=None):
509 self.fname = fname
509 self.fname = fname
510 self.eolmode = eolmode
510 self.eolmode = eolmode
511 self.eol = None
511 self.eol = None
@@ -513,36 +513,43 b' class patchfile(object):'
513 self.ui = ui
513 self.ui = ui
514 self.lines = []
514 self.lines = []
515 self.exists = False
515 self.exists = False
516 self.missing = missing
516 self.missing = True
517 self.mode = mode
517 self.mode = mode
518 self.copysource = copysource
518 self.create = create
519 self.create = create
519 self.remove = remove
520 self.remove = remove
520 if not missing:
521 try:
521 try:
522 if copysource is None:
522 data, mode = self.backend.getfile(fname)
523 data, mode = backend.getfile(fname)
523 if data:
524 self.lines = data.splitlines(True)
525 if self.mode is None:
526 self.mode = mode
527 if self.lines:
528 # Normalize line endings
529 if self.lines[0].endswith('\r\n'):
530 self.eol = '\r\n'
531 elif self.lines[0].endswith('\n'):
532 self.eol = '\n'
533 if eolmode != 'strict':
534 nlines = []
535 for l in self.lines:
536 if l.endswith('\r\n'):
537 l = l[:-2] + '\n'
538 nlines.append(l)
539 self.lines = nlines
540 self.exists = True
524 self.exists = True
541 except IOError:
525 else:
542 if self.mode is None:
526 data, mode = store.getfile(copysource)
543 self.mode = (False, False)
527 self.exists = backend.exists(fname)
544 else:
528 self.missing = False
545 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
529 if data:
530 self.lines = data.splitlines(True)
531 if self.mode is None:
532 self.mode = mode
533 if self.lines:
534 # Normalize line endings
535 if self.lines[0].endswith('\r\n'):
536 self.eol = '\r\n'
537 elif self.lines[0].endswith('\n'):
538 self.eol = '\n'
539 if eolmode != 'strict':
540 nlines = []
541 for l in self.lines:
542 if l.endswith('\r\n'):
543 l = l[:-2] + '\n'
544 nlines.append(l)
545 self.lines = nlines
546 except IOError:
547 if create:
548 self.missing = False
549 if self.mode is None:
550 self.mode = (False, False)
551 if self.missing:
552 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname)
546
553
547 self.hash = {}
554 self.hash = {}
548 self.dirty = 0
555 self.dirty = 0
@@ -569,7 +576,7 b' class patchfile(object):'
569 rawlines.append(l)
576 rawlines.append(l)
570 lines = rawlines
577 lines = rawlines
571
578
572 self.backend.setfile(fname, ''.join(lines), mode)
579 self.backend.setfile(fname, ''.join(lines), mode, self.copysource)
573
580
574 def printfile(self, warn):
581 def printfile(self, warn):
575 if self.fileprinted:
582 if self.fileprinted:
@@ -623,7 +630,11 b' class patchfile(object):'
623 return -1
630 return -1
624
631
625 if self.exists and self.create:
632 if self.exists and self.create:
626 self.ui.warn(_("file %s already exists\n") % self.fname)
633 if self.copysource:
634 self.ui.warn(_("cannot create %s: destination already "
635 "exists\n" % self.fname))
636 else:
637 self.ui.warn(_("file %s already exists\n") % self.fname)
627 self.rej.append(h)
638 self.rej.append(h)
628 return -1
639 return -1
629
640
@@ -1005,10 +1016,10 b' def selectfile(backend, afile_orig, bfil'
1005 # Git patches do not play games. Excluding copies from the
1016 # Git patches do not play games. Excluding copies from the
1006 # following heuristic avoids a lot of confusion
1017 # following heuristic avoids a lot of confusion
1007 fname = pathstrip(gp.path, strip - 1)[1]
1018 fname = pathstrip(gp.path, strip - 1)[1]
1008 create = gp.op == 'ADD'
1019 create = gp.op in ('ADD', 'COPY', 'RENAME')
1009 remove = gp.op == 'DELETE'
1020 remove = gp.op == 'DELETE'
1010 missing = not create and not backend.exists(fname)
1021 missing = not create and not backend.exists(fname)
1011 return fname, missing, create, remove
1022 return fname, create, remove
1012 nulla = afile_orig == "/dev/null"
1023 nulla = afile_orig == "/dev/null"
1013 nullb = bfile_orig == "/dev/null"
1024 nullb = bfile_orig == "/dev/null"
1014 create = nulla and hunk.starta == 0 and hunk.lena == 0
1025 create = nulla and hunk.starta == 0 and hunk.lena == 0
@@ -1050,7 +1061,7 b' def selectfile(backend, afile_orig, bfil'
1050 else:
1061 else:
1051 raise PatchError(_("undefined source and destination files"))
1062 raise PatchError(_("undefined source and destination files"))
1052
1063
1053 return fname, missing, create, remove
1064 return fname, create, remove
1054
1065
1055 def scangitpatch(lr, firstline):
1066 def scangitpatch(lr, firstline):
1056 """
1067 """
@@ -1177,7 +1188,7 b' def iterhunks(fp):'
1177 gp = gitpatches.pop()[1]
1188 gp = gitpatches.pop()[1]
1178 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp)
1189 yield 'file', ('a/' + gp.path, 'b/' + gp.path, None, gp)
1179
1190
1180 def applydiff(ui, fp, changed, backend, strip=1, eolmode='strict'):
1191 def applydiff(ui, fp, changed, backend, store, strip=1, eolmode='strict'):
1181 """Reads a patch from fp and tries to apply it.
1192 """Reads a patch from fp and tries to apply it.
1182
1193
1183 The dict 'changed' is filled in with all of the filenames changed
1194 The dict 'changed' is filled in with all of the filenames changed
@@ -1188,10 +1199,11 b' def applydiff(ui, fp, changed, backend, '
1188 read in binary mode. Otherwise, line endings are ignored when
1199 read in binary mode. Otherwise, line endings are ignored when
1189 patching then normalized according to 'eolmode'.
1200 patching then normalized according to 'eolmode'.
1190 """
1201 """
1191 return _applydiff(ui, fp, patchfile, backend, changed, strip=strip,
1202 return _applydiff(ui, fp, patchfile, backend, store, changed, strip=strip,
1192 eolmode=eolmode)
1203 eolmode=eolmode)
1193
1204
1194 def _applydiff(ui, fp, patcher, backend, changed, strip=1, eolmode='strict'):
1205 def _applydiff(ui, fp, patcher, backend, store, changed, strip=1,
1206 eolmode='strict'):
1195
1207
1196 def pstrip(p):
1208 def pstrip(p):
1197 return pathstrip(p, strip - 1)[1]
1209 return pathstrip(p, strip - 1)[1]
@@ -1214,30 +1226,42 b' def _applydiff(ui, fp, patcher, backend,'
1214 rejects += current_file.close()
1226 rejects += current_file.close()
1215 current_file = None
1227 current_file = None
1216 afile, bfile, first_hunk, gp = values
1228 afile, bfile, first_hunk, gp = values
1229 copysource = None
1217 if gp:
1230 if gp:
1218 path = pstrip(gp.path)
1231 path = pstrip(gp.path)
1232 if gp.oldpath:
1233 copysource = pstrip(gp.oldpath)
1219 changed[path] = gp
1234 changed[path] = gp
1220 if gp.op == 'DELETE':
1235 if gp.op == 'DELETE':
1221 backend.unlink(path)
1236 backend.unlink(path)
1222 continue
1237 continue
1223 if gp.op == 'RENAME':
1238 if gp.op == 'RENAME':
1224 backend.unlink(pstrip(gp.oldpath))
1239 backend.unlink(copysource)
1225 if gp.mode and not first_hunk:
1240 if not first_hunk:
1226 data = None
1241 data, mode = None, None
1227 if gp.op == 'ADD':
1242 if gp.op in ('RENAME', 'COPY'):
1228 # Added files without content have no hunk and
1243 data, mode = store.getfile(copysource)
1229 # must be created
1244 if gp.mode:
1230 data = ''
1245 mode = gp.mode
1231 backend.setfile(path, data, gp.mode)
1246 if gp.op == 'ADD':
1247 # Added files without content have no hunk and
1248 # must be created
1249 data = ''
1250 if data or mode:
1251 if (gp.op in ('ADD', 'RENAME', 'COPY')
1252 and backend.exists(path)):
1253 raise PatchError(_("cannot create %s: destination "
1254 "already exists") % path)
1255 backend.setfile(path, data, mode, copysource)
1232 if not first_hunk:
1256 if not first_hunk:
1233 continue
1257 continue
1234 try:
1258 try:
1235 mode = gp and gp.mode or None
1259 mode = gp and gp.mode or None
1236 current_file, missing, create, remove = selectfile(
1260 current_file, create, remove = selectfile(
1237 backend, afile, bfile, first_hunk, strip, gp)
1261 backend, afile, bfile, first_hunk, strip, gp)
1238 current_file = patcher(ui, current_file, backend, mode,
1262 current_file = patcher(ui, current_file, backend, store, mode,
1239 create, remove, missing=missing,
1263 create, remove, eolmode=eolmode,
1240 eolmode=eolmode)
1264 copysource=copysource)
1241 except PatchError, inst:
1265 except PatchError, inst:
1242 ui.warn(str(inst) + '\n')
1266 ui.warn(str(inst) + '\n')
1243 current_file = None
1267 current_file = None
@@ -1245,7 +1269,9 b' def _applydiff(ui, fp, patcher, backend,'
1245 continue
1269 continue
1246 elif state == 'git':
1270 elif state == 'git':
1247 for gp in values:
1271 for gp in values:
1248 backend.copy(pstrip(gp.oldpath), pstrip(gp.path))
1272 path = pstrip(gp.oldpath)
1273 data, mode = backend.getfile(path)
1274 store.setfile(path, data, mode)
1249 else:
1275 else:
1250 raise util.Abort(_('unsupported parser state: %s') % state)
1276 raise util.Abort(_('unsupported parser state: %s') % state)
1251
1277
@@ -1316,17 +1342,20 b' def internalpatch(ui, repo, patchobj, st'
1316 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1342 raise util.Abort(_('unsupported line endings type: %s') % eolmode)
1317 eolmode = eolmode.lower()
1343 eolmode = eolmode.lower()
1318
1344
1345 store = filestore()
1319 backend = workingbackend(ui, repo, similarity)
1346 backend = workingbackend(ui, repo, similarity)
1320 try:
1347 try:
1321 fp = open(patchobj, 'rb')
1348 fp = open(patchobj, 'rb')
1322 except TypeError:
1349 except TypeError:
1323 fp = patchobj
1350 fp = patchobj
1324 try:
1351 try:
1325 ret = applydiff(ui, fp, files, backend, strip=strip, eolmode=eolmode)
1352 ret = applydiff(ui, fp, files, backend, store, strip=strip,
1353 eolmode=eolmode)
1326 finally:
1354 finally:
1327 if fp != patchobj:
1355 if fp != patchobj:
1328 fp.close()
1356 fp.close()
1329 files.update(dict.fromkeys(backend.close()))
1357 files.update(dict.fromkeys(backend.close()))
1358 store.close()
1330 if ret < 0:
1359 if ret < 0:
1331 raise PatchError(_('patch failed to apply'))
1360 raise PatchError(_('patch failed to apply'))
1332 return ret > 0
1361 return ret > 0
@@ -1370,7 +1399,7 b' def changedfiles(ui, repo, patchpath, st'
1370 changed.add(pathstrip(gp.oldpath, strip - 1)[1])
1399 changed.add(pathstrip(gp.oldpath, strip - 1)[1])
1371 if not first_hunk:
1400 if not first_hunk:
1372 continue
1401 continue
1373 current_file, missing, create, remove = selectfile(
1402 current_file, create, remove = selectfile(
1374 backend, afile, bfile, first_hunk, strip, gp)
1403 backend, afile, bfile, first_hunk, strip, gp)
1375 changed.add(current_file)
1404 changed.add(current_file)
1376 elif state not in ('hunk', 'git'):
1405 elif state not in ('hunk', 'git'):
@@ -436,7 +436,9 b' Copy and changes with existing destinati'
436 > +b
436 > +b
437 > EOF
437 > EOF
438 applying patch from stdin
438 applying patch from stdin
439 abort: cannot create b: destination already exists
439 cannot create b: destination already exists
440 1 out of 1 hunks FAILED -- saving rejects to file b.rej
441 abort: patch failed to apply
440 [255]
442 [255]
441 $ cat b
443 $ cat b
442 b
444 b
@@ -617,7 +617,7 b' test paths outside repo root'
617 > rename to bar
617 > rename to bar
618 > EOF
618 > EOF
619 applying patch from stdin
619 applying patch from stdin
620 abort: ../outside/foo not under root
620 abort: path contains illegal component: ../outside/foo
621 [255]
621 [255]
622 $ cd ..
622 $ cd ..
623
623
@@ -502,6 +502,7 b' but only after writing the bad name into'
502 refresh interrupted while patch was popped! (revert --all, qpush to recover)
502 refresh interrupted while patch was popped! (revert --all, qpush to recover)
503 abort: username 'foo\nbar' contains a newline!
503 abort: username 'foo\nbar' contains a newline!
504 [255]
504 [255]
505 $ rm a
505 $ cat .hg/patches/a
506 $ cat .hg/patches/a
506 # HG changeset patch
507 # HG changeset patch
507 # Parent 0000000000000000000000000000000000000000
508 # Parent 0000000000000000000000000000000000000000
@@ -115,6 +115,8 b' check patch does not overwrite untracked'
115 $ ln -s linkbb linkb
115 $ ln -s linkbb linkb
116 $ hg qpush
116 $ hg qpush
117 applying movelink
117 applying movelink
118 cannot create linkb: destination already exists
119 1 out of 1 hunks FAILED -- saving rejects to file linkb.rej
118 patch failed, unable to continue (try -v)
120 patch failed, unable to continue (try -v)
119 patch failed, rejects left in working dir
121 patch failed, rejects left in working dir
120 errors during apply, please fix and refresh movelink
122 errors during apply, please fix and refresh movelink
General Comments 0
You need to be logged in to leave comments. Login now