Git是一个分布式的版本控制系统。Linux kernel源码管理就是通过git管理的。Linux源码最开始用社区版的bitkeeper管理源码,后来bitkeeper社区版不再开源了。Linus觉得CVS和SVN都还有需要改进的地方,就从头开发了一个分布式的版本控制系统git。鉴于“git详解系列”文章已经达到了不可企及的高度。这篇文章主要从实践的角度出发,更多的介绍git操作。
常见的版本控制系统还有IBM的clearcase,SVN,CVS等。假设你有个文件,需要不停地修改,然后还需要追踪文件的每一修改记录,需要方便的取出任一次修改记录或者恢复到任一次修改。这时你需要一个版本控制系统,通常也就变更控制系统,用来专门记录你的每一次变更。对于软件开发人员来说版本控制系统经常是指源码管理系统,上边提到的几个都是经常用到的源码管理系统,不过它们都是集中式的版本控制系统,而Git是一个分布式的控制系统,所以在概念方面要比其他几个系统复杂一些。
分布式版本控制系统和集中式的版本控制系统的区别在于,集中式的版本控制系统都有一个集中的源码库,所有的人都修改这个集中源码库的文件。分布式的版本控制系统不必有一个中央的源码库,所有的源码都在本地源码库中进行追踪和管理。需要的时候才上传到中央服务器。集中式的版本控制系统,中央服务器可能成为单点故障,导致不能工作。分布式的版本控制系统,每一个本地的源码都是一个独立的源码库,即使在飞机上,不能连接网络,还是可以照常工作。
Git配置存在三个级别,取决于我们需要这些配置怎么样应用于我们的系统。
第一种,系统配置。应用于这台电脑的任何用户。这种配置不太经常用,现在的操作系统都是多用户的,每一个用户应该有一个git的配置。对于家庭电脑,这个系统就一个人用,那是没有问题的。如果是公司的电脑,可能有多个人在使用,那么系统配置就不合适了。在linux系统上系统级的配置的配置文件一般在/etc/gitconfig,对于windos系统,系统级别的配置一般在Program Files\Git\etc\gitconfig。配置方式:git config --system。
第二种,用户配置。这种配置只对一个具体的用户生效。这种配置是经常使用的配置git的方式。在linux系统上这种配置的配置文件通常在你的home目录下的.gitconfig文件中。在widnows上这种配置的配置文件也在home目录$HOME\.gitconfig文件。配置方式git config --global。
第三种,项目配置。这种级别的配置文件时针对一个项目的具体配置。这种配置文件在my_project/.git/config目录。配置方式git config。
常用的git 配置。这里以用户配置为例,其他的类似。
git config --global user.name “zsli” git config --global user.email "[email protected]" git config --global core.editor “vim” git config --global color.ui true git config --list
初始化一个仓库。git init。将在项目的目录下创建一个.git的隐藏文件夹,这个文件夹里边存放了git管理这个项目的所有信息。包括刚才上边提到的项目级别的config目录,保存了项目的配置。同时还包括HEAD,branches,description,hooks,info,objects,refs等几个目录。
git的基本工作步骤:
1,做出更改,vim first_file.txt
2,增加更改,git add first_file.txt
3,提交更改,git commit -m ‘Initial commit’
Git commit log最佳实践:
1,包含bugid类似的信息。
2,一个简短的描述,描述commit的概要信息。
3,详细的描述。
版本树
Git使用三个版本树的模式管理源码,而其他的基本都使用两棵版本树的方式管理源码。
两棵版本树
仓库和工作目录。从仓库获取源码,在本地工作目录编辑修改,然后提交到仓库。不停重复这个工作一直到任务完成。
三课版本树
Git中增加一棵版本树,一个staging index树。从仓库获取源码到本地工作目录,做出修改,这时git不是直接提交到仓库,而是先提交到staging index。其他的版本管理工具中如果修改一个bug,今天修改了一个bug,但是还没有修改完成,明天还要修改几个文件。这时你就需要commit两次。在git中你可以先把今天的修改add到staging index中,明天修改完成后,把明天的修改一块添加到staging index中,然后一次commit。
Git 工作流程
新建一个文件 file.txt
Git add file.txt
Git commit file.txt
最开始三棵版本树上的文件的版本是相同的。这时我们对工作目录的文件作出修改。
这时工作目录的文件变成v2了。当我们执行了git add file.txt后,我们就把工作目录的修改暂存到了staging index中。这时版本如下:
这时我们提交修改。git commit file.txt。版本变成如下:
这时文件的三棵版本树的版本又一致了。Git的工作流程就是修改文件,增加修改,然后提交修改。不停地重复这个工作。
Hash values
git是如何引用版本变更中的任何一个快照状态的呢?Git为每一个变更都会生成一个checksum,使用SHA-1生成checksum,40个十六进制字符。比如:c905d813e590157d816bbd3603bcef10ad03e166。每一个文件或者目录的变更都对应一个checksum值,所有的checksum之间连接起来,表示修改顺序。
HEAD指针
HEAD指针始终指向了当前仓库的分支,在我们做出修改,然后提交修改的过程中,HEAD指针是在变化的。它也是我们最初从仓库的那个分支开始的指针,就是我们checkout的地方,也是下一个commit的父指针。HEAD是我们理解分支的一个重要概念。比较拗口和难理解。
举个栗子。HEAD就像录音机的录音头,当我们按下录音时,HEAD指向开始录音的地方。当我们结束录音,然后再开始录音,这时HEAD就指向了第二次录音的位置。始终指向我们开始工作的地方。对应于git就是我们上次commit的地方,当我们再次commit的时候,git会移动HEAD到最新的地方。对于两个不同的分支也是一样的情况,始终指向当前分支的开始工作的地方。
上边我们提到.git/HEAD文件。这时一个文本文件,里边记录了HEAD指针的位置。比如:
ref: refs/heads/master
这是一个指针,说明HEAD指向refs/heads/master中的变更。查看refs/heads/master,里边是一个checksum:
c905d813e590157d816bbd3603bcef10ad03e166
查看git log:
commit c905d813e590157d816bbd3603bcef10ad03e166 Author: shunliz <[email protected]> Date: Sun Sep 8 09:39:23 2013 +0800 Initial commit
可以看到,这个checksum就是我们最新的一次提交的checksum。
HEAD就是当前分支对应到仓库中的一个指针。
git status用来查看当前git的状态。下边给出三个文件的变化状态。仔细观察文件不同变化状态以及git stauts的输出。第一行指示当前分支,第二行开始显示各种状态的文件列表。任何时候先看看git status是一个好的习惯。
$ vim first.txt $ vim second.txt $ vim third.txt $ git status # On branch master # Untracked files: # (use "git add <file>..." to include in what will be committed) # # first.txt # sencod.txt # third.txt nothing added to commit but untracked files present (use "git add" to track) $ git add first.txt $ git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # new file: first.txt # # Untracked files: # (use "git add <file>..." to include in what will be committed) # # sencod.txt # third.txt $ git commit -m 'first.txt commited' [master adf64aa] first.txt commited 1 file changed, 1 insertion(+) create mode 100644 first.txt $ git status # On branch master # Untracked files: # (use "git add <file>..." to include in what will be committed) # #sencod.txt # third.txt nothing added to commit but untracked files present (use "git add" to track) $ git add . $ git status # On branch master # Changes to be committed: # (use "git reset HEAD <file>..." to unstage) # # new file: sencod.txt # new file: third.txt # $ git commit -m 'add other two file' [master 3ef326c] add other two file 2 files changed, 2 insertions(+) create mode 100644 sencod.txt create mode 100644 third.txt $ git status # On branch master nothing to commit (working directory clean)
git diff 默认对比staging index和当前工作目录的内容。带有+号的是当前工作目录增加的内容,带有-号的是staging的被删除内容。
$ git diff diff --git a/sencod.txt b/sencod.txt index 8a68632..41bfa64 100644 --- a/sencod.txt +++ b/sencod.txt @@ -1 +1 @@ -Second file contentx +modified second line of txt file
这个时候如果我们执行git add sencod.txt,再执行git diff。我们将什么都看不到。因为git diff默认是查看staging和工作目录的差别。Sencod.txt已经暂存,这时diff什么也看不到。那么如何查看staging和仓库的差别呢?
$ git diff --staged diff --git a/sencod.txt b/sencod.txt index 8a68632..41bfa64 100644 --- a/sencod.txt +++ b/sencod.txt @@ -1 +1 @@ -Second file contentx +modified second line of txt file
(1.7之前用git diff --cached)
git diff filename可以查看某一个文件的差别。适用上边两种情况。
git checkout -- filename 撤销工作区的修改
git reset HEAD filename撤销暂存,保留工作区的修改。
git reset --hard filename 撤销暂存,撤销工作区的修改。
git reset --soft只修改指针指向,暂存和工作区不做修改。
git ls-tree 查看目录树
$ git log commit 462d5fe241b10b193ada252e7b8ccb2b6c08e6d9 Author: shunliz <[email protected]> Date: Sun Sep 8 12:12:18 2013 +0800 asdfasdfasdfasdf commit 3ef326c1e033ecab192e7207fbd1dbd93a6cab95 Author: shunliz <[email protected]> Date: Sun Sep 8 10:59:38 2013 +0800 add other two file commit adf64aa26eba3ccfd53ea4b4ab7d8aa8ac241c63 Author: shunliz <[email protected]> Date: Sun Sep 8 10:56:42 2013 +0800 first.txt commited commit c905d813e590157d816bbd3603bcef10ad03e166 Author: shunliz <[email protected]> Date: Sun Sep 8 09:39:23 2013 +0800 Initial commit $ git ls-tree 462d5fe241b10b 100644 blob a135e5a18155983180a484700b0dc0ff4afd1632 first.txt 100644 blob dba0775758f0948f5f02f7caa2db7bbc16f7f290 first_file.txt 100644 blob 41bfa64572b77c8d11ad9566de6be7610179f6d6 sencod.txt 100644 blob 569bde967162f309235160276df47422bb09232d third.txt $ git ls-tree HEAD 100644 blob a135e5a18155983180a484700b0dc0ff4afd1632 first.txt 100644 blob dba0775758f0948f5f02f7caa2db7bbc16f7f290 first_file.txt 100644 blob 41bfa64572b77c8d11ad9566de6be7610179f6d6 sencod.txt 100644 blob 569bde967162f309235160276df47422bb09232d third.txt $ git ls-tree HEAD^ (=git ls-tree HEAD^1) 100644 blob a135e5a18155983180a484700b0dc0ff4afd1632 first.txt 100644 blob dba0775758f0948f5f02f7caa2db7bbc16f7f290 first_file.txt 100644 blob 8a686325f3551d8d8d2738b0ac7380b56f0cd4aa sencod.txt 100644 blob 569bde967162f309235160276df47422bb09232d third.txt $ git ls-tree HEAD^^ (=git ls-tree HEAD^2) 100644 blob a135e5a18155983180a484700b0dc0ff4afd1632 first.txt 100644 blob dba0775758f0948f5f02f7caa2db7bbc16f7f290 first_file.txt $ git ls-tree HEAD^^^ (=git ls-tree HEAD^3) 100644 blob dba0775758f0948f5f02f7caa2db7bbc16f7f290 first_file.txt
git show 可以查看任何东西,目录树,commit,log等等
$ git show HEAD^1 commit 3ef326c1e033ecab192e7207fbd1dbd93a6cab95 Author: shunliz <[email protected]> Date: Sun Sep 8 10:59:38 2013 +0800 add other two file diff --git a/sencod.txt b/sencod.txt new file mode 100644 index 0000000..8a68632 --- /dev/null +++ b/sencod.txt @@ -0,0 +1 @@ +Second file contentx diff --git a/third.txt b/third.txt new file mode 100644 index 0000000..569bde9 --- /dev/null +++ b/third.txt @@ -0,0 +1 @@ +Third file content $ git show 41bfa6457 modified second line of txt file $ git show sencod.txt commit 462d5fe241b10b193ada252e7b8ccb2b6c08e6d9 Author: shunliz <[email protected]> Date: Sun Sep 8 12:12:18 2013 +0800 asdfasdfasdfasdf diff --git a/sencod.txt b/sencod.txt index 8a68632..41bfa64 100644 --- a/sencod.txt +++ b/sencod.txt @@ -1 +1 @@ -Second file contentx +modified second line of txt file $ git show 462d5fe24..ef3ed93dbd commit ef3ed93dbdf57a48e325240cdb31b2a846b5daa8 Author: shunliz <[email protected]> Date: Sun Sep 8 15:38:20 2013 +0800 more commit diff --git a/sencod.txt b/sencod.txt index f05f181..17ddfeb 100644 --- a/sencod.txt +++ b/sencod.txt @@ -1,2 +1,4 @@ modified second line of txt fil e + +more add commit commit a58af56f31db6ee9261c3dbf1665ee41e4214159 Author: shunliz <[email protected]> Date: Sun Sep 8 15:37:37 2013 +0800 another commit diff --git a/sencod.txt b/sencod.txt index 41bfa64..f05f181 100644 --- a/sencod.txt +++ b/sencod.txt @@ -1 +1,2 @@ -modified second line of txt file +modified second line of txt fil +e $ git show --since=2013-08-24 commit ef3ed93dbdf57a48e325240cdb31b2a846b5daa8 Author: shunliz <[email protected]> Date: Sun Sep 8 15:38:20 2013 +0800 more commit diff --git a/sencod.txt b/sencod.txt index f05f181..17ddfeb 100644 --- a/sencod.txt +++ b/sencod.txt @@ -1,2 +1,4 @@ modified second line of txt fil e + +more add commit
在git中,分支是廉价的。在其他的源码管理系统中,如果需要在另外一个分支fix一个bug,这时我们需要把那个分支checkout下来,然后fix bug,然后提交。这时就存在两份code。而在git中,你只要保存一份code,然后创建不同的分支,然后fix bug在一个分支,开发新feature在一个分支,试验一个idea在另外一个分支。各个分支之间互不干扰,只需要在各个分支之间切换就ok。比如我们正在开发一个新的feature,这时线上突然发现一个重大bug需要fix,这时其他的源码管理系统就需要把线上的那个分支取下来,然后fix bug,然后提交。如果我们需要试验一下我们的想法,我们还需要再copy下来一份代码在里边试验。在git中就不需要这么麻烦,我们可以创建不同的分支。Fix bug 我们可以git checkout -b bug123 master,fix bug,然后commit我们的修改。然后git checkout master继续我们的新feature的开发。这时如果我们有一个新的想法需要验证一下,我们可以git checkout -b ideaxxx master,实现我们的想法,然后验证。验证通过后,我们可以把ieadxxx的代码merge到master上边来。
从上边已经可以看到git分支的强大。下边就看一下git的一些分支操作。
git branch显示当前机器的所有分支列表。Git默认的当前分支为master分支。*表示的是当前的分支。
git branch xxxx创建一个xxx的新分支。
git checkout xxx 切换到一个xxx分支。
git checkout -b xxxx 创建一个新分支同时切换到新分支。
git diff master..xxxx 对比master和xxx分支。
git diff master^..xxxx 对比master分支的前一个提交和xxx分支。
git branch -m newxxxx重命名一个分支。
git branch -d xxxx删除一个分支。必须在其他分支上才能删除需要删除的分支,不能删除当前正在操作的分支。如果需要删除的分支上有没有commit的内容,这时需要-D才能删除分支。
git merge sourcebranch destbranch 将source分支上的变更合并到dst分支。Dest分支如果是source分支的祖先,将会发生一个fast forward合并,如果dest分支上边的HEAD有新的提交,已经不用与source分支的checkout时,这时将发生一个真正的合并操作。
分支合并过程中如果发生冲突,这时就需要解决冲突。编辑发生冲突的文件,里边通过<<<<<<<<<HEAD和>>>>>>>>>>>>>>source标记冲突。<<<<<<<HEAD 标记合并的目标分支的内容,>>>>>>>>>>>>>>source标记源分支的代码。有三种方法解决冲突:
1,放弃merge操作。git merge --abort。
2,手动解决冲突,然后提交。手动决定是需要source还是dest分支上的内容,然后git add 修改后的文件,然后git commit xxxx。
3,使用一个merge工具合并冲突。常用的vimdiff, opendidff等。具体看以参考工具说明。
merge是一项头疼的工作。
减少merge的策略:
1,尽量短小的代码行。
2,尽量保证commit短小和集中在一个方面。
3,注意你的代码编辑器增加的不必要的空格,tab和换行等内容。
4,经常merge。如果不经常merge,攒在一起merge将是一项让人崩溃的事情。
5,经常地merge master分支的内容到你的开发分支,这样保证你的开发分支不会和master分支相差太远,减少将来merge到master的工作量。
Git是一个分布式的版本控制系统,可以和远程的其他人合作。我们可以把别人的内容拖下来,然后在别人的基础上工作。也可以把我们的工作分支发布出去,别人可以基于我们的分支工作。要使用这些特性,就需要用到git的远程分支。
本地的分支提交到远程分支的过程,本地和远程分支的变化。
从远程分支fetch更新下来内容,分支的变化。
远程分支情况下的工作流程:
1,本地做出修改。然后本地commit。
2,获取远程分支的最新更新,使你的origin/master分支更新。
3,合并你的本地的commit到origin/master。
4,push你的更新到远程服务器。
git remote显示所有远程服务器列表。
git remote add origin https://github.com/openstack/nova.git增加一个远程服务器。
git push -u origin master把本地的master分支push到远程的origin服务器。
git clone https://github.com/openstack/nova.git nova克隆一个远程服务器上的代码到nova目录。
git fetch https://github.com/openstack/nova.git 获取远程服务器的更新。并不自动合并到本地的master分支。需要合并到本地master时,origin/master到本地master的合并和其他分支没有差别。
git pull http://github.com/openstack/nova.git 获取远程更新并自动合并到本地master,存在冲突时会提示冲突。解决后本地commit。然后再push到服务器。
git push origin remotebranch 创建远程分支。
git push oringin :remotebranch删除远程分支。
git push origin --delete remotebranch 删除远程分支。
————————————————————————————————————————————————————————————————————————————
Openstack相关技术交流请加群:314889201