This text is a work in progress—highly subject to change—and may not accurately describe any released version of the Apache™ 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.
At this point, you should understand how each commit creates a new state of the filesystem tree (called a “revision”) in the repository. If you don't, go back and read about revisions in the section called “Phiên bản”.
Let's revisit the example from
Chapter 1, Những Khái Niệm Cơ Bản. Remember that you and your
collaborator, Sally, are sharing a repository that contains two
projects, paint
and
calc
. Notice that in Figure 4.2, “Starting repository layout”, however, each project
directory now contains subdirectories named
trunk
and branches
.
The reason for this will soon become clear.
As before, assume that Sally and you both have working
copies of the “calc” project. Specifically, you
each have a working copy of /calc/trunk
.
All the files for the project are in this subdirectory rather
than in /calc
itself, because your team has
decided that /calc/trunk
is where the
“main line” of development is going to take
place.
Let's say that you've been given the task of implementing a
large software feature. It will take a long time to write, and
will affect all the files in the project. The immediate problem
is that you don't want to interfere with Sally, who is in the
process of fixing small bugs here and there. She's depending on
the fact that the latest version of the project (in
/calc/trunk
) is always usable. If you
start committing your changes bit by bit, you'll surely break
things for Sally (and other team members as well).
One strategy is to crawl into a hole: you can stop sharing information for a week or two, gutting and reorganizing all the files in your private working copy but not committing or updating until you're completely finished with your task. There are a number of problems with this, though. First, it's not very safe. Should something bad happen to your working copy or computer, you risk losing all your changes. Second, it's not very flexible. Unless you manually replicate your changes across different working copies or computers, you're stuck trying to make your changes in a single working copy. Similarly, it's difficult to share your work-in-progress with anyone else. A common software development “best practice” is to allow your peers to review your work as you go. If nobody sees your intermediate commits, you lose potential feedback and may end up going down the wrong path for weeks before another person on your team notices. Finally, when you're finished with all your changes, you might find it very difficult to merge your completed work with the rest of the company's main body of code. Sally (or others) may have made many other changes in the repository that are difficult to incorporate into your working copy when you eventually run svn update after weeks of isolation.
The better solution is to create your own branch, or line of development, in the repository. This allows you to save your not-yet-completed work frequently without interfering with others' changes and while still selectively sharing information with your collaborators. You'll see exactly how this works as we continue.
Creating a branch is very simple—you make a copy of
your project tree in the repository using the svn
copy command. Since your project's source code is
rooted in the /calc/trunk
directory, it's
that directory that you'll copy. Where should the new
copy live? Wherever you wish. The repository location in
which branches are stashed is left by Subversion as a matter
of project policy. Finally, your branch will need a name to
distinguish it from other branches. Once again, the name you
choose is unimportant to Subversion—you can use whatever
name works best for you and your team.
Let's assume that your team (like most) has a policy of
creating branches in the branches
directory that is a sibling of the project's trunk
(the /calc/branches
directory in our
scenario). Lacking inspiration, you settle
on my-calc-branch
as the name you wish to
give your branch. This means that you'll create a new
directory, /calc/branches/my-calc-branch
,
which begins its life as a copy
of /calc/trunk
.
You may already have seen svn copy used to copy one file to another within a working copy. But it can also be used to do a remote copy—a copy that immediately results in a newly committed repository revision and for which no working copy is required at all. Just copy one URL to another:
$ svn copy ^/calc/trunk ^/calc/branches/my-calc-branch \ -m "Creating a private branch of /calc/trunk." Committed revision 341. $
This command causes a near-instantaneous commit in the
repository, creating a new directory in revision 341. The new
directory is a copy of /calc/trunk
. This
is shown in Figure 4.3, “Repository with new copy”.[31] While
it's also possible to create a branch by using svn
copy to duplicate a directory within the working
copy, this technique isn't recommended. It can be quite slow,
in fact! Copying a directory on the client side is a
linear-time operation, in that it actually has to duplicate
every file and subdirectory within that working copy directory
on the local disk. Copying a directory on the server,
however, is a constant-time operation, and it's the way most
people create branches. In addition, this practice raises the
possibility of copying mixed-revision working copies. This isn't
inherently dangerous, but can cause unnecessary complications later
during merging. If you do choose to create a branch by copying a
working copy path, you should be sure the source directory has no
local modifications and is not at mixed-revisions.
Now that you've created a branch of the project, you can check out a new working copy to start using it:
$ svn checkout http://svn.example.com/repos/calc/branches/my-calc-branch A my-calc-branch/doc A my-calc-branch/src A my-calc-branch/doc/INSTALL A my-calc-branch/src/real.c A my-calc-branch/src/main.c A my-calc-branch/src/button.c A my-calc-branch/src/integer.c A my-calc-branch/Makefile A my-calc-branch/README Checked out revision 341. $
There's nothing special about this working copy; it simply
mirrors a different directory in the repository. When you
commit changes, however, Sally won't see them when she
updates, because her working copy is of
/calc/trunk
. (Be sure to read the section called “Traversing Branches” later in this chapter: the
svn switch command is an alternative way of
creating a working copy of a branch.)
Let's pretend that a week goes by, and the following commits happen:
You make a change to
/calc/branches/my-calc-branch/src/button.c
,
which creates revision 342.
You make a change to
/calc/branches/my-calc-branch/src/integer.c
,
which creates revision 343.
Sally makes a change to
/calc/trunk/src/integer.c
, which creates
revision 344.
Now two independent lines of development (shown
in Figure 4.4, “The branching of one file's history”) are happening on
integer.c
.
Things get interesting when you look at the history of
changes made to your copy of integer.c
:
$ pwd /home/user/my-calc-branch $ svn log -v src/integer.c ------------------------------------------------------------------------ r343 | user | 2013-02-15 14:11:09 -0500 (Fri, 15 Feb 2013) | 1 line Changed paths: M /calc/branches/my-calc-branch/src/integer.c * integer.c: frozzled the wazjub. ------------------------------------------------------------------------ r341 | user | 2013-02-15 07:41:25 -0500 (Fri, 15 Feb 2013) | 1 line Changed paths: A /calc/branches/my-calc-branch (from /calc/trunk:340) Creating a private branch of /calc/trunk. ------------------------------------------------------------------------ r154 | sally | 2013-01-30 04:20:03 -0500 (Wed, 30 Jan 2013) | 2 lines Changed paths: M /calc/trunk/src/integer.c * integer.c: changed a docstring. ------------------------------------------------------------------------ … ------------------------------------------------------------------------ r113 | sally | 2013-01-26 15:50:21 -0500 (Sat, 26 Jan 2013) | 2 lines Changed paths: M /calc/trunk/src/integer.c * integer.c: Revise the fooplus API. ------------------------------------------------------------------------ r8 | sally | 2013-01-17 16:55:36 -0500 (Thu, 17 Jan 2013) | 1 line Changed paths: A /calc/trunk/Makefile A /calc/trunk/README A /calc/trunk/doc/INSTALL A /calc/trunk/src/button.c A /calc/trunk/src/integer.c A /calc/trunk/src/main.c A /calc/trunk/src/real.c Initial trunk code import for calc project. ------------------------------------------------------------------------
Notice that Subversion is tracing the history of your
branch's integer.c
all the way back
through time, even traversing the point where it was copied.
It shows the creation of the branch as an event in the
history, because integer.c
was implicitly
copied when all of /calc/trunk/
was
copied. Now look at what happens when Sally runs the same
command on her copy of the file:
$ pwd /home/sally/calc $ svn log -v src/integer.c ------------------------------------------------------------------------ r344 | sally | 2013-02-15 16:44:44 -0500 (Fri, 15 Feb 2013) | 1 line Changed paths: M /calc/trunk/src/integer.c Refactor the bazzle functions. ------------------------------------------------------------------------ r154 | sally | 2013-01-30 04:20:03 -0500 (Wed, 30 Jan 2013) | 2 lines Changed paths: M /calc/trunk/src/integer.c * integer.c: changed a docstring. ------------------------------------------------------------------------ … ------------------------------------------------------------------------ r113 | sally | 2013-01-26 15:50:21 -0500 (Sat, 26 Jan 2013) | 2 lines Changed paths: M /calc/trunk/src/integer.c * integer.c: Revise the fooplus API. ------------------------------------------------------------------------ r8 | sally | 2013-01-17 16:55:36 -0500 (Thu, 17 Jan 2013) | 1 line Changed paths: A /calc/trunk/Makefile A /calc/trunk/README A /calc/trunk/doc/INSTALL A /calc/trunk/src/button.c A /calc/trunk/src/integer.c A /calc/trunk/src/main.c A /calc/trunk/src/real.c Initial trunk code import for calc project. ------------------------------------------------------------------------
Sally sees her own revision 344 change, but not the change you made in revision 343. As far as Subversion is concerned, these two commits affected different files in different repository locations. However, Subversion does show that the two files share a common history. Before the branch copy was made in revision 341, the files used to be the same file. That's why you and Sally both see the changes made between revisions 8 and 154.
You should remember two important lessons from this section. First, Subversion has no internal concept of a branch—it knows only how to make copies. When you copy a directory, the resultant directory is only a “branch” because you attach that meaning to it. You may think of the directory differently, or treat it differently, but to Subversion it's just an ordinary directory that happens to carry some extra historical information.
Second, because of this copy mechanism, Subversion's
branches exist as normal filesystem
directories in the repository. This is different
from other version control systems, where branches are
typically defined by adding
extra-dimensional “labels” to collections of
files. The location of your branch directory doesn't matter
to Subversion. Most teams follow a convention of putting all
branches into a /branches
directory, but
you're free to invent any policy you wish.
[31] Subversion does not support copying between different repositories. When using URLs with svn copy or svn move, you can only copy items within the same repository.