Subversion notes

Just a collection of SVN stuff that I've found useful... I've tried to make all the links relative to v1.8. Manual here. Basic work cycle described here.

Page Contents

TODOs

http://stackoverflow.com/questions/14889395/concrete-sample-merges-of-git-that-wont-work-in-svn<

http://svn.apache.org/repos/asf/subversion/trunk/notes/skip-deltas

Install On Ubuntu Or Upgrade

sudo apt-get install subversion --upgrade

Make SVN use P4Merge For Diff

I really like the Perforce merge and diff tools so after a bit of searching around now use the following script on my Linux box

#!/bin/sh

LEFT_FILE_DESCR=$3
RIGHT_FILE_DESCR=$5
LEFT=$6
RIGHT=$7

echo STARTING DIFF
echo LEFT IS  "$LEFT_FILE_DESCR"
echo RIGHT IS "$RIGHT_FILE_DESCR"
p4merge -nl "$LEFT_FILE_DESCR" -nr "$RIGHT_FILE_DESCR" "$LEFT" "$RIGHT"

Save the above script somewhere on your PATH. I called it p4_diff_helper.sh. Then in the subversion config file (~/.subversion/config for linux and %appdata%\subversion\config for windows) add in the diff command under the [helpers] section.

[helpers]
<snip>
### Set diff-cmd to the absolute path of your 'diff' program.
###  diff-cmd = diff_program (diff, gdiff, etc.)
###  diff-cmd = kdiff3
diff-cmd = p4_diff_helper.sh

This took me quite a while the first time round, reading on various threads, then I found out that the "Using External Differencing and Merge Tools" section of the Red Book had the answer all along, so use that link for more information :)

Doing the same for a merge is even easier:

#!/bin/sh
BASE=$1
THEIRS=$2
MINE=$3
MERGED=$4
WCPATH=$5
p4merge "$BASE" "$MINE" "$THEIRS" "$MERGED"

Again, assuming you save the above on the PATH as p4_merge_helper.sh, modify the subversion config file (~/.subversion/config for linux and %appdata%\subversion\config for windows) add in the merge command under the [helpers] section.

[helpers]
<snip>
merge-tool-cmd = p4_merge_helper.sh

SVN Log

Only Report Changes After Branch

snv log --stop-on-copy 

Get More Merge Information

Use the -g flag (and also useful is the verbosity flag -v) to get more information in the log dump. The -g flag adds extra information into the trace for instances where merges where made back into the line, or where the line was copied out to another branch.

Properties

Too see changes in properties use the following command:

svn log -v --diff --depth=empty .

Grep the output for "Modified: svn:externals"

SVN Ignore Files

The following will ignore all *.orig files from the current directory and all subdirectories

svn propset svn:ignore '*.orig' . --recursive

It's usually a good idea to use svn propedit instread of propset if you want to ignore many things. To use this make sure you have set your SVN_EDITOR environment variable to, for example, gvim -f on Windows or gvim --no-fork on Linux.

From SVN 1.8 onwards there is a new property svn:global-ignores:

global-ignores ... is a whitespace-delimited collection of file patterns. The Subversion client checks these patterns against the names of the files that are candidates for addition to version control, as well as to unversioned files that the svn status command notices. If any file's name matches one of the patterns, Subversion will basically act as if the file didn't exist at all...

-- Subversion manual

Branch, Checkout, Merge and Reintegrate

Branching & Checkout

From SVN manual: Subversion has no internal concept of a branch - it knows only how to make copies ... that happens to carry some extra historical information.

To branch off, for example trunk, to new workshpace:

svn cp svn+ssh://myserver.com/trunk/src svn+ssh://myserver.com/0_developers/my_name/branch_name -m "a description"
cd /where/you/want/to/checkout/to
svn co svn+ssh://myserver.com/0_developers/my_name/branch_name

Merging

To update my local branch with changes from trunk make sure branch is cleann no local modifications and up to date.

cd /where/you/want/to/checkout/to
svn merge ^/trunk/src

Note that this has only affected your working copy! The changes have not been submitted to the repository yet. For that you must do an svn commit. This is also known as a sync merge.

Useful tips from the Red Book...

svn mergeinfo --show-revs=merged ^/trunk ^/branches/0_developers/my_name/branch_name
svn mergeinfo --show-revs=eligible ^/trunk ^/branches/0_developers/my_name/branch_name

