symlinks on Windows

Dov Feldstern dfeldstern at fastimap.com
Fri Apr 25 10:35:18 CDT 2008


Georg wrote:
> Hi,
> 
> I am trying out Mercurial on Windows because some of my colleagues will 
> use it, and I noticed symlinks are turned into plain files containing 
> the path of the link target.
> 
> I understand that Windows only supports symlinks starting with Vista, so 
> in principle this behavior is "expected".
> 
> However, I am looking for a solution that makes life a little easier for 
> my Windows colleagues.  Would it be possible to create a plain copy of 
> the file in Windows instead of the file containing the link?  Maybe not 
> by default, but as an option that can be turned on if one prefers that 
> behavior.
> 
> Maybe it could be done in an extension? I'd be grateful for hints.
> 
> I could also think of a separate script to be run after the hg update 
> which turns symlink files into full copies. How would I best get the 
> list of symlinks from Hg, as input for this kind of script?
> 
> --
> Regards,
> Georg.
> 

Hi!

I would also very much like to see something like this. I raised the issue in 
the past 
(http://thread.gmane.org/gmane.comp.version-control.mercurial.devel/13579), but 
the feedback I got was not very positive --- along the lines of "if you're 
working on Windows, you shouldn't be using symlinks in your repository". 
However, I still think that this kind of support would be extremely useful to 
those unfortunate enough to be working on Windows.

I already have a plan for an extension which would provide this support (I even 
have a name for it --- SimSyms), but I just haven't had the time yet to 
implement it, nor am I familiar yet with mercurial's internals. If there's 
anyone out there who's interested in this feature, and who has some experience 
writing extensions, I'd be happy to collaborate...

The basic idea is this:

First of all, it's important to note that the extension would never touch the 
repository --- only the working directory. Any interaction with the repository 
would only happen via the working directory, and from there to the repository 
using existing mercurial commands.

Secondly, the extension will identify whether the given repository is on a 
symlink-supporting filesystem, and will only do anything if it is not.

Three commands would be added: slexpand, slcollapse and slupdate.

The workflow would be something like this (assuming we already have a clone and 
a checked-out revision on a non-symlink-supporting fs):

1. To get (optionally, only part of) the working directory to look the way it 
would on a symlink-supporting fs, we "expand" the symlinks:

slexpand [base-path] [--stop-expansion-at]

The end result of this command should be:
(a) the working directory will have all symlinks under base-path expanded, i.e., 
in place of a symlink, the working directory will contain the real contents of 
the pointed-to file or directory (as reflected in the local filesystem --- it 
doesn't matter whether that file is part of the manifest or not; specifically, a 
common situation would be links to other repositories in a forest). This 
expansion will not extend beyond any path specified in --stop-expansion-at.
(b) in the dirstate, expanded symlinks will be specially marked, so that 
mercurial will know not to identify the links as modified; most (all?) commands 
will have to treat expanded symlinks as if they are unmodified.
(c) extension-specific sldirstate: dirstate-like information for each file which 
is copied from (even if not in the original repository) and every copied file, 
which will allow to recognize whether any of these files have been modified.
(d) extension-specific persistent dictionaries: slmap and reverseslmap: the keys 
in slmap will be the full paths of each file and directory in the working 
directory which is an expanded symlink, or under an expanded symlink; the values 
will be the path to the original of that file, i.e., the path from which the 
given file was copied. reverseslmap will have as the keys each of the original 
files listed in any of the values of slmap; its values will be a list of paths 
which were copied from 'key'.

For example, given the following working directory structure before expansion:

my-repo/
         .hg/
         file1
         dir1 (identified by metadata as a symlink, pointing to ../repo2)
repo2/
       .hg/
       file2

The result after slexpand will be:

my-repo/
         .hg/
         file1
         dir1/
              file2 (contents identical to contents of ../repo2/dir1/file2)
              (.hg should probably be ignored during expansion)
repo2/
       .hg/
       file2

dirstate: my-repo/dir1 specially marked as "expanded"

sldirstate: dirstate-like information for all copied or copied-from files

slmap:
{ 'dir1' : '../repo2/dir1',
   'dir1/file2' : '../repo2/dir1/file2' }

reverseslmap:
{ '../repo2/dir1' : ['dir1'],
   '../repo2/dir1/file2' : ['dir1/file2'] }


Now work in the working directory can proceed as normal. hg add and hg remove, 
when applied to a file contained in slmap, will mark the addition/deletion in 
slmap or an associated structure, instead of in the normal place (dirstate?).

However, before any hg command is executed, one should execute (hopefully, this 
would be automatic):

slupdate

which does the following:
(a) for each key in reverseslmap: for each of the associated values: merge 
'value' into 'key' (initially, this merge can be very simplistic --- only allow 
one of the files to have been modified, abort with message if both have been 
modified). Also addition/removal, as marked previously in slmap, will be merged 
at this point.
The end result of stage (a) is that the originals (copied-from) files reflect 
the latest changes; the working directory, however, may not yet be up-to-date.
(b) for each key in slmap: if the file pointed to by value has changed, update 
'key' accordingly.
Now, all links in the working directory pointing to the same file should be 
identical with each other and with the original; additionally, because the 
originals reflect any changes made, these changes --- even if made on an 
expansion of the symlink --- will make there way to the repository upon a 
commit, will be reflected by stat, etc.

3. slcollapse will return everything to the way it was before slexpand was executed.

Open issues:

*) I haven't though through what should happen when the working directory is 
updated to a different revision. Hopefully this can be done in a smart way; 
trivially, before the update we can slcollapse, and after the update slexpand again.

I'm sure there are other things which have to be thought out, but I think that 
this should provide what we're looking for.

Any comments and implementation help would be welcome! (I myself have very 
little time at the moment to work on this...)

Dov



More information about the Mercurial-devel mailing list