[PATCH] issue 1364

Petr Kodl petrkodl at gmail.com
Mon Oct 27 09:48:18 CDT 2008


# HG changeset patch
# User Petr Kodl <petrkodl at gmail.com>
# Date 1225118880 14400
# Node ID 66e3a3f253d5a123a90f09b8cb7af83988bbddc2
# Parent  c4461ea8b4c8022568080ecc847ba4ed3e17906c
issue 1364

This is a patch working around a problem in msvcrt library. The fstat call as
implemented by msvcrt uses incorrectly DST information and as a result the
file time reported can be off by 1 hour. Python 2.4 suffers from this problem
in os.lstat and os.stat calls. Python 2.5 fixes it by calling Win32 API directly.

Since osutil.c uses Win32 API there can be a difference between times reported
in by osutil.listdir and os.lstat implementation that trigger file loading.

The patch implements native Win32 lstat call with Python 2.5 compatible time
and redirects os.stat, os.lstat to util.stat and util.lstat.

diff -r c4461ea8b4c8 -r 66e3a3f253d5 hgext/convert/darcs.py
--- a/hgext/convert/darcs.py	Sun Oct 26 17:26:28 2008 +0100
+++ b/hgext/convert/darcs.py	Mon Oct 27 10:48:00 2008 -0400
@@ -119,7 +119,7 @@
         return open(os.path.join(self.tmppath, name), 'rb').read()
 
     def getmode(self, name, rev):
-        mode = os.lstat(os.path.join(self.tmppath, name)).st_mode
+        mode = util.lstat(os.path.join(self.tmppath, name)).st_mode
         return (mode & 0111) and 'x' or ''
 
     def gettags(self):
diff -r c4461ea8b4c8 -r 66e3a3f253d5 hgext/convert/gnuarch.py
--- a/hgext/convert/gnuarch.py	Sun Oct 26 17:26:28 2008 +0100
+++ b/hgext/convert/gnuarch.py	Mon Oct 27 10:48:00 2008 -0400
@@ -173,7 +173,7 @@
                 self._parsechangeset(changeset, rev)
 
     def _getfile(self, name, rev):
-        mode = os.lstat(os.path.join(self.tmppath, name)).st_mode
+        mode = util.lstat(os.path.join(self.tmppath, name)).st_mode
         if stat.S_ISLNK(mode):
             data = os.readlink(os.path.join(self.tmppath, name))
             mode = mode and 'l' or ''
diff -r c4461ea8b4c8 -r 66e3a3f253d5 hgext/inotify/server.py
--- a/hgext/inotify/server.py	Sun Oct 26 17:26:28 2008 +0100
+++ b/hgext/inotify/server.py	Mon Oct 27 10:48:00 2008 -0400
@@ -184,7 +184,7 @@
 
     def dirstate_info(self):
         try:
-            st = os.lstat(self.repo.join('dirstate'))
+            st = util.lstat(self.repo.join('dirstate'))
             return st.st_mtime, st.st_ino
         except OSError, err:
             if err.errno != errno.ENOENT:
@@ -398,7 +398,7 @@
 
     def stat(self, wpath):
         try:
-            st = os.lstat(join(self.wprefix, wpath))
+            st = util.lstat(join(self.wprefix, wpath))
             ret = st.st_mode, st.st_size, st.st_mtime
             self.statcache[wpath] = ret
             return ret
diff -r c4461ea8b4c8 -r 66e3a3f253d5 mercurial/byterange.py
--- a/mercurial/byterange.py	Sun Oct 26 17:26:28 2008 +0100
+++ b/mercurial/byterange.py	Mon Oct 27 10:48:00 2008 -0400
@@ -24,6 +24,7 @@
 import urllib
 import urllib2
 import rfc822
+import util
 
 try:
     from cStringIO import StringIO
@@ -212,7 +213,7 @@
         host = req.get_host()
         file = req.get_selector()
         localfile = urllib.url2pathname(file)
-        stats = os.stat(localfile)
+        stats = util.stat(localfile)
         size = stats[stat.ST_SIZE]
         modified = rfc822.formatdate(stats[stat.ST_MTIME])
         mtype = mimetypes.guess_type(file)[0]
