Now you and Sally are working on parallel branches of the project: you're working on a private branch, and Sally is working on the trunk, or main line of development.
For projects that have a large number of contributors, it's common for most people to have working copies of the trunk. Whenever someone needs to make a long-running change that is likely to disrupt the trunk, a standard procedure is to create a private branch and commit changes there until all the work is complete.
So, the good news is that you and Sally aren't interfering with each other. The bad news is that it's very easy to drift too far apart. Remember that one of the problems with the “crawl in a hole” strategy is that by the time you're finished with your branch, it may be near-impossible to merge your changes back into the trunk without a huge number of conflicts.
Instead, you and Sally might continue to share changes as you work. It's up to you to decide which changes are worth sharing; Subversion gives you the ability to selectively “copy” changes between branches. And when you're completely finished with your branch, your entire set of branch changes can be copied back into the trunk. In Subversion terminology, the general act of replicating changes from one branch to another is called merging, and it is performed using various invocations of the svn merge subcommand.
In the examples that follow, we're assuming that both your Subversion client and server are running Subversion 1.7 (or later). If either client or server is older than version 1.5, things are more complicated: the system won't track changes automatically, forcing you to use painful manual methods to achieve similar results. That is, you'll always need to use the detailed merge syntax to specify specific ranges of revisions to replicate (see the section called “Merge Syntax: Full Disclosure” later in this chapter), and take special care to keep track of what's already been merged and what hasn't. For this reason, we strongly recommend that you make sure your client and server are at least at version 1.5.
Before we proceed further, we should warn you that there's a lot of discussion of “changes” in the pages ahead. A lot of people experienced with version control systems use the terms “change” and “changeset” interchangeably, and we should clarify what Subversion understands as a changeset.
Everyone seems to have a slightly different definition of changeset, or at least a different expectation of what it means for a version control system to have one. For our purposes, let's say that a changeset is just a collection of changes with a unique name. The changes might include textual edits to file contents, modifications to tree structure, or tweaks to metadata. In more common speak, a changeset is just a patch with a name you can refer to.
In Subversion, a global revision number N
names a tree in the repository: it's the way the repository looked after the N
th commit. It's also the name of an implicit changeset: if you compare tree N
with tree N
-1, you can derive the exact patch that was committed. For this reason, it's easy to think of revision N
as not just a tree, but a changeset as well. If you use an issue tracker to manage bugs, you can use the revision numbers to refer to particular patches that fix bugs—for example, “this issue was fixed by r9238.” Somebody can then run svn log -r 9238
to read about the exact changeset that fixed the bug, and run svn diff -c 9238
to see the patch itself. And (as you'll see shortly) Subversion's svn merge command is able to use revision numbers. You can merge specific changesets from one branch to another by naming them in the merge arguments: passing -c 9238
to svn merge would merge changeset r9238 into your working copy.
Continuing with our running example, let's suppose that a week has passed since you started working on your private branch. Your new feature isn't finished yet, but at the same time you know that other people on your team continue to make important changes in the project's /trunk
. It's in your best interest to replicate those changes to your own branch, just to make sure they mesh well with your changes. This is done by performing a sync merge—a merge operation designed to bring your branch up to date with any changes made to its ancestral parent branch since your branch was created.
Tip |
Frequently keeping your branch in sync with the main development line helps prevent “surprise” conflicts when the time comes for you to fold your changes back into the trunk. |
Subversion is aware of the history of your branch and knows when it split away from the trunk. To perform a sync merge, first make sure your working copy of the branch is “clean”—that it has no local modifications reported by svn status. Then simply run:
$ pwd /home/user/my-calc-branch $ svn merge ^/calc/trunk --- Merging r345 through r356 into '.': U button.c U integer.c --- Recording mergeinfo for merge of r345 through r356 into '.': U . $
This basic syntax—svn merge
—tells Subversion to merge all changes which have not been previously merged from the URL to the current working directory (which is typically the root of your working copy). Notice that we're using the caret (URL
^
) syntax[26] to avoid having to type out the entire /trunk
URL. Also note the “Recording mergeinfo for merge…” notification. This tells you that the merge is updating the svn:mergeinfo
property. We'll discuss both this property and these notifications later in this chapter, in the section called “Mergeinfo and Previews”.
After running the prior example, your branch working copy now contains new local modifications, and these edits are duplications of all of the changes that have happened on the trunk since you first created your branch:
$ svn status M . M button.c M integer.c $
At this point, the wise thing to do is look at the changes carefully with svn diff, and then build and test your branch. Notice that the current working directory (“.
”) has also been modified; svn diff will show that its svn:mergeinfo
property has been either created or modified. This is important merge-related metadata that you should not touch, since it is needed by future svn merge commands. (We'll learn more about this metadata later in the chapter.)
After performing the merge, you might also need to resolve some conflicts—just as you do with svn update—or possibly make some small edits to get things working properly. (Remember, just because there are no syntactic conflicts doesn't mean there aren't any semantic conflicts!) If you encounter serious problems, you can always abort the local changes by running svn revert . -R
(which will undo all local modifications) and starting a long “what's going on?” discussion with your collaborators. If things look good, however, you can submit these changes into the repository:
$ svn commit -m "Merged latest trunk changes to my-calc-branch." Sending . Sending button.c Sending integer.c Transmitting file data .. Committed revision 357. $
At this point, your private branch is now “in sync” with the trunk, so you can rest easier knowing that as you continue to work in isolation, you're not drifting too far away from what everyone else is doing.
Suppose that another week has passed. You've committed more changes to your branch, and your comrades have continued to improve the trunk as well. Once again, you want to replicate the latest trunk changes to your branch and bring yourself in sync. Just run the same merge command again!
$ svn merge ^/calc/trunk svn: E195020: Cannot merge into mixed-revision working copy [357:378]; try up\ dating first $
Well that was unexpected! After making changes to your branch over the past week you now find yourself with a working copy that contains a mixture of revisions (see the section called “Mixed-revision working copies”). With the release of Subversion 1.7 the svn merge subcommand disables merges into mixed-revision working copies by default. Without going into too much detail, this is because of limitations in the way merges are tracked by the svn:mergeinfo
property (see the section called “Mergeinfo and Previews” for details). These limitations mean that merges into mixed-revision working copies can result in unexpected text and tree conflicts.[27] We don't want any needless conflicts, so we update the working copy and then reattempt the merge.
$ svn up Updating '.': At revision 380. $ svn merge ^/calc/trunk --- Merging r357 through r380 into '.': U integer.c U Makefile A README --- Recording mergeinfo for merge of r357 through r380 into '.': U . $
Subversion knows which trunk changes you previously replicated to your branch, so it carefully replicates only those changes you don't yet have. And once again, you build, test, and svn commit the local modifications to your branch.
Tip |
Prior to Subversion 1.7, merges unconditionally updated all of the subtree mergeinfo under the target to describe the merge. For users with a lot of subtree mergeinfo this meant that relatively “simple” merges (e.g. one which applied a diff to only a single file) resulted in changes to every subtree with mergeinfo, even those that were not parents of the effected path(s). This caused some level of confusion and frustration. Subversion 1.7 addresses this problem by only updating the mergeinfo on subtrees which are parents of the paths modified by the merge (i.e. paths changed, added, or deleted by application of the difference, see the section called “Merge Syntax: Full Disclosure”). The one exception to this behavior regards the actual merge target; the merge target's mergeinfo is always updated to describe the merge, even if the applied difference made no changes. |
What happens when you finally finish your work, though? Your new feature is done, and you're ready to merge your branch changes back to the trunk (so your team can enjoy the bounty of your labor). The process is simple. First, bring your branch into sync with the trunk again, just as you've been doing all along[28]:
$ svn merge ^/calc/trunk --- Merging r381 through r385 into '.': U button.c U README --- Recording mergeinfo for merge of r381 through r385 into '.': U . $ # build, test, ... $ svn commit -m "Final merge of trunk changes to my-calc-branch." Sending . Sending button.c Sending README Transmitting file data .. Committed revision 390.
Now, use svn merge with the --reintegrate
option to replicate your branch changes back into the trunk. You'll need a working copy of /trunk
. You can get one by doing an svn checkout, dredging up an old trunk working copy from somewhere on your disk, or using svn switch (see the section called “Traversing Branches”). Your trunk working copy cannot have any local edits or contain a mixture of revisions (see the section called “Mixed-revision working copies”). While these are typically best practices for merging anyway, they are required when using the --reintegrate
option.
Once you have a clean working copy of the trunk, you're ready to merge your branch back into it:
$ pwd /home/user/calc-trunk $ svn update # (make sure the working copy is up to date) Updating '.': At revision 390. $ svn merge --reintegrate ^/calc/branches/my-calc-branch --- Merging differences between repository URLs into '.': U button.c U integer.c U Makefile --- Recording mergeinfo for merge between repository URLs into '.': U . $ # build, test, verify, ... $ svn commit -m "Merge my-calc-branch back into trunk!" Sending . Sending button.c Sending integer.c Sending Makefile Transmitting file data .. Committed revision 391.
Congratulations, your branch-specific changes have now been merged back into the main line of development. Notice our use of the --reintegrate
option this time around. The option is critical for reintegrating changes from a branch back into its original line of development—don't forget it! It's needed because this sort of “merge back” is a different sort of work than what you've done up until now. Previously, we were asking svn merge to grab the “next set” of changes from one line of development (the trunk) and duplicate them to another (your branch). This is fairly straightforward, and each time Subversion knows how to pick up where it left off. In our prior examples, you can see that first it merges the ranges 345:356 from trunk to branch; later on, it continues by merging the next contiguously available range, 356:380. When doing the final sync, it merges the range 380:385.
When merging your branch back to the trunk, however, the underlying mathematics are quite different. Your feature branch is now a mishmash of both duplicated trunk changes and private branch changes, so there's no simple contiguous range of revisions to copy over. By specifying the --reintegrate
option, you're asking Subversion to carefully replicate only those changes unique to your branch. (And in fact, it does this by comparing the latest trunk tree with the latest branch tree: the resulting difference is exactly your branch changes!)
Keep in mind that the --reintegrate
option is quite specialized in contrast to the more general nature of most Subversion subcommand options. It supports the use case described above, but has little applicability outside of that. Because of this narrow focus, in addition to requiring an up-to-date working copy[29] with no mixed-revisions, it will not function in combination with most of the other svn merge options. You'll get an error if you use any non-global options but these: --accept
, --dry-run
, --diff3-cmd
, --extensions
, or --quiet
.
Now that your private branch is merged to trunk, you may wish to remove it from the repository:
$ svn delete ^/calc/branches/my-calc-branch \ -m "Remove my-calc-branch, reintegrated with trunk in r391." Committed revision 392.
But wait! Isn't the history of that branch valuable? What if somebody wants to audit the evolution of your feature someday and look at all of your branch changes? No need to worry. Remember that even though your branch is no longer visible in the /branches
directory, its existence is still an immutable part of the repository's history. A simple svn log command on the /branches
URL will show the entire history of your branch. Your branch can even be resurrected at some point, should you desire (see the section called “Resurrecting Deleted Items”).
Once a --reintegrate
merge is done from branch to trunk, the branch is no longer usable for further work. It's not able to correctly absorb new trunk changes, nor can it be properly reintegrated to trunk again. For this reason, if you want to keep working on your feature branch, we recommend destroying it and then re-creating it from the trunk:
$ svn delete http://svn.example.com/repos/calc/branches/my-calc-branch \ -m "Remove my-calc-branch, reintegrated with trunk in r391." Committed revision 392. $ svn copy http://svn.example.com/repos/calc/trunk \ http://svn.example.com/repos/calc/branches/my-calc-branch \ -m "Recreate my-calc-branch from trunk@HEAD." Committed revision 393.
There is another way of making the branch usable again after reintegration, without deleting the branch. See the section called “Keeping a Reintegrated Branch Alive”.
The basic mechanism Subversion uses to track changesets—that is, which changes have been merged to which branches—is by recording data in versioned properties. Specifically, merge data is tracked in the svn:mergeinfo
property attached to files and directories. (If you're not familiar with Subversion properties, see the section called “Properties”.)
You can examine the property, just like any other:
$ cd my-calc-branch $ svn propget svn:mergeinfo . /trunk:341-390 $
Warning |
While it is possible to modify |
Tip |
The amount of |
The svn:mergeinfo
property is automatically maintained by Subversion whenever you run svn merge. Its value indicates which changes made to a given path have been replicated into the directory in question. In our previous example, the path which is the source of the merged changes is /trunk
and the directory which has received the changes is /branches/my-calc-branch
. Earlier versions of Subversion maintained the svn:mergeinfo
property silently. You could still detect the changes, after a merge completed, with the svn diff or svn status subcommands, but the merge itself gave no indication when it changed the svn:mergeinfo
property. This is no longer true in Subversion 1.7, which has several new notifications to alert you when a merge updates the svn:mergeinfo
property. These notifications all begin with “--- Recording mergeinfo for” and appear at the end of the merge. Unlike other merge notifications, these don't describe the application of a difference to a working copy (see the section called “Merge Syntax: Full Disclosure”), but instead describe "housekeeping" changes made to keep track of what was merged.
Subversion also provides a subcommand, svn mergeinfo, which is helpful in seeing not only which changesets a directory has absorbed, but also which changesets it's still eligible to receive. This gives a sort of preview of which changes a subsequent svn merge operation would replicate to your branch.
$ cd my-calc-branch # Which changes have already been merged from trunk to branch? $ svn mergeinfo ^/calc/trunk r341 r342 r343 … r388 r389 r390 # Which changes are still eligible to merge from trunk to branch? $ svn mergeinfo ^/calc/trunk --show-revs eligible r391 r392 r393 r394 r395 $
The svn mergeinfo command requires a “source” URL (where the changes come from), and takes an optional “target” URL (where the changes merge to). If no target URL is given, it assumes that the current working directory is the target. In the prior example, because we're querying our branch working copy, the command assumes we're interested in receiving changes to /branches/mybranch
from the specified trunk URL.