Patchwork update: do not remove cwd

login
register
mail settings
Submitter Stanislau Hlebik
Date Aug. 29, 2016, 6:50 p.m.
Message ID <e1962781ce84040746ef.1472496628@dev1918.lla1.facebook.com>
Download mbox | patch
Permalink /patch/16478/
State Changes Requested
Headers show

Comments

Stanislau Hlebik - Aug. 29, 2016, 6:50 p.m.
# HG changeset patch
# User Stanislau Hlebik <stash@fb.com>
# Date 1472496038 25200
#      Mon Aug 29 11:40:38 2016 -0700
# Node ID e1962781ce84040746ef79c0084b8fd70cfcd4b4
# Parent  318e2b600b80e4ed3c6f37df46ec7544f60d4c0b
update: do not remove cwd

During update directories are deleted as soon as they have no entries.
But current working directory shouldn't be deleted because it
causes problems for users after hg finishes. Also it makes complex commands
like 'hg split' fail.
Matt Mackall - Aug. 29, 2016, 10:32 p.m.
On Mon, 2016-08-29 at 11:50 -0700, Stanislau Hlebik wrote:
> # HG changeset patch
> # User Stanislau Hlebik <stash@fb.com>
> # Date 1472496038 25200
> #      Mon Aug 29 11:40:38 2016 -0700
> # Node ID e1962781ce84040746ef79c0084b8fd70cfcd4b4
> # Parent  318e2b600b80e4ed3c6f37df46ec7544f60d4c0b
> update: do not remove cwd
> 
> During update directories are deleted as soon as they have no entries.
> But current working directory shouldn't be deleted because it
> causes problems for users after hg finishes.

It is a perfectly valid state that POSIX allows. So we shouldn't make those
problems our problems.

>  Also it makes complex commands
> like 'hg split' fail.

Those failures are also arguably bugs in their own right.

But this approach introduces new complexities. For instance, what happens if the
current directory gets replaced by a symlink? Whether that works depends on
where you run the command. And how does the not-deleted directory get cleaned up
so as not to interfere with future operations?

If we wanted to disallow this, we'd ideally abort early so the user could run
the operation in a sane location. But that's not generally easy to discover. And
aborting later is likely to make a mess.

(Alternate idea: rename the current directory to .oh-no-you-ran-an-hg-command-
that-deleted-your-current-directory-please-clean-up-your-mess-kthxbye.)

-- 
Mathematics is the supreme nostalgia of our time.
Augie Fackler - Aug. 30, 2016, 3:13 p.m.
On Mon, Aug 29, 2016 at 11:50:28AM -0700, Stanislau Hlebik wrote:
> # HG changeset patch
> # User Stanislau Hlebik <stash@fb.com>
> # Date 1472496038 25200
> #      Mon Aug 29 11:40:38 2016 -0700
> # Node ID e1962781ce84040746ef79c0084b8fd70cfcd4b4
> # Parent  318e2b600b80e4ed3c6f37df46ec7544f60d4c0b
> update: do not remove cwd

I like it! Good improvement. However, see below for a concern.

[...]

>
> diff --git a/tests/test-update-names.t b/tests/test-update-names.t
> --- a/tests/test-update-names.t
> +++ b/tests/test-update-names.t
> @@ -72,3 +72,13 @@
>    $ cd ..
>
>  #endif
> +
> +Test that cwd is not deleted during update
> +  $ hg init r4 && cd r4
> +  $ mkdir dir
> +  $ cd dir
> +  $ echo a > a
> +  $ hg add a
> +  $ hg ci -m "file and dir"
> +  $ hg up -q null
> +  $ cd .

This wants to be 'cd ..'


What happens in this case:

  $ hg init r5 && cd r5
  $ echo foo > foo
  $ hg addr && hg ci -m foo
  $ hg co null
  $ mkdir foo
  $ echo bar > foo/bar
  $ hg addr && hg ci -m foo/bar
  $ cd foo
Move from r1 to r0, which will want to replace cwd with a file
  $ hg co 0

I suspect this is broken somehow, but didn't test it myself.

> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Durham Goode - Aug. 30, 2016, 4:45 p.m.
On 8/29/16 11:50 AM, Stanislau Hlebik wrote:
> # HG changeset patch
> # User Stanislau Hlebik <stash@fb.com>
> # Date 1472496038 25200
> #      Mon Aug 29 11:40:38 2016 -0700
> # Node ID e1962781ce84040746ef79c0084b8fd70cfcd4b4
> # Parent  318e2b600b80e4ed3c6f37df46ec7544f60d4c0b
> update: do not remove cwd
>
> During update directories are deleted as soon as they have no entries.
> But current working directory shouldn't be deleted because it
> causes problems for users after hg finishes. Also it makes complex commands
> like 'hg split' fail.
Won't this cause update to fail if the parent directory is to be deleted 
and replaced with a file?  I'd almost err towards just printing a 
warning message when the current working directory is deleted (and 
recommending they cd to the root of their repo).
>
> diff --git a/mercurial/merge.py b/mercurial/merge.py
> --- a/mercurial/merge.py
> +++ b/mercurial/merge.py
> @@ -1043,7 +1043,7 @@
>               repo.ui.note(_("removing %s\n") % f)
>           audit(f)
>           try:
> -            unlink(wjoin(f), ignoremissing=True)
> +            unlink(wjoin(f), ignoremissing=True, donotremovecwd=True)
>           except OSError as inst:
>               repo.ui.warn(_("update failed to remove %s: %s!\n") %
>                            (f, inst.strerror))
> diff --git a/mercurial/posix.py b/mercurial/posix.py
> --- a/mercurial/posix.py
> +++ b/mercurial/posix.py
> @@ -23,6 +23,7 @@
>   from .i18n import _
>   from . import (
>       encoding,
> +    osutil,
>   )
>   
>   posixfile = open
> @@ -496,7 +497,31 @@
>   def makedir(path, notindexed):
>       os.mkdir(path)
>   
> -def unlinkpath(f, ignoremissing=False):
> +def removedirs(name, donotremovecwd=False):
> +    """special version of os.removedirs that does not remove symlinked
This mentions symlinks are also affected.  That seems unrelated to the 
purpose of this patch though, and I think we do want to allow symlinks 
to be deleted if their target contains files.
> +    directories or junction points if they actually contain files"""
> +    if not donotremovecwd:
> +        return os.removedirs(name)
> +
> +    cwd = os.getcwd()
> +    if cwd == name:
> +        return
> +    if osutil.listdir(name):
> +        return
> +    os.rmdir(name)
> +    head, tail = os.path.split(name)
> +    if not tail:
> +        head, tail = os.path.split(head)
> +    while head and tail:
> +        try:
> +            if osutil.listdir(head) or cwd == head:
> +                return
> +            os.rmdir(head)
> +        except (ValueError, OSError):
> +            break
> +        head, tail = os.path.split(head)
> +
> +def unlinkpath(f, ignoremissing=False, donotremovecwd=False):
>       """unlink and remove the directory if it is empty"""
>       try:
>           os.unlink(f)
> @@ -505,7 +530,7 @@
>               raise
>       # try removing directories that might now be empty
>       try:
> -        os.removedirs(os.path.dirname(f))
> +        removedirs(os.path.dirname(f), donotremovecwd)
>       except OSError:
>           pass
>   
> diff --git a/mercurial/windows.py b/mercurial/windows.py
> --- a/mercurial/windows.py
> +++ b/mercurial/windows.py
> @@ -369,11 +369,14 @@
>       If gid is None, return the name of the current group."""
>       return None
>   
> -def removedirs(name):
> +def removedirs(name, donotremovecwd=False):
>       """special version of os.removedirs that does not remove symlinked
>       directories or junction points if they actually contain files"""
>       if osutil.listdir(name):
>           return
> +    cwd = os.getcwd()
> +    if donotremovecwd and cwd == name:
> +        return
>       os.rmdir(name)
>       head, tail = os.path.split(name)
>       if not tail:
> @@ -382,12 +385,14 @@
>           try:
>               if osutil.listdir(head):
>                   return
> +            if donotremovecwd and cwd == head:
> +                return
>               os.rmdir(head)
>           except (ValueError, OSError):
>               break
>           head, tail = os.path.split(head)
>   
> -def unlinkpath(f, ignoremissing=False):
> +def unlinkpath(f, ignoremissing=False, donotremovecwd=False):
>       """unlink and remove the directory if it is empty"""
>       try:
>           unlink(f)
> @@ -396,7 +401,7 @@
>               raise
>       # try removing directories that might now be empty
>       try:
> -        removedirs(os.path.dirname(f))
> +        removedirs(os.path.dirname(f), donotremovecwd)
>       except OSError:
>           pass
>   
> diff --git a/tests/test-update-names.t b/tests/test-update-names.t
> --- a/tests/test-update-names.t
> +++ b/tests/test-update-names.t
> @@ -72,3 +72,13 @@
>     $ cd ..
>   
>   #endif
> +
> +Test that cwd is not deleted during update
> +  $ hg init r4 && cd r4
> +  $ mkdir dir
> +  $ cd dir
> +  $ echo a > a
> +  $ hg add a
> +  $ hg ci -m "file and dir"
> +  $ hg up -q null
> +  $ cd .
Stanislau Hlebik - Aug. 30, 2016, 5:41 p.m.
Yeap, Augie already noted it.
Warning is probably fine (it’s better than nothing).
Another option is to try to save cwd before update and try to restore it after.

On 8/30/16, 5:45 PM, "Durham Goode" <durham@fb.com> wrote:

    
    
    On 8/29/16 11:50 AM, Stanislau Hlebik wrote:
    > # HG changeset patch

    > # User Stanislau Hlebik <stash@fb.com>

    > # Date 1472496038 25200

    > #      Mon Aug 29 11:40:38 2016 -0700

    > # Node ID e1962781ce84040746ef79c0084b8fd70cfcd4b4

    > # Parent  318e2b600b80e4ed3c6f37df46ec7544f60d4c0b

    > update: do not remove cwd

    >

    > During update directories are deleted as soon as they have no entries.

    > But current working directory shouldn't be deleted because it

    > causes problems for users after hg finishes. Also it makes complex commands

    > like 'hg split' fail.

    Won't this cause update to fail if the parent directory is to be deleted 
    and replaced with a file?  I'd almost err towards just printing a 
    warning message when the current working directory is deleted (and 
    recommending they cd to the root of their repo).
    >

    > diff --git a/mercurial/merge.py b/mercurial/merge.py

    > --- a/mercurial/merge.py

    > +++ b/mercurial/merge.py

    > @@ -1043,7 +1043,7 @@

    >               repo.ui.note(_("removing %s\n") % f)

    >           audit(f)

    >           try:

    > -            unlink(wjoin(f), ignoremissing=True)

    > +            unlink(wjoin(f), ignoremissing=True, donotremovecwd=True)

    >           except OSError as inst:

    >               repo.ui.warn(_("update failed to remove %s: %s!\n") %

    >                            (f, inst.strerror))

    > diff --git a/mercurial/posix.py b/mercurial/posix.py

    > --- a/mercurial/posix.py

    > +++ b/mercurial/posix.py

    > @@ -23,6 +23,7 @@

    >   from .i18n import _

    >   from . import (

    >       encoding,

    > +    osutil,

    >   )

    >   

    >   posixfile = open

    > @@ -496,7 +497,31 @@

    >   def makedir(path, notindexed):

    >       os.mkdir(path)

    >   

    > -def unlinkpath(f, ignoremissing=False):

    > +def removedirs(name, donotremovecwd=False):

    > +    """special version of os.removedirs that does not remove symlinked

    This mentions symlinks are also affected.  That seems unrelated to the 
    purpose of this patch though, and I think we do want to allow symlinks 
    to be deleted if their target contains files.
    > +    directories or junction points if they actually contain files"""

    > +    if not donotremovecwd:

    > +        return os.removedirs(name)

    > +

    > +    cwd = os.getcwd()

    > +    if cwd == name:

    > +        return

    > +    if osutil.listdir(name):

    > +        return

    > +    os.rmdir(name)

    > +    head, tail = os.path.split(name)

    > +    if not tail:

    > +        head, tail = os.path.split(head)

    > +    while head and tail:

    > +        try:

    > +            if osutil.listdir(head) or cwd == head:

    > +                return

    > +            os.rmdir(head)

    > +        except (ValueError, OSError):

    > +            break

    > +        head, tail = os.path.split(head)

    > +

    > +def unlinkpath(f, ignoremissing=False, donotremovecwd=False):

    >       """unlink and remove the directory if it is empty"""

    >       try:

    >           os.unlink(f)

    > @@ -505,7 +530,7 @@

    >               raise

    >       # try removing directories that might now be empty

    >       try:

    > -        os.removedirs(os.path.dirname(f))

    > +        removedirs(os.path.dirname(f), donotremovecwd)

    >       except OSError:

    >           pass

    >   

    > diff --git a/mercurial/windows.py b/mercurial/windows.py

    > --- a/mercurial/windows.py

    > +++ b/mercurial/windows.py

    > @@ -369,11 +369,14 @@

    >       If gid is None, return the name of the current group."""

    >       return None

    >   

    > -def removedirs(name):

    > +def removedirs(name, donotremovecwd=False):

    >       """special version of os.removedirs that does not remove symlinked

    >       directories or junction points if they actually contain files"""

    >       if osutil.listdir(name):

    >           return

    > +    cwd = os.getcwd()

    > +    if donotremovecwd and cwd == name:

    > +        return

    >       os.rmdir(name)

    >       head, tail = os.path.split(name)

    >       if not tail:

    > @@ -382,12 +385,14 @@

    >           try:

    >               if osutil.listdir(head):

    >                   return

    > +            if donotremovecwd and cwd == head:

    > +                return

    >               os.rmdir(head)

    >           except (ValueError, OSError):

    >               break

    >           head, tail = os.path.split(head)

    >   

    > -def unlinkpath(f, ignoremissing=False):

    > +def unlinkpath(f, ignoremissing=False, donotremovecwd=False):

    >       """unlink and remove the directory if it is empty"""

    >       try:

    >           unlink(f)

    > @@ -396,7 +401,7 @@

    >               raise

    >       # try removing directories that might now be empty

    >       try:

    > -        removedirs(os.path.dirname(f))

    > +        removedirs(os.path.dirname(f), donotremovecwd)

    >       except OSError:

    >           pass

    >   

    > diff --git a/tests/test-update-names.t b/tests/test-update-names.t

    > --- a/tests/test-update-names.t

    > +++ b/tests/test-update-names.t

    > @@ -72,3 +72,13 @@

    >     $ cd ..

    >   

    >   #endif

    > +

    > +Test that cwd is not deleted during update

    > +  $ hg init r4 && cd r4

    > +  $ mkdir dir

    > +  $ cd dir

    > +  $ echo a > a

    > +  $ hg add a

    > +  $ hg ci -m "file and dir"

    > +  $ hg up -q null

    > +  $ cd .
