changeset 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 926bc0d3b595
children a74d05878a8d
files hgext/convert/bzr.py hgext/convert/common.py hgext/convert/cvs.py hgext/convert/darcs.py hgext/convert/git.py hgext/convert/gnuarch.py hgext/convert/hg.py hgext/convert/monotone.py hgext/convert/p4.py hgext/convert/subversion.py hgext/histedit.py hgext/largefiles/lfcommands.py mercurial/cmdutil.py mercurial/context.py mercurial/localrepo.py mercurial/patch.py
diffstat 16 files changed, 66 insertions(+), 52 deletions(-) [+]
line wrap: on
line diff
--- 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: