[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

Re: limitations of svn:externals

From: Narayana Prasad <nprasad_at_embeddedinfotech.com>
Date: 2006-06-25 13:06:31 CEST
    I had initiated a discussion on introducing an "svn link" command some time back to solve the lack of CVS's alias capability in Subversion. I finally found some time to spare on that issue. I'm attaching a patch against 1.3.2 which achieves most of what i set out do.

The patch will allow a dummy link directory to be created which points to another directory in the same repository. Checkouts, commits, diffs, updates made to this link directory will be performed on the real directory and users of the svn client dont notice a difference if a directory is link or exists for real. I'vent finished what i set out to do in terms of user interface(i.e. i havent yet added an "svn link" command). That is only because i wanted expert reviews of this patch, before making the UI change. Its possible the fix is not elegant, but right now it solves my purpose.

After applying the patch you should be able to use the patched version on your existing svn repo. To setup a link to another directory in the repo, the following steps should be used.

$ cd "/svn/working/copy"      <- your working copy
$ mkdir link_dir              <- create a dir called "link_dir"
$ svn add link_dir            <- add it to the repo.
$ svn propset svn:link link_dir "/full/path/to/another/directory/in/the/repo"  <- set the "svn:link" property on it
$ svn ci -m "commit" link_dir <- commit the svn:link property

Once the link is setup, the next time when you checkout your working copy using svn co "URL://svn/working/copy", it will create a directory called "link_dir" and its contents will be same as "/full/path/to/another/directory/in/the/repo". Please note that the path to which you are linking should not have the "URL://" in it. It should start with a "/"(for now :(). The "svn:link" is currently supported for directories only.

I've tried simple commands like diff, commit, status(the link_dir shows up as a switch i think), tag, update. I've also run make check with the patch in place and all tests passed successfully. But i did get the following at the end of all tests. It seemed benign to me, but i'm quoting it anyways.
Running all tests in key-test...success
Running all tests in skel-test...success
Running all tests in strings-reps-test...success
At least one test was SKIPPED, checking /root/svntest/subversion-1.3.2/tests.log
SKIP:  utf8_tests.py 1: conversion of paths and logs to/from utf8
SKIP:  authz_tests.py 1: authz issue #2486 - open root
SKIP:  authz_tests.py 2: authz issue #2486 - open directory
I'd like some comments on the patch, particularly on memory leaks in the server code. I believe there is some automatic cleanup happening, but i wasnt certain. I'd also like to know if any svn commands dont behave properly on a directory that has the "svn:link" property set on it. All other directories should be unaffected and behave as before.


PS: There are some debugging statements in there within comments. If you hit issues, please email me logs with those statements commented out.

Narayana Prasad wrote:
Hi Moelle
It's a sort of "don't follow symlinks when copying, just make a copy
of the darn symlink itself" option.
It doesn't seem wildly useful since 'svn cp' is mostly used to branch
or tag, but there might be someone who wants parallel development
branches or what not, and they should have the possibility of 'raw'
svn cp.
Thats the same as not changing the link property when tagging/branching. The link will continue to point to the head revision(if that is what it was set to initially) in the tag or branch. I have no objections to such a switch.

I'm not sure what you mean by "dependent modules".
same as common code. Sorry for introducing extra terms.

There's relatively easy ways to do that.  You could:
 * Rename the property to 'svn:fixated-links' on the destination
(parent) folder.
 * Add a word for each link in the property, eg. ' fixated '.
I didnt understand the need to change the property name.

The rest of what you said was very arcane to me. Perhaps you are describing situations that i'm unable to imagine. So just to be clearer, i'd like to describe the series of actions and effects as i visualize it.


$ svn import NCdir URL/NCdir
$ svn import COdir URL/COdir
$ svn co URL/NCdir
$ svn co URL/COdir
$ svn link <PATH>/COdir NCdir/COdir    <- there is now a "real" directory(not symlink) inside NCdir which empty, which has
                                          the link property set. PATH can be relative to NCdir or absolute from repo
                                          root(if it starts with /).
$ svn commit URL/NCdir                 <- Commits the link created above into the server. The server now knows of a
                                          directory with a link property with value to <PATH>/COdir.
$ svn up NCdir                         <- Now the NCdir/COdir should be populated with files from COdir
$ svn cp NCdir URL/branches/bNCdir     <- Now there is branch in the repo call bNCdir, which contains a directory called
                                          COdir. However the link property is set to branches/bCOdir. The bCOdir is created
                                          as part of the svn cp command. There is a switch available to the cp command to
                                          skip-links if need be(perhaps the user can be thrown a dialogue to choose which                                           links to branch)
$ touch NCdir/COdir/file
$ svn add NCdir/COdir/file
$ svn status                           <- An L flag should appear against file, along with the A flag
$ svn commit URL/NCdir                 <- file should be committed to <PATH>/COdir
$ svn co URL/branches/bNCdir           <- The file committed above should NOT appear in this checkout. However, if
                                          skip-links was given during the svn cp above, then it will appear.

The gist of this is that COdir is symlink for SVN and a directory for the OS. SVN already knows how to traverse symlinks as identified by the OS. The link property will act as one such identifier.

I'm still not conversant with the command syntax. So hope there arent any mistakes.


Molle Bestefich wrote:
Russell Hind wrote:
Molle Bestefich wrote:
Narayana Prasad wrote:
But the common code must stay common and
maintain a common history.
Except in the case of 'svn cp' to branches or tags.
When you branch or tag, you want an actual repository copy of your
common files, fixated at the version that is in use when you release
(or branch).
Yes, we use externals for 'proper' externals in another repo, and for
links to common code in the same repo.  For the proper externals, on a
copy we'd like to be able to have the -r automatically added to the
external, but with the externals in the same repo, a copy would be required.

Currently we have to do this manually for all our externals when
branching for a release which isn't that usable.

Oh yeah.  Fixating real _external_ svn:externals is very useful too.
Guess I sort of skipped over that point, *whistle hum* :-).

(I fixate about 30 svn:externals with -r on a monthly basis btw, but
none of them are really external per se.)

Narayana Prasad wrote:
Except in the case of 'svn cp' to branches or tags.
When you branch or tag, you want an actual repository copy of your
common files, fixated at the version that is in use when you release
(or branch).

Sometimes (albeit more rarely) you want to do a copy where you copy
the link as a link instead of fixating it. So there should be an
option to 'svn cp' to do that too.
I understood the first behavior. But the second one isnt clear.

It's a sort of "don't follow symlinks when copying, just make a copy
of the darn symlink itself" option.
It doesn't seem wildly useful since 'svn cp' is mostly used to branch
or tag, but there might be someone who wants parallel development
branches or what not, and they should have the possibility of 'raw'
svn cp.

The more useful behavior is if "svn cp" is done recursively on all the
dependent modules. If that is what you meant by the second point, then its fine.

I'm not sure what you mean by "dependent modules".

But I agree that svn cp could recursively copy the linked-to items as
a means of fixating (instead of just copying the property which would
be the 'raw' copy method).

Only obvious thing to consider with this method is that while we'd
like to do a repository copy of the folders and files, we would also
like to pertain the property that tells us that it actually is (or
rather was) a link.

There's relatively easy ways to do that.  You could:
 * Rename the property to 'svn:fixated-links' on the destination
(parent) folder.
 * Add a word for each link in the property, eg. ' fixated '.

But here comes the hard part of the deal:

The problem arises when you realize that fixating a particular
revision has another use besides making sure that the correct versions
are referenced by your release in /tags/whatever-version.  It is very
often useful to be able to later apply changes to the fixated files. 
It's useful both for branches where active development happens, and
for tags where you might need to hotfix the common code.  And it's
actually useful for *real* svn:externals too.  And *that's* hard to
implement.  Or hard-ish to implement in a good way, at the least :-).