Augie Fackler - Aug. 30, 2016, 5:50 p.m.
On Tue, Aug 30, 2016 at 1:41 PM, Stanislau Hlebik <stash@fb.com> wrote:
> Yeap, Augie already noted it.
> Warning is probably fine (it’s better than nothing).
> Another option is to try to save cwd before update and try to restore it after.

That won't help the user, since their cwd won't be altered by changes
made in the subprocess.

(Unless you mean "try to recreate cwd as a directory if it appears to
have been removed", in which case that *might* work, but I don't think
so because the directory's inode will change out from under the user
and I think they'll still be in a busted state.)
Stanislau Hlebik - Aug. 30, 2016, 6:51 p.m.
Yes, but it will help users of histedit, split etc.
Maybe we can even combine warning and restoring cwd.



Sent from Samsung Mobile on O2


-------- Original message --------
From: Augie Fackler <raf@durin42.com>
Date: 30/08/2016 18:51 (GMT+00:00)
To: Stanislau Hlebik <stash@fb.com>
Cc: Durham Goode <durham@fb.com>, mercurial-devel@mercurial-scm.org, Ryan McElroy <rm@fb.com>
Subject: Re: [PATCH] update: do not remove cwd

On Tue, Aug 30, 2016 at 1:41 PM, Stanislau Hlebik <stash@fb.com> wrote:
> Yeap, Augie already noted it.
> Warning is probably fine (it’s better than nothing).
> Another option is to try to save cwd before update and try to restore it after.

That won't help the user, since their cwd won't be altered by changes
made in the subprocess.

(Unless you mean "try to recreate cwd as a directory if it appears to
have been removed", in which case that *might* work, but I don't think
so because the directory's inode will change out from under the user
and I think they'll still be in a busted state.)
Stanislau Hlebik - Aug. 30, 2016, 8:15 p.m.
Makes sense. But Durham's idea about warning seems legit. When i ran into this problem when doing 'hg split', i spent a few minutes figuring out where the problem came from. If i had warning like 'your cwd was deleted; if it causes problems go to the root' i'd found the solution sooner.



Sent from Samsung Mobile on O2


-------- Original message --------
From: Matt Mackall <mpm@selenic.com>
Date: 30/08/2016 20:23 (GMT+00:00)
To: Stanislau Hlebik <stash@fb.com>, mercurial-devel@mercurial-scm.org
Subject: Re: [PATCH] update: do not remove cwd

On Mon, 2016-08-29 at 11:50 -0700, Stanislau Hlebik wrote:
> # HG changeset patch
> # User Stanislau Hlebik <stash@fb.com>
> # Date 1472496038 25200
> #      Mon Aug 29 11:40:38 2016 -0700
> # Node ID e1962781ce84040746ef79c0084b8fd70cfcd4b4
> # Parent  318e2b600b80e4ed3c6f37df46ec7544f60d4c0b
> update: do not remove cwd
>
> During update directories are deleted as soon as they have no entries.
> But current working directory shouldn't be deleted because it
> causes problems for users after hg finishes.

It is a perfectly valid state that POSIX allows. So we shouldn't make those
problems our problems.

>  Also it makes complex commands
> like 'hg split' fail.

Those failures are also arguably bugs in their own right.

But this approach introduces new complexities. For instance, what happens if the
current directory gets replaced by a symlink? Whether that works depends on
where you run the command. And how does the not-deleted directory get cleaned up
so as not to interfere with future operations?

If we wanted to disallow this, we'd ideally abort early so the user could run
the operation in a sane location. But that's not generally easy to discover. And
aborting later is likely to make a mess.

(Alternate idea: rename the current directory to .oh-no-you-ran-an-hg-command-
that-deleted-your-current-directory-please-clean-up-your-mess-kthxbye.)

--
Mathematics is the supreme nostalgia of our time.
Matt Mackall - Aug. 30, 2016, 9:42 p.m.
On Tue, 2016-08-30 at 20:15 +0000, Stanislau Hlebik wrote:
> Makes sense. But Durham's idea about warning seems legit. When i ran into this
> problem when doing 'hg split', i spent a few minutes figuring out where the
> problem came from. If i had warning like 'your cwd was deleted; if it causes
> problems go to the root' i'd found the solution sooner.

A warning seems reasonable to me.

-- 
Mathematics is the supreme nostalgia of our time.
Gregory Szorc - Sept. 1, 2016, 10:06 p.m.
On Mon, Aug 29, 2016 at 3:32 PM, Matt Mackall <mpm@selenic.com> wrote:

> On Mon, 2016-08-29 at 11:50 -0700, Stanislau Hlebik wrote:
> > # HG changeset patch
> > # User Stanislau Hlebik <stash@fb.com>
> > # Date 1472496038 25200
> > #      Mon Aug 29 11:40:38 2016 -0700
> > # Node ID e1962781ce84040746ef79c0084b8fd70cfcd4b4
> > # Parent  318e2b600b80e4ed3c6f37df46ec7544f60d4c0b
> > update: do not remove cwd
> >
> > During update directories are deleted as soon as they have no entries.
> > But current working directory shouldn't be deleted because it
> > causes problems for users after hg finishes.
>
> It is a perfectly valid state that POSIX allows. So we shouldn't make those
> problems our problems.
>

Windows does not allow the deletion of opened files. And having
explorer.exe or a command prompt/shell in a directory is enough to maintain
an open file handle. In short, you can't delete cwd on Windows.


>
> >  Also it makes complex commands
> > like 'hg split' fail.
>
> Those failures are also arguably bugs in their own right.
>
> But this approach introduces new complexities. For instance, what happens
> if the
> current directory gets replaced by a symlink? Whether that works depends on
> where you run the command. And how does the not-deleted directory get
> cleaned up
> so as not to interfere with future operations?
>
> If we wanted to disallow this, we'd ideally abort early so the user could
> run
> the operation in a sane location. But that's not generally easy to
> discover. And
> aborting later is likely to make a mess.
>
> (Alternate idea: rename the current directory to
> .oh-no-you-ran-an-hg-command-
> that-deleted-your-current-directory-please-clean-up-your-mess-kthxbye.)
>
> --
> Mathematics is the supreme nostalgia of our time.
>
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
>

Patch

diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -1043,7 +1043,7 @@ 
             repo.ui.note(_("removing %s\n") % f)
         audit(f)
         try:
-            unlink(wjoin(f), ignoremissing=True)
+            unlink(wjoin(f), ignoremissing=True, donotremovecwd=True)
         except OSError as inst:
             repo.ui.warn(_("update failed to remove %s: %s!\n") %
                          (f, inst.strerror))
diff --git a/mercurial/posix.py b/mercurial/posix.py
--- a/mercurial/posix.py
+++ b/mercurial/posix.py
@@ -23,6 +23,7 @@ 
 from .i18n import _
 from . import (
     encoding,
+    osutil,
 )
 
 posixfile = open
@@ -496,7 +497,31 @@ 
 def makedir(path, notindexed):
     os.mkdir(path)
 
-def unlinkpath(f, ignoremissing=False):
+def removedirs(name, donotremovecwd=False):
+    """special version of os.removedirs that does not remove symlinked
+    directories or junction points if they actually contain files"""
+    if not donotremovecwd:
+        return os.removedirs(name)
+
+    cwd = os.getcwd()
+    if cwd == name:
+        return
+    if osutil.listdir(name):
+        return
+    os.rmdir(name)
+    head, tail = os.path.split(name)
+    if not tail:
+        head, tail = os.path.split(head)
+    while head and tail:
+        try:
+            if osutil.listdir(head) or cwd == head:
+                return
+            os.rmdir(head)
+        except (ValueError, OSError):
+            break
+        head, tail = os.path.split(head)
+
+def unlinkpath(f, ignoremissing=False, donotremovecwd=False):
     """unlink and remove the directory if it is empty"""
     try:
         os.unlink(f)
@@ -505,7 +530,7 @@ 
             raise
     # try removing directories that might now be empty
     try:
-        os.removedirs(os.path.dirname(f))
+        removedirs(os.path.dirname(f), donotremovecwd)
     except OSError:
         pass
 
diff --git a/mercurial/windows.py b/mercurial/windows.py
--- a/mercurial/windows.py
+++ b/mercurial/windows.py
@@ -369,11 +369,14 @@ 
     If gid is None, return the name of the current group."""
     return None
 
-def removedirs(name):
+def removedirs(name, donotremovecwd=False):
     """special version of os.removedirs that does not remove symlinked
     directories or junction points if they actually contain files"""
     if osutil.listdir(name):
         return
+    cwd = os.getcwd()
+    if donotremovecwd and cwd == name:
+        return
     os.rmdir(name)
     head, tail = os.path.split(name)
     if not tail:
@@ -382,12 +385,14 @@ 
         try:
             if osutil.listdir(head):
                 return
+            if donotremovecwd and cwd == head:
+                return
             os.rmdir(head)
         except (ValueError, OSError):
             break
         head, tail = os.path.split(head)
 
-def unlinkpath(f, ignoremissing=False):
+def unlinkpath(f, ignoremissing=False, donotremovecwd=False):
     """unlink and remove the directory if it is empty"""
     try:
         unlink(f)
@@ -396,7 +401,7 @@ 
             raise
     # try removing directories that might now be empty
     try:
-        removedirs(os.path.dirname(f))
+        removedirs(os.path.dirname(f), donotremovecwd)
     except OSError:
         pass
 
diff --git a/tests/test-update-names.t b/tests/test-update-names.t
--- a/tests/test-update-names.t
+++ b/tests/test-update-names.t
@@ -72,3 +72,13 @@ 
   $ cd ..
 
 #endif
+
+Test that cwd is not deleted during update
+  $ hg init r4 && cd r4
+  $ mkdir dir
+  $ cd dir
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m "file and dir"
+  $ hg up -q null
+  $ cd .