comparison mercurial/cmdutil.py @ 27370:d9e3ebe56970 stable

record: don't dereference symlinks while copying over stat data Previously, we could be calling os.utime or os.chflags (via shutil.copystat) on a symlink. These functions dereference symlinks, so this would have caused the timestamp of the target to be set. On a read-only or similarly weird filesystem, this might cause an exception to be raised. This is pretty hard to test because conjuring up a read-only filesystem for test purposes is non-trivial.
author Siddharth Agarwal <sid0@fb.com>
date Sat, 12 Dec 2015 10:58:05 -0800
parents 1aee2ab0f902
children 4eeef1b2d689 84d686cb62c4
comparison
equal deleted inserted replaced
27369:c48ecc0b5bc9 27370:d9e3ebe56970
5 # This software may be used and distributed according to the terms of the 5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version. 6 # GNU General Public License version 2 or any later version.
7 7
8 from node import hex, bin, nullid, nullrev, short 8 from node import hex, bin, nullid, nullrev, short
9 from i18n import _ 9 from i18n import _
10 import os, sys, errno, re, tempfile, cStringIO, shutil 10 import os, sys, errno, re, tempfile, cStringIO
11 import util, scmutil, templater, patch, error, templatekw, revlog, copies 11 import util, scmutil, templater, patch, error, templatekw, revlog, copies
12 import match as matchmod 12 import match as matchmod
13 import repair, graphmod, revset, phases, obsolete, pathutil 13 import repair, graphmod, revset, phases, obsolete, pathutil
14 import changelog 14 import changelog
15 import bookmarks 15 import bookmarks
164 for f in tobackup: 164 for f in tobackup:
165 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.', 165 fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
166 dir=backupdir) 166 dir=backupdir)
167 os.close(fd) 167 os.close(fd)
168 ui.debug('backup %r as %r\n' % (f, tmpname)) 168 ui.debug('backup %r as %r\n' % (f, tmpname))
169 util.copyfile(repo.wjoin(f), tmpname) 169 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
170 shutil.copystat(repo.wjoin(f), tmpname)
171 backups[f] = tmpname 170 backups[f] = tmpname
172 171
173 fp = cStringIO.StringIO() 172 fp = cStringIO.StringIO()
174 for c in chunks: 173 for c in chunks:
175 fname = c.filename() 174 fname = c.filename()
214 # without normallookup, restoring timestamp 213 # without normallookup, restoring timestamp
215 # may cause partially committed files 214 # may cause partially committed files
216 # to be treated as unmodified 215 # to be treated as unmodified
217 dirstate.normallookup(realname) 216 dirstate.normallookup(realname)
218 217
219 util.copyfile(tmpname, repo.wjoin(realname)) 218 # copystat=True here and above are a hack to trick any
220 # Our calls to copystat() here and above are a 219 # editors that have f open that we haven't modified them.
221 # hack to trick any editors that have f open that
222 # we haven't modified them.
223 # 220 #
224 # Also note that this racy as an editor could 221 # Also note that this racy as an editor could notice the
225 # notice the file's mtime before we've finished 222 # file's mtime before we've finished writing it.
226 # writing it. 223 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
227 shutil.copystat(tmpname, repo.wjoin(realname))
228 os.unlink(tmpname) 224 os.unlink(tmpname)
229 if tobackup: 225 if tobackup:
230 os.rmdir(backupdir) 226 os.rmdir(backupdir)
231 except OSError: 227 except OSError:
232 pass 228 pass