The line os.unlink(f) in util.opener.__call__ fails with an OSError on Windows, if the file exists and is held open by another process (e.g. Python's open()). Which has the effect that the unlink() call is a noop, so the hardlink is not broken up. Affected: Mercurial 1.7.1
If a file f has been opened for reading by another process with 'posixfile(f)', calling unlink will send that file into a "scheduled delete" state on Windows. Scheduled delete has the following characteristics: (a) the entry in the directory for f is still kept (b) calling 'fd = posixfile(f, 'w')' will raise 'IOError: [Errno 13] <f>: Access is denied' (c) calling 'os.rename(f, f+'.foo')' will raise 'WindowsError: [Error 5] Access is denied' (d) calling 'os.lstat(f)' will raise 'WindowsError: [Error 5] Access is denied: <f>' Scheduled delete is left as soon as the other process closes the file.
Matters are actually a bit worse. Consider: (1) Another process opens file f using posixfile(f) (or equivalent). (2) Mercurial calls fd = opener(f, 'w') on e.g. 'hg update' As of (now) Mercurial 1.7.2, the os.unlink(f) will send the file into "scheduled delete" and the subsequent posixfile(f, 'w') fails with an "Access denied" IOError. But the unlink has already been done. The mercurial operation is aborted like this: $ hg update abort: <f>: Access is denied and the file f Mercurial tried to update is gone. The directory entry of f will vanish as soon as the other process closes the file. Adjusting title.
(from my posting http://markmail.org/message/kwiciiu5ttmlgytt) How to reproduce this: On Windows, in one shell start python and open a working dir file with posixfile: $ python Python 2.6.5 (r265:79096, Mar 19 2010, 21:48:26) [MSC v.1500 32 bit (Intel)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> from mercurial.windows import posixfile >>> fd = posixfile('a.txt') >>> then in a second cmd.exe shell instance do: $ hg version Mercurial Distributed SCM (version 1.7.2) (see http://mercurial.selenic.com for more information) [..] $ hg parent -q 17:e86f3b869e53 $ hg update 16 abort: C:\Users\adi\hgrepos\tests\c\a.txt: Access is denied Now, the directory entry for 'a.txt' still shows up: $ dir /w Volume in drive C has no label. Volume Serial Number is F80E-0A52 Directory of C:\Users\adi\hgrepos\tests\c [.] [..] [.hg] a.txt b.txt 2 File(s) 18 bytes 3 Dir(s) 473'414'610'944 bytes free But the file a.txt is now a ghost in "scheduled delete" state [1]. In that state you can't even read the file ("type" is the Windows equivalent of "cat" for the Linux fraction): $ type a.txt Access is denied. Now, let's close the file handle in the first shell: >>> fd.close() >>> Another look in the second command shell now shows the file is gone from the directory: $ dir /w Volume in drive C has no label. Volume Serial Number is F80E-0A52 Directory of C:\Users\adi\hgrepos\tests\c [.] [..] [.hg] b.txt 1 File(s) 6 bytes 3 Dir(s) 473'414'606'848 bytes free And status agrees with that: $ hg status ! a.txt Boom. In short: doing a 'hg update' while another process is reading the file nukes the file on Windows. [1] http://mercurial.selenic.com/wiki/UnlinkingFilesOnWindows
Fixed by http://selenic.com/repo/hg/rev/a98a90023261 Adrian Buehlmann <adrian@cadifra.com> localrepo: remove unneeded os.unlink call in wwrite (please test the fix)
Dear hgbot, a98a90023261 is helpful but not sufficient
Changing title from opener loses files on 'w'rite for open files on Windows to update loses working copy files on Windows for open files Confirmed to still be present in release 1.7.3 (as expected).
Also, confirmed to still be present current default tip f3058dd05281.
Fixed by http://selenic.com/repo/hg/rev/c01eea6c455b Adrian Buehlmann <adrian@cadifra.com> opener: use util.unlink (issue2524) (please test the fix)
--- Bug imported by bugzilla@serpentine.com 2012-05-12 09:14 EDT --- This bug was previously known as _bug_ 2524 at http://mercurial.selenic.com/bts/issue2524