Patchwork [2,of,3] dirstate: walk returns None for files under symlink directory

login
register
mail settings
Submitter Durham Goode
Date Feb. 5, 2013, 11:38 p.m.
Message ID <edbb3ea6c81389151eca.1360107519@dev350.prn1.facebook.com>
Download mbox | patch
Permalink /patch/815/
State Changes Requested, archived
Headers show

Comments

Durham Goode - Feb. 5, 2013, 11:38 p.m.
# HG changeset patch
# User Durham Goode <durham@fb.com>
# Date 1360016835 28800
# Node ID edbb3ea6c81389151eca1f352cbd7265cc47fd42
# Parent  f2a1cf2cbb4ac88f922c5bcb5792c2c7c4c236d5
dirstate: walk returns None for files under symlink directory

Previously dirstate.walk would return a stat object for files in the dmap
that were under a symlink directory.  Now it will return None (when the unknown
parameter is True) to indicate that they are no longer considered part of the
repository.

In a situation like this:
  mkdir foo && touch foo/a && hg commit -Am "a"
  mv foo bar
  ln -s bar foo

'hg status' will now show '! foo/a', whereas before it incorrectly considered
'foo/a' to be unchanged.

In addition to making 'hg status' report the correct information, this will
allow callers to dirstate.walk to not have to detect symlinks themselves,
which can be very expensive.
Bryan O'Sullivan - Feb. 8, 2013, 11:58 a.m.
On Tue, Feb 5, 2013 at 3:38 PM, Durham Goode <durham@fb.com> wrote:

> dirstate: walk returns None for files under symlink directory
>

I don't understand what "under symlink directory" means. Could you explain,
please?
Durham Goode - Feb. 8, 2013, 1:03 p.m.
> I don't understand what "under symlink directory" means. Could you
>explain, please?


If you have a path in your repository like "foo/bar/a" and 'foo' is a
symlink, then 'bar' and 'bar/a' are considered under a symlink directory.
So in the commit messages example:

  mkdir foo && touch foo/a && hg commit -Am "a"
  mv foo bar
  ln -s bar foo


'a' was originally not under a symlink, but then foo became a symlink.
Therefore 'a' is under a symlink directory and should be considered
missing.
Bryan O'Sullivan - Feb. 9, 2013, 5:32 p.m.
On Tue, Feb 5, 2013 at 3:38 PM, Durham Goode <durham@fb.com> wrote:

> dirstate: walk returns None for files under symlink directory
>
> Previously dirstate.walk would return a stat object for files in the dmap
> that were under a symlink directory.  Now it will return None (when the
> unknown
> parameter is True) to indicate that they are no longer considered part of
> the
> repository.
>

This patch looks good, but "symlink directory" is awkward and confusing.

I'd use something like "symlink that replaces a directory" instead - it's
not great wording, but it doesn't make me go "what does that mean?".

Patch

diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
--- a/mercurial/dirstate.py
+++ b/mercurial/dirstate.py
@@ -710,9 +710,26 @@ 
         # step 3: report unseen items in the dmap hash
         if not skipstep3 and not exact:
             visit = sorted([f for f in dmap if f not in results and matchfn(f)])
-            nf = iter(visit).next
-            for st in util.statfiles([join(i) for i in visit]):
-                results[nf()] = st
+            if unknown:
+                # unknown == True means we walked the full directory tree above.
+                # So if a file is not seen it was either a) not matching matchfn
+                # b) ignored, c) missing, or d) under a symlink directory.
+                audit_path = scmutil.pathauditor(self._root)
+
+                for nf in iter(visit):
+                    # Report ignored items in the dmap as long as they are not
+                    # under a symlink directory.
+                    if ignore(nf) and audit_path.isvalidpath(nf):
+                        results[nf] = util.statfiles([join(nf)])[0]
+                    else:
+                        # It's either missing or under a symlink directory
+                        results[nf] = None
+            else:
+                # We may not have walked the full directory tree above,
+                # so stat everything we missed.
+                nf = iter(visit).next
+                for st in util.statfiles([join(i) for i in visit]):
+                    results[nf()] = st
         for s in subrepos:
             del results[s]
         del results['.hg']
diff --git a/tests/test-symlinks.t b/tests/test-symlinks.t
--- a/tests/test-symlinks.t
+++ b/tests/test-symlinks.t
@@ -149,6 +149,10 @@ 
   adding foo/a
   $ mv foo bar
   $ ln -s bar foo
+  $ hg status
+  ! foo/a
+  ? bar/a
+  ? foo
 
 now addremove should remove old files