diff -r c4461ea8b4c8 -r 66e3a3f253d5 mercurial/cmdutil.py
--- a/mercurial/cmdutil.py	Sun Oct 26 17:26:28 2008 +0100
+++ b/mercurial/cmdutil.py	Mon Oct 27 10:48:00 2008 -0400
@@ -1168,7 +1168,7 @@
                 rf = repo.wjoin(f)
                 rel = repo.pathto(f)
                 try:
-                    mode = os.lstat(rf)[stat.ST_MODE]
+                    mode = util.lstat(rf)[stat.ST_MODE]
                 except OSError:
                     if is_dir(f): # deleted directory ?
                         continue
diff -r c4461ea8b4c8 -r 66e3a3f253d5 mercurial/context.py
--- a/mercurial/context.py	Sun Oct 26 17:26:28 2008 +0100
+++ b/mercurial/context.py	Mon Oct 27 10:48:00 2008 -0400
@@ -674,11 +674,11 @@
     def children(self):
         return []
 
-    def size(self): return os.stat(self._repo.wjoin(self._path)).st_size
+    def size(self): return util.stat(self._repo.wjoin(self._path)).st_size
     def date(self):
         t, tz = self._changectx.date()
         try:
-            return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
+            return (int(util.lstat(self._repo.wjoin(self._path)).st_mtime), tz)
         except OSError, err:
             if err.errno != errno.ENOENT: raise
             return (t, tz)
diff -r c4461ea8b4c8 -r 66e3a3f253d5 mercurial/dirstate.py
--- a/mercurial/dirstate.py	Sun Oct 26 17:26:28 2008 +0100
+++ b/mercurial/dirstate.py	Mon Oct 27 10:48:00 2008 -0400
@@ -253,7 +253,7 @@
         'mark a file normal and clean'
         self._dirty = True
         self._addpath(f)
-        s = os.lstat(self._join(f))
+        s = util.lstat(self._join(f))
         self._map[f] = ('n', s.st_mode, s.st_size, int(s.st_mtime))
         if f in self._copymap:
             del self._copymap[f]
@@ -315,7 +315,7 @@
     def merge(self, f):
         'mark a file merged'
         self._dirty = True
-        s = os.lstat(self._join(f))
+        s = util.lstat(self._join(f))
         self._addpath(f)
         self._map[f] = ('m', s.st_mode, s.st_size, int(s.st_mtime))
         if f in self._copymap:
@@ -440,7 +440,7 @@
         normpath = util.normpath
         normalize = self.normalize
         listdir = osutil.listdir
-        lstat = os.lstat
+        lstat = util.lstat
         pconvert = util.pconvert
         getkind = stat.S_IFMT
         dirkind = stat.S_IFDIR
@@ -537,7 +537,7 @@
         removed, deleted, clean = [], [], []
 
         _join = self._join
-        lstat = os.lstat
+        lstat = util.lstat
         cmap = self._copymap
         dmap = self._map
         ladd = lookup.append
diff -r c4461ea8b4c8 -r 66e3a3f253d5 mercurial/hgweb/common.py
--- a/mercurial/hgweb/common.py	Sun Oct 26 17:26:28 2008 +0100
+++ b/mercurial/hgweb/common.py	Mon Oct 27 10:48:00 2008 -0400
@@ -6,7 +6,7 @@
 # This software may be used and distributed according to the terms
 # of the GNU General Public License, incorporated herein by reference.
 
-import errno, mimetypes, os
+import errno, mimetypes, os, util
 
 HTTP_OK = 200
 HTTP_BAD_REQUEST = 400
@@ -39,9 +39,9 @@
         store_path = os.path.join(store_path, "store")
     cl_path = os.path.join(store_path, "00changelog.i")
     if os.path.exists(cl_path):
-        return os.stat(cl_path).st_mtime
+        return util.stat(cl_path).st_mtime
     else:
-        return os.stat(store_path).st_mtime
+        return util.stat(store_path).st_mtime
 
 def staticfile(directory, fname, req):
     """return a file inside directory with guessed Content-Type header
diff -r c4461ea8b4c8 -r 66e3a3f253d5 mercurial/localrepo.py
--- a/mercurial/localrepo.py	Sun Oct 26 17:26:28 2008 +0100
+++ b/mercurial/localrepo.py	Mon Oct 27 10:48:00 2008 -0400
@@ -1067,7 +1067,7 @@
             for f in list:
                 p = self.wjoin(f)
                 try:
-                    st = os.lstat(p)
+                    st = util.lstat(p)
                 except:
                     self.ui.warn(_("%s does not exist!\n") % f)
                     rejected.append(f)
diff -r c4461ea8b4c8 -r 66e3a3f253d5 mercurial/osutil.c
--- a/mercurial/osutil.c	Sun Oct 26 17:26:28 2008 +0100
+++ b/mercurial/osutil.c	Mon Oct 27 10:48:00 2008 -0400
@@ -248,6 +248,62 @@
 	return rval;
 }
 
+static PyObject *win32_lstat(PyObject *self, PyObject* args, PyObject* kwargs)
+{
+	PyObject* st = NULL;
+	BY_HANDLE_FILE_INFORMATION fi;
+	HANDLE fh;
+	struct hg_stat *stp;
+	char *path, *dot;
+
+	if (!PyArg_ParseTuple(args, "s:listdir", &path))
+		goto error_out;
+
+	fh = CreateFileA(path, 0, 0, NULL, OPEN_EXISTING,
+		FILE_FLAG_BACKUP_SEMANTICS, NULL);
+
+	if (fh==INVALID_HANDLE_VALUE) {
+		PyErr_SetFromWindowsErrWithFilename(GetLastError(), path);
+		goto error_out;
+	}
+
+	if (!GetFileInformationByHandle(fh, &fi)) {
+		PyErr_SetFromWindowsErrWithFilename(GetLastError(), path);
+		goto error_info;
+	}
+
+	st = PyObject_CallObject((PyObject *)&listdir_stat_type, NULL);
+	if (!st)
+		goto error_info;
+
+	stp = &((struct listdir_stat *)st)->st;
+
+	/*
+	produce just enough stat information to satisfy mercurial needs
+	the .bat magic is required because the EXE flag is used in some places
+	*/
+	stp->st_mode  = ((fi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
+			? (_S_IFDIR | 0111) : _S_IFREG )
+			| ((fi.dwFileAttributes & FILE_ATTRIBUTE_READONLY)
+			  ? 0444 : 0666 );
+	stp->st_mtime = to_python_time(&fi.ftLastWriteTime);
+	stp->st_ctime = to_python_time(&fi.ftCreationTime);
+	if (!(fi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
+		stp->st_size =	((__int64)fi.nFileSizeHigh << 32)
+				+ fi.nFileSizeLow;
+
+		dot = strrchr(path,'.');
+		if (dot && (!stricmp(dot,".bat") || !stricmp(dot,".cmd")
+			|| !stricmp(dot,".exe") || !stricmp(dot,".com")))
+			stp->st_mode |= 0111;
+	}
+error_info:
+	CloseHandle(fh);
+error_out:
+	return st;
+}
+
+
 #else
 
 int entkind(struct dirent *ent)
@@ -397,6 +453,10 @@
 static PyMethodDef methods[] = {
 	{"listdir", (PyCFunction)listdir, METH_VARARGS | METH_KEYWORDS,
 	 "list a directory\n"},
+#ifdef _WIN32
+	 {"win32_lstat", (PyCFunction)win32_lstat, METH_VARARGS,
+	 "Win32 specific lstat implementation\n"},
+#endif
 	{NULL, NULL}
 };
 
diff -r c4461ea8b4c8 -r 66e3a3f253d5 mercurial/osutil.py
--- a/mercurial/osutil.py	Sun Oct 26 17:26:28 2008 +0100
+++ b/mercurial/osutil.py	Mon Oct 27 10:48:00 2008 -0400
@@ -1,4 +1,4 @@
-import os
+import os, util
 import stat as _stat
 
 def _mode_to_kind(mode):
@@ -30,7 +30,7 @@
     names = os.listdir(path)
     names.sort()
     for fn in names:
-        st = os.lstat(prefix + fn)
+        st = util.lstat(prefix + fn)
         if fn == skip and _stat.S_ISDIR(st.st_mode):
             return []
         if stat:
diff -r c4461ea8b4c8 -r 66e3a3f253d5 mercurial/patch.py
--- a/mercurial/patch.py	Sun Oct 26 17:26:28 2008 +0100
+++ b/mercurial/patch.py	Mon Oct 27 10:48:00 2008 -0400
@@ -325,7 +325,7 @@
                 dest = self.fname
             st = None
             try:
-                st = os.lstat(dest)
+                st = util.lstat(dest)
             except OSError, inst:
                 if inst.errno != errno.ENOENT:
                     raise
diff -r c4461ea8b4c8 -r 66e3a3f253d5 mercurial/store.py
--- a/mercurial/store.py	Sun Oct 26 17:26:28 2008 +0100
+++ b/mercurial/store.py	Mon Oct 27 10:48:00 2008 -0400
@@ -128,7 +128,7 @@
 def _calcmode(path):
     try:
         # files in .hg/ will be created using this mode
-        mode = os.stat(path).st_mode
+        mode = util.stat(path).st_mode
             # avoid some useless chmods
         if (0777 & ~util._umask) == (0777 & mode):
             mode = None
@@ -264,7 +264,7 @@
         for f in fncache(self._op):
             ef = hybridencode(f)
             try:
-                st = os.stat(pjoin(spath, ef))
+                st = util.stat(pjoin(spath, ef))
                 yield f, ef, st.st_size
                 existing.append(f)
             except OSError:
diff -r c4461ea8b4c8 -r 66e3a3f253d5 mercurial/util.py
--- a/mercurial/util.py	Sun Oct 26 17:26:28 2008 +0100
+++ b/mercurial/util.py	Mon Oct 27 10:48:00 2008 -0400
@@ -14,7 +14,7 @@
 
 from i18n import _
 import cStringIO, errno, getpass, re, shutil, sys, tempfile
-import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil
+import os, stat as stat_, threading, time, calendar, ConfigParser, locale, glob, osutil
 import imp, urlparse
 
 # Python compatibility
@@ -193,6 +193,13 @@
             return rawval
         return ConfigParser.SafeConfigParser._interpolate(self, section,
                                                           option, rawval, vars)
+
+lstat, stat = os.lstat, os.stat
+if sys.platform == 'win32':
+    try:
+        lstat, stat = osutil.win32_lstat, osutil.win32_lstat
+    except AttributeError:
+        pass
 
 def cachefunc(func):
     '''cache the result of function calls'''
@@ -427,11 +434,11 @@
         # `name', compare dev/inode numbers.  If they match, the list `rel'
         # holds the reversed list of components making up the relative file
         # name we want.
-        root_st = os.stat(root)
+        root_st = stat(root)
         rel = []
         while True:
             try:
-                name_st = os.stat(name)
+                name_st = stat(name)
             except OSError:
                 break
             if samestat(name_st, root_st):
@@ -675,7 +682,7 @@
 def lexists(filename):
     "test whether a file with this name exists. does not follow symlinks"
     try:
-        os.lstat(filename)
+        lstat(filename)
     except:
         return False
     return True
@@ -726,8 +733,8 @@
     """Copy a directory tree using hardlinks if possible"""
 
     if hardlink is None:
-        hardlink = (os.stat(src).st_dev ==
-                    os.stat(os.path.dirname(dst)).st_dev)
+        hardlink = (stat(src).st_dev ==
+                    stat(os.path.dirname(dst)).st_dev)
 
     if os.path.isdir(src):
         os.mkdir(dst)
@@ -771,17 +778,17 @@
         def check(prefix):
             curpath = os.path.join(self.root, prefix)
             try:
-                st = os.lstat(curpath)
+                st = lstat(curpath)
             except OSError, err:
                 # EINVAL can be raised as invalid path syntax under win32.
                 # They must be ignored for patterns can be checked too.
                 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
                     raise
             else:
-                if stat.S_ISLNK(st.st_mode):
+                if stat_.S_ISLNK(st.st_mode):
                     raise Abort(_('path %r traverses symbolic link %r') %
                                 (path, prefix))
-                elif (stat.S_ISDIR(st.st_mode) and
+                elif (stat_.S_ISDIR(st.st_mode) and
                       os.path.isdir(os.path.join(curpath, '.hg'))):
                     raise Abort(_('path %r is inside repo %r') %
                                 (path, prefix))
@@ -810,7 +817,7 @@
 
 def nlinks(pathname):
     """Return number of hardlinks for the given file."""
-    return os.lstat(pathname).st_nlink
+    return lstat(pathname).st_nlink
 
 if hasattr(os, 'link'):
     os_link = os.link
@@ -823,7 +830,7 @@
     try:
         return os.fstat(fp.fileno())
     except AttributeError:
-        return os.stat(fp.name)
+        return stat(fp.name)
 
 posixfile = file
 
@@ -833,10 +840,10 @@
 
 def _statfiles(files):
     'Stat each file in files and yield stat or None if file does not exist.'
-    lstat = os.lstat
+    local_stat = lstat
     for nf in files:
         try:
-            st = lstat(nf)
+            st = local_stat(nf)
         except OSError, err:
             if err.errno not in (errno.ENOENT, errno.ENOTDIR):
                 raise
@@ -846,7 +853,6 @@
 def _statfiles_clustered(files):
     '''Stat each file in files and yield stat or None if file does not exist.
     Cluster and cache stat per directory to minimize number of OS stat calls.'''
-    lstat = os.lstat
     ncase = os.path.normcase
     sep   = os.sep
     dircache = {} # dirname -> filename -> status | None if file does not exist
@@ -932,13 +938,13 @@
     Requires a path (like /foo/.hg) ending with a foldable final
     directory component.
     """