The first line reports a summary of changelists where trunk was merged to branch. The second line reports a summary of changelists where trunk has not yet been merged to branch

Reintegrating

To re-integrate to trunk, first make sure dev-line is up to date by merging in from trunk again and committing any changes resulting from the merge back to the repo.

Don't forget the --dry-run flag... tries the operation, shows you the output, but make no actual changes. It's a nice way to see what your merge will do before you actually do it!

Pre v1.8

cd /where/truck/is/checked/out
svn up
svn merge --reintegrate ^/0_developers/my_name/branch_name
... test ...
svn commit -m "message"

For subversion < v1.8, the --reintegrate option is critical for reintegrating changes from a branch back into its original line of development so that only changes unique to your branch are copied back!

Pre v1.8: The [--reintegrate] option is critical for reintegrating changes from a branch back into its original line of developmentā€”don't forget it! ... By specifying the --reintegrate option, you're asking Subversion to carefully replicate only those changes unique to your branch.
-- See "Svn Red Bean".

Pre v1.8: Once you have reintegrated a branch into trunk, it is no longer "alive". It is generally unfit for futher use after it has been reintegrated!
-- See "Keeping a Reintegrated Branch Alive" for further information.

It is possible, pre 1.8, to keep the re-integrated branch alive. But, you must do this as soon as you have done the original reintegrate!

SVN 1.8 onwards

Joy! None of the above problems occur in v1.8... it is is smarter! Merging back into trunk lines is now called an "automatic reintegrate".
You do not need to use --reintegrate and the branch remains useable after the merge...

Your trunk working copy cannot have any local edits, switched paths, or contain a mixture of revisions so commit any pending edits and then svn update the branch.

cd /where/truck/is/checked/out
svn up
svn merge ^/0_developers/my_name/branch_name
... test ...
svn commit -m "message"

Resolving Conflicts

This section of the Red Book covers this.

This SO thread discusses the difference between left, right, working etc in merge conflicts.

Evil Twins

Evil twins are two files with the same name and location in two different branches that do not have a common ancestor. For example, if B was a branch of A and in A I create the file dummy.txt, then file-copy (not svn cp) that file into B and then try to reintegrate B to A, SVN will throw a wobbly.

Why is this? It is basically because SVN has absolutely no way to know how to merge these files. Who's correct? A or B? The files might look similar, but may not be even related! Had there been a common ancestor at least SVN would know they're related and could have a stab at a merge.

Then this is the case it is best to pospone resolution of the conflict and compare the two files. Your only option will probably to be to accept the working copy as the file to keep. Then you will have to manually edit that working copy file to reflect the changes that you want to keep between the evil twins.

To do this you use...

svn resolve --accept='working' file-name

This removes the conflict marker and says that the working copy contains the resolved file. (At this point, note however that it does NOT!).

Now you need to manually resolve the changes. Edit the working copy or overwrite it. Then when you commit the working copy will become the updated file in A. Note, that if you use B further you still face the same problem on the next merge. The best thing to do would be to remove the bopy in B and replace it with a branch from A.

See Merge History

Use the command svn mergeinfo src_branch targ_branch. It will print an ascii-art graph of the merge history between the two branches.

See also these tips in merge section.

Rollback To A Previous Revision

From your repo directory use the following, which is described in the svb redbook here.

svn update
svn merge -r <newer-rev>:<older-rev> <filename or . for current dir>
svn commit

As the manual says.... You can use svn merge to "undo" the change in your working copy, and then commit the local modification to the repository. All you need to do is to specify a reverse difference. (You can do this by specifying --revision 392:391, or by an equivalent --change -392.)

SVN Properties - Recursive

Arg! Setting the ignore property recursively is deceptive. The problem and several really good answers are found in this SO thread.

Another useful command is svn plist -v branch to see all the properties set on a directory or file.

Externals

Can kinda emulate Perforce client specs, but either way are pretty useful if you want your development line to include other bits of code from different development lines. I.e. ...contruct a working copy that is made out of a number of different checkouts. When a branch has externals, after the branch is checked out or updated, subversion will also automatically chekout/update the external items into the working copy.

To set up externals use the svn property svn:externals. As the manual points out, it is a multiline property so best use svn propedit as oppsed to svn propset.

The manual also recommends using explicit revisions in your external definitions so that the snapshot of the external soruce is well controlled and you don't get unexpected updates to external code that you may not control. Note that this does mean, however, that you will not be able to modify the external.

