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 第 2.2 节 “修订版本”.
For this chapter, we'll go back to the same example from 第 1 章 基本概念. Remember that you and your collaborator, Sally, are
sharing a repository that contains two projects, paint
and calc
. Notice that in 图 4.2 “开始规划版本库”, 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 and Sally can stop sharing
information for a week or two. That is, start gutting and reorganizing all
the files in your working copy, but don't commit or update until you're
completely finished with the task. There are a number of problems with
this, though. First, it's not very safe. Most people like to save their
work to the repository frequently, should something bad accidentally happen
to their working copy. Second, it's not very flexible. If you do your work
on different computers (perhaps you have a working copy of
/calc/trunk
on two different machines), you'll need to
manually copy your changes back and forth or just do all the work on a
single computer. By that same token, it's difficult to share your changes
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 remerge your final 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—especially if you 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 half-broken work frequently without interfering with others, yet you can still selectively share information with your collaborators. You'll see exactly how this works as we go.
Creating a branch is very simple—you make a copy of the project in the
repository using the svn copy command. Subversion is
able to copy not only single files, but whole directories as well. In this
case, you want to make a copy of the /calc/trunk
directory. Where should the new copy live? Wherever you wish—it's a
matter of project policy. Let's say that your team has a policy of creating
branches in the /calc/branches
area of the repository,
and you want to name your branch my-calc-branch
. You'll
want to 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 entirely within the repository. Just copy one URL to another:
$ svn copy http://svn.example.com/repos/calc/trunk \ http://svn.example.com/repos/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 图 4.3 “版本库与复制”.[23] 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.
现在你已经在项目里建立分支了,你可以取出一个新的工作副本来开始使用:
$ svn checkout http://svn.example.com/repos/calc/branches/my-calc-branch A my-calc-branch/Makefile A my-calc-branch/integer.c A my-calc-branch/button.c 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 第 5 节 “使用分支” later in this chapter: the svn
switch command is an alternative way of creating a working copy of
a branch.)
我们假定本周就要过去了,如下的提交发生:
你修改了/calc/branches/my-calc-branch/button.c
,生成修订版本342。
你修改了/calc/branches/my-calc-branch/integer.c
,生成修订版本343。
Sally修改了/calc/trunk/integer.c
,生成了修订版本344。
Now two independent lines of development (shown in 图 4.4 “一个文件的分支历史”) are happening on
integer.c
.
当你看到integer.c
的改变时,你会发现很有趣:
$ pwd /home/user/my-calc-branch $ svn log -v integer.c ------------------------------------------------------------------------ r343 | user | 2002-11-07 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines Changed paths: M /calc/branches/my-calc-branch/integer.c * integer.c: frozzled the wazjub. ------------------------------------------------------------------------ r341 | user | 2002-11-03 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines Changed paths: A /calc/branches/my-calc-branch (from /calc/trunk:340) Creating a private branch of /calc/trunk. ------------------------------------------------------------------------ r303 | sally | 2002-10-29 21:14:35 -0600 (Tue, 29 Oct 2002) | 2 lines Changed paths: M /calc/trunk/integer.c * integer.c: changed a docstring. ------------------------------------------------------------------------ r98 | sally | 2002-02-22 15:35:29 -0600 (Fri, 22 Feb 2002) | 2 lines Changed paths: A /calc/trunk/integer.c * integer.c: adding this file to the 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 integer.c ------------------------------------------------------------------------ r344 | sally | 2002-11-07 15:27:56 -0600 (Thu, 07 Nov 2002) | 2 lines Changed paths: M /calc/trunk/integer.c * integer.c: fix a bunch of spelling errors. ------------------------------------------------------------------------ r303 | sally | 2002-10-29 21:14:35 -0600 (Tue, 29 Oct 2002) | 2 lines Changed paths: M /calc/trunk/integer.c * integer.c: changed a docstring. ------------------------------------------------------------------------ r98 | sally | 2002-02-22 15:35:29 -0600 (Fri, 22 Feb 2002) | 2 lines Changed paths: A /calc/trunk/integer.c * integer.c: adding this file to the 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 in revisions 303 and 98.
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.
[23] 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.