This text is a work in progress—highly subject to change—and may not accurately describe any released version of the ApacheTM Subversion® software. Bookmarking or otherwise referring others to this page is probably not such a smart idea. Please visit http://www.svnbook.com/ for stable versions of this book.
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.8 (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.
Merge Tracking
Subversion 1.5 introduced the merge tracking feature to Subversion. Prior to this feature keeping track of merges required cumbersome manual procedures or the use of external tools. Subsequent releases of Subversion introduced many enhancements and bug fixes to merge tracking, which is why we recommend using the most recent versions for both your server and client. Keep in mind that even if your server is running 1.5-1.7, you can still use a 1.8 client. This is particularly important with regard to merge tracking, because the overwhelming majority of fixes and enhancements to it are on the client side.
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 an automatic 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.
An "automatic" merge is simply
one in which you provide the bare minimum of information required
for a merge (i.e. a single merge source and a working copy target)
and let Subversion determine which changes need merging—no
changesets are passed to svn merge via the
-r
or -c
options in an automatic
merge.
[Tip] | 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 r341 through r351 into '.': U doc/INSTALL U src/real.c U src/button.c U Makefile --- Recording mergeinfo for merge of r341 through r351 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[33] 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".
[Tip] | Tip |
---|---|
In this book and elsewhere (Subversion mailing
lists, articles on merge tracking, etc.) you will frequently
come across the term mergeinfo. This
is simply shorthand for the |
Keeping a Branch in Sync Without Merge Tracking
You may not always be able to use Subversion's merge tracking feature, perhaps because your server is running Subversion 1.4 or earlier or you must use an older client. In such a scenario, you can of course still perform merges, but Subversion will need you to manually do many of the historical calculations that it automatically does on your behalf when the merge tracking feature is available.
To replicate the most recent trunk changes you need to perform sync merges the "old-fashioned" way—by specifying ranges of revisions you wish to merge.
Using the ongoing example, you know that you branched
/calc/trunk
to
/calc/branches/my-calc-branch
in revision
341:
$ svn log -v -r341 ------------------------------------------------------------------------ r341 | user | 2013年02月15日 07:41:25 -0500 (2013年2月15日) | 1 line Changed paths: A /calc/branches/my-calc-branch (from /calc/trunk:340) Creating a private branch of /calc/trunk. ------------------------------------------------------------------------
When you are ready to synchronize your branch with the
ongoing changes from trunk, you specify the starting
revision as the revision of /calc/trunk
which the branch was copied from and the ending revision as
the youngest change on /calc/trunk
. You
can find the latter with the svn log command
with the -r
set to HEAD
:
$ svn log -q -rHEAD http://svn.example.com/repos/calc/trunk ------------------------------------------------------------------------ r351 | sally | 2013年02月16日 08:04:22 -0500 (2013年2月16日) ------------------------------------------------------------------------ $ svn merge http://svn.example.com/repos/calc/trunk -r340:351 U doc/INSTALL U src/real.c U src/button.c U Makefile
After any conflicts have been resolved, you can commit the merged changes to your branch. Now, to avoid accidentally trying to merge these same changes into your branch again in the future, you'll need to record the fact that you've already merged them. But where should that record be kept? One of the simplest places to record this information is in the log message for the commit of the merge:
$ svn ci -m "Sync the my-calc-branch with ^/calc/trunk through r351." ...
The next time you sync
/calc/branches/my-calc-branch
with
/calc/trunk
you repeat this process, except
that the starting revision is the youngest
revision that's already been merged in from the trunk.
If you've been keeping good records of your merges in the
commit log messages, you should be able to determine what
that youngest revision was by reading the revision logs
associated with your branch. Once you know your starting
revision, you can perform another sync merge:
$ svn log -q -rHEAD http://svn.example.com/repos/calc/trunk ------------------------------------------------------------------------ r959 | sally | 2013年03月5日 7:30:21 -0500 (2013年3月05日) ------------------------------------------------------------------------ $ svn merge http://svn.example.com/repos/calc/trunk -r351:959 ...
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 Makefile M doc/INSTALL M src/button.c M src/real.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 shows that
its svn:mergeinfo
property has been created.
$ svn diff --depth empty . Index: . =================================================================== --- . (revision 351) +++ . (working copy) Property changes on: . ___________________________________________________________________ Added: svn:mergeinfo Merged /calc/trunk:r341-351
This new property 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 "Sync latest trunk changes to my-calc-branch." Sending . Sending Makefile Sending doc/INSTALL Sending src/button.c Sending src/real.c Transmitting file data .... Committed revision 352.
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.
Why Not Use Patches Instead?
A question may be on your mind, especially if you're a Unix user: why bother to use svn merge at all? Why not simply use svn patch or the operating system's patch command to accomplish the same job? For example:
$ cd my-calc-branch $ svn diff -r 341:351 ^/calc/trunk > my-patch-file $ svn patch my-patch-file U doc/INSTALL U src/real.c U src/button.c U Makefile
In this particular example, there really isn't much difference. But svn merge has special abilities that surpass the patch program. The file format used by patch is quite limited; it's able to tweak file contents only. There's no way to represent changes to trees, such as the addition, removal, or renaming of files and directories. Nor can the patch program notice changes to properties. If Sally's change had, say, added a new directory, the output of svn diff wouldn't have mentioned it at all. svn diff outputs only the limited patch format, so there are some ideas it simply can't express. Even Subversion's own svn patch subcommand, while more flexible than the patch program, still has similar limitations.
The svn merge command, however, can express changes in tree structure and properties by directly applying them to your working copy. Even more important, this command records the changes that have been duplicated to your branch so that Subversion is aware of exactly which changes exist in each location (see the section called "Mergeinfo and Previews"). This is a critical feature that makes branch management usable; without it, users would have to manually keep notes on which sets of changes have or haven't been merged yet.
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 [352:357]; 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 Subversion
1.7 and later, 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.[34] We don't want any needless conflicts, so
we update the working copy and then reattempt the
merge.
$ svn up Updating '.': At revision 361. $ svn merge ^/calc/trunk --- Merging r352 through r361 into '.': U src/real.c U src/main.c --- Recording mergeinfo for merge of r352 through r361 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.
In most of the examples in this chapter the merge target is the root directory of a branch (see the section called "What's a Branch?"). While this is a best practice, you may occasionally need to merge directly to some child of the branch root. This type of merge is called a subtree merge and the mergeinfo recorded to describe it is called subtree mergeinfo. There is nothing special about subtree merges or subtree mergeinfo. In fact there is really only one important point to keep in mind about these concepts: the complete record of merges to a branch may not be contained solely in the mergeinfo on the branch root. You may have to consider subtree mergeinfo to get a full accounting. Fortunately Subversion does this for you and rarely will you need to concern yourself with it. A brief example will help explain:
# We need to merge r958 from trunk to branches/proj-X/doc/INSTALL, # but that revision also affects main.c, which we don't want to merge: $ svn log --verbose --quiet -r 958 ^/ ------------------------------------------------------------------------ r958 | bruce | 2011年10月20日 13:28:11 -0400 (2011年10月20日) Changed paths: M /trunk/doc/INSTALL M /trunk/src/main.c ------------------------------------------------------------------------ # No problem, we'll do a subtree merge targeting the INSTALL file # directly, but first take a note of what mergeinfo exists on the # root of the branch: $ cd branches/proj-X $ svn propget svn:mergeinfo --recursive Properties on '.': svn:mergeinfo /trunk:651-652 # Now we perform the subtree merge, note that merge source # and target both point to INSTALL: $ svn merge ^/trunk/doc/INSTALL doc/INSTALL -c 958 --- Merging r958 into 'doc/INSTALL': U doc/INSTALL --- Recording mergeinfo for merge of r958 into 'doc/INSTALL': G doc/INSTALL # Once the merge is complete there is now subtree mergeinfo on INSTALL: $ svn propget svn:mergeinfo --recursive Properties on '.': svn:mergeinfo /trunk:651-652 Properties on 'doc/INSTALL': svn:mergeinfo /trunk/doc/INSTALL:651-652,958 # What if we then decide we do want all of r958? Easy, all we need do is # repeat the merge of that revision, but this time to the root of the # branch, Subversion notices the subtree mergeinfo on INSTALL and doesn't # try to merge any changes to it, only the changes to main.c are merged: $ svn merge ^/subversion/trunk . -c 958 --- Merging r958 into '.': U src/main.c --- Recording mergeinfo for merge of r958 into '.': U . --- Eliding mergeinfo from 'doc/INSTALL': U doc/INSTALL
You might be wondering
why INSTALL
in the above example has
mergeinfo for r651-652, when we only merged r958. This is
due to mergeinfo inheritance, which we'll cover in the
sidebar
Mergeinfo Inheritance. Also note that the subtree mergeinfo on
doc/INSTALL
was removed, or
"elided". This is called
mergeinfo elision and it occurs
whenever Subversion detects redundant subtree mergeinfo.
[Tip] | 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 affected path(s). This caused some level of confusion and frustration. Subversion 1.7 and later 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[35]:
$ svn up # (make sure the working copy is up to date) Updating '.': At revision 378. $ svn merge ^/calc/trunk --- Merging r362 through r378 into '.': U src/main.c --- Recording mergeinfo for merge of r362 through r378 into '.': U . $ # build, test, ... $ svn commit -m "Final merge of trunk changes to my-calc-branch." Sending . Sending src/main.c Transmitting file data . Committed revision 379.
Now, use svn merge subcommand to automatically
replicate your branch changes back into the trunk. This type of
merge is called an
"automatic reintegrate" merge. You'll need
a working copy of /calc/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").
[Tip] | Tip |
---|---|
The term "reintegrating" comes from the
merge option |
Your trunk working copy cannot have any local edits, switched paths, 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 for automatic reintegrate merges.
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 Updating '.': At revision 379. $ svn merge ^/calc/branches/my-calc-branch --- Merging differences between repository URLs into '.': U src/real.c U src/main.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 Makefile Sending src/main.c Sending src/real.c Transmitting file data ... Committed revision 380.
Congratulations, your branch-specific changes have now
been merged back into the main line of development. Notice that
the automatic reintegrate merge did 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 341:351 from
/calc/trunk
to
/calc/branches/my-calc-branch
;
later on, it continues by merging the next contiguously
available range, 351:361. When doing the final sync, it
merges the range 361:378.
When merging /calc/branches/my-calc-branch
back to the /calc/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 using an automatic merge, 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 automatic reintegrate merges only support
the use case described above. Because of this narrow focus, in
addition to the requirements previously mentioned (up-to-date working
copy [36]
with no mixed-revisions, switched paths or local changes) 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 r381." ...
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 /calc/branches
directory, its
existence is still an immutable part of the repository's
history. A simple svn log command on
the /calc/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").
If you choose not to delete your branch after reintegrating it to the trunk you may continue to perform sync merges from the trunk and then reintegrate the branch again[37]. If you do this, only the changes made on your branch after the first reintegrate are merged to the trunk.
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 mergeinfo property, just like any other versioned property:
$ cd my-calc-branch $ svn pg svn:mergeinfo -v Properties on '.': svn:mergeinfo /calc/trunk:341-378
[Warning] | Warning |
---|---|
While it is possible to
modify |
[Tip] | 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
/calc/trunk
and the directory which has
received the changes is
/calc/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. In
Subversion 1.7 and later this is no longer true as there are
several 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 the merge
relationships between two branches; specifically which changesets
a directory has absorbed or which changesets it's still eligible
to receive. The latter gives a sort of preview of which changes a
subsequent svn merge operation would replicate
to your branch. By default, svn mergeinfo gives
an graphical overview of the relationship between to branches.
Returning to our earlier example, we use the subcommand to analyze
the relationship between /calc/trunk
and
/calc/branches/my-calc-branch
:
$ cd my-calc-branch $ svn mergeinfo ^/calc/trunk youngest common ancestor | last full merge | | tip of branch | | | repository path 340 382 | | -------| |------------ calc/trunk \ / \ / --| |------------ calc/branches/my-calc-branch | | 379 382
The diagram shows that /calc/branches/my-calc-branch
was copied from /calc/trunk@340
and
that most recent automatic merge was the reintegrate merge we made
from the branch to the trunk in r380. Notice that the diagram does
not show the four automatic sync merges we made
in revisions 352, 362, 372, and 379. Only the most recent automatic
merge, in either direction[38], is shown. This
default output is useful for obtaining an overview of the merges
between two branches, but to see the specific revisions which were
merged we use the --show-revs=merged
option:
$ svn mergeinfo ^/calc/trunk --show-revs merged r344 r345 r346 ... r366 r367 r368
Likewise, to see which changes are eligible to merge from the
trunk to the branch we can use the --show-revs=eligible
option:
$ svn mergeinfo ^/calc/trunk --show-revs eligible r380 r381 r382
Operative and Inoperative Merge Revisions
The revision lists produced by the --show-revs
option include only revisions which made (or would make)
changes when merged. So while we have merged a contiguous range of
revisions (i.e. r341-378) from /calc/trunk
to
/calc/branches/my-calc-branch
, only the
revisions listed with the --show-revs=merged
option
actually represent changes made on /calc/trunk
.
These revisions are described as "operative"
revisions as regards merging, not to be confused with the
operative revision used with the -r
option, see
the section called "Peg and Operative Revisions". Not suprisingly, the
revisions in the range r341-378 that are not
listed as merged are termed "inoperative" revisions.
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 /calc/branches/my-calc-branch
from the specified trunk URL.
Since Subversion 1.7, the
svn mergeinfo subcommand can also account for
subtree mergeinfo and non-inheritable mergeinfo. It accounts for
subtree mergeinfo by use of the --recursive
or
--depth
options, while non-inheritable mergeinfo
is considered by default.
Mergeinfo Inheritance
When a path has
the svn:mergeinfo
property set on it we
say it has explicit mergeinfo. This
explicit mergeinfo describes not only what changes were
merged into that particular directory, but also all the
children of that directory (because those children inherit
the mergeinfo of their parent path). For example:
# What explicit mergeinfo exists on a branch? $ svn propget svn:mergeinfo ^/branches/proj-X --recursive /trunk:651-652 # What children does proj-X have? $ svn list --recursive ^/branches/proj-X doc/ doc/INSTALL README src/main.c # Ask what revs were merged to a file with no explicit mergeinfo $ svn mergeinfo ^/trunk/src/main.c ^/branches/proj-X/src/main.c \ --show-revs merged 651 652
Notice from our first subcommand that only the root of
/branches/proj-X
has any explicit
mergeinfo. However, when we use
svn mergeinfo to ask what was merged to
/branches/proj-X/src/main.c
it reports
that the two revisions described in the explicit mergeinfo
on /branches/proj-X
were merged. This is
because /branches/proj-X/src/main.c
, having
no explicit mergeinfo of its own, inherits the mergeinfo from
its nearest parent with explicit mergeinfo,
/branches/proj-X
.
There are two cases in which mergeinfo is not inherited.
First, if a path has explicit mergeinfo, then it never inherits
mergeinfo. Another way to think of this is that explicit
mergeinfo is always a complete record of the merges to a given
path. Once it exists it overrides any mergeinfo that path might
otherwise inherit. The second way is when dealing with
non-inheritable mergeinfo, a special type of explicit mergeinfo
that applies only to the directory on which
the svn:mergeinfo
property is set (and it's
only directories, non-inheritable mergeinfo is never set on
files). For example:
# The '*' decorator indicates non-inheritable mergeinfo $ svn propget svn:mergeinfo ^/branches/proj-X /trunk:651-652,758* # Revision 758 is non-inheritable, but still applies to the path it is # set on. Here the '*' decorator signals that r758 is only partially # merged from trunk. $ svn mergeinfo ^/trunk ^/branches/proj-X --show-revs merged 651 652 758* # Revision 758 is not reported as merged because it is non-inheritable # and applies only to ^/trunk $ svn mergeinfo ^/trunk/src/main.c ^/branches/proj-X/src/main.c \ --show-revs merged 651 652
You might never have to think about mergeinfo inheritance or encounter non-inheritable mergeinfo in your own repository. A discussion of the full ramifications of mergeinfo inheritance are beyond the scope of this book. If you have more questions check out some of the references mentioned in the section called "The Final Word on Merge Tracking"
Let's say we have a branch with both subtree and non-inheritable mergeinfo:
$ svn pg svn:mergeinfo -vR # Non-inheritable mergeinfo Properties on '.': svn:mergeinfo /calc/trunk:354,385-388* # Subtree mergeinfo Properties on 'Makefile': svn:mergeinfo /calc/trunk/Makefile:354,380
From the above mergeinfo we see that r385-388 has only been
merged into the root of the branch, but not any of the root's
children. We also see that r380 has only been merged to
Makefile
.
When we use svn mergeinfo with the
--recursive
option to see what has been merged
from /calc/trunk
to this branch, we see three
revisions are flagged with the *
marker:
$ svn mergeinfo -R --show-revs=merged ^/calc/trunk . r354 r380* r385 r386 r387* r388*
The *
indicates revisions that are only
partially merged to the target in question
(the meaning is the same if we are checking for eligible
revisions). What this means in this example is that if we tried
to merge r380, r387, or r388 from ^/trunk
then
more changes would result. Likewise, because r354, r385 and r386 are
not flagged with a *
,
we know that re-merging those revisions would have no result.
[39]
Another way to get a more precise preview of a merge
operation is to use the --dry-run
option:
$ svn merge ^/paint/trunk paint-feature-branch --dry-run --- Merging r290 through r383 into 'paint-feature-branch': U paint-feature-branch/src/palettes.c U paint-feature-branch/src/brushes.c U paint-feature-branch/Makefile $ 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] | Tip |
---|---|
After performing a merge operation, but before
committing the results of the merge, you can use
|
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 (unless you've made local
modifications to your working copy—but we already
stressed that you shouldn't merge into such an
environment). If you don't like the results of the merge,
simply run svn revert . -R
to revert
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.
An extremely common use for svn merge
is to roll back a change that has already been committed.
Suppose you're working away happily on a working copy of
/calc/trunk
, and you discover that the
change made back in revision 392, which changed
several code files, is completely wrong. It never
should have been committed. 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 merge ^/calc/trunk . -c-392 --- Reverse-merging r392 into '.': U src/real.c U src/main.c U src/button.c U src/integer.c --- Recording mergeinfo for reverse merge of r392 into '.': U . $ svn st M src/button.c M src/integer.c M src/main.c M src/real.c $ svn diff ... # verify that the change is removed ... $ svn commit -m "Undoing erroneous change committed in r392." Sending src/button.c Sending src/integer.c Sending src/main.c Sending src/real.c Transmitting file data .... Committed revision 399.
As we mentioned earlier, one way to think about a
repository revision is as a specific changeset. By using the
-r
option, you can ask svn
merge to apply a changeset, or a whole range of
changesets, to your working copy. In our case of undoing a
change, we're asking svn merge to apply
changeset r392 to our working copy
backward.
Keep in mind that rolling back a change like this is just
like any other svn merge operation, so you
should use svn status and svn
diff to confirm that your work is in the state you
want it to be in, and then use svn commit
to send the final version to the repository. After
committing, this particular changeset is no longer reflected
in the HEAD
revision.
Again, you may be thinking: well, that really didn't undo
the commit, did it? The change still exists in revision 392.
If somebody checks out a version of the
calc
project between revisions 392 and
398, she'll still see the bad change, right?
Yes, that's true. When we talk about
"removing" a change, we're really talking about
removing it from the HEAD
revision. The
original change still exists in the repository's history. For
most situations, this is good enough. Most people are only
interested in tracking the HEAD
of a
project anyway. There are special cases, however, where you
really might want to destroy all evidence of the commit.
(Perhaps somebody accidentally committed a confidential
document.) This isn't so easy, it turns out, because
Subversion was deliberately designed to never lose
information. Revisions are immutable trees that build upon
one another. Removing a revision from history would cause a
domino effect, creating chaos in all subsequent revisions and
possibly invalidating all working copies.[40]
The great thing about version control systems is that
information is never lost. Even when you delete a file or
directory, it may be gone from the HEAD
revision, but the object still exists in earlier revisions.
One of the most common questions new users ask is, "How
do I get my old file or directory back?"
The first step is to define exactly which item you're trying to resurrect. Here's a useful metaphor: you can think of every object in the repository as existing in a sort of two-dimensional coordinate system. The first coordinate is a particular revision tree, and the second coordinate is a path within that tree. So every version of your file or directory is defined by a specific coordinate pair. (Remember the "peg revision" syntax—foo.c@224—mentioned back in the section called "Peg and Operative Revisions".)
First, you might need to use svn log to
discover the exact coordinate pair you wish to resurrect. A
good strategy is to run svn log --verbose
in a directory that used to contain your deleted item. The
--verbose
(-v
) option shows
a list of all changed items in each revision; all you need to
do is find the revision in which you deleted the file or
directory. You can do this visually, or by using another tool
to examine the log output (via grep, or
perhaps via an incremental search in an editor). If you know that
the item in question was recently deleted you might also use
the --limit
option to keep the log output brief
enough to examine manually.
$ cd calc/trunk $ svn log -v --limit 3 ------------------------------------------------------------------------ r401 | sally | 2013年02月19日 23:15:44 -0500 (2013年2月19日) | 1 line Changed paths: M /calc/trunk/src/main.c Follow-up to r400: Fix typos in help text. ------------------------------------------------------------------------ r400 | bill | 2013年02月19日 20:55:08 -0500 (2013年2月19日) | 4 lines Changed paths: M /calc/trunk/src/main.c D /calc/trunk/src/real.c * calc/trunk/src/main.c: Update help text. * calc/trunk/src/real.c: Remove this file, none of the APIs implemented here are used anymore. ------------------------------------------------------------------------ r399 | sally | 2013年02月19日 20:05:14 -0500 (2013年2月19日) | 1 line Changed paths: M /calc/trunk/src/button.c M /calc/trunk/src/integer.c M /calc/trunk/src/main.c M /calc/trunk/src/real.c Undoing erroneous change committed in r392. ------------------------------------------------------------------------
In the example, we're assuming that you're looking for a
deleted file real.c
. By looking through
the logs of a parent directory, you've spotted that this file
was deleted in revision 400. Therefore, the last version of
the file to exist was in the revision right before that.
Conclusion: you want to resurrect the path
/calc/trunk/real.c
from revision
399.
That was the hard part—the research. Now that you know what you want to restore, you have two different choices.
One option is to use svn merge to apply
revision 400 "in reverse." (We already
discussed how to undo changes in
the section called "Undoing Changes".) This
would have the effect of re-adding real.c
as a local modification. The file would be scheduled for
addition, and after a commit, the file would again exist
in HEAD
.
In this particular example, however, this is probably not
the best strategy. Reverse-applying revision 400 would not
only schedule real.c
for addition, but
the log message indicates that it would also undo certain
changes to main.c
, which you don't
want. Certainly, you could reverse-merge revision 400 and
then svn revert the local modifications to
main.c
, but this technique doesn't
scale well. What if 90 files were changed in revision
400?
A second, more targeted strategy is not to use svn merge at all, but rather to use the svn copy command. Simply copy the exact revision and path "coordinate pair" from the repository to your working copy:
$ svn copy ^/calc/trunk/src/real.c@399 ./real.c A real.c $ svn st A + real.c # Commit the resurrection. ...
The plus sign in the status output indicates that the item
isn't merely scheduled for addition, but scheduled for
addition "with history." Subversion remembers
where it was copied from. In the future, running svn
log on this file will traverse back through the
file's resurrection and through all the history it had prior
to revision 399. In other words, this new
real.c
isn't really new; it's a direct
descendant of the original, deleted file. This is usually
considered a good and useful thing. If, however, you wanted
to resurrect the file without
maintaining a historical link to the old file, this technique
works just as well:
$ svn cat ^/calc/trunk/src/real.c@399 > ./real.c $ svn add real.c A real.c # Commit the resurrection. ...
Although our example shows us resurrecting a file, note that these same techniques work just as well for resurrecting deleted directories. Also note that a resurrection doesn't have to happen in your working copy—it can happen entirely in the repository:
$ svn copy ^/calc/trunk/src/real.c@399 ^/calc/trunk/src/real.c \ -m "Resurrect real.c from revision 399." Committed revision 402. $ svn up Updating '.': A real.c Updated to revision 402.
[33] This was introduced in svn 1.6.
[34] The svn
merge subcommand
option --allow-mixed-revisions
allows you to
override this prohibition, but you should only do so if you
understand the ramifications and have a good reason for
it.
[35] Since Subversion 1.7 you don't absolutely have to do all your sync merges to the root of your branch as we do in this example. If your branch is effectively synced via a series of subtree merges then the reintegrate will work, but ask yourself, if the branch is effectively synced, then why are you doing subtree merges? Doing so is almost always needlessly complex.
[36] Automatic reintegrate merges are allowed if the target is a shallow checkout (see the section called "Sparse Directories") but any paths affected by the diff which are "missing" due to the sparse working copy will be skipped—this is probably not what you intended!
[37] Only Subversion 1.8 supports this reuse of a feature branch. Earlier versions require some special handling before a feature branch can be reintegrated more than once. See the earlier version of this chapter for more information: https://svnbook.red-bean.com/en/1.7/svn.branchmerge.basicmerging.html#svn.branchemerge.basicmerging.reintegrate
[38] By "direction" we mean either trunk-to-branch (automatic sync) or branch-to-trunk (automatic reintegrate) merges.
[39] This is a good example of inoperative merge revisions.
[40] The Subversion project has plans, however, to someday implement a command that would accomplish the task of permanently deleting information. In the meantime, see the section called "svndumpfilter" for a possible workaround.