If you do modify an external, you must explicity commit the external. Doing a commit on the "mother" checkout will not recurse into the externals.

If the external definition references something in the same repository it is also good practice to use paths relative to the root of the repository. I.e. paths prefixed with "^/".

The format for the svn:externals property is this:

^/path/to/external[@revision] local/path

Or...

[-r revision] ^/path/to/external local/path

My Repository Moved!

Recently a repository I had checked out was moved for various reasons. I still had some files in my checkout that were modified so I didn't want to have to re-checkout from the new location and manually add these edits. Solution was the command svn relocate.

svn relocate FROM-PREFIX TO-PREFIX

Misc

Generate A Patch File

Useful when uploading diffs to ReviewBoard for example. Use the following:

svn diff --internal-diff --patch-compatible -rX:Y > patch.diff

Note the use of the --internal-diff. If your SVN config file specifies a diff tool other than the internal SVN diff tool your patch won't be generated. This option ensures that the SVN internal diff tool is used to the patch file comes out right. Also notice --patch-compatible. This will make sure new, previously non-existent, files are added to the diff. (You might then have to edit the resulting patch file to replace the text (nonexistent) with (revision 0)).

Check Your SSH Connection

If your are having trouble with your SSH connection, you can check that your keys are correctly loaded using the following command (on Linux with OpenSSH) to list your loaded keys...

ssh-add -L

If you're still having problems look in ~/.subversion/config and go to the [tunnels] section. In the SSH command take out the -q option to get better debug messages to the console.

OR... even better just type...

export SVN_SSH="ssh -v "

...either on the same line as your svn command to just apply it to that command, or on a line by its own to make the setting apply to all svn commands. (Note the trailing space after the -v).

Add all untracked files

svn add $(svn status | grep ? | awk '{print $2}')

Diff all changes in changeset

svn diff -c <changeset> [filename]

This is the same as running the following:

 svn diff -r rev-1:rev [filename]

If filename is ommitted all changes in that changeset are examined

Nice Diff Syntax For Same-File-Different-Branch

Useful command example is:

svn diff ^/branches/{PATH_1,PATH_2}/some/repo/path

Where:

  • ^/ is a shortcut for the root of the current repository, and
  • xx{A,B}yy expands to xxAyy <space> xxByy

Therefore the whole line expands to the following.

svn diff <root>/branches/PATH_1/some/repo/path <root>/branches/PATH_2/some/repo/path

This is really a linux variable expansion, it's not something SVN actually offers.

Diff Two Paths But Select Exactly What To Diff

I found it really useful to take a summary of SVN diffs, delete the stuff I wasn't interested in and then only diff the remaining when considering two paths, one being a branch of the other...

Lets say that branch 1 was at this location: svn+ssh://your.server.xxx/subversion/branches/branch_A"

And lets say that branch 2 was at this location: svn+ssh://your.server.xxx/subversion/branches/branch_B/some/other/path"

Then I could use the following script. It would summarise the differences to a file. I could then cull any files I didn't want to diff for whatever reason, save the file, exit the editor and then diff what I'd left in the file...

#!/bin/bash
svnroot="svn+ssh://your.server.xxx/subversion/branches/"
b1="branch_A"
b2="branch_B/some/other/path"

diffFile=$(mktemp)
echo "Diffing to $diffFile"
echo svn diff --summarize $svnroot{$b1,$b2}
svn diff --summarize $svnroot{$b1,$b2} | tee $diffFile

echo "Firing up you editor"
gvim -f $diffFile

while IFS='' read -r line || [[ -n "$line" ]]
do
   line=$(echo $line | awk '{print $2}')
   line=${line:${#svnroot}}
   echo $line
   if [ "${line::${#b1}}" == "$b1" ]
   then
     base=${line:${#b1}}
     echo -e "\n\n"
     echo "DIFFING $svnroot{$b1,$b2}$base"
     svn diff $svnroot{$b1,$b2}$base
   elif [ "${line::${#b2}}" == "$b2" ]
   then
     base=${line:${#b2}}
     echo -e "\n\n"
     echo "DIFFING $svnroot{$b1,$b2}$base"
     svn diff $svnroot{$b1,$b2}$base
   else
     echo "WARNING Skipping line '$line'"
   fi
done < $diffFile

echo "The diff file is $diffFile. Delete it if you wish or copy it to save"
echo "it somewhere safe..."