Subversion Branches and Tags
From Ece
Branching, merging, and tagging are powerful tools, but they may take more time to understand. It is beyond the scope of this article to fully explain the concepts of branching and tagging, so it is recommended that you read the chapter Branching and Merging in the free online Subversion book. This article will attempt to give a brief overview of branching and tagging, recommended repository structure, and a more detailed explanation of correct merging procedures.
Note: Yavin is running Subversion 1.4. You may connect with a v1.4 or v1.5 client. However, merge tracking is not supported.
Contents |
Overview
Branches
The basic features of subversion are quite valuable. Every change to the files in a Subversion repository is permanently saved. Any previous state of the versioned directory tree can be examined and checked out if later needed. The basic system of checking out files, editing them, and then committing the changes is great, as long as the changes are relatively small.
However, sometimes this may become inconvenient. If a developer must make substantial changes to the codebase, it can become a problem.
- Large changes may destabilize the repository, introducing compile errors, bugs, etc.
- Completing the entire change locally before committing can be a risk, and leaves a poor record of intermediate changes—it defeats the purpose of using Subversion.
Subversion provides a way for developers to work on slightly different versions, or branches, of the repository, in order to provide insulation from other changes. Essentially, a copy of all of the code is created inside the repository. While the main development continues on the original copy, known as the trunk, the destabilizing code is developed on the new copy, known as a branch.
Another problem that can arise is the need to "freeze" the codebase with a stable set of features in preparation for a software release. A branch can be used in this case as well as a stable line of development. New features and changes may be added to the trunk, but the release branch will only be updated to incorporate bug fixes that are added to the trunk.
In summary, there are generally two kinds of branches:
- Feature branches, which are used to create a new feature, bug fix, etc., that might destabilize the main line of development if not isolated until complete, and
- Release branches, which are used to "freeze" the code with a stable feature set for testing, leading up to a release.
Tags
Because Subversion tracks every change committed to the repository, it is theoretically possible to go to a desired "state" of the repository just by finding the appropriate global revision number. Practically speaking, however, when the revision number climbs into the hundreds and thousands, this may not be the best solution.
Thus, a common practice is to create tags, or snapshots, of the repository at certain points in time. For example, when the codebase, perhaps existing in a stable branch, is ready to be released, it is usually tagged at that point in time, so that despite any future commits to the repository, it is always a trivial matter to find the precise state of the repository that was released and distributed to the users.
In fact, one common pattern is to maintain a stable branch, perhaps for version 1.0.x of a program, and overtime release patched versions such as 1.0.1, 1.0.2, etc., until the next release version 1.1.x comes along. In this case, each of these "patched versions" could be tagged in order to easily identify them later.
Repository Layout
A repository can be laid out any way you prefer, and you can make branches and tags anywhere you like in the repository, but there is a commonly-recommended layout:
- Place related projects at the top level of the repository.
- Within each project, create a
trunk/directory to contain the main line of development. - Alongside it, create
branches/andtags/directories to hold branches and tags.
An example tree might look like:
\- repos <--------- this is the repos dir., and root of the "virtual" file system
\- projname1
| \- trunk
| | |- foo.c
| | |- bar.c
| \- branches
| | \+ 1.0.x
| | \- somenewfeature
| | | |- foo.c
| | | |- bar.c
| | | |- widget.c
| | \+ reallycoolfeature
| \- tags
| | \+ 1.0.0
| | \+ 1.0.0-beta1
| | \+ 1.0.0-rc1
| | \+ 1.0.1
| | \+ 1.0.2
\- projname2
\+ trunk
\+ branches
\+ tags
As discussed earlier, some branches, like somenewfeature and reallycoolfeature, are created to isolate the trunk from destabilizing changes while features or bug fixes are implemented.
Other branches, though, like 1.0.x, are used to hold a stable feature set for eventual release, and for maintenance on that release. For the tree above, something like this may have happened:
- At some point in time, the developers on
projname1were ready to prepare the code for release, and created the branch1.0.x. - After some initial testing and bug fixes, they decided to pre-release the program to users as a beta, in order to get some feedback and a wide range of user testing. This beta release was tagged as
1.0.0-beta1. - After the beta period was over, they pre-released the software as a release candidate to allow for last-minute feedback from users. This release candidate was tagged as
1.0.0-rc1. - Finally, the code was ready for official release, and was tagged as
1.0.0. - As time passed, new bugs were discovered by users, and the developers worked to fix them. Meanwhile, a few bug fixes implemented on the trunk were ported into the
1.0.xbranch. At certain points in time, after a number of fixes were implemented, updated patch releases were tagged as1.0.1and, subsequently,1.0.2.
For further example of what a repository structure might look like, browse the repository that contains the Subversion codebase. Have a cursory look at the relationship between the trunk, branches, and tags.
Creating a Branch or Tag
Subversion does not inherently "understand" the concept of a branch or a tag. It knows only the concept of a copy. A user can create branches and tags using svn copy, and Subversion will track the fact that one copy (i.e. branch or tag) came from another copy (i.e. trunk, branch, or tag).
It is important to note that Subversion uses so-called "cheap" copies. Files and directories are not actually copied internally. Subversion just records the fact that a copy was made, and stores any additional edits created on top of that copy. This is consistent with how it stores data in general—by differencing. That is, on any commit to the repository, the difference is computed, and only that diff is then stored.
To see how a branch is created, take the example repository above. Perhaps the trunk of projname1 has been under very active development since the 1.0.0 release, and some major new features have been added and are stable. It is time to create a stable branch to prepare for the 1.1.0 release.
Simply run svn copy directly on the repository URLs:
svn copy svn+ssh://user@hostname/path/to/repos/projname1/trunk \
svn+ssh://user@hostname/path/to/repos/projname1/branches/1.1.x \
-m "Creating stable branch for 1.1.x release"
Similarly, some time passes, and it becomes time for a release candidate, so we want to tag Release Candidate 1 (RC1):
svn copy svn+ssh://user@hostname/path/to/repos/projname1/branches/1.1.x \
svn+ssh://user@hostname/path/to/repos/projname1/tags/1.1.0-rc1 \
-m "Tagging 1.1.0 RC1"
Switching Between Branches
After creating a new branch, you might be tempted to checkout a separate working copy for that branch. For a large repository, checking out a new branch can take a while and be taxing on your hard drive storage. The same is true for managing multiple different branches. Luckily, Subversion offers a way to convert your working copy from one branch to another, downloading only the necessary differences.
Warning: For maximum predictability, you should only switch a working copy that has no uncommitted edits.
Simply use svn switch, providing it the URL to another tree. For example, if you have the trunk checked out, and want to switch to a branch:
svn switch svn+ssh://user@hostname/path/to/repos/projname1/branches/somenewfeature
Merging
Subversion provides a merging command, svn merge, to allow for merging changes from one branch (or the trunk) into another branch (or the trunk). This allows you to keep a long-lived feature branch in-sync with the trunk, merge a completed feature branch back into the trunk, port bug fixes from the trunk to a release branch, etc. Its operation may not be entirely intuitive, so be sure to read Understanding Merging below.
Warning: Yavin is running Subversion 1.4, and does not support Merge Tracking. You must track merges by hand; it is recommended to carefully document merges in log messages.
Understanding Merging
The svn merge command is not difficult to understand, but can be less than intuitive. It is important to start by understanding what exactly svn merge does.
The command performs a comparison between two trees, computes the difference, and applies the difference to a target working copy. Thus, it takes three arguments:
- The left side tree of the comparison
- The right side tree of the comparison
- The target working copy that will accept the difference
The Subversion book describes merge as a diff-and-apply, because it computes the differences (deletions and additions) from one tree to another, and then applies them to your working copy.
For the programmers, it may help to think about merge as performing the following:
target = target + (right_side - left_side);
This helps reveal something very important. The left side and right side of the comparison are 'not interchangeable. When computing a numerical difference, swapping the order of the operands produces a result of the same magnitude, but an opposite sign. The same is true for diff-ing files and trees. Two trees have an absolute difference in content, but the "sign" depends on which tree you are going from and which tree you are going to.
For example, the command svn diff foobar.c 8:9 might produce:
-foo() +bar()
But svn diff foobar.c 9:8—the opposite comparison—would produce:
-bar() +foo()
The difference in content is the same; one has foo() while the other has bar(). But the "sign"—the deletions vs. the additions—is now the opposite. Since svn merge is basically a diff-and-apply, then you can see how the same concept applies to merging.
Merge Conflicts
Merging can cause conflicts just like updating a working copy does. When a conflict occurs, svn merge also creates temporary files, but they are named differently than those created by svn update.
-
filename.workingis the last state of your working copy of the file -
filename.leftis the state of the file in the left-side tree from themergecommand -
filename.rightlikewise corresponds to the right-side tree
Merging a Short-Lived Branch into the Trunk
When a branch is relatively short-lived, it can be merged back into the trunk with first every being synchronized with the trunk. That is, the branch was created and directly edited without regard for any changes made to the trunk in the meantime.
In general, it is good practice to always synchronize with the trunk before performing a merge back into the trunk, so it might be wise to avoid this technique altogether. At minimum, use it in moderation, and realize that how "short-lived" a branch is relates less to do with the activity on the branch and much more to the amount of activity on the trunk since the branch was created.
In order to determine the changes made privately to the branch and apply them to the trunk, you must tell Subversion to compute a a diff from the original state of the branch to the current state of the branch, and apply it to (a current working copy of) the trunk.
In the repository example above, you may have quickly completed a new feature using the branch somenewfeature and are now ready to apply that feature to the trunk.
- First, from a working copy of the branch, use
svn logto find the revision number at which you created the branch:svn log -v --stop-on-copy
- Switch your working copy to the trunk:
svn switch svn+ssh://user@hostname/path/to/repos/projname1/trunk
- Assume we found that the branch was created at revision 31, and the last revision at which you changed it was 45. Find the branch changes from r31 to 45, and apply them to the working copy of the trunk:
merge -r 31:45 svn+ssh://user@hostname/path/to/repos
- If your branch had changes that overlaps with changes made to the trunk in the meantime, you will need to handle any conflicts by hand.
- Use
svn statusandsvn diffto make sure everything looks right. - Commit with a useful log message:
svn commit -m "Merged somenewfeature changes r31:45 into the trunk"
Now that the merge is complete, if you are finished with the branch, you could simply delete it with svn delete. However, you might also leave it alone for now in case any bugs arise. If you find out you need to further edit your feature, you can continue working on the branch where you left off.
When you finish updating the branch, you will need to repeat the above steps for merging your branch changes. However, use only the range of revisions since you last merged the branch with the trunk. In our example, if your newest change to the branch resulted in revision 60, then you would use svn log to find that you last merged the branch in at revision 45. Thus, your revision range for this merge would be r45:60.
Synchronizing a Branch with the Trunk
If a branch is not short-lived, such as a major feature addition or a stable/release branch, then the branch may become seriously out-of-sync with the trunk over time. To make sure that your code and the trunk code are not drifting too far apart, you will want to synchronize your branch with the trunk from time-to-time.
First, make sure you understand the previous section, because the mechanics are the same, but opposite. In this case, every so often you will want to merge a range of revisions in the trunk into your working copy of the branch.
For example, you have created a branch reallycoolfeature that will require significant development time, and you want to keep it in sync with the trunk. So one day you perform the following steps to synchronize:
- With the branch checked out, find the revision at which you created the branch using
svn log -v --stop-on-copy. Let's assume you find that the revision number was 73. - Run
svn logon the trunk to find out the most recent revision at which it was changed. Let's assume that revision is 88. - Make sure you have no uncommitted edits, and then merge the changes in the trunk occurring since the branch creation into your working copy:
svn merge -r 73:88 svn+ssh://user@hostname/path/to/repos/projname1/trunk
- Inspect the results, and then perform a commit with a useful log message:
svn commit -m "Merged trunk changes r73:88 into branch reallycoolfeature"
After more time passes, you will want to synchronize with the trunk again. Since Subversion 1.4 does not employ automated merge tracking, you will need to make sure you don't try to merge in the revisions you merged last time.
- Use
svn logand look for the log message you entered that tells you your last synchronize was for the range r73:88. - Use
svn logto find the most recent revision at which the trunk was changed. Let's assume revision 99. - Merge the newest series of trunk changes into your working copy of the branch:
svn merge -r 88:99 svn+ssh://user@hostname/path/to/repos/projname1/trunk
Disciplined use of this procedure should keep you in-sync with the trunk.
Porting a Single Change into a Branch
Now, what if you are maintaining release branch 1.0.x, and a long-standing bug was just fixed in revision 125 on the trunk? After inspecting the bugfix, you believe it can be applied cleanly to the stable branch. In order to port in only this fix:
- Checkout or switch your working copy to the
1.0.xbranch. - Merge the bugfix into your working copy, by comparing r125 with its previous revision. (Note
-c Ndenotes changes occuring in revision N, and is equivalent to-r N-1:N)svn merge -c 125 svn+ssh://user@hostname/path/to/repos/projname1/trunk
- Examine the changes and perform some testing. If you decide to keep the bug fix, commit with a useful log message:
svn commit -m "Merged trunk change c125 to branch 1.0.x"
Merging a Synchronized Branch into the Trunk
If you have kept a branch perfectly in sync with the trunk, then you should be able to merge the branch changes into the trunk as described earlier. All changes since the branch was created—both local changes and changes merged from the trunk—will be part of the computed diff. Obviously, there is some redundancy here, because you are re-applying the trunk changes—as they were incorporated into the branch—to the trunk.
A better approach, if the branch is truly in sync with the trunk, is to directly compare the branch to the trunk, and apply the changes to a working copy of the trunk. But make sure your merge is from the trunk to the branch, though it may seem counterintuitive. This is because the trunk and branch are equal, except for the changes to the branch. We want to compute the diff that would turn the trunk into the branch, and apply that diff to the trunk, so that it is equal to the branch.
The syntax here would be something like:
svn merge svn+ssh://user@hostname/path/to/repos/projname1/trunk \
svn+ssh://user@hostname/path/to/repos/projname1/branches/mysyncedbranch
You should still use the logs to determine what range of branch changes you are bringing into the trunk, in order to commit with a useful log message:
svn commit -m "Merged (synchronized) branch changes r150:170 into the trunk"
You can now delete the branch or continue to maintain it and synchronize it with the trunk. Occasionally merge it into the trunk again by the same procedure: compare the trunk to the branch, and merge.
See Also
- Subversion
- Subversion Basic Use
- Connecting to a Subversion Repository
- Subversion Repository Administration
- TortoiseSVN
External Links
- Subversion (official Web site)
- Version Control with Subversion is the official (online) Subversion book.
- A Unix-orientated tutorial with an interesting scheme for release branching.