There are three obvious ways around this problem:
 1.) Do not support fixating real, external svn:externals.
 2.) Make a hardcopy in the repository of the external files.
 3.) Store diffs in the local repo against the files in the external repo.

2. is not pretty because it's a waste of space that 'svn cp' usually
does not do.

3. is not pretty because you don't know if they decide to 'svnadmin
obliterate' a revision and bring it back up or some such sheenanigans.
 OTOH, that's less of a concern, since they might do that anyway as
things are right now (with dump and load), in which case they would
disturb svn:externals fixated with '-r'.  (Seems the only way forward
for 'svn obliterate' and such is to support repositories where there
are holes in the revision numbers, but that's another discussion..)

I'd recommend to go with 1.), but make sure that the concept and
syntax is extensible to real svn:externals so a similar feature can be
implemented there some day.  That way you won't have the
implementation hassle right now, and your only worry would be fending
of the folks that doesn't like the fact that only internal links can
be fixated.  Also, you'd have to comfort Russell Hind :-).

3. is also a good way forward in *my* perspective, but you are
probably going to hit a wall with the real svn developers.

Blerh, what a long text.
Hope my english is understandable.
If not, read it again and try harder to decipher :-D.

Otherwise, we have 3rd variety i guess, in which case i'd like to understand
how copying the information as a link would be useful.

I hope we're talking about the same thing (copying the "link" as a "link").
 * It might be useful for parallel feature development (eg. you might
want to branch but allow common code updates).
 * If you are copying your development trunk to a new place, but you
have to do a couple of things on the old trunk before deleting it, you
can't just use 'svn mv', so you have to make an exact copy of the
trunk folder.

I guess I'd just like there to still be an 'exact copy' feature.

I'm ok with a property as well. In that case i suppose a
directory entry would be created and the property would
apply to that, correct?

Yes, the property would apply to the directories you wish to contain
your "common code" folders in.

--------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org For additional commands, e-mail: dev-help@subversion.tigris.org

diff -Naur subversion-1.3.2/subversion/include/svn_props.h sub/subversion/include/svn_props.h
--- subversion-1.3.2/subversion/include/svn_props.h 2005-10-19 00:10:02.000000000 +0530
+++ sub/subversion/include/svn_props.h 2006-06-19 23:10:35.000000000 +0530
@@ -213,6 +213,9 @@
 /** The value to force the special property to when set. */
+/** Set if the directory should be treated as a link to another directory. */
 /** Describes external items to check out into this directory.
  * The format is a series of lines, such as:
diff -Naur subversion-1.3.2/subversion/libsvn_repos/reporter.c sub/subversion/libsvn_repos/reporter.c
--- subversion-1.3.2/subversion/libsvn_repos/reporter.c 2005-07-19 21:15:30.000000000 +0530
+++ sub/subversion/libsvn_repos/reporter.c 2006-06-19 23:08:11.000000000 +0530
@@ -566,7 +566,7 @@
               const svn_fs_dirent_t *s_entry, const char *t_path,
               const svn_fs_dirent_t *t_entry, void *dir_baton,
               const char *e_path, path_info_t *info, svn_boolean_t recurse,
- apr_pool_t *pool)
+ svn_boolean_t islink, apr_pool_t *pool)
   svn_fs_root_t *s_root;
   svn_boolean_t allowed, related;
@@ -652,9 +652,18 @@
         SVN_ERR (b->editor->open_directory (e_path, dir_baton, s_rev, pool,
- SVN_ERR (b->editor->add_directory (e_path, dir_baton, NULL,
- &new_baton));
+ {
+ SVN_ERR (b->editor->add_directory (e_path, dir_baton, NULL,
+ &new_baton));
+ if (islink)
+ {
+ /*printf("DBG: linkprop = '%s'\n", t_path);*/
+ SVN_ERR (change_dir_prop(b, new_baton, SVN_PROP_LINK,
+ svn_string_create(t_path, pool),
+ pool));
+ }
+ }
       SVN_ERR (delta_dirs (b, s_rev, s_path, t_path, new_baton, e_path,
                            info ? info->start_empty : FALSE, pool));
       return b->editor->close_directory (new_baton, pool);