-    s1 = os.stat(path)
+    s1 = stat(path)
     d, b = os.path.split(path)
     p2 = os.path.join(d, b.upper())
     if path == p2:
         p2 = os.path.join(d, b.lower())
     try:
-        s2 = os.stat(p2)
+        s2 = stat(p2)
         if s2 == s1:
             return False
         return True
@@ -1004,14 +1010,14 @@
     # with exec bit on.
 
     try:
-        EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
+        EXECFLAGS = stat_.S_IXUSR | stat_.S_IXGRP | stat_.S_IXOTH
         fh, fn = tempfile.mkstemp("", "", path)
         try:
             os.close(fh)
-            m = os.stat(fn).st_mode & 0777
+            m = stat(fn).st_mode & 0777
             new_file_has_exec = m & EXECFLAGS
             os.chmod(fn, m ^ EXECFLAGS)
-            exec_flags_cannot_flip = ((os.stat(fn).st_mode & 0777) == m)
+            exec_flags_cannot_flip = ((stat(fn).st_mode & 0777) == m)
         finally:
             os.unlink(fn)
     except (IOError, OSError):
@@ -1294,12 +1300,12 @@
 
     def is_exec(f):
         """check whether a file is executable"""
-        return (os.lstat(f).st_mode & 0100 != 0)
+        return (lstat(f).st_mode & 0100 != 0)
 
     def set_flags(f, l, x):
