这仅仅适用于服务器支持这两种协议的情况。如果服务器是GitHub的话,这两种方式都是可以的。
第三课 Git 工作流程
与远程仓库同步,修改,载入(stage)和提交项目
现在你已经有一个git本地仓库,一切都配置完毕。然后该怎么办?
一般来说,跟其他的源码控制系统的工作流程没什么两样,唯一一个区别就是载入(stage)的过程。整个工作流程大致是这样(流程1):
与远程仓库同步
修改文件
查看变更
载入变更
提交载入的变更
重复
上传
这是最复杂的情况,如果你不与别人合作开发的话,就不需要上传到仓库中去(流程2):
* 修改文件
* 提交变更
* 重复
简单吧。要记得,git是分布式的,所以如果不是合作项目的话,实际上不需要提交到一个公共的共享服务器上--你可以像使用RCS一样,只用来追踪本地文件变更。下面,让我们先来看个简单的示例,紧接着再来看用git协作开发的实例。
如果你想跟着做这个例子,请克隆这个项目:
$ git clone git://github.com/schacon/simplegit
例子开始,按照流程2,我们首先要修改README文件,将自己添加到项目作者中去。所以我们修改这个文
件。然后我们希望提交这个变更,所以我们运行'git commit -a' 命令。 -a
的意思是告诉git先将变更了的文件先载入(stage),然后提交-我们后面会通过'staging area'命令实现,但是现在运行 'git
commit -a' 命令,效果跟在SVN中使用'commit'命令一样。
执行完之后,一个提交信息的提示会出现在编辑器中(这里$EDITOR环境变量或'core.editor'这两个git配置变量的默认值都是vim)类似下面这样的内容:
_
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch main
# Changes to be committed:
# (use "git reset HEAD ..." to unstage)
#
# modified: README
#
~
~
".git/COMMIT_EDITMSG" 9L, 253C
输入一些提交的信息,譬如"added myself to the README as an author"然后退出。
vim操作提示:
按下I,o,a进入编辑模式,编辑完毕按Esc,输入:wq保存退出。
然后会看到这样的提示:
[master]: created 5896d4d: "added myself to the README as an author"
1 files changed, 2 insertions(+), 1 deletions(-)
显示我们刚刚输入的提交信息,并且有一组关于这次提交项目中文件变更的统计数字。同时还给我们一个提交的校验和,'5896d4d',这个校验和可以用来日后确切的查看这次提交的细节。
这就是简单用例。修改文件,'git commit -a',重复
现在,我们来介绍一个复杂点的实例,这次我们使用远程仓库,将项目上传上去,从而跟其他的开发者一起协同工作。同时,我们会介绍staging area。
如果你会从远程仓库中克隆项目,那么与远程仓库项目同步也是相当简单的--只需要执行'git pull'.如果是远程仓库项目没有变化,也就是说其他的开发者没有对项目进行变更,会显示这样的信息:
Already up-to-date.
相反,执行这条命令之后会将你上次同步之后远程仓库中的变更同步到本地项目中,并且git会合并这些新的变更:
$ git pull
Updating c264051..b04dc3d
Fast forward
lib/simplegit.rb | 4 ++++
1 files changed, 4 insertions(+), 0 deletions(-)
例子开始,重新克隆远程仓库中的项目,修改README文件和lib/simplegit.rb文件(不要执行git commit -a)。现在你可以使用'git status'命令来查看工作目录发生了什么变更:
$ git status
# On branch master
# Changed but not updated:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: README
# modified: lib/simplegit.rb
#
no changes added to commit (use "git add" and/or "git commit -a")
我们看到,有两个文件是在"changed but not updated"段落中出现 ,这意味着这两个文件还没有载入(unstaged). 如果现在我们提交,什么也不会发生。也就是说文件必须先载入(stage),然后才能提交。
所以,我们先来载入(stage) 文件,git中使用'git add'命令不仅可以开始追逐文件而且可以对他们载入stage变更。所以让我们载入(stage) README文件的变更,然后再来查看一下状态。
$ git add README
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD ..." to unstage)
#
# modified: README
#
# Changed but not updated:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: lib/simplegit.rb
现在'lib/simplegit.rb'文件还是未载入(unstaged),
但是README文件现在已经到了'changes to be
committed'段落中-它几经载入(stage)了。现在如果我们运行提交命令(不要-a,这个会自动stage所有的东西),只有这个文件会被提
交-而simplegit.rb依然是unstaged。这时,我们使用-m选项来执行'git
commit',这样后面跟上字符串表示这次提交的信息。
$ git commit -m 'updated the README'
[master]: created 14bb3c6: "updated the README"
1 files changed, 1 insertions(+), 2 deletions(-)
如果现在再执行'git status',我们会看到stage之后的文件现在已经提交了,只剩下了unstaged的'simplegit.rb'文件。
$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
#
# Changed but not updated:
# (use "git add ..." to update what will be committed)
# (use "git checkout -- ..." to discard changes in working directory)
#
# modified: lib/simplegit.rb
#
现在我们可以stage并且提交这个文件:
$ git commit -a -m 'added a staging command to the library'
[master]: created bbaee85: "added a staging command to the library"
1 files changed, 4 insertions(+), 0 deletions(-)
$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 2 commits.
#
nothing to commit (working directory clean)
现在我们已经将两次提交的变更都搞定了,而且加了提交信息,可以让我们的合作伙伴很容易理解我们做的变更。经过最后一次提交,我们看到'git status'执行之后显示我们的工作目录clean了(同时提示我们现在的分枝上有两个提交还没有上传)
所以,现在我们将这些变更上传到服务器端与我们的合作伙伴分享,前提是我们有上传的权限,(如果没有上传的权限,我们可以在网络上创建一个自己的git仓库,将其上传),然后让朋友下载。
运行'git push'会将我们的变更上传到服务器。
$ git push
Counting objects: 11, done.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (7/7), 744 bytes, done.
Total 7 (delta 3), reused 0 (delta 0)
To git@github.com:schacon/simplegit.git
b04dc3d..bbaee85 master -> master
到目前为止,我们看到了新数据都已经上传完毕,服务器上的主分支也已经更新了。现在我们可以复习一下这整个的过程,让我们可以更加熟练的将git应用到我们的项目中去。
第四课 分枝和合并基础知识介绍
内容提要:创建工作分枝,合并分枝以及删除分枝
从第一课的介绍中,我们了解到,git处理分枝和合并是非常独到的。首先,无论是创建新分枝还是分枝之间切换,都表现出一个共同的特征--快! git 有一个独立的工作目录,专门用来存放所有分枝的内容,所以你没必要为每一个分枝再分别创建目录。
这一节,我们的任务是:
1. 创建一个新的分枝;
2.在这个分枝上做一些工作;
3.切换回稳定的主分枝(一般git默认的主分枝名叫master);
4.在主分枝上再做点工作;
5.再切换到刚那个临时分枝完成工作;
6.最后将它们合并成为一个稳定的主分枝。
首先,查看现存的分枝,可以使用不带任何参数的'git branch'命令。
可以看到,我们现在只有一个分枝叫做'master',*代表的意思是我们正在这个主分枝上工作。下图是在主分支上的提交历史模型,绿框表示提交,箭头指向的是它的父节点。这就是git提交数据的方式。
从图中可以看出,在git中,分枝都是由一些具体的提交点组成。整个分枝的历史都是这样串联起来的,一次只有一个提交点。
可以用'git branch (branchname)'命令在当前分枝上创建一个新的分枝:
为了将我们的工作保存到experiment分枝而不是master分枝上,我们需要切换到experiment分枝上,执行'git checkout'命令:
$ git checkout experiment
Switched to branch "experiment"
$ git branch
* experiment
master
执行完毕后,我们就切换到了新的分枝(experiment)分枝上,看到experiment前面有了*号了。现在,我们修改文件、提交就不用再担心跟master分枝混在一起了。也没必要在我们确定一切搞定之前,将experiment分枝上的变更共享了。
在多分枝上进行工作
现在,让我们添加一个TODO文件,并修改simplegit.rb文件,然后将这些变更都提交。
$ vim lib/simplegit.rb
$ vim TODO
$ git add TODO
$ git commit -am 'added a todo and added simplegit functions'
[experiment]: created 4682c32: "added a todo and added simplegit functions"
2 files changed, 10 insertions(+), 0 deletions(-)
create mode 100644 TODO
现在,我们来看一下,整个项目中有3个文件和一个子目录。
$ ls
README Rakefile TODO lib
然后,我们假设需要回到原始版本来调试simplegit.rb文件。
$ git checkout master
Switched to branch "master"
$ ls
README Rakefile lib
可以看到我们已经回到了master分枝,工作目录中也没有TODO文件,这是因为master分枝上我们就没有创建过这个文件。
如果现在我们再切换到experiment分枝,我们又会看到TODO文件,并且simplegit.rb也是我们在experiment修改后的内容。
下面这段代码是在master分枝上修改simplegit.rb文件,在文件内添加一个commit function.提交,然后再切换到experiment分枝。
$ vim lib/simplegit.rb
$ git commit -am 'added a commit function'
[master]: created 0b7434d: "added a commit function"
1 files changed, 4 insertions(+), 0 deletions(-)
$ git checkout experiment
Switched to branch "experiment"
$ ls
README Rakefile TODO lib
很多使用git的开发者通常会同时拥有很多个分枝,每一个分枝上都会进行着某个具体功能的开发,这些开发可能持续几分钟、几小时,也可能长时间的在某个分枝上进行大规模重构的工作,并定期的将其合并到主分枝上。
如果你需要在一个长期工作的分支上与其他开发者进行协作,你可以将这个分枝上传到共享服务器端。譬如,如果你想跟某人共享experiment分枝,你可以这样:
$ git push origin experiment
让你的合作伙伴同步这个分枝,然后与你协同工作。当然你也可以自己保留这些分枝作为自己的工作分枝--不用上传就可以了。
当你在一个分枝上完成你的工作了,这时,如果你觉得所做的工作没什么意义,那么你可以忽略它并且将其移除;相反,你应该将这些工作合并到一个你
会长期使用的分枝中(一般来说,开发者会用'master'分枝来存放稳定的代码,并行的'develop'分枝用来整合或测试变更的内容,或者将一些短
期的分枝合并进来)。
合并分枝的方法:首先切换到想要合并到的分枝下,运行'git merge'命令,(例如本例中将experiment分枝合并到master分枝的话,进入master分枝运行git merge experiment命令)如果合并顺利的话:
$ git merge experiment
Auto-merging lib/simplegit.rb
Merge made by recursive.
lib/simplegit.rb | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
然而,有时候合并并不一定会顺利进行,如果碰到了问题,就会出现下面这些内容:
$ git merge experiment
Auto-merging lib/simplegit.rb
CONFLICT (content): Merge conflict in lib/simplegit.rb
Automatic merge failed; fix conflicts and then commit the result.
这种情形下,提示合并冲突,你可以通过下面的方式来解决:打开提示冲突的文件,会看到冲突标记:
<<<<<<< HEAD:lib/simplegit.rb
def commit(message)
command("git commit -m '#{message}'")
=======
def add(path)
command("git add #{path}")
>>>>>>> experiment:lib/simplegit.rb
end
修改完成之后,运行'git add'重新载入(re-stage)这个文件,然后合并:
$ git add lib/simplegit.rb
$ git commit
[master]: created 6d52a27: "Merge branch 'experiment'"
多次合并
这个问题之所以重要是因为在其他的VCS工具中多次合并实现起来是很麻烦的,但是用git,很容易解决。合并一个分枝之后,再继续在这个分枝上
工作,然后再合并。这种情形一般是这样的:如果你有一个'development'分枝,你正在进行集成测试、合并实验中的变更,然后定期的将其合并到稳
定的'master'分枝中。
用我们正在进行的例子来讲,假如我们现在又切换到'experiment'分枝,做点小小的改动,然后再将其合并到'master'分枝中,整个的过程大致是这样:
因为git合并是基于提交历史的快照,所以多次合并显得so easy。当你在一个分枝上做完你的工作之后,譬如例子中的'experiment'分枝,那么我们只需使用'git branch -d'命令即可删除此分枝
$ git branch -d experiment
如果分枝还没有被合并,那么执行这个命令就会将分枝上所做的工作一并删除,git是不允许你这么干的。如果你实在想删除的话,那么使用'-D'参数强行删除吧。
这就是git中分枝和合并的所有内容,我想你理解了git的这一功能之后会觉得它确实是个非常棒的工具了。
第五课 Git标签
内容提要:创建有签名,无签名、轻量级标签来永久的标记项目历史中的关键点
跟大多数的VCS工具一样,git也有在历史状态的关键点“贴标签”的功能--一般人们用这个功能来标记发布点(例如'v1.0')。这节课我们学习如何使用标签列表,创建新标签,以及在git中有哪些不同类别的标签。
要想列出git中现有的所有标签,输入'git tag'命令运行即可:
这个列表是按照字母表顺序给出的,其实排名先后跟重要程度没有直接联系。
当然,你也可以按照特定表达式搜索某些标签。假如在一个git仓库中有超过240个标签,而你只想得到1.4.2序列的标签,那么你可以:
$ git tag -l v1.4.2.*
v1.4.2.1
v1.4.2.2
v1.4.2.3
v1.4.2.4
在git中有两种最主要的标签--轻量级标签(lightweight)和带注释的标签(annotated)。轻量级标签跟分枝一样,不会改
变--它就是针对某个特定提交的指针。然而,带注释的标签是git仓库中的对象。它是一组校验和,包含标签名、email、日期,标签信息,GPG签名和
验证。一般情况下,建议创建带注释的标签,这样就会保留这些信息,但是如果你只是需要临时性标签或者某些原因你不想在标签中附带上面说的这些信
息,lightweight标签更合适些。
带注释的标签
在git中创建带注释的标签非常简单,在运行'tag'命令时加上-a就可以了。
$ git tag -a v1.4 -m 'version 1.4'
$ git tag
v0.1
v1.3
v1.4
'-m'指明标签信息,跟标签一起存储。如果你不使用-m指明标签信息,git会自动启动文本编辑器让你输入。
可以使用 git show 命令查看相应标签的版本信息,并连同显示打标签时的提交对象。
$ git show v1.4
tag v1.4
Tagger: Scott Chacon <>
Date: Mon Feb 9 14:45:11 2009 -0800
my version 1.4
commit 15027957951b64cf874c3557a0f3547bd83b3ff6
Merge: 4a447f7... a6b4c97...
Author: Scott Chacon <>
Date: Sun Feb 8 19:02:46 2009 -0800
Merge branch 'experiment'
我们可以看到,在提交对象信息上面,列出了此标签的提交者和提交时间,以及相应的标签信息。
如果你有GPG私钥的话,你也可以用GPG来给你的标签签名,把-a换成-s就可以了:
[master]$ git tag -s v1.5 -m 'my signed 1.5 tag'
You need a passphrase to unlock the secret key for
user: "Scott Chacon <>"
1024-bit DSA key, ID F721C45A, created 2009-02-09
然后,如果你对某个标签运行'git show'的话,你就会看到你的GPG前面附加上去了。
[master]$ git show v1.5
tag v1.5
Tagger: Scott Chacon <>
Date: Mon Feb 9 15:22:20 2009 -0800
my signed 1.5 tag
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.8 (Darwin)
iEYEABECAAYFAkmQurIACgkQON3DxfchxFr5cACeIMN+ZxLKggJQf0QYiQBwgySN
Ki0An2JeAVUCAiJ7Ox6ZEtK+NvZAj82/
=WryJ
-----END PGP SIGNATURE-----
commit 15027957951b64cf874c3557a0f3547bd83b3ff6
Merge: 4a447f7... a6b4c97...
Author: Scott Chacon <>
Date: Sun Feb 8 19:02:46 2009 -0800
Merge branch 'experiment'
稍后,我们会介绍如何验证签名标签。
轻量级标签实际上就是存在一个文件中的提交校验和--没有附加任何其他信息。创建轻量级标签的方法就是把上面'-a','-s','-m'这些选项都去掉。
$ git tag v1.4-lw
$ git tag
v0.1
v1.3
v1.4
v1.4-lw
v1.5
如果现在对这个标签使用'git show'命令,不会看到像上面那种标签显示的那么多内容,仅仅显示这次提交的有关信息。
$ git show v1.4-lw
commit 15027957951b64cf874c3557a0f3547bd83b3ff6
Merge: 4a447f7... a6b4c97...
Author: Scott Chacon
Date: Sun Feb 8 19:02:46 2009 -0800
Merge branch 'experiment'
使用'git tag -v (tag)'就可以验证一个签名标签了。这个命令会用到GPG来验证签名。前提是:你必须在密钥环中存放着签名者的公钥。
$ git tag -v v1.4.2.1
object 883653babd8ee7ea23e6a5c392bb739348b1eb61
type commit
tag v1.4.2.1
tagger Junio C Hamano <> 1158138501 -0700
GIT 1.4.2.1
Minor fixes since 1.4.2, including git-mv and git-http with alternates.
gpg: Signature made Wed Sep 13 02:08:25 2006 PDT using DSA key ID F3119B9A
gpg: Good signature from "Junio C Hamano <>"
gpg: aka "[jpeg image of size 1513]"
Primary key fingerprint: 3565 2A26 2040 E066 C9A7 4A7D C0C6 D9A4 F311 9B9A
如果你没有这个公钥的话,你会看到这样的信息:
gpg: Signature made Wed Sep 13 02:08:25 2006 PDT using DSA key ID F3119B9A
gpg: Can't check signature: public key not found
error: could not verify the tag 'v1.4.2.1'
这种情况是说你想对以前的某次提交贴个标签,如果整个提交历史是这样的:
$ git log --pretty=oneline
15027957951b64cf874c3557a0f3547bd83b3ff6 Merge branch 'experiment'
a6b4c97498bd301d84096da251c98a07c7723e65 beginning write support
0d52aaab4479697da7686c15f77a3d64d9165190 one more thing
6d52a271eda8725415634dd79daabbc4d9b6008e Merge branch 'experiment'
0b7434d86859cc7b8c3d5e1dddfed66ff742fcbc added a commit function
4682c3261057305bdd616e23b64b0857d832627b added a todo file
166ae0c4d3f420721acbb115cc33848dfcc2121a started write support
9fceb02d0ae598e95dc970b74767f19372d61af8 updated rakefile
964f16d36dfccde844893cac5b347e7b3d44abbc commit the todo
8a5cbc430f1a9c3d00faaeffd07798508422908a updated readme
忘记在那个提交信息为'updated rakefile'的点上贴一个'v1.2'的标签了,我可以现在贴一个上去。你可以在执行创建标签的语句后面跟上那次提交的校验和(或者部分校验和)。
$ git tag -a v1.2 9fceb02
现在我们可以列出所有的标签:
$ git tag
v0.1
v1.2
v1.3
v1.4
v1.4-lw
v1.5
$ git show v1.2
tag v1.2
Tagger: Scott Chacon <>
Date: Mon Feb 9 15:32:16 2009 -0800
version 1.2
commit 9fceb02d0ae598e95dc970b74767f19372d61af8
Author: Magnus Chacon <>
Date: Sun Apr 27 20:43:35 2008 -0700
updated rakefile
...
默认情况下,'git push'命令不会将标签上传到远程服务器上。为了共享这些标签,你必须在'git push'命令后明确添加-tags选项
[master]$ git push --tags
Counting objects: 50, done.
Compressing objects: 100% (38/38), done.
Writing objects: 100% (44/44), 4.56 KiB, done.
Total 44 (delta 18), reused 8 (delta 1)
To
* [new tag] v0.1 -> v0.1
* [new tag] v1.2 -> v1.2
* [new tag] v1.4 -> v1.4
* [new tag] v1.4-lw -> v1.4-lw
* [new tag] v1.5 -> v1.5