comparison mercurial/patch.py @ 22296:650b5b6e75ed

convert: use None value for missing files instead of overloading IOError The internal API used IOError to indicate that a file should be marked as removed. There is some correlation between IOError (especially with ENOENT) and files that should be removed, but using IOErrors to represent file removal internally required some hacks. Instead, use the value None to indicate that the file not is present. Before, spurious IO errors could cause commits that silently removed files. They will now be reported like all other IO errors so the root cause can be fixed.
author Mads Kiilerich <madski@unity3d.com>
date Tue, 26 Aug 2014 22:03:32 +0200
parents c1ceec0c8cb4
children c343557a8442
comparison
equal deleted inserted replaced
22295:926bc0d3b595 22296:650b5b6e75ed
380 def __init__(self, ui): 380 def __init__(self, ui):
381 self.ui = ui 381 self.ui = ui
382 382
383 def getfile(self, fname): 383 def getfile(self, fname):
384 """Return target file data and flags as a (data, (islink, 384 """Return target file data and flags as a (data, (islink,
385 isexec)) tuple. 385 isexec)) tuple. Data is None if file is missing/deleted.
386 """ 386 """
387 raise NotImplementedError 387 raise NotImplementedError
388 388
389 def setfile(self, fname, data, mode, copysource): 389 def setfile(self, fname, data, mode, copysource):
390 """Write data to target file fname and set its mode. mode is a 390 """Write data to target file fname and set its mode. mode is a
424 try: 424 try:
425 isexec = self.opener.lstat(fname).st_mode & 0100 != 0 425 isexec = self.opener.lstat(fname).st_mode & 0100 != 0
426 except OSError, e: 426 except OSError, e:
427 if e.errno != errno.ENOENT: 427 if e.errno != errno.ENOENT:
428 raise 428 raise
429 return (self.opener.read(fname), (False, isexec)) 429 try:
430 return (self.opener.read(fname), (False, isexec))
431 except IOError, e:
432 if e.errno != errno.ENOENT:
433 raise
434 return None, None
430 435
431 def setfile(self, fname, data, mode, copysource): 436 def setfile(self, fname, data, mode, copysource):
432 islink, isexec = mode 437 islink, isexec = mode
433 if data is None: 438 if data is None:
434 self.opener.setflags(fname, islink, isexec) 439 self.opener.setflags(fname, islink, isexec)
526 531
527 def getfile(self, fname): 532 def getfile(self, fname):
528 if fname in self.data: 533 if fname in self.data:
529 return self.data[fname] 534 return self.data[fname]
530 if not self.opener or fname not in self.files: 535 if not self.opener or fname not in self.files:
531 raise IOError 536 return None, None, None
532 fn, mode, copied = self.files[fname] 537 fn, mode, copied = self.files[fname]
533 return self.opener.read(fn), mode, copied 538 return self.opener.read(fn), mode, copied
534 539
535 def close(self): 540 def close(self):
536 if self.opener: 541 if self.opener:
552 557
553 def getfile(self, fname): 558 def getfile(self, fname):
554 try: 559 try:
555 fctx = self.ctx[fname] 560 fctx = self.ctx[fname]
556 except error.LookupError: 561 except error.LookupError:
557 raise IOError 562 return None, None
558 flags = fctx.flags() 563 flags = fctx.flags()
559 return fctx.data(), ('l' in flags, 'x' in flags) 564 return fctx.data(), ('l' in flags, 'x' in flags)
560 565
561 def setfile(self, fname, data, mode, copysource): 566 def setfile(self, fname, data, mode, copysource):
562 if copysource: 567 if copysource:
595 self.missing = True 600 self.missing = True
596 self.mode = gp.mode 601 self.mode = gp.mode
597 self.copysource = gp.oldpath 602 self.copysource = gp.oldpath
598 self.create = gp.op in ('ADD', 'COPY', 'RENAME') 603 self.create = gp.op in ('ADD', 'COPY', 'RENAME')
599 self.remove = gp.op == 'DELETE' 604 self.remove = gp.op == 'DELETE'
600 try: 605 if self.copysource is None:
601 if self.copysource is None: 606 data, mode = backend.getfile(self.fname)
602 data, mode = backend.getfile(self.fname) 607 else:
603 self.exists = True 608 data, mode = store.getfile(self.copysource)[:2]
604 else: 609 if data is not None:
605 data, mode = store.getfile(self.copysource)[:2] 610 self.exists = self.copysource is None or backend.exists(self.fname)
606 self.exists = backend.exists(self.fname)
607 self.missing = False 611 self.missing = False
608 if data: 612 if data:
609 self.lines = mdiff.splitnewlines(data) 613 self.lines = mdiff.splitnewlines(data)
610 if self.mode is None: 614 if self.mode is None:
611 self.mode = mode 615 self.mode = mode
620 for l in self.lines: 624 for l in self.lines:
621 if l.endswith('\r\n'): 625 if l.endswith('\r\n'):
622 l = l[:-2] + '\n' 626 l = l[:-2] + '\n'
623 nlines.append(l) 627 nlines.append(l)
624 self.lines = nlines 628 self.lines = nlines
625 except IOError: 629 else:
626 if self.create: 630 if self.create:
627 self.missing = False 631 self.missing = False
628 if self.mode is None: 632 if self.mode is None:
629 self.mode = (False, False) 633 self.mode = (False, False)
630 if self.missing: 634 if self.missing:
1378 backend.unlink(gp.path) 1382 backend.unlink(gp.path)
1379 continue 1383 continue
1380 data, mode = None, None 1384 data, mode = None, None
1381 if gp.op in ('RENAME', 'COPY'): 1385 if gp.op in ('RENAME', 'COPY'):
1382 data, mode = store.getfile(gp.oldpath)[:2] 1386 data, mode = store.getfile(gp.oldpath)[:2]
1387 # FIXME: failing getfile has never been handled here
1388 assert data is not None
1383 if gp.mode: 1389 if gp.mode:
1384 mode = gp.mode 1390 mode = gp.mode
1385 if gp.op == 'ADD': 1391 if gp.op == 'ADD':
1386 # Added files without content have no hunk and 1392 # Added files without content have no hunk and
1387 # must be created 1393 # must be created
1402 rejects += 1 1408 rejects += 1
1403 continue 1409 continue
1404 elif state == 'git': 1410 elif state == 'git':
1405 for gp in values: 1411 for gp in values:
1406 path = pstrip(gp.oldpath) 1412 path = pstrip(gp.oldpath)
1407 try: 1413 data, mode = backend.getfile(path)
1408 data, mode = backend.getfile(path) 1414 if data is None:
1409 except IOError, e:
1410 if e.errno != errno.ENOENT:
1411 raise
1412 # The error ignored here will trigger a getfile() 1415 # The error ignored here will trigger a getfile()
1413 # error in a place more appropriate for error 1416 # error in a place more appropriate for error
1414 # handling, and will not interrupt the patching 1417 # handling, and will not interrupt the patching
1415 # process. 1418 # process.
1419 pass
1416 else: 1420 else:
1417 store.setfile(path, data, mode) 1421 store.setfile(path, data, mode)
1418 else: 1422 else:
1419 raise util.Abort(_('unsupported parser state: %s') % state) 1423 raise util.Abort(_('unsupported parser state: %s') % state)
1420 1424