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.
--- a/hgext/convert/bzr.py Wed Aug 27 12:30:28 2014 +0200
+++ b/hgext/convert/bzr.py Tue Aug 26 22:03:32 2014 +0200
@@ -122,8 +122,7 @@
kind = revtree.kind(fileid)
if kind not in supportedkinds:
# the file is not available anymore - was deleted
- raise IOError(_('%s is not available in %s anymore') %
- (name, rev))
+ return None, None
mode = self._modecache[(name, rev)]
if kind == 'symlink':
target = revtree.get_symlink_target(fileid)
--- a/hgext/convert/common.py Wed Aug 27 12:30:28 2014 +0200
+++ b/hgext/convert/common.py Tue Aug 26 22:03:32 2014 +0200
@@ -88,8 +88,8 @@
def getfile(self, name, rev):
"""Return a pair (data, mode) where data is the file content
as a string and mode one of '', 'x' or 'l'. rev is the
- identifier returned by a previous call to getchanges(). Raise
- IOError to indicate that name was deleted in rev.
+ identifier returned by a previous call to getchanges().
+ Data is None if file is missing/deleted in rev.
"""
raise NotImplementedError
--- a/hgext/convert/cvs.py Wed Aug 27 12:30:28 2014 +0200
+++ b/hgext/convert/cvs.py Tue Aug 26 22:03:32 2014 +0200
@@ -220,7 +220,7 @@
self._parse()
if rev.endswith("(DEAD)"):
- raise IOError
+ return None, None
args = ("-N -P -kk -r %s --" % rev).split()
args.append(self.cvsrepo + '/' + name)
--- a/hgext/convert/darcs.py Wed Aug 27 12:30:28 2014 +0200
+++ b/hgext/convert/darcs.py Tue Aug 26 22:03:32 2014 +0200
@@ -8,7 +8,7 @@
from common import NoRepo, checktool, commandline, commit, converter_source
from mercurial.i18n import _
from mercurial import util
-import os, shutil, tempfile, re
+import os, shutil, tempfile, re, errno
# The naming drift of ElementTree is fun!
@@ -192,8 +192,13 @@
if rev != self.lastrev:
raise util.Abort(_('internal calling inconsistency'))
path = os.path.join(self.tmppath, name)
- data = util.readfile(path)
- mode = os.lstat(path).st_mode
+ try:
+ data = util.readfile(path)
+ mode = os.lstat(path).st_mode
+ except IOError, inst:
+ if inst.errno == errno.ENOENT:
+ return None, None
+ raise
mode = (mode & 0111) and 'x' or ''
return data, mode
--- a/hgext/convert/git.py Wed Aug 27 12:30:28 2014 +0200
+++ b/hgext/convert/git.py Tue Aug 26 22:03:32 2014 +0200
@@ -135,7 +135,7 @@
def getfile(self, name, rev):
if rev == hex(nullid):
- raise IOError
+ return None, None
if name == '.hgsub':
data = '\n'.join([m.hgsub() for m in self.submoditer()])
mode = ''
--- a/hgext/convert/gnuarch.py Wed Aug 27 12:30:28 2014 +0200
+++ b/hgext/convert/gnuarch.py Tue Aug 26 22:03:32 2014 +0200
@@ -137,9 +137,8 @@
if rev != self.lastrev:
raise util.Abort(_('internal calling inconsistency'))
- # Raise IOError if necessary (i.e. deleted files).
if not os.path.lexists(os.path.join(self.tmppath, name)):
- raise IOError
+ return None, None
return self._getfile(name, rev)
--- a/hgext/convert/hg.py Wed Aug 27 12:30:28 2014 +0200
+++ b/hgext/convert/hg.py Tue Aug 26 22:03:32 2014 +0200
@@ -134,6 +134,8 @@
def getfilectx(repo, memctx, f):
v = files[f]
data, mode = source.getfile(f, v)
+ if data is None:
+ return None
if f == '.hgtags':
data = self._rewritetags(source, revmap, data)
return context.memfilectx(self.repo, f, data, 'l' in mode,
@@ -351,8 +353,8 @@
try:
fctx = self.changectx(rev)[name]
return fctx.data(), fctx.flags()
- except error.LookupError, err:
- raise IOError(err)
+ except error.LookupError:
+ return None, None
def getchanges(self, rev):
ctx = self.changectx(rev)
--- a/hgext/convert/monotone.py Wed Aug 27 12:30:28 2014 +0200
+++ b/hgext/convert/monotone.py Tue Aug 26 22:03:32 2014 +0200
@@ -282,11 +282,11 @@
def getfile(self, name, rev):
if not self.mtnisfile(name, rev):
- raise IOError # file was deleted or renamed
+ return None, None
try:
data = self.mtnrun("get_file_of", name, r=rev)
except Exception:
- raise IOError # file was deleted or renamed
+ return None, None
self.mtnloadmanifest(rev)
node, attr = self.files.get(name, (None, ""))
return data, attr
--- a/hgext/convert/p4.py Wed Aug 27 12:30:28 2014 +0200
+++ b/hgext/convert/p4.py Tue Aug 26 22:03:32 2014 +0200
@@ -183,7 +183,7 @@
contents += data
if mode is None:
- raise IOError(0, "bad stat")
+ return None, None
if keywords:
contents = keywords.sub("$\\1$", contents)
--- a/hgext/convert/subversion.py Wed Aug 27 12:30:28 2014 +0200
+++ b/hgext/convert/subversion.py Tue Aug 26 22:03:32 2014 +0200
@@ -933,7 +933,7 @@
def getfile(self, file, rev):
# TODO: ra.get_file transmits the whole file instead of diffs.
if file in self.removed:
- raise IOError
+ return None, None
mode = ''
try:
new_module, revnum = revsplit(rev)[1:]
@@ -954,7 +954,7 @@
notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
if e.apr_err in notfound: # File not found
- raise IOError
+ return None, None
raise
if mode == 'l':
link_prefix = "link "
@@ -1236,9 +1236,8 @@
# Apply changes to working copy
for f, v in files:
- try:
- data, mode = source.getfile(f, v)
- except IOError:
+ data, mode = source.getfile(f, v)
+ if data is None:
self.delete.append(f)
else:
self.putfile(f, mode, data)
--- a/hgext/histedit.py Wed Aug 27 12:30:28 2014 +0200
+++ b/hgext/histedit.py Tue Aug 26 22:03:32 2014 +0200
@@ -283,7 +283,7 @@
isexec='x' in flags,
copied=copied.get(path))
return mctx
- raise IOError()
+ return None
if commitopts.get('message'):
message = commitopts['message']
--- a/hgext/largefiles/lfcommands.py Wed Aug 27 12:30:28 2014 +0200
+++ b/hgext/largefiles/lfcommands.py Tue Aug 26 22:03:32 2014 +0200
@@ -146,7 +146,7 @@
try:
fctx = ctx.filectx(lfutil.standin(f))
except error.LookupError:
- raise IOError
+ return None
renamed = fctx.renamed()
if renamed:
renamed = lfutil.splitstandin(renamed[0])
@@ -248,7 +248,7 @@
try:
fctx = ctx.filectx(srcfname)
except error.LookupError:
- raise IOError
+ return None
renamed = fctx.renamed()
if renamed:
# standin is always a largefile because largefile-ness
@@ -298,7 +298,7 @@
try:
fctx = ctx.filectx(f)
except error.LookupError:
- raise IOError
+ return None
renamed = fctx.renamed()
if renamed:
renamed = renamed[0]
--- a/mercurial/cmdutil.py Wed Aug 27 12:30:28 2014 +0200
+++ b/mercurial/cmdutil.py Tue Aug 26 22:03:32 2014 +0200
@@ -2130,7 +2130,7 @@
copied=copied.get(path))
return mctx
except KeyError:
- raise IOError
+ return None
else:
ui.note(_('copying changeset %s to %s\n') % (old, base))
@@ -2139,7 +2139,7 @@
try:
return old.filectx(path)
except KeyError:
- raise IOError
+ return None
user = opts.get('user') or old.user()
date = opts.get('date') or old.date()
--- a/mercurial/context.py Wed Aug 27 12:30:28 2014 +0200
+++ b/mercurial/context.py Tue Aug 26 22:03:32 2014 +0200
@@ -347,7 +347,10 @@
def makememctx(repo, parents, text, user, date, branch, files, store,
editor=None):
def getfilectx(repo, memctx, path):
- data, (islink, isexec), copied = store.getfile(path)
+ data, mode, copied = store.getfile(path)
+ if data is None:
+ return None
+ islink, isexec = mode
return memfilectx(repo, path, data, islink=islink, isexec=isexec,
copied=copied, memctx=memctx)
extra = {}
@@ -1626,7 +1629,9 @@
self._repo.savecommitmessage(self._text)
def filectx(self, path, filelog=None):
- """get a file context from the working directory"""
+ """get a file context from the working directory
+
+ Returns None if file doesn't exist and should be removed."""
return self._filectxfn(self._repo, self, path)
def commit(self):
--- a/mercurial/localrepo.py Wed Aug 27 12:30:28 2014 +0200
+++ b/mercurial/localrepo.py Tue Aug 26 22:03:32 2014 +0200
@@ -1394,9 +1394,12 @@
self.ui.note(f + "\n")
try:
fctx = ctx[f]
- new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
- changed)
- m1.set(f, fctx.flags())
+ if fctx is None:
+ removed.append(f)
+ else:
+ new[f] = self._filecommit(fctx, m1, m2, linkrev,
+ trp, changed)
+ m1.set(f, fctx.flags())
except OSError, inst:
self.ui.warn(_("trouble committing %s!\n") % f)
raise
@@ -1404,9 +1407,7 @@
errcode = getattr(inst, 'errno', errno.ENOENT)
if error or errcode and errcode != errno.ENOENT:
self.ui.warn(_("trouble committing %s!\n") % f)
- raise
- else:
- removed.append(f)
+ raise
# update manifest
m1.update(new)
--- a/mercurial/patch.py Wed Aug 27 12:30:28 2014 +0200
+++ b/mercurial/patch.py Tue Aug 26 22:03:32 2014 +0200
@@ -382,7 +382,7 @@
def getfile(self, fname):
"""Return target file data and flags as a (data, (islink,
- isexec)) tuple.
+ isexec)) tuple. Data is None if file is missing/deleted.
"""
raise NotImplementedError
@@ -426,7 +426,12 @@
except OSError, e:
if e.errno != errno.ENOENT:
raise
- return (self.opener.read(fname), (False, isexec))
+ try:
+ return (self.opener.read(fname), (False, isexec))
+ except IOError, e:
+ if e.errno != errno.ENOENT:
+ raise
+ return None, None
def setfile(self, fname, data, mode, copysource):
islink, isexec = mode
@@ -528,7 +533,7 @@
if fname in self.data:
return self.data[fname]
if not self.opener or fname not in self.files:
- raise IOError
+ return None, None, None
fn, mode, copied = self.files[fname]
return self.opener.read(fn), mode, copied
@@ -554,7 +559,7 @@
try:
fctx = self.ctx[fname]
except error.LookupError:
- raise IOError
+ return None, None
flags = fctx.flags()
return fctx.data(), ('l' in flags, 'x' in flags)
@@ -597,13 +602,12 @@
self.copysource = gp.oldpath
self.create = gp.op in ('ADD', 'COPY', 'RENAME')
self.remove = gp.op == 'DELETE'
- try:
- if self.copysource is None:
- data, mode = backend.getfile(self.fname)
- self.exists = True
- else:
- data, mode = store.getfile(self.copysource)[:2]
- self.exists = backend.exists(self.fname)
+ if self.copysource is None:
+ data, mode = backend.getfile(self.fname)
+ else:
+ data, mode = store.getfile(self.copysource)[:2]
+ if data is not None:
+ self.exists = self.copysource is None or backend.exists(self.fname)
self.missing = False
if data:
self.lines = mdiff.splitnewlines(data)
@@ -622,7 +626,7 @@
l = l[:-2] + '\n'
nlines.append(l)
self.lines = nlines
- except IOError:
+ else:
if self.create:
self.missing = False
if self.mode is None:
@@ -1380,6 +1384,8 @@
data, mode = None, None
if gp.op in ('RENAME', 'COPY'):
data, mode = store.getfile(gp.oldpath)[:2]
+ # FIXME: failing getfile has never been handled here
+ assert data is not None
if gp.mode:
mode = gp.mode
if gp.op == 'ADD':
@@ -1404,15 +1410,13 @@
elif state == 'git':
for gp in values:
path = pstrip(gp.oldpath)
- try:
- data, mode = backend.getfile(path)
- except IOError, e:
- if e.errno != errno.ENOENT:
- raise
+ data, mode = backend.getfile(path)
+ if data is None:
# The error ignored here will trigger a getfile()
# error in a place more appropriate for error
# handling, and will not interrupt the patching
# process.
+ pass
else:
store.setfile(path, data, mode)
else: