Mercurial > public > mercurial-scm > hg
comparison mercurial/patch.py @ 14348:c1c719103392
patch: extract fs access from patchfile into fsbackend
Most filesystem calls are already isolated in patchfile but this is not enough:
renames are performed before patchfile is available and some chmod calls are
even done outside of the applydiff call. Once all these calls are extracted
into a backend class, we can provide cleaner APIs to write to a working
directory context directly into the repository.
author | Patrick Mezard <pmezard@gmail.com> |
---|---|
date | Tue, 17 May 2011 23:46:15 +0200 |
parents | e8debe1eb255 |
children | 776ae95b8835 |
comparison
equal
deleted
inserted
replaced
14347:e8debe1eb255 | 14348:c1c719103392 |
---|---|
379 l = self.readline() | 379 l = self.readline() |
380 if not l: | 380 if not l: |
381 break | 381 break |
382 yield l | 382 yield l |
383 | 383 |
384 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1 | 384 class abstractbackend(object): |
385 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@') | 385 def __init__(self, ui): |
386 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)') | 386 self.ui = ui |
387 eolmodes = ['strict', 'crlf', 'lf', 'auto'] | 387 |
388 | 388 def readlines(self, fname): |
389 class patchfile(object): | 389 """Return target file lines, or its content as a single line |
390 def __init__(self, ui, fname, opener, missing=False, eolmode='strict'): | 390 for symlinks. |
391 self.fname = fname | 391 """ |
392 self.eolmode = eolmode | 392 raise NotImplementedError |
393 self.eol = None | 393 |
394 def writelines(self, fname, lines): | |
395 """Write lines to target file.""" | |
396 raise NotImplementedError | |
397 | |
398 def unlink(self, fname): | |
399 """Unlink target file.""" | |
400 raise NotImplementedError | |
401 | |
402 def writerej(self, fname, failed, total, lines): | |
403 """Write rejected lines for fname. total is the number of hunks | |
404 which failed to apply and total the total number of hunks for this | |
405 files. | |
406 """ | |
407 pass | |
408 | |
409 class fsbackend(abstractbackend): | |
410 def __init__(self, ui, opener): | |
411 super(fsbackend, self).__init__(ui) | |
394 self.opener = opener | 412 self.opener = opener |
395 self.ui = ui | |
396 self.lines = [] | |
397 self.exists = False | |
398 self.missing = missing | |
399 if not missing: | |
400 try: | |
401 self.lines = self.readlines(fname) | |
402 self.exists = True | |
403 except IOError: | |
404 pass | |
405 else: | |
406 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname) | |
407 | |
408 self.hash = {} | |
409 self.dirty = False | |
410 self.offset = 0 | |
411 self.skew = 0 | |
412 self.rej = [] | |
413 self.fileprinted = False | |
414 self.printfile(False) | |
415 self.hunks = 0 | |
416 | 413 |
417 def readlines(self, fname): | 414 def readlines(self, fname): |
418 if os.path.islink(fname): | 415 if os.path.islink(fname): |
419 return [os.readlink(fname)] | 416 return [os.readlink(fname)] |
420 fp = self.opener(fname, 'r') | 417 fp = self.opener(fname, 'r') |
421 try: | 418 try: |
422 lr = linereader(fp, self.eolmode != 'strict') | 419 return list(fp) |
423 lines = list(lr) | |
424 self.eol = lr.eol | |
425 return lines | |
426 finally: | 420 finally: |
427 fp.close() | 421 fp.close() |
428 | 422 |
429 def writelines(self, fname, lines): | 423 def writelines(self, fname, lines): |
430 # Ensure supplied data ends in fname, being a regular file or | 424 # Ensure supplied data ends in fname, being a regular file or |
440 except OSError, e: | 434 except OSError, e: |
441 if e.errno != errno.ENOENT: | 435 if e.errno != errno.ENOENT: |
442 raise | 436 raise |
443 fp = self.opener(fname, 'w') | 437 fp = self.opener(fname, 'w') |
444 try: | 438 try: |
445 if self.eolmode == 'auto': | 439 fp.writelines(lines) |
446 eol = self.eol | |
447 elif self.eolmode == 'crlf': | |
448 eol = '\r\n' | |
449 else: | |
450 eol = '\n' | |
451 | |
452 if self.eolmode != 'strict' and eol and eol != '\n': | |
453 for l in lines: | |
454 if l and l[-1] == '\n': | |
455 l = l[:-1] + eol | |
456 fp.write(l) | |
457 else: | |
458 fp.writelines(lines) | |
459 if islink: | 440 if islink: |
460 self.opener.symlink(fp.getvalue(), fname) | 441 self.opener.symlink(fp.getvalue(), fname) |
461 if st_mode is not None: | 442 if st_mode is not None: |
462 os.chmod(fname, st_mode) | 443 os.chmod(fname, st_mode) |
463 finally: | 444 finally: |
464 fp.close() | 445 fp.close() |
465 | 446 |
466 def unlink(self, fname): | 447 def unlink(self, fname): |
467 os.unlink(fname) | 448 os.unlink(fname) |
449 | |
450 def writerej(self, fname, failed, total, lines): | |
451 fname = fname + ".rej" | |
452 self.ui.warn( | |
453 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") % | |
454 (failed, total, fname)) | |
455 fp = self.opener(fname, 'w') | |
456 fp.writelines(lines) | |
457 fp.close() | |
458 | |
459 # @@ -start,len +start,len @@ or @@ -start +start @@ if len is 1 | |
460 unidesc = re.compile('@@ -(\d+)(,(\d+))? \+(\d+)(,(\d+))? @@') | |
461 contextdesc = re.compile('(---|\*\*\*) (\d+)(,(\d+))? (---|\*\*\*)') | |
462 eolmodes = ['strict', 'crlf', 'lf', 'auto'] | |
463 | |
464 class patchfile(object): | |
465 def __init__(self, ui, fname, backend, missing=False, eolmode='strict'): | |
466 self.fname = fname | |
467 self.eolmode = eolmode | |
468 self.eol = None | |
469 self.backend = backend | |
470 self.ui = ui | |
471 self.lines = [] | |
472 self.exists = False | |
473 self.missing = missing | |
474 if not missing: | |
475 try: | |
476 self.lines = self.backend.readlines(fname) | |
477 if self.lines: | |
478 # Normalize line endings | |
479 if self.lines[0].endswith('\r\n'): | |
480 self.eol = '\r\n' | |
481 elif self.lines[0].endswith('\n'): | |
482 self.eol = '\n' | |
483 if eolmode != 'strict': | |
484 nlines = [] | |
485 for l in self.lines: | |
486 if l.endswith('\r\n'): | |
487 l = l[:-2] + '\n' | |
488 nlines.append(l) | |
489 self.lines = nlines | |
490 self.exists = True | |
491 except IOError: | |
492 pass | |
493 else: | |
494 self.ui.warn(_("unable to find '%s' for patching\n") % self.fname) | |
495 | |
496 self.hash = {} | |
497 self.dirty = 0 | |
498 self.offset = 0 | |
499 self.skew = 0 | |
500 self.rej = [] | |
501 self.fileprinted = False | |
502 self.printfile(False) | |
503 self.hunks = 0 | |
504 | |
505 def writelines(self, fname, lines): | |
506 if self.eolmode == 'auto': | |
507 eol = self.eol | |
508 elif self.eolmode == 'crlf': | |
509 eol = '\r\n' | |
510 else: | |
511 eol = '\n' | |
512 | |
513 if self.eolmode != 'strict' and eol and eol != '\n': | |
514 rawlines = [] | |
515 for l in lines: | |
516 if l and l[-1] == '\n': | |
517 l = l[:-1] + eol | |
518 rawlines.append(l) | |
519 lines = rawlines | |
520 | |
521 self.backend.writelines(fname, lines) | |
468 | 522 |
469 def printfile(self, warn): | 523 def printfile(self, warn): |
470 if self.fileprinted: | 524 if self.fileprinted: |
471 return | 525 return |
472 if warn or self.ui.verbose: | 526 if warn or self.ui.verbose: |
501 def write_rej(self): | 555 def write_rej(self): |
502 # our rejects are a little different from patch(1). This always | 556 # our rejects are a little different from patch(1). This always |
503 # creates rejects in the same form as the original patch. A file | 557 # creates rejects in the same form as the original patch. A file |
504 # header is inserted so that you can run the reject through patch again | 558 # header is inserted so that you can run the reject through patch again |
505 # without having to type the filename. | 559 # without having to type the filename. |
506 | |
507 if not self.rej: | 560 if not self.rej: |
508 return | 561 return |
509 | 562 self.backend.writerej(self.fname, len(self.rej), self.hunks, |
510 fname = self.fname + ".rej" | 563 self.makerejlines(self.fname)) |
511 self.ui.warn( | |
512 _("%d out of %d hunks FAILED -- saving rejects to file %s\n") % | |
513 (len(self.rej), self.hunks, fname)) | |
514 | |
515 fp = self.opener(fname, 'w') | |
516 fp.writelines(self.makerejlines(self.fname)) | |
517 fp.close() | |
518 | 564 |
519 def apply(self, h): | 565 def apply(self, h): |
520 if not h.complete(): | 566 if not h.complete(): |
521 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") % | 567 raise PatchError(_("bad hunk #%d %s (%d %d %d %d)") % |
522 (h.number, h.desc, len(h.a), h.lena, len(h.b), | 568 (h.number, h.desc, len(h.a), h.lena, len(h.b), |
533 self.rej.append(h) | 579 self.rej.append(h) |
534 return -1 | 580 return -1 |
535 | 581 |
536 if isinstance(h, binhunk): | 582 if isinstance(h, binhunk): |
537 if h.rmfile(): | 583 if h.rmfile(): |
538 self.unlink(self.fname) | 584 self.backend.unlink(self.fname) |
539 else: | 585 else: |
540 self.lines[:] = h.new() | 586 self.lines[:] = h.new() |
541 self.offset += len(h.new()) | 587 self.offset += len(h.new()) |
542 self.dirty = True | 588 self.dirty = True |
543 return 0 | 589 return 0 |
561 # if there's skew we want to emit the "(offset %d lines)" even | 607 # if there's skew we want to emit the "(offset %d lines)" even |
562 # when the hunk cleanly applies at start + skew, so skip the | 608 # when the hunk cleanly applies at start + skew, so skip the |
563 # fast case code | 609 # fast case code |
564 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0: | 610 if self.skew == 0 and diffhelpers.testhunk(old, self.lines, start) == 0: |
565 if h.rmfile(): | 611 if h.rmfile(): |
566 self.unlink(self.fname) | 612 self.backend.unlink(self.fname) |
567 else: | 613 else: |
568 self.lines[start : start + h.lena] = h.new() | 614 self.lines[start : start + h.lena] = h.new() |
569 self.offset += h.lenb - h.lena | 615 self.offset += h.lenb - h.lena |
570 self.dirty = True | 616 self.dirty = True |
571 return 0 | 617 return 0 |
1110 def _applydiff(ui, fp, patcher, copyfn, changed, strip=1, eolmode='strict'): | 1156 def _applydiff(ui, fp, patcher, copyfn, changed, strip=1, eolmode='strict'): |
1111 rejects = 0 | 1157 rejects = 0 |
1112 err = 0 | 1158 err = 0 |
1113 current_file = None | 1159 current_file = None |
1114 cwd = os.getcwd() | 1160 cwd = os.getcwd() |
1115 opener = scmutil.opener(cwd) | 1161 backend = fsbackend(ui, scmutil.opener(cwd)) |
1116 | 1162 |
1117 for state, values in iterhunks(fp): | 1163 for state, values in iterhunks(fp): |
1118 if state == 'hunk': | 1164 if state == 'hunk': |
1119 if not current_file: | 1165 if not current_file: |
1120 continue | 1166 continue |
1128 rejects += current_file.close() | 1174 rejects += current_file.close() |
1129 afile, bfile, first_hunk = values | 1175 afile, bfile, first_hunk = values |
1130 try: | 1176 try: |
1131 current_file, missing = selectfile(afile, bfile, | 1177 current_file, missing = selectfile(afile, bfile, |
1132 first_hunk, strip) | 1178 first_hunk, strip) |
1133 current_file = patcher(ui, current_file, opener, | 1179 current_file = patcher(ui, current_file, backend, |
1134 missing=missing, eolmode=eolmode) | 1180 missing=missing, eolmode=eolmode) |
1135 except PatchError, inst: | 1181 except PatchError, inst: |
1136 ui.warn(str(inst) + '\n') | 1182 ui.warn(str(inst) + '\n') |
1137 current_file = None | 1183 current_file = None |
1138 rejects += 1 | 1184 rejects += 1 |