[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