@@ -740,7 +749,7 @@
       SVN_ERR (update_entry (b, s_rev, s_fullpath, s_entry, t_fullpath,
                              t_entry, dir_baton, e_fullpath, info,
- b->recurse, subpool));
+ b->recurse, FALSE, subpool));
       /* Don't revisit this name in the target or source entries. */
       apr_hash_set (t_entries, name, APR_HASH_KEY_STRING, NULL);
@@ -779,6 +788,7 @@
   /* Loop over the dirents in the target. */
   for (hi = apr_hash_first (pool, t_entries); hi; hi = apr_hash_next (hi))
+ svn_boolean_t islink = FALSE;
       svn_pool_clear (subpool);
       apr_hash_this (hi, NULL, NULL, &val);
       t_entry = val;
@@ -793,9 +803,27 @@
       s_fullpath = s_entry ? svn_path_join (s_path, t_entry->name, subpool)
         : NULL;
+ if (s_fullpath == NULL)
+ {
+ svn_string_t * linkdir = NULL;
+ svn_boolean_t isdir;
+ SVN_ERR(svn_fs_is_dir (&isdir, b->t_root, t_fullpath, subpool));
+ if (isdir)
+ {
+ SVN_ERR (svn_fs_node_prop (&linkdir, b->t_root, t_fullpath,
+ "svn:link", subpool));
+ if (linkdir)
+ {
+ /*printf("DBG[%s:%d]: linkdir = %s -> %s\n", __FILE__, __LINE__,
+ t_fullpath, linkdir->data);*/
+ t_fullpath = linkdir->data;
+ islink = TRUE;
+ }
+ }
+ }
       SVN_ERR (update_entry (b, s_rev, s_fullpath, s_entry, t_fullpath,
                              t_entry, dir_baton, e_fullpath, NULL,
- b->recurse, subpool));
+ b->recurse, islink, subpool));
   /* Destroy iteration subpool. */
