源码控制
源码控制
源码控制系统,也叫版本控制系统,是一种存储软件的方式。它不仅存储当前版本,而且还存储所有以前的版本,这是通过维护一个包含所有版本的存储库来实现的,同时一个或多个用户在一个 ‘ check out ‘ 的最新版本上工作。那些作为开发者的用户可以将他们的修改提交到存储库中,其他用户然后更新他们的本地拷贝。版本库通常存在远程设备上,并有可靠的备份。
将我们的源代码保存在资源库里有各种原因:
- 如果我们在一个团队中工作,它是与我们的同事同步工作的最佳方式。它也是记录哪些改动是由谁做的,以及为什么这样做的。
- 它可以让我们把有缺陷的代码回滚到可以工作的版本。
- 它允许我们有一些分支,例如用于需要保持在主线之外的定制化的开发线之外。如果我们在一个团队中工作,分支是开发一个主要功能的一种方式,可以保持分支是一种开发主要功能的方式,可以及时了解同事们所做的修改,并在充分测试后将功能添加到主开发中。
- 如果我们独自工作,它是一种在多台机器之间进行同步的方式。(我们甚至可以想象在旅行中不带所有的文件,而从版本库中安装到借来的的机器上)。
- 拥有一个源代码库是备份我们工作的一种方式。
有各种源码控制系统;在本教程中,我们可以学习 Subversion 的基本知识( 也叫 svn ),它可能是最流行的传统源码控制系统,和 Mercurial(或 hg ),它是新一代分布式源码控制系统的示例。
源码控制系统的工作流程
源码控制系统是围绕着资源库的概念建立的:一个项目的文件的中央存储,以及它们的全部历史。因此,版本库允许我们与多人共享文件,也可以回滚修改,在旧版本上打补丁。
对存储库的基本操作是:
- 创建版本库;这需要我们在一些服务器上有空间和写权限,也许我们的系统管理员必须为我们做这件事。
- 检查版本库,也就是在我们自己的空间里制作一份本地内容的拷贝。
- 将我们的修改添加到版本库中,并且
- 用别人的修改来更新我们的本地拷贝。
添加自己的修改并不总是可能的:有很多项目的开发者允许我们可以查看版本库,但不能加入修改,这样的版本库是只读的。
可以编辑文件,并使用提交命令检查新版本;要获得其他用户提交的修改,则使用更新命令。 其他用户提交的修改,我们可以使用 update。
提交的用途之一是,如果我们意识到我们犯了错误或引入了一个错误,我们可以将我们的代码回滚到一个较早的版本。它还可以让我们轻松地看到不同的代码版本的区别。然而,提交许多小改动可能会让其他开发者感到困惑,例如,如果他们会依赖我们引入的东西,而我们后来又将其删除。由于这个原因,分布式源代码控制系统使用两个级别的存储库。
它仍然有一个权威的顶层,但现在有一个较低的层级,通常是本地副本,在那里我们可以提交我们的修改,并积累到最后添加到中央版本库中。这也使得为只读版本库做贡献变得更容易:我们在本地做修改,当完成后,告诉开发者检查我们的修改,并把它们拉到顶级版本库中。这个结构如图 23.2 所示。
Mercurial ( hg ) 和 Git
Mercurial 和 Git 是新一代分布式源代码控制系统中最著名的。许多命令与 Subversion 相同,但也有一些新的命令,与新的先进水平相对应。Mercurial 和 Git 共享一些命令,但也有区别。后期使用 Git 更加强大,但 Mercurial 一开始更容易使用。
下面是两个系统之间的转换:https://github.com/sympy/sympy/wiki/Git-hg-rosetta-stone.
Git 的最低限度介绍: http://rogerdudler.github.io/git-guide/
这个实验应该由两个人完成,来模拟一群程序员在一个联合项目上工作。我们也可以通过使用两个版本库的克隆,最好在我们的电脑上打开两个窗口来做这个实验。
分布式版本控制的最佳实践: https://homes.cs.washington.edu/˜mernst/advice/version-control.html
创建和填充一个存储库
目的:在这一节中,我们将创建一个资源库,并制作一个本地副本来工作。
Mercurial | Git |
---|---|
hg clone [ localdir ] | git clone [ localdir ] |
首先,我们需要有一个版本库。在实践中,我们通常会使用一个已经建立好的版本库,但也有几种方法可以自己建立一个版本库。有一些商业和免费的托管服务,如 http://bitbucket.org。(学术用户可以有更多的私人资源库)。
我们假设一个学生在 Bitbucket 上创建了一个版本库 your-project,两个学生都可以然后克隆它:
%% hg clone https://YourName@bitbucket.org/YourName/your-project
updating to branch default
0 files updated, 0 files merged, 0 files removed,
0 files unresolved
或:
%% git clone git@bitbucket.org:YourName/yourproject.git
Cloning into ’yourproject’...
warning: You appear to have cloned an empty repository.
然后我们就会有一个空目录 your-project。
练习:进入项目目录,看看它是否真的为空。
预期结果:有一个隐藏的目录 .hg 或 .git 。
新文件
创建一个未监控的文件
目的:在本节中,我们将做一些简单的修改:创建一个新文件和编辑一个现有的文件。
Mercurial | Git |
---|---|
hg status [ path] | git status [ path ] |
hg add [ files ] | git add [ files ] |
每个文件都使用一次 | 每次文件被改变时 |
一个学生现在做了一个文件添加到资源库中:
%% cat > firstfile
a
b
c
d
e
f
ˆD
(其中 ˆD 代表 control-D,它终止了输入。) 这个文件对 hg 来说是未知的:
%% hg status
? firstfile
Git会更麻烦一点:
git status
On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
firstfile
nothing added to commit but untracked files present
(use "git add" to track)
!
添加文件到版本库我们需要声明该文件属于版本库;随后的一个 hg commit 命令就会把它复制到版本库中。
%% hg add firstfile
%% hg status
A firstfile
%% hg commit -m "made a first file"
或是
%% git add firstfile
%% git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: firstfile
%% git commit -a -m "adding a first file"
[master (root-commit) f4b738c] adding a first file
1 file changed, 5 insertions(+)
create mode 100644 firstfile
Mercurial | git |
---|---|
hg commit -m |
git commit -m \ |
hg push | git push origin master |
与 Subversion 不同的是,现在该文件只被复制到本地版本库中,这样我们就可以回滚我们的修改。如果我们想把这个文件添加到主版本库,我们需要使用 hg push 命令。
%% hg push https://YourName@bitbucket.org/YourName/your-project
pushing to https://YourName@bitbucket.org/YourName/your-project
searching for changes
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 1 changesets with 1 changes to 1 files
remote: bb/acl: YourName is allowed. accepted payload.
在 push 步骤中,我们可能被要求提供我们的密码。我们可以通过在 $HOME/.hgrc 文件中设置几行来防止这种情况:
[paths]
projectrepo = https://YourName:yourpassword@bitbucket.org/YourName/my-project
[ui]
username=Your Name <you@somewhere.youruniversity.edu>
现在,hg push projectrepo 命令将把本地的修改推送到全局版本库,而不需要我们的密码。当然,如果我们有一个带有明文密码的文件,我们应该正确设置这个文件的权限。
使用 Git ,我们需要更明确,因为我们的本地拷贝和 “上游 “仓库之间的联系可能更不稳定。
git remote add origin git@bitbucket.org:YourName/yourrepo.git
git push origin master
第二个学生现在需要更新他们的版本库。就像上传需要两个命令一样,这个过程也需要两个命令。首先,我们需要 hg pull 来更新我们的本地版本库。这并不更新我们所拥有的本地文件:因此我们需要 hg update 。
练习:如上这样做并检查文件的内容是否正确。
预期的结果:为了执行更新命令,我们必须在一个检验过的拷贝中储存库的。
练习:让两个学生创建一个带有几个文件的新目录。声明该目录并提交,pull 之后使用update,以获得另一个 mde 的变化。
预期结果:我们可以对该目录进行 hg 添加,这也会添加其中的文件。
由于我们大多会在 pull 后立即进行 update,我们可以把它们结合起来:
hg pull -u
Git 会报告哪些文件被更新了;对于 Hg 来说,我们需要获取更改的编号然后进行查询:
hg status --change 82ffb99c79fd
注释 18:为了让 Mercurial 追踪我们的文件,我们不应该对版本库中的文件执行 shell 中 cp 命令 或 mv 命令。相反,应该使用 hg cp 或 hg mv 。同样地,也有一个 hg rm 命令。
哎呀! 撤销!
拥有源码控制的原因之一是为了能够恢复修改,最简单的撤销方式是回到回到版本库中最后存储的版本。
Mercurial | Git |
---|---|
hg revert \ |
git checkout — \ |
冲突
目的:在本节中,我们将了解如何处理同一版本库的两个用户的冲突编辑的两个用户在同一个版本库中的冲突编辑。
现在让我们看看当两个人编辑同一个文件时会发生什么。让两个学生都对 firstfile 进行编辑。但一个在上面,另一个在下面。一个学生提交编辑后,另一个可以提交修改。毕竟,这些只影响本地仓库。然而,试图推送该修改时却出现了错误;
%% emacs firstfile # make some change
%% hg commit -m ‘‘first again’’
%% hg push test
abort: push creates new remote head b0d31ea209b3!
(you should pull and merge or use push -f to force)
解决的办法是获得另一个编辑,并再次提交。这需要几个命令:
%% hg pull myproject
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)
(run ’hg heads’ to see heads, ’hg merge’ to merge)
%% hg merge
merging firstfile
0 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don’t forget to commit)
%% hg status
M firstfile
%% hg commit -m ‘‘my edit again’’
%% hg push test
pushing to https://VictorEijkhout:***@bitbucket.org/VictorEijkhout/my-project
searching for changes
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 2 changesets with 2 changes to 1 files
remote: bb/acl: VictorEijkhout is allowed. accepted payload.
这可能看起来很复杂,但我们看到 Mercurial 提示我们要执行什么命令,如果我们参考图23.2,工作流程很清楚。
练习:在我们们两个人都在编辑的文件上使用 cat 命令 。我们应该发现,两个人的编辑都合并了。这就是 Mercurial 提到的 “合并”。
如果两个学生都对文件的同一个部分进行编辑,版本控制就不能再解决冲突。例如,让一个学生在第一行和第二行之间插入一行,并让第二个学生编辑第二行。无论谁尝试推送第二条,都会得到这样的信息:
%% hg pull test
added 3 changesets with 3 changes to 1 files (+1 heads)
(run ’hg heads’ to see heads, ’hg merge’ to merge)
%% hg merge
merging firstfile
warning: conflicts during merge.
merging firstfile incomplete!
(edit conflicts, then use ’hg resolve --mark’)
0 files updated, 0 files merged, 0 files removed, 1 files unresolved
use ’hg resolve’ to retry unresolved file merges
or ’hg update -C .’ to abandon
现在有以下选项:
- 通常有一种方法可以表明是使用本地版本还是远程版本。
- 有一些图形化的程序来解决冲突。它们通常会向我们显示三栏,分别是两个版本和我们的解决方案。然后我们可以表示 “从本地版本中取这个,从远程版本中取那个“。
- 我们也可以自己编辑文件来解决冲突,我们很快会讨论这个问题。
两者都会给我们几个选项。用文本编辑器来解决冲突是最容易的。如果我们打开有冲突的文件我们会看到类似的东西:
<<<<<<< local
aa
bbbb
=======
aaa
a2
b
>>>>>>> other
c
表明本地版本和另一版本之间的差异,也就是我们 pull 过并试图合并的版本,我们需要编辑该文件来解决冲突。
在这之后,我们告诉 hg 冲突已经解决:
hg resolve --mark
%% hg status
M firstfile
? firstfile.orig
或是
git add <name of that file>
在这之后,我们可以提交并再次 push ,另一个学生就需要再一次更新来更正。
不是所有的文件都可以合并:对于二进制文件,Mercurial 会询问我们:
%% hg merge
merging proposal.tex
merging summary.tex
merking references.tex
no tool found to merge proposal.pdf
keep (l)ocal or take (o)ther? o
这意味着唯一的选择是保留我们的本地版本(输入 l 并点击回车)或采用另一个版本(输入 o并按回车键)。在二进制文件显然是自动生成的情况下,有些人认为他们不应该在版本库中出现。
检查历史
目的:在本节中,我们将学习如何查看和比较版本库中的文件。
如果我们想知道我们是从哪里克隆的版本库,可以在 .hg/hgrc 文件中查看。
关于版本库的主要信息来源是 hg log 和 hg id,后者给我们的是否是全局信息,取决于我们使用的选项。例如,hg id -n 给出了本地版本号。
hg log 给我们一个到目前为止所有更改集的列表,以及我们输入的注释。
hg log -v 告诉我们在每个变化集中有哪些文件被影响。
hg log -r 5 或 hg log -r 6:8 给出了一个或多个变化集的信息。
要查看单个文件的不同修订版的差异,可以使用 hg diff 。首先确保我们的版本是最新的,输入 hg diff firstfile ,没有输出,对吗?再在 firstfile 中做一个编辑,然后输入 hg diff firstfile,这样我们就能得到最后一个提交的版本和工作副本之间的差异。
Mercurial | Git |
---|---|
hg diff \ |
git diff HEAD \ |
hg diff -r A -r B \ |
git diff Aˆ..B \ |
自行检查一下,当我们做了提交但没有推送,并发出上述 diff 命令时会发生什么。
我们也可以用 hg diff -r 4:6 firstfile 询问提交版本之间的差异。
diff 命令的输出有点隐秘,但我们可以轻松就理解它。还有也有针对每个平台的 hg 的不同 GUI实现,以更好的方式显示差异。
如果我们只是想看看一个文件以前是什么样子的,可以输入 hg cat -r 2 firstfile。要获得一份 仓库的某个版本,输入 hg export -r 3 …/rev3,它将仓库在当前目录下(’ dot ‘)的版本库导出到 ./rev3 目录。
传输
Mercurial 和 Git 可以使用 ssh 或 http 作为传输。对于 Git,我们可能需要重新定义推送的运输方式来进行推送。
git remote rm origin
git remote add origin git@github.com:TACC/pylauncher.git