Patchwork chmod: create a new file when flags are set on a hardlinked file

login
register
mail settings
Submitter Koen Van Hoof
Date April 27, 2017, 7:32 a.m.
Message ID <e47e40bff9e070950767.1493278342@devws223>
Download mbox | patch
Permalink /patch/20305/
State Superseded
Headers show

Comments

Koen Van Hoof - April 27, 2017, 7:32 a.m.
# HG changeset patch
# User Koen Van Hoof <koen.van_hoof@nokia.com>
# Date 1493215522 -7200
#      Wed Apr 26 16:05:22 2017 +0200
# Branch stable
# Node ID e47e40bff9e07095076753e3e5288625951776ea
# Parent  6e0368b6e0bb2aa5210daec091c0200583553a78
chmod: create a new file when flags are set on a hardlinked file

For performance reasons we have several repositories where the files in the working
directory of 1 repo are hardlinks to the files of the other repo
When an update in one repo results in a chmod of a such a file, the hardlink
has to be deleted and replaced by a regular file to make sure that the change
does not happen in the other repo
Augie Fackler - April 28, 2017, 2:21 p.m.
On Thu, Apr 27, 2017 at 09:32:22AM +0200, Koen Van Hoof wrote:
> # HG changeset patch
> # User Koen Van Hoof <koen.van_hoof@nokia.com>
> # Date 1493215522 -7200
> #      Wed Apr 26 16:05:22 2017 +0200
> # Branch stable
> # Node ID e47e40bff9e07095076753e3e5288625951776ea
> # Parent  6e0368b6e0bb2aa5210daec091c0200583553a78
> chmod: create a new file when flags are set on a hardlinked file

This looks pretty good. Can I ask you to add a test? This seems like
the sort of edge case we're very likely to overlook and break in some
future refactor.

>
> For performance reasons we have several repositories where the files in the working
> directory of 1 repo are hardlinks to the files of the other repo
> When an update in one repo results in a chmod of a such a file, the hardlink
> has to be deleted and replaced by a regular file to make sure that the change
> does not happen in the other repo
>
> diff --git a/mercurial/posix.py b/mercurial/posix.py
> --- a/mercurial/posix.py
> +++ b/mercurial/posix.py
> @@ -98,7 +98,8 @@ def isexec(f):
>      return (os.lstat(f).st_mode & 0o100 != 0)
>
>  def setflags(f, l, x):
> -    s = os.lstat(f).st_mode
> +    st = os.lstat(f)
> +    s = st.st_mode
>      if l:
>          if not stat.S_ISLNK(s):
>              # switch file to link
> @@ -124,6 +125,14 @@ def setflags(f, l, x):
>          fp.close()
>          s = 0o666 & ~umask # avoid restatting for chmod
>
> +    if st.st_nlink > 1: # the file is a hardlink, break the hardlink
> +        fpin = open(f,"r")
> +        unlink(f)
> +        fpout = open(f, "w")
> +        fpout.write(fpin.read())
> +        fpin.close()
> +        fpout.close()
> +
>      sx = s & 0o100
>      if x and not sx:
>          # Turn on +x for every +r bit when making a file executable
> @@ -132,6 +141,8 @@ def setflags(f, l, x):
>      elif not x and sx:
>          # Turn off all +x bits
>          os.chmod(f, s & 0o666)
> +    elif st.st_nlink > 1: # new file still needs its stat to be copied
> +        os.chmod(f, s)
>
>  def copymode(src, dst, mode=None):
>      '''Copy the file mode from the file at path src to dst.
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel

Patch

diff --git a/mercurial/posix.py b/mercurial/posix.py
--- a/mercurial/posix.py
+++ b/mercurial/posix.py
@@ -98,7 +98,8 @@  def isexec(f):
     return (os.lstat(f).st_mode & 0o100 != 0)
 
 def setflags(f, l, x):
-    s = os.lstat(f).st_mode
+    st = os.lstat(f)
+    s = st.st_mode
     if l:
         if not stat.S_ISLNK(s):
             # switch file to link
@@ -124,6 +125,14 @@  def setflags(f, l, x):
         fp.close()
         s = 0o666 & ~umask # avoid restatting for chmod
 
+    if st.st_nlink > 1: # the file is a hardlink, break the hardlink
+        fpin = open(f,"r")
+        unlink(f)
+        fpout = open(f, "w")
+        fpout.write(fpin.read())
+        fpin.close()
+        fpout.close()
+
     sx = s & 0o100
     if x and not sx:
         # Turn on +x for every +r bit when making a file executable
@@ -132,6 +141,8 @@  def setflags(f, l, x):
     elif not x and sx:
         # Turn off all +x bits
         os.chmod(f, s & 0o666)
+    elif st.st_nlink > 1: # new file still needs its stat to be copied
+        os.chmod(f, s)
 
 def copymode(src, dst, mode=None):
     '''Copy the file mode from the file at path src to dst.