-        s = os.lstat(f).st_mode
+        s = lstat(f).st_mode
         if l:
-            if not stat.S_ISLNK(s):
+            if not stat_.S_ISLNK(s):
                 # switch file to link
                 data = file(f).read()
                 os.unlink(f)
@@ -1310,7 +1316,7 @@
                     file(f, "w").write(data)
             # no chmod needed at this point
             return
-        if stat.S_ISLNK(s):
+        if stat_.S_ISLNK(s):
             # switch link to file
             data = os.readlink(f)
             os.unlink(f)
@@ -1444,7 +1450,7 @@
     # what we want.  If the original file already exists, just copy
     # its mode.  Otherwise, manually obey umask.
     try:
-        st_mode = os.lstat(name).st_mode & 0777
+        st_mode = lstat(name).st_mode & 0777
     except OSError, inst:
         if inst.errno != errno.ENOENT:
             raise
@@ -1836,7 +1842,7 @@
         def _add_dir_if_not_there(dirlst, dirname):
             match = False
             samestat = os.path.samestat
-            dirstat = os.stat(dirname)
+            dirstat = stat(dirname)
             for lstdirstat in dirlst:
                 if samestat(dirstat, lstdirstat):
                     match = True
diff -r c4461ea8b4c8 -r 66e3a3f253d5 mercurial/util_win32.py
--- a/mercurial/util_win32.py	Sun Oct 26 17:26:28 2008 +0100
+++ b/mercurial/util_win32.py	Mon Oct 27 10:48:00 2008 -0400
@@ -172,7 +172,7 @@
         fh.Close()
         return res[7]
     except pywintypes.error:
-        return os.lstat(pathname).st_nlink
+        return util.lstat(pathname).st_nlink
 
 def testpid(pid):
     '''return True if pid is still running or unable to


More information about the Mercurial-devel mailing list