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