@@ -153,7 +153,7 @@ def setflags(f, l, x):
# Turn off all +x bits
os.chmod(f, s & 0o666)
-def copymode(src, dst, mode=None):
+def copymode(src, dst, mode=None, enforcewritable=False):
'''Copy the file mode from the file at path src to dst.
If src doesn't exist, we're using mode instead. If mode is None, we're
using umask.'''
@@ -166,7 +166,13 @@ def copymode(src, dst, mode=None):
if st_mode is None:
st_mode = ~umask
st_mode &= 0o666
- os.chmod(dst, st_mode)
+
+ new_mode = st_mode
+
+ if enforcewritable:
+ new_mode |= stat.S_IWUSR
+
+ os.chmod(dst, new_mode)
def checkexec(path):
"""
@@ -2045,7 +2045,7 @@ def splitpath(path):
function if need.'''
return path.split(pycompat.ossep)
-def mktempcopy(name, emptyok=False, createmode=None):
+def mktempcopy(name, emptyok=False, createmode=None, enforcewritable=False):
"""Create a temporary file with the same contents from name
The permission bits are copied from the original file.
@@ -2061,7 +2061,8 @@ def mktempcopy(name, emptyok=False, crea
# Temporary files are created with mode 0600, which is usually not
# what we want. If the original file already exists, just copy
# its mode. Otherwise, manually obey umask.
- copymode(name, temp, createmode)
+ copymode(name, temp, createmode, enforcewritable)
+
if emptyok:
return temp
try:
@@ -2204,7 +2205,9 @@ class atomictempfile(object):
def __init__(self, name, mode='w+b', createmode=None, checkambig=False):
self.__name = name # permanent name
self._tempname = mktempcopy(name, emptyok=('w' in mode),
- createmode=createmode)
+ createmode=createmode,
+ enforcewritable=('w' in mode))
+
self._fp = posixfile(self._tempname, mode)
self._checkambig = checkambig
new file mode 100644
@@ -0,0 +1,142 @@
+#require execbit
+
+Checking that experimental.atomic-file works.
+
+ $ cat > $TESTTMP/show_mode.py <<EOF
+ > from __future__ import print_function
+ > import sys
+ > import os
+ > from stat import ST_MODE
+ >
+ > for file_path in sys.argv[1:]:
+ > file_stat = os.stat(file_path)
+ > octal_mode = oct(file_stat[ST_MODE] & 0o777)
+ > print("%s:%s" % (file_path, octal_mode))
+ >
+ > EOF
+
+ $ hg init repo
+ $ cd repo
+
+ $ cat > .hg/showwrites.py <<EOF
+ > def uisetup(ui):
+ > from mercurial import vfs
+ > class newvfs(vfs.vfs):
+ > def __call__(self, *args, **kwargs):
+ > print('vfs open', args, sorted(list(kwargs.items())))
+ > return super(newvfs, self).__call__(*args, **kwargs)
+ > vfs.vfs = newvfs
+ > EOF
+
+ $ for v in a1 a2 b1 b2 c ro; do echo $v > $v; done
+ $ chmod +x b*
+ $ hg commit -Aqm _
+
+# We check that
+# - the changes are actually atomic
+# - that permissions are correct (all 4 cases of (executable before) * (executable after))
+# - that renames work, though they should be atomic anyway
+# - that it works when source files are read-only (but directories are read-write still)
+
+ $ for v in a1 a2 b1 b2 ro; do echo changed-$v > $v; done
+ $ chmod -x *1; chmod +x *2
+ $ hg rename c d
+ $ hg commit -qm _
+
+Check behavior without update.atomic-file
+
+ $ hg update -r 0 -q
+ $ hg update -r 1 --config extensions.showwrites=.hg/showwrites.py 2>&1 | grep "a1'.*wb"
+ ('vfs open', ('a1', 'wb'), [('atomictemp', False), ('backgroundclose', True)])
+
+ $ python $TESTTMP/show_mode.py *
+ a1:0644
+ a2:0755
+ b1:0644
+ b2:0755
+ d:0644
+ ro:0644
+
+Add a second revision for the ro file so we can test update when the file is
+present or not
+
+ $ echo "ro" > ro
+
+ $ hg commit -qm _
+
+Check behavior without update.atomic-file first
+
+ $ hg update -C -r 0 -q
+
+ $ hg update -r 1
+ 6 files updated, 0 files merged, 1 files removed, 0 files unresolved
+
+ $ python $TESTTMP/show_mode.py *
+ a1:0644
+ a2:0755
+ b1:0644
+ b2:0755
+ d:0644
+ ro:0644
+
+Manually reset the mode of the read-only file
+
+ $ chmod a-w ro
+
+ $ python $TESTTMP/show_mode.py ro
+ ro:0444
+
+Now the file is present, try to update and check the permissions of the file
+
+ $ hg up -r 2
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+ $ python $TESTTMP/show_mode.py ro
+ ro:0644
+
+# The file which was read-only is now writable in the default behavior
+
+Check behavior with update.atomic-files
+
+
+ $ cat >> .hg/hgrc <<EOF
+ > [experimental]
+ > update.atomic-file = true
+ > EOF
+
+ $ hg update -C -r 0 -q
+ $ hg update -r 1 --config extensions.showwrites=.hg/showwrites.py 2>&1 | grep "a1'.*wb"
+ ('vfs open', ('a1', 'wb'), [('atomictemp', True), ('backgroundclose', True)])
+ $ hg st -A --rev 1
+ C a1
+ C a2
+ C b1
+ C b2
+ C d
+ C ro
+
+Check the file permission after update
+ $ python $TESTTMP/show_mode.py *
+ a1:0644
+ a2:0755
+ b1:0644
+ b2:0755
+ d:0644
+ ro:0644
+
+Manually reset the mode of the read-only file
+
+ $ chmod a-w ro
+
+ $ python $TESTTMP/show_mode.py ro
+ ro:0444
+
+Now the file is present, try to update and check the permissions of the file
+
+ $ hg update -r 2 --traceback
+ 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+ $ python $TESTTMP/show_mode.py ro
+ ro:0644
+
+# The behavior is the same as without atomic update