@@ -854,7 +882,7 @@
     SVN_ERR (update_entry (b, s_rev, s_fullpath, s_entry, b->t_path,
                            t_entry, root_baton, b->s_operand, info,
- TRUE, pool));
+ TRUE, FALSE, pool));
   SVN_ERR (b->editor->close_directory (root_baton, pool));
   SVN_ERR (b->editor->close_edit (b->edit_baton, pool));
diff -Naur subversion-1.3.2/subversion/libsvn_wc/update_editor.c sub/subversion/libsvn_wc/update_editor.c
--- subversion-1.3.2/subversion/libsvn_wc/update_editor.c 2005-12-13 04:29:56.000000000 +0530
+++ sub/subversion/libsvn_wc/update_editor.c 2006-06-19 23:07:56.000000000 +0530
@@ -369,6 +369,7 @@
   apr_pool_t *subpool;
   svn_wc_entry_t *current_entry;
   const char *name;
+ const svn_string_t *link_path;
   /* If this is the root directory and there is a target, we can't
      mark this directory complete. */
@@ -387,6 +388,8 @@
                               svn_path_local_style (path, pool));
   entry->incomplete = FALSE;
+ SVN_ERR (svn_wc_prop_get (&link_path, SVN_PROP_LINK, path, adm_access, pool));
   /* Remove any deleted or missing entries. */
   subpool = svn_pool_create (pool);
   for (hi = apr_hash_first (pool, entries); hi; hi = apr_hash_next (hi))
@@ -398,6 +401,28 @@
       apr_hash_this (hi, &key, NULL, &val);
       name = key;
       current_entry = val;
+ if (link_path && current_entry->url != NULL)
+ {
+ const char * newurl = NULL;
+ if (current_entry->repos != NULL)
+ {
+ newurl = svn_path_join(current_entry->repos,
+ link_path->data[0] == '/' ?
+ &link_path->data[1] :
+ link_path->data, pool);
+ }
+ else
+ newurl = link_path->data;
+ newurl = svn_path_join(newurl, current_entry->name, pool);
+ if (strcmp(current_entry->url, newurl) != 0)
+ {
+ /*printf("DBG[%s:%d]: '%s' URL Change %s -> %s\n",
+ __FILE__, __LINE__, current_entry->name,
+ current_entry->url, newurl);*/
+ current_entry->url = newurl;
+ }
+ }
       /* Any entry still marked as deleted (and not schedule add) can now
          be removed -- if it wasn't undeleted by the update, then it
@@ -1204,6 +1229,15 @@
   propchange->name = apr_pstrdup (db->pool, name);
   propchange->value = value ? svn_string_dup (value, db->pool) : NULL;
+ if (strcmp (propchange->name, SVN_PROP_LINK) == 0)
+ {
+ /*printf("DBG[%s:%d]: Old URL %s\n", __FILE__, __LINE__, db->new_URL);*/
+ db->new_URL = svn_path_join(db->edit_baton->repos,
+ propchange->value->data[0] == '/' ?
+ &propchange->value->data[1] :
+ propchange->value->data, pool);
+ /*printf("DBG[%s:%d]: New URL %s\n", __FILE__, __LINE__, db->new_URL);*/
+ }
   return SVN_NO_ERROR;

To unsubscribe, e-mail: dev-unsubscribe@subversion.tigris.org
For additional commands, e-mail: dev-help@subversion.tigris.org
Received on Sun Jun 25 13:00:30 2006

This is an archived mail posted to the Subversion Dev mailing list.