Basic Merging

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 command.

In the examples that follow, we're assuming that both your Subversion client and server are running Subversion 1.5 (or later). If either client or server is older than version 1.5, then things are more complicated: the system won't track changes automatically, and you'll have 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 making sure that your client and server are at least version 1.5 or later.

Changesets

Before we get too far in, we should warn you that there's going to be 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 purpose, 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 Nth 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 revision 9238.” 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 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: svn merge -c 9238 would merge changeset #9238 into your working copy.

Keeping a Branch in Sync

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 have continued 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. In fact, this is a best practice: by frequently keeping your branch in sync with the main development line, it helps prevent “surprise” conflicts when it comes time for you to fold your changes back into the trunk.

Subversion is aware of the history of your branch and knows when it divided away from the trunk. To replicate the latest, greatest trunk changes to your branch, 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 http://svn.example.com/repos/calc/trunk
--- Merging r345 through r356 into '.':
U    button.c
U    integer.c

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      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. You might 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 and start a long “what's going on?” discussion with your collaborators. If things look good, however, then you can submit your changes into the repository:

$ svn commit -m "Merged latest trunk changes to my-calc-branch."
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'd like to replicate the latest trunk changes to your branch and bring yourself in sync. Just run the same merge command again!

$ svn merge http://svn.example.com/repos/calc/trunk
--- Merging r357 through r380 into '.':
U    integer.c
U    Makefile
A    README

Subversion knows which trunk changes you've already replicated to your branch, so it carefully replicates only those changes you don't yet have. Once again, you'll have to build, test, and svn commit the local modifications to your branch.

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 in sync with the trunk again, just as you've been doing all along:

$ svn merge http://svn.example.com/repos/calc/trunk
--- Merging r381 through r385 into '.':
U    button.c
U    README

$ # build, test, ...

$ svn commit -m "Final merge of trunk changes to my-calc-branch."
Sending        button.c
Sending        README
Transmitting file data ..
Committed revision 390.

Now, you use svn merge to replicate your branch changes back into the trunk. You'll need an up-to-date working copy of /trunk. You can do this by either doing an svn checkout, dredging up an old trunk working copy from somewhere on your disk, or by using svn switch (see the section called “Traversing Branches”.) However you get a trunk working copy, remember that it's a best practice to do your merge into a working copy that has no local edits and has been recently updated. If your working copy isn't “clean” in these ways, you can run into some unnecessary conflict-related headaches.

Once you have a clean working copy of the trunk, you're ready merge your branch back into it:

$ pwd
/home/user/calc-trunk

$ svn merge --reintegrate http://svn.example.com/repos/calc/branches/my-calc-branch
--- Merging r341 through r390 into '.':
U    button.c
U    integer.c
U    Makefile

$ # build, test, verify, ...

$ svn commit -m "Merge my-calc-branch back into trunk!"
Sending        button.c
Sending        integer.c
Sending        Makefile
Transmitting file data ..
Committed revision 391.

Congratulations, your branch has now been re-merged back into the main line of development. Notice our use of the --reintegrate option this time around. The option is needed because this sort of “merge back” is a different sort of work than what you've been doing up until now. Previously, we had been asking svn merge to grab the “next set” of changes from one branch and duplicate them to another. 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 is quite different. Your feature branch is now a mish-mosh 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!)

Now that your branch is merged to trunk, you'll no longer need your branch. You can destroy your working copy of the branch, and also do some basic housecleaning in the repository:

$ svn delete http://svn.example.com/repos/calc/branches/my-calc-branch
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”).

Mergeinfo and Previews

The basic mechanism Subversion uses to track changesets—that is, which changes have been merged to which branches—is by recording data in properties. Specifically, merge data is tracked in the svn:mergeinfo property attached to files and directories. (If you're not familiar with Subversion properties, now is the time to go skim over the section called “Properties”.)

You can examine the property, just like any other:

$ cd my-calc-branch
$ svn propget svn:mergeinfo .
/trunk:341-390

It is not recommended that you change the value of this property yourself, unless you really know what you're doing. (An example of this comes up in the section called “Blocking Changes”.) In general, this property is automatically maintained by Subversion whenever you run svn merge, and its value indicates which changes have already been replicated into a particular directory.

As we saw earlier, there's also a subcommand svn mergeinfo, which can be 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 the next set of changes that svn merge will replicate to your branch.

$ svn mergeinfo .
Path: .
  Source path: /trunk
    Merged ranges: r341:390
    Eligible ranges: r391:395

The svn mergeinfo command, by default, looks back at the history of the thing you're querying and tries to make a sane guess about the “source” from which you want to be copying changes. In our prior example, because we're querying our branch working copy, the command assumes we're interested in receiving changes from /trunk (since that's where our branch was originally copied from). However, if another coworker had a different branch going on that we wanted to merge changes from, we could have this command tell us about eligible changesets from that source too:

$ svn mergeinfo --from-source \
http://svn.example.com/repos/calc/branches/other-branch  .
Path: .
  Source path: /branches/other-branch
    Merged ranges:
    Eligible ranges: r360:364

Another way to get a more precise preview of a merge operation is to use the --dry-run option.

$ svn merge http://svn.example.com/repos/calc/trunk --dry-run
U    integer.c

$ svn status
#  nothing printed, working copy is still unchanged.

The --dry-run option doesn't actually apply any local changes to the working copy. It shows only status codes that would be printed in a real merge. It's useful for getting a “high level” preview of the potential merge, for those times when running svn diff gives too much detail.

Tip

After performing a merge operation, but before committing the results of the merge, you can use svn diff --depth=empty /path/to/merge/target to see only the changes to the immediate target of your merge. If your merge target was a directory, only property differences will be displayed. This is a handy way to see the changes to the svn:mergeinfo property recorded by the merge operation, which will remind you about what you've just merged.

Of course, the best way to preview a merge operation is to just do it! Remember, running svn merge isn't an inherently risky thing; if you don't like the results, simply svn revert -R the changes from your working copy and retry the command with different options. The merge isn't final until you actually svn commit the results.

Tip

While it's perfectly fine to experiment with merges by running svn merge and svn revert over and over, you may run into some annoying (but easily bypassed) roadblocks. For example, if the merge operation adds a new file (i.e., schedules it for addition), then svn revert won't actually remove the file; it simply unschedules the addition. You're left with an unversioned file. If you then attempt to run the merge again, you may get conflicts due to the unversioned file “being in the way.” Solution? After performing a revert, be sure to clean up the working copy and remove unversioned files and directories. The output of svn status should be as clean (read: empty) as possible!