From WhyNotWiki
(Difference between revisions)
Jump to: navigation, search
(How do I create a new repository for use by my user only?: Talked more about spaces in a path)
m (How do I create a new repository for use by my user only?: Wrong path)
Line 224: Line 224:
Internally, however, it URL-encodes characters such as space; so space is represented as '%20'.
Internally, however, it URL-encodes characters such as space; so space is represented as '%20'.
~/Projects/Some project > svn info
~/Projects/Some project/dev > svn info
Path: .
Path: .
URL: file:///home/tyler/Projects/Some%20project/svn_repository
URL: file:///home/tyler/Projects/Some%20project/svn_repository
Repository Root: file:///home/tyler/Projects/Some%20project/svn_repository
Repository Root: file:///home/tyler/Projects/Some%20project/svn_repository

Revision as of 19:35, 31 July 2009

Subversion  edit   (Category  edit)



Cheat sheet


svn:ignore     - A newline separated list of file patterns to ignore.
> svn propedit svn:ignore .

# Or, if you're sure you're not wiping out an existing svn:ignore attribute, just do
> svn propset svn:ignore "tmp" .

# How do you set it to ignore multiple things using a single propset command? I don't think you can. These don't work:
> svn propset svn:ignore "tmp log config"` .
> svn propset svn:ignore "tmp\nlog\nconfig" .
> svn propset svn:ignore `echo -e "tmp\nlog\nconfig"` .

# But you can do this:
> echo -e "tmp\nlog\nconfig" > t; svn propset svn:ignore -F t .; rm t
> echo -e "tmp\nlog\nconfig" > t; svn propset svn:ignore --file t .; rm t


From Agile Web Development with Rails (2nd ed).2.pdf:

> svn propset svn:externals "rails" vendor

This tells Subversion that the directory vendor/rails actually comes from a remote repository. Type

> svn up vendor

and you'll see Rails being installed into your application. From now on, every time you run svn up you'll update both your application's code and you'll pick up any changes to Rails.

project/vendor/plugins $ svn propset svn:externals "tyler_shared" .

To clear the svn:externals property:

svn prodel svn:externals vendor

svn export

"Destination directory exists" -- Should I force it?


> svn export
svn: Destination directory exists; please remove the directory or use --force to overwrite
svn: 'test' already exists

You might be concerned that if you force it, it will cause you to lose all of the existing contents of your test/ directory. Never fear, you won't lose anything as long as long as you don't have any files named the same as they are in the directory you are exporting. If all the filenames are different then it will simply result in a merge of {the contents of your directory before the export} and {the contents of {the contents of the repository directory that you are exporting}.

Even if you do overwrite some files, that shouldn't be a problem if they are under version control and you don't have any local modifications that you're afraid of losing — just do an svn diff or svn status afterwards to find out what was overwritten. (If your directory is not already under version control or you do have local modifications, then you may want to be a bit more cautious...)

> ll test
total 16
-rw-rw-r-- 1 tyler tyler 1016 May 14 16:03 label_test.rb
-rw-rw-r-- 1 tyler tyler  461 May  4 12:17 link_test.rb
-rw-rw-r-- 1 tyler tyler  492 May  4 12:17 popup_test.rb
-rw-rw-r-- 1 tyler tyler  217 May 14 16:01 test_helper.rb

> svn export --force
A    test
A    test/test_helper.rb
A    test/rails_root
A    test/rails_root/config
A    test/rails_root/config/database.yml
A    test/rails_root/vendor
A    test/rails_root/vendor/plugins
A    test/rails_root/vendor/plugins/plugin_name
A    test/rails_root/vendor/plugins/plugin_name/init.rb
Exported revision 2945.

> ll test
total 20
-rw-rw-r-- 1 tyler tyler 1016 May 14 16:03 label_test.rb
-rw-rw-r-- 1 tyler tyler  461 May  4 12:17 link_test.rb
-rw-rw-r-- 1 tyler tyler  492 May  4 12:17 popup_test.rb
drwxrwxr-x 4 tyler tyler 4096 May 14 16:09 rails_root/        # <- new
-rw-rw-r-- 1 tyler tyler  314 May  4 13:01 test_helper.rb     # <- changed

If you are at all concerned about overwriting files in the destination directory, then you are advised to export to a different directory and then merge the two directories manually...

> svn export test.exported
> mv test.exported/one_file test/at_a_time

Helper scripts/tools -- an excellent wrapper for the svn command that adds a couple new features and subcommands

Commit mailers

Provided by Subversion itself produce a GraphViz .dot graph for the branch history of a node

Reference links


Sometimes it is useful to construct a working copy that is made out of a number of different checkouts. For example, you may want different subdirectories to come from different locations in a repository, or perhaps from different repositories altogether. You could certainly setup such a scenario by hand—using svn checkout to create the sort of nested working copy structure you are trying to achieve. But if this layout is important for everyone who uses your repository, every other user will need to perform the same checkout operations that you did.
Fortunately, Subversion provides support for externals definitions. An externals definition is a mapping of a local directory to the URL—and possibly a particular revision—of a versioned resource.
Subversion still only truly operates on non-disjoint working copies. So, for example, if you want to commit changes that you've made in one or more of those external working copies, you must run svn commit explicitly on those working copies—committing on the primary working copy will not recurse into any external ones.

Gotcha: OLD directories may appear

Sometimes it will create directories named my_external.OLD (or similar) where my_external is the name of the external.

I think this happens when I have local modifications in my external working copy and do an update after someone else has committed changes to the target of that external. Rather than trying to merge, Subversion cowardly just renames your directory to make room for a clean export of the external.


Gotcha: directory must be committed, not local

If you have a working directory that is scheduled for addition but hasn't been added yet, and then you svn propedit svn:externals, and then you svn update, you may find that it doesn't work.

If you try it from within the directory that you addded the svn:external property to, you'll get this:

[tyler: /whatever/doc]> svn st
A      .
[tyler: /whatever/doc]> svn up
svn: REPORT request failed on '/!svn/vcc/default'
svn: Two top-level reports with no target

If you go up one level, it won't give an error, but neither will it pull in those changes.

[tyler: /whatever/]> svn up doc
At revision 26.

Workaround: Do an intermediate commit. Then try updating.

Apparently it's not a bug [1]:

If the schedule-add directory is either the explicit anchor or explicit

target of an update, then I think we should print an error. It's nonsensical to try to update something that only exists in a working copy.

Questions (How do I...?)

(Basics) How do I check out a working copy of a repository?

svn checkout http://host/path/RepositoryName

How do I create a new repository for use by my user only?

~/path/to/project > svnadmin create svn_repository

But keep in mind that the repository is stored in a separate directory from your working tree (or "checkout") of that repository. (This is different from git, where there is no central repository and everyone's working tree also contains its own copy of the repository under a subdirectory .git.) So you still need to check out the repository you just created.

I like to organize everything by project and often will have both the repository and my checkout of that repository as subdirectories within the same directory. The repository I call svn_repository and my checkout of that repository I call dev (for "development").

I don't know if there's a way to specify a relative path when you do a checkout (even though relative paths work fine with svnadmin create); this is the error I get when I try to:

~/path/to/project > svn co file://./svn_repository/ dev
svn: Unable to open an ra_local session to URL
svn: Local URL 'file://svn_repository' contains only a hostname, no path

So you'll have to specify an absolute path (and issue a svn switch [relocate?] that path later changes):

~/path/to/project > svn checkout "file:///home/tyler/path/to/project/svn_repository/" dev
Checked out revision 0.

As you can see, you can omit the hostname if you have an absolute path that begins with a '/'.

Also, spaces are allowed but you'll need to enclose them in quotes (or equivalent method) so that your shell passes that as a single argument.

~/Projects/Some project > svn checkout "file:///home/tyler/Projects/Some project/svn_repository/" dev
Checked out revision 0.

Internally, however, it URL-encodes characters such as space; so space is represented as '%20'.

~/Projects/Some project/dev > svn info
Path: .
URL: file:///home/tyler/Projects/Some%20project/svn_repository
Repository Root: file:///home/tyler/Projects/Some%20project/svn_repository

How do I import a dump file into a new repository for use by my user only?

If you received a dump file of someone else's repository, you can just as easily import it and then check it out:

~/path/to/project > svnadmin create svn_repository

~/path/to/project > svnadmin load svn_repository < my_project.svn_dump
<<< Started new transaction, based on original revision 1
------- Committed revision 1 >>>

~/path/to/project > svn checkout "file:///home/tyler/path/to/project/svn_repository/" dev
A    dev/include
Checked out revision 56.

How do I create a new repository on a server?

Something like this perhaps:

$ sudo svnadmin create /var/www/svn/RepositoryName

$ sudo chown -R apache:apache /var/www/svn/RepositoryName

How do I create a new svn user on a server?

$ sudo htpasswd /etc/httpd/svnpasswd username
New password:
Re-type new password:
Adding password for user username

where username is the name they will use to log in to connect to Subversion.

(Uncommon use case) How do I move directory a/sub_dir up a level to take the place of its parent directory a?

Why would you want to do this? Maybe sub_dir is the only subdirectory of a, and you decide it's useless to have that extra level in the hierarchy so you want to promote the subdirectory up one level.

It would be a simple matter if you just wanted to svn mv a/sub_dir ./, but you don't, because then you would have a/ and sub_dir/ as sibling directories, a/ being empty (useless) and sub_dir/ being misnamed/ambiguous (no longer having the scope information from the a/ path segment to clue you in to the scope).

In other words, is there a more concise way to do this?:

svn mv a/sub_dir a_new
svn rm a
svn ci -m 'Moving a/sub_dir to a/ (step 1 of 2)' a a_new
svn mv a_new a
svn ci -m 'Moving a/sub_dir to a/ (step 2 of 2)' a a_new

You might try doing this:

svn mv a/sub_dir a

...but then it thinks you want to move sub_dir into a/ -- and that's already where it is!

You will just get this error if you try it:

svn: Cannot move path 'a/sub_dir' into itself

So I guess we're stuck with doing a 5-step process for what should simply be a 2-stop process. Oh well. This doesn't come up all to frequently, I suppose...

How do I have a working copy directory that is a combination of several different repositories?

You can't, using plain old Subversion. Each directory on your file system can be a working copy for one and only one repository.

However, it appears that a nifty utility called 'layered Subversion' (laysvn) will let you achieve this.

Create two directories. One for the real svn checkouts (I called it 'storage', you can place it anywhere, and one directory for your working copy. Inside the working copy directory, create a dir named .laysvn, in there make a new file called laysvn.xml with contents following this schema:

<config title="LaySVN test">
  <storage path="/path/to/where/your/storage/dir/is" />
  <layer id="base" source="http://subversionserver/repository/layers/base" />
  <layer id="dns" source="http://subversionserver/repository/layers/dns" />
  <layer id="apache" source="http://subversionserver/repository/layers/apache" />
  <layer id="hostname" source="http://subversionserver/repository/layers/hostname" />

Now you can run laysvn in this directory, it will checkout your layers, and then copy the files from the layers into your working dir! To check in your changes, you have to chdir to your storage/layer directory and run 'svn ci' there. This is also where you can resolve conflicts.

How do I tell Subversion to ignore a file?

svn propedit svn:ignore DirName

Then type in the file names to ignore (wildcards are allowed) and save that file. (One filename per line if you want to ignore multiple patterns.)

You will need to commit this property change if you want it to affect all users of the repository.

No, that property is not inherited by subdirectories. Yes, that is unfortunate.

How do I tell Subversion to globally ignore a file pattern from being listed in svn status (for all repositories)?

Global for one user:


### Set global-ignores to a set of whitespace-delimited globs
### which Subversion will ignore in its 'status' output.
# global-ignores = *.o *.lo *.la #*# .*.rej *.rej .*~ *~ .#* .DS_Store

Globally for all users:

Edit the file /etc/subversion/config:

global-ignores=*.pyc *.swp *.swo *.tmp *.*~ # etc.

Changes take place immediately.

How do I keep informed of changes made to our repositories?

Create a post-commit hook and put some kind of diff-mailer program in the hook script.





echo "Just committed '$REPOS' revision '$REV'" >> $REPOS/hooks/post-commit.log

previous_revision=`expr $REV - 1`
skip_commit_notification=`svnlook propget $REPOS --revprop svn:skip_commit_notification_for_next_commit -r $previous_revision`
echo "svnlook propget dir --revprop svn:skip_commit_notification_for_next_commit -r $previous_revision returned '$skip_commit_notification'" >> /var/w

if [[ $skip_commit_notification == 'true' ]]; then
    # Skipping
    echo "Skipped notification" >> $REPOS/hooks/post-commit.log
    svnnotify \
        --repos-path $REPOS \
        --revision $REV \
        --subject-prefix "[your repository name]" \
        --revision-url 'http://code/?rev=%s' \
        --to \
        --handler HTML::ColorDiff \
        --subject-cx \
        --with-diff \
        --author-url 'mailto:%s' \
        --footer "Powered by SVN-Notify <>" \
        --max-diff-length 1000
    echo "Sent notification" >> $REPOS/hooks/post-commit.log

How do "unadd" something that I've added but not yet committed?

svn revert

"Won't that delete it from my working copy too?" No, it won't. You're safe.

"What about local changes I've made since doing the svn add? Will I lose those changes?" No. It's a little bit confusing since that's what would happen if it was already in the repository and you made changes to it (had an M as the status), but if it has an A, the most damage an svn revert will do is to cause it to not be scheduled for addition any longer...

What if I change which URL my working copy uses?

No problem. svn switch.

switch (sw): Update the working copy to a different URL.
usage: 1. switch URL [PATH]
       2. switch --relocate FROM TO [PATH...]

  1. Update the working copy to mirror a new URL within the repository.
     This behaviour is similar to 'svn update', and is the way to
     move a working copy to a branch or tag within the same repository.

  2. Rewrite working copy URL metadata to reflect a syntactic change only.
     This is used when repository's root URL changes (such as a scheme
     or hostname change) but your working copy still reflects the same
     directory within the same repository.

Changing root URL (domain, etc.) of repository

svn switch --relocate

Changing to different path within same repository

Suppose svn info reports that the repository URL is http://svn/svn/wiki/web but it's been renamed (by another user using another working copy, let's say) to http://svn/svn/wiki/trunk . How would you update your working copy to reflect this?

This worked:

svn switch http://svn/svn/wiki/trunk


How do I rename a repository (and have your working copy still work when you're done)?

Here's a simple example that I did on a test repository...

Creating the test repository (you can skip this step if you already have a real repository)

~/svn $ sudo svnadmin create /var/www/svn/svntest

~/svn $ svn co http://svn/svn/svntest
Checked out revision 0.

~/svn $ sudo svn mkdir -m "Initial directories" http://svn/svn/svntest/trunk http://svn/svn/svntest/cows
svn: MKACTIVITY of '/svn/svntest/!svn/act/f0042fb8-ea13-0410-bd81-bbd14e186309': 500 Internal Server Error (http://svn)

~/svn $ sudo svn mkdir -m "Initial directories" file:///var/www/svn/svntest/cows file:///var/www/svn/svntest/trunk
Committed revision 1.

~/svn $ svn up svntest/
A    svntest/trunk
A    svntest/cows
Updated to revision 1.

~/svn $ cd svntest/

The actual rename

~/svn/svntest $ sudo mv /var/www/svn/svntest /var/www/svn/mysvntest

How to fix your working copy

~/svn/svntest $ svn up
svn: PROPFIND request failed on '/svn/svntest'
svn: Could not open the requested SVN filesystem

~/svn/svntest $ svn switch http://svn/svn/mysvntest
svn: PROPFIND request failed on '/svn/svntest'
svn: Could not open the requested SVN filesystem

~/svn/svntest $ svn switch --relocate http://svn/svn/svntest http://svn/svn/mysvntest   # <----

~/svn/svntest $ svn up
At revision 1.

How do I remove a file from the repository without removing it from the working copy?

Obviously you can't just svn rm it because that removes it from the working copy.

When would you want to do this? Perhaps with a temp directory or config file or something that you accidentally added but now have decided doesn't belong in the repository but you still need to retain the file/directory (perhaps it contains live session data or a live configuration file).

I haven't yet found an easy/atomic way to do this.

But you can do it in a few steps:

  1. Create backup copy. cp -R dir dir.backup
  2. Delete .svn directories from dir.backup
  3. svn rm dir
  4. mv dir.backup dir

Can I change my log message after it's committed?


If you made a mistake in a log message for a particular revision and want to change it, use --revprop and set svn:log to the new log message:
$ svn propset --revprop -r 25 svn:log "Journaled about trip to New York."
property 'svn:log' set on repository revision '25'


You need to create a pre-revprop-change hook for the repository. I find that their template file for this works nicely:

# WARNING: unlike other hooks, this hook MUST exist for revision
# properties to be changed.  If the hook does not exist, Subversion
# will behave as if the hook were present, but failed.  The reason
# for this is that revision properties are UNVERSIONED, meaning that
# a successful propchange is destructive;  the old value is gone
# forever.  We recommend the hook back up the old value somewhere.

> sudo cp /var/www/svn/.../hooks/pre-revprop-change.tmpl  /var/www/svn/.../hooks/pre-revprop-change
> sudo chmod a+x /var/www/svn/.../hooks/pre-revprop-change

Or to add the ability to skip notification for certain commits:


echo "Attempt to change '$PROPNAME' for '$REPOS' revision '$REV'" >> /var/www/svn/code/hooks/pre-revprop-change.log
if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
if [ "$PROPNAME" = "svn:skip_commit_notification_for_next_commit" ]; then exit 0; fi

echo "Failed!" >> /var/www/svn/code/hooks/pre-revprop-change.log
echo "Changing revision properties other than those listed in $0 is prohibited" >&2
exit 1

Can I change the author after it's been committed?


How are credentials cached?

Under your user's home directory, under ~/.subversion/auth/svn.simple/.

Oh yeah, and your password is in plain-text. Don't worry, though.

The auth/ caching area is permission-protected so that only the user (owner) can read data from it, not the world at large. The operating system's own file permissions are protecting the password.

$ cat ~somebody/.subversion/auth/svn.simple/2d01559fa5e48db39f96392824b23572
cat: /home/somebody/.subversion/auth/svn.simple/2d01559fa5e48db39f96392824b23572: Permission denied

Problem: Two svn users sharing the same Linux account

The problem is, when user A commits something, it saves A's credentials. Then when B commits something, it will say that A is the author.

You can get around this by telling Subversion to use a different username, when you start your session:

svn command --username MyName

(refer to Switches)

It will prompt you for your password and then cache it... so the next person to use this account needs to remember to do that too!

Can you just disable authenticating caching altogether for that Linux account? Yes.

~/.subversion/config :

### Section for authentication and authorization customizations.
### Set store-passwords to 'no' to avoid storing passwords in the
### auth/ area of your config directory.  It defaults to 'yes'.
### Note that this option only prevents saving of *new* passwords;
### it doesn't invalidate existing passwords.  (To do that, remove
### the cache files by hand as described in the Subversion book.)
# store-passwords = no
### Set store-auth-creds to 'no' to avoid storing any subversion
### credentials in the auth/ area of your config directory.
### It defaults to 'yes'.  Note that this option only prevents
### saving of *new* credentials;  it doesn't invalidate existing
### caches.  (To do that, remove the cache files by hand.)
store-auth-creds = no

How do I get it to ignore externals

svn st --ignore-externals

How do I follow changes to a remote repository?

If it were your own repository, you could set up a post-commit hook and just add yourself to the commit-watchers list. But with external repositories, you usually don't have that option. Wouldn't it be great if you could get an RSS feed of the changes in someone's public repository? Well, you can!

RSSin' Your SVN ( (2007-01-21). Retrieved on 2007-02-15 22:28.

How do you know when a remote repository is updated? You need to know about changes to sync (read: update). Heaven forbid we miss a killer new feature. You can follow the blogs, or check the Rubyforge, or grab the Trac feed, but sometimes none of these things are available. Sometimes all you really have is the repository.

I'm trying to look at an old version of my file, but svn says something about "path not found". What's going on?

Subversion FAQ ( Retrieved on 2007-03-05 17:11.

I'm trying to look at an old version of my file, but svn says something about "path not found". What's going on? A nice feature of Subversion is that the repository understands copies and renames, and preserves the historical connections. For example, if you copy /trunk to /branches/mybranch, then the repository understands that every file in the branch has a "predecessor" in the trunk. Running svn log --verbose will show you the historical copy, so you can see the rename: r7932 | joe | 2003-12-03 17:54:02 -0600 (Wed, 03 Dec 2003) | 1 line Changed paths: A /branches/mybranch (from /trunk:7931) Unfortunately, while the repository is aware of copies and renames, almost all the svn client subcommands are not aware. Commands like svn diff, svn merge, and svn cat ought to understand and follow renames, but don't yet do this. It's scheduled as post-1.0 feature, currently issue #1093. For example, if you ask svn diff to compare two earlier versions of /branches/mybranch/foo.c, the command will not automatically understand that the task actually requires comparing two versions of /trunk/foo.c, due to the rename. Instead, you'll see an error about how the branch-path doesn't exist in the earlier revisions.

It looks like issue 1093 was closed in 2004, so maybe this is no longer an issue?

Example of what is/isn't still an issue:

[tyler: ~/code/gemables]> /usr/bin/svn log -v -r 2279
r2279 | tyler | 2007-02-08 15:42:57 -0800 (Thu, 08 Feb 2007) | 1 line
Changed paths:
   A /gemables/subversion

Starting work on a Subversion gemable

[tyler: ~/code/gemables]> /usr/bin/svn log -v -r 2345
r2345 | tyler | 2007-03-02 12:07:48 -0800 (Fri, 02 Mar 2007) | 1 line
Changed paths:
   D /gemables/subversion
   A /gemables/svn-command (from /gemables/subversion:2342)
   R /gemables/svn-command/README (from /gemables/subversion/README:2344)
   R /gemables/svn-command/Rakefile (from /gemables/subversion/Rakefile:2344)
   R /gemables/svn-command/agiledox.txt (from /gemables/subversion/agiledox.txt:2344)
   R /gemables/svn-command/bin (from /gemables/subversion/bin:2344)
   R /gemables/svn-command/doc_include (from /gemables/subversion/doc_include:2344)
   R /gemables/svn-command/lib (from /gemables/subversion/lib:2344)
   R /gemables/svn-command/pkg (from /gemables/subversion/pkg:2344)
   R /gemables/svn-command/ruby_subversion.rb (from /gemables/subversion/ruby_subversion.rb:2344)
   R /gemables/svn-command/tasks (from /gemables/subversion/tasks:2344)
   R /gemables/svn-command/test (from /gemables/subversion/test:2344)

Renaming to svn-command

[tyler: ~/code/gemables]> /usr/bin/svn log -v -r 2354
r2354 | tyler | 2007-03-05 12:30:39 -0800 (Mon, 05 Mar 2007) | 1 line
Changed paths:
   M /gemables/svn-command/lib/svn_command.rb
   M /gemables/svn-command/test/svn_command_test.rb

each_unadded now does a preview automatically (no need to press p)

> /usr/bin/svn diff --diff-cmd colordiff
svn: '' was not found in the repository at revision 2354

> /usr/bin/svn diff -r 2279:2354 --diff-cmd colordiff                 

              svn: REPORT request failed on '/!svn/bc/2354/gemables/subversion'
svn: '/!svn/bc/2354/gemables/subversion' path not found

[tyler: ~/code/gemables]> /usr/bin/svn diff subversion -r 2279:2354 --diff-cmd colordiff
svn: 'subversion' is not under version control

These work:

> /usr/bin/svn diff --diff-cmd colordiff

> /usr/bin/svn diff -r 2279:2354 --diff-cmd colordiff

[tyler: ~/code/gemables]> /usr/bin/svn diff svn-command -r 2279:2354 --diff-cmd colordiff

Similarly with svn log:

[tyler: ~/code/gemables]> /usr/bin/svn log -v -r 2345
... [works]

[tyler: ~/code/gemables]> /usr/bin/svn log -v -r 2345 svn-command
... [works]

[tyler: ~/code/gemables]> /usr/bin/svn log -v -r 2345 subversion
svn: 'subversion' is not under version control

> /usr/bin/svn log -v -r 2345
r2345 | tyler | 2007-03-02 12:07:48 -0800 (Fri, 02 Mar 2007) | 1 line
... [works]

> /usr/bin/svn log -v -r 2345
svn: REPORT request failed on '/!svn/bc/2345/gemables/subversion'
svn: '/!svn/bc/2345/gemables/subversion' path not found

How do I import part of repository A into repository B with full revision history intact?

See Subversion / Dump and loading

How do I compare the contents of two directories/branches?

See Subversion / How do I compare the contents of two directories/branches?


Problem: an svn:externals directory A/B in repository A doesn't have your latest changes if you make those changes in another working copy of B

Concrete example: WC ~/svn/rails_backend has svn:externals directory ~/svn/rails_backend/lib/shared which links to ~/svn/shared .

But suppose you change the API of a class in ~/svn/shared and you want to fix all references to that class in ~/svn/rails_backend before you commit ~/svn/shared. That is, you want to integrate your changes to both working copies and then commit only when you've fixed it in both places. That's a problem.

Solution: change your svn:externals directory into a plain old symlink during development??

In our example, that means rm -rf ~/svn/rails_backend/lib/shared; ln -s ~/svn/shared ~/svn/rails_backend/lib/shared

Problem: You make changes to working copy A and an svn:externals directory A/B (which links to another path in repository A) and you want to commit it all at once.

But if you commit from within A (and not within B), the commit will exclude the changes made to A/B. If you are within A/B when you commit, it will only commit your changes to A/B (obviously).

Let's the layout is this:

  • A
    • Subproject B
      • shared -> svn:externals to A/shared
    • Subproject C
      • shared -> svn:externals to A/shared
    • shared

If you have changed A/B and A/B/shared, you can't just go up to A and commit A/B, because A/B/shared won't be commited then.

You could get it to work if you temporarily removed A/B/shared during development and symlinked it to A/shared. Then your changes would be made to A/shared yet still show up in A/B/shared. And when you committed A, A/shared would be committed too because it is not svn:external.

[Caveat/reminder] Can't copy/move things from a working copy like you can in a normal directory

If you treat your working copy directory like a "normal" directory, you may notice some strange behavior.

You copied/moved a directory from your WC to a different WC?
It will still think it's a WC of the old repository.
Solution:svn cp/svn mv if copying/moving within the same repository; svn export otherwise
You copied/moved a directory from your WC to a non-WC directory?
It will still think it's a WC of the old repository. And you'll have .svn directories that you don't want.
Solution: svn export
You moved a file from your WC to somewhere else?
It will show up as missing (!) when you do a svn st in the original repository.
Solution: follow up with a svn rm
You copied a file from your WC to somewhere else?
Okay, that works.

Doing an external to a foreign repository has disadvantages

Among them:

  • speed -- you don't have to access the foreign repository every time you do an svn update
  • reliability/dependency on external hosts -- if the external host goes down, svn update will hang (you can use the --skip-externals option, but then none of your other externals would get updated either)
  • unless you tie it to a certain revision, you will always be grabbing the bleeding edge latest version from the developer -- risky!

Doing an export of that foreign repository location also has its share of problems:

  • there's no record kept of what revision you've exported (unless you keep those records manually)
  • there's no record kept even of what URL you exported from!
  • updating is a pain, mostly due to the previous 2 reasons (you can commit the exported directories to your own repository, but svn update will not automatically re-export your exported directories from the original location and get the latest--you'd have to manually re-export)

Solution: Piston

Understand mixed-revision working copies Retrieved on 2007-04-25 11:08.

Your working copy's directories and files can be at different "working" revisions: this is a deliberate feature which allows you to mix and match older versions of things with newer ones. But there are few facts you must be aware of:

  1. After every svn commit, your working copy has mixed revisions. The things you just committed are now at the HEAD revision, and everything else is at an older revision.
  2. Certain commits are disallowed:
    • You cannot commit the deletion of a file or directory which doesn't have a working revision of HEAD.
    • You cannot commit a property change to a directory which doesn't have a working revision of HEAD.
  3. svn update will bring your entire working copy to one working revision, and is the typical solution to the problems mentioned in point #2.
Book reference: The limitation of mixed revisions.

Be patient with large files Retrieved on 2007-04-25 11:08.

A nice feature of Subversion is that by design, there is no limit to the size of files it can handle. Files are sent "streamily" in both directions between Subversion client and server, using a small, constant amount of memory on each side of the network. Of course, there are a number of practical issues to consider. While there's no need to worry about files in the kilobyte-sized range (e.g. typical source-code files), committing larger files can take a tremendous amount of both time and space (e.g. files that are dozens or hundreds of megabytes large.) To begin with, remember that your Subversion working copy stores pristine copies of all version-controlled files in the .svn/text-base/ area. This means that your working copy takes up at least twice as much disk space as the original dataset. Beyond that, the Subversion client follows a (currently unadjustable) algorithm for committing files:

  • Copies the file to .svn/tmp/ (can take a while, and temporarily uses extra disk space))
  • Performs a binary diff between the tmpfile and the pristine copy, or between the tmpfile and an empty-file if newly added. (can take a very long time to compute, even though only a small amount of data might ultimately be sent over the network)
  • Sends the diff to the server, then moves the tmpfile into .svn/text-base/
So while there's no theoretical limit to the size of your files, you'll need to be aware that very large files may require quite a bit of patient waiting while your client chugs away. You can rest assured, however, that unlike CVS, your large files won't incapacitate the server or affect other users.

Work around commands that don't understand copies/renames Retrieved on 2007-04-25 11:08.

When a file or directory is copied or renamed, the Subversion repository tracks that history. Unfortunately in Subversion 1.0, the only client subcommand which actually takes advantage of this feature is svn log. A number of other commands (such as svn diff and svn cat) ought to be automatically following rename-history, but aren't doing so yet. In all of these cases, a basic workaround is to use 'svn log -v' to discover the proper path within the older revision. For example, suppose you copied /trunk to /branches/mybranch in revision 200, and then committed some changes to /branches/mybranch/foo.c in subsequent revisions. Now you'd like to compare revisions 80 and 250 of the file. If you have a working copy of the branch and run svn diff -r80:250 foo.c, you'll see an error about /branches/mybranch/foo.c not existing in revision 80. To remedy, you would run svn log -v on your branch or file to discover that it was named /trunk/foo.c prior to revision 200, and then compare the two URLs directly:

   $ svn diff http://.../trunk/foo.c@80 \

My repository doesn't seem to have a UUID!,Philip Martin,2003-10-19 14:33:50 CEST

Even if I do a fresh checkout, there seem to be repositories which don't have an uuid. Why is that?

I would guess the repositories were created before the UUID code existed. A dump/load cycle will create a UUID if one does not already exist.,Philip Martin,

Yes, that's definitely a bug. I note that ra_local and ra_dav behave differently, and that for ra_local revision 0 behaves differently from the other revisions. Care to raise an issue?,SteveKing,

Issue filed as number 1557.

This bug seems to still exist, at least in the version of Subversion used on [Dreamhost (category)]...

Here is evidence: This is from a repository I have hosted there:

 > svn info
Path: .
Repository Root:
Repository UUID: 1bae183a-7f2a-0410-9965-889560e31888
Revision: 17
Node Kind: directory
Schedule: normal
Last Changed Author: tyler
Last Changed Rev: 17
Last Changed Date: 2007-03-21 11:34:31 -0700 (Wed, 21 Mar 2007)

PROPFIND of '...': 501 Not Implemented


> svn co
svn: PROPFIND request failed on '/svn/trunk/vendor/plugins/foreign_key_migrations'
svn: PROPFIND of '/svn/trunk/vendor/plugins/foreign_key_migrations': 501 Not Implemented (

I could not find any much relevant/useful information about this. (

The most likely reason for this that I could find was that they are using an old (1.1) Subversion server, and I was accessing it with too new of a client (tried it with command-line client 1.2.3 and 1.4.4, and TortoiseSvn)... but that still doesn't make a lot of sense, because I thought the clients were backwards-compatible with older servers...

Anyway, here's what I did find out...

I was under the impression that was a valid Subversion repository/path URL. I was able to go there in my web browser and browse the repository and browse the source, which served to further confirm my theory. (The output on that web page was "Revision 271: /svn/trunk/vendor/plugins/foreign_key_migrations/ ...", which is not something that Apache would display if it were just a normal directory listing...)

Anyway, I'm not sure how they have their web server configured or why, but it looks like you have to use one URL when accessing the repository via HTTP and a different URL when accessing from a Subversion client... Retrieved on 2007-05-11 11:18.

To install using SVN:

ruby script/plugin install svn://

or using HTTP:

ruby script/install

Complaints / Wish list

If you change the indent (beginning-of-line whitespace), it treats it as a changed line

So if you change the indent of every line on the file, you lose any useful diffing information; the whole file basically looks like it was replaced with entirely new content, which is not the case.

It is useless mostly because it does line based diffing. If it could identify the characterwise differences on each line, we would be in better shape.

Copying/moving files to different repositories should work better than it does

Currently it will not give you any error/warning when you do the svn mv/cp but will throw an error when you try to commit.

Higher-level usage / Best practices

When should I commit?

Depends on the policies of the particular repository.

Usually, it's good to commit pretty frequently so that you can do frequent integration with other developers (and find out about conflicts sooner).

On the other hand, you shouldn't commit anything that's not "ready" yet. In particular, you should not commit buggy code. If you have automated tests, you should make sure these are all passing for your code before you commit.

What if I am working on two unrelated changes in my working copy? Can I commit them separately?

If the affected files of each overlap... Nope. You would need two different working copies (checkouts) in order to keep them separate.

svn ci file1 overlapping_file2 -m "Change 1"                    # oops, you just committed part of Change 2 and said that it was for Change 1 in your log message!
svn ci overlapping_file2 file3 -m "Change 2 (unrelated to 1)"   # by the time you do this, overlapping_file2 will have no uncommitted changes

If they don't overlap, then yes, you can. You'd just need to list the files to commit for each of your commits:

svn ci file1 file2 -m "Change 1"
svn ci file3 file4 -m "Change 2 (unrelated to 1)"

If one of my changes is really minor and hardly worth making a separate commit for (like a typo fix or removing some old debug code or minorly changing a comment, for example), then I usually don't worry about that commit being subsumed as part of a larger commit (an unrelated change). I may or may not mention the minor change in the commit log for that change set.

Commit logical changesets Retrieved on 2007-04-25 11:08.

When you commit a change to the repository, make sure your change reflects a single purpose: the fixing of a specific bug, the addition of a new feature, or some particular task. Your commit will create a new revision number which can forever be used as a "name" for the change. You can mention this revision number in bug databases, or use it as an argument to svn merge should you want to undo the change or port it to another branch. Book reference: "Subversion and Changesets" sidebar, within chapter 4.

Use the issue-tracker wisely Retrieved on 2007-04-25 11:08.

Try to create as many two-way links between Subversion changesets and your issue-tracking database as possible:

  • If possible, refer to a specific issue ID in every commit log message.
  • When appending information to an issue (to describe progress, or to close the issue) name the revision number(s) responsible for the change.

I agree!!

Track merges manually Retrieved on 2007-04-25 11:08.

When committing the result of a merge, be sure to write a descriptive log message that explains what was merged, something like:

Merged revisions 3490:4120 of /branches/foobranch to /trunk.

Book reference: Tracking merges manually, and Merging a whole branch to another.

(This shouldn't be necessary, because Subversion should track it for you ... but it doesn't, so it is.)

How do I copy an existing working-copy directory somewhere else and not have the new directory think it's a working-copy too? / how do you make a working copy into a not-a-working-copy?

(For example, copying my_shared_code/gems/template to my_shared_code/gems/a_new_gem...)

Ah, you'd think it would be as simple as just cp -R source_dir destination_dir/

The problem with that, though, is that all the .svn files are included with it.

What's wrong with that, you ask? Well, as you start modifying, renaming, or deleting those files, it will think you are modifying a working copy.

> svn st
A      test/test_helper.rb
!      test/some_test.rb
!  +   _.gemspec
M  +   Rakefile

Solution 1:

If you just want to start fresh (so that you can svn add the whole directory at once as if it were a new directory), it's really easy: you just need to remove all the .svn directories inside of it.

> find -name .svn
> find -name .svn | xargs rm -rf
> svn st
svn: '.' is not a working copy

Solution 2: Use svn export instead of cp -R !

Know when to create branches Retrieved on 2007-04-25 11:08.

This is a hotly debated question, and it really depends on the culture of your software project. Rather than prescribe a universal policy, we'll describe three common ones here.

The Never-Branch system

(Often used by nascent projects that don't yet have runnable code.)

  • Users commit their day-to-day work on /trunk.
  • Occasionally /trunk "breaks" (doesn't compile, or fails functional tests) when a user begins to commit a series of complicated changes.

Pros: Very easy policy to follow. New developers have low barrier to entry. Nobody needs to learn how to branch or merge.

Cons: Chaotic development, code could be unstable at any time.

A side note: this sort of development is a bit less risky in Subversion than in CVS. Because Subversion commits are atomic, it's not possible for a checkout or update to receive a "partial" commit while somebody else is in the process of committing.

The Always-Branch system

(Often used by projects that favor heavy management and supervision.)

  • Each user creates/works on a private branch for every coding task.
  • When coding is complete, someone (original coder, peer, or manager) reviews all private branch changes and merges them to /trunk.

Pros: /trunk is guaranteed to be extremely stable at all times.

Cons: Coders are artificially isolated from each other, possibly creating more merge conflicts than necessary. Requires users to do lots of extra merging.

star_full.gif star_full.gif star_full.gif The Branch-When-Needed system

(This is the system used by the Subversion project.)

  • Users commit their day-to-day work on /trunk.
  • Rule #1: /trunk must compile and pass regression tests at all times. Committers who violate this rule are publically humiliated.
  • Rule #2: a single commit (changeset) must not be so large so as to discourage peer-review.
  • Rule #3: if rules #1 and #2 come into conflict (i.e. it's impossible to make a series of small commits without disrupting the trunk), then the user should create a branch and commit a series of smaller changesets there. This allows peer-review without disrupting the stability of /trunk.

Pros: /trunk is guaranteed to be stable at all times. The hassle of branching/merging is somewhat rare.

Cons: Adds a bit of burden to users' daily work: they must compile and test before every commit.

Personal tools