git实战

Git是一个分布式的版本控制系统。Linux kernel源码管理就是通过git管理的。Linux源码最开始用社区版的bitkeeper管理源码,后来bitkeeper社区版不再开源了。Linus觉得CVSSVN都还有需要改进的地方,就从头开发了一个分布式的版本控制系统git。鉴于“git详解系列”文章已经达到了不可企及的高度。这篇文章主要从实践的角度出发,更多的介绍git操作。

常见的版本控制系统还有IBMclearcaseSVNCVS等。假设你有个文件,需要不停地修改,然后还需要追踪文件的每一修改记录,需要方便的取出任一次修改记录或者恢复到任一次修改。这时你需要一个版本控制系统,通常也就变更控制系统,用来专门记录你的每一次变更。对于软件开发人员来说版本控制系统经常是指源码管理系统,上边提到的几个都是经常用到的源码管理系统,不过它们都是集中式的版本控制系统,而Git是一个分布式的控制系统,所以在概念方面要比其他几个系统复杂一些。

分布式版本控制系统和集中式的版本控制系统的区别在于,集中式的版本控制系统都有一个集中的源码库,所有的人都修改这个集中源码库的文件。分布式的版本控制系统不必有一个中央的源码库,所有的源码都在本地源码库中进行追踪和管理。需要的时候才上传到中央服务器。集中式的版本控制系统,中央服务器可能成为单点故障,导致不能工作。分布式的版本控制系统,每一个本地的源码都是一个独立的源码库,即使在飞机上,不能连接网络,还是可以照常工作。


1 Git config

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

2 git init

初始化一个仓库。git init。将在项目的目录下创建一个.git的隐藏文件夹,这个文件夹里边存放了git管理这个项目的所有信息。包括刚才上边提到的项目级别的config目录,保存了项目的配置。同时还包括HEADbranches,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,详细的描述。

3 Git concepts

版本树

Git使用三个版本树的模式管理源码,而其他的基本都使用两棵版本树的方式管理源码。

两棵版本树

仓库和工作目录。从仓库获取源码,在本地工作目录编辑修改,然后提交到仓库。不停重复这个工作一直到任务完成。

三课版本树

Git中增加一棵版本树,一个staging index树。从仓库获取源码到本地工作目录,做出修改,这时git不是直接提交到仓库,而是先提交到staging index。其他的版本管理工具中如果修改一个bug,今天修改了一个bug,但是还没有修改完成,明天还要修改几个文件。这时你就需要commit两次。在git中你可以先把今天的修改addstaging index中,明天修改完成后,把明天的修改一块添加到staging index中,然后一次commit

git实战_第1张图片

Git 工作流程

新建一个文件 file.txt

Git add file.txt

Git commit file.txt


最开始三棵版本树上的文件的版本是相同的。这时我们对工作目录的文件作出修改。

这时工作目录的文件变成v2了。当我们执行了git add file.txt后,我们就把工作目录的修改暂存到了staging index中。这时版本如下:

这时我们提交修改。git commit file.txt。版本变成如下:

git实战_第2张图片

这时文件的三棵版本树的版本又一致了。Git的工作流程就是修改文件,增加修改,然后提交修改。不停地重复这个工作。

Hash values

git是如何引用版本变更中的任何一个快照状态的呢?Git为每一个变更都会生成一个checksum,使用SHA-1生成checksum40个十六进制字符。比如: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就是当前分支对应到仓库中的一个指针。

4 git status

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)

5 git diff 

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可以查看某一个文件的差别。适用上边两种情况。 

6 Git undo

git checkout -- filename 撤销工作区的修改

git reset HEAD filename撤销暂存,保留工作区的修改。

git reset --hard filename 撤销暂存,撤销工作区的修改。

git reset --soft只修改指针指向,暂存和工作区不做修改。

7 查看git

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 可以查看任何东西,目录树,commitlog等等

$ 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

8 Git branch

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的代码mergemaster上边来。

从上边已经可以看到git分支的强大。下边就看一下git的一些分支操作。

git实战_第3张图片

git branch显示当前机器的所有分支列表。Git默认的当前分支为master分支。*表示的是当前的分支。

git branch xxxx创建一个xxx的新分支。

git checkout xxx 切换到一个xxx分支。

git checkout -b xxxx 创建一个新分支同时切换到新分支。

git diff master..xxxx 对比masterxxx分支。

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分支相差太远,减少将来mergemaster的工作量。

9 Git remote

Git是一个分布式的版本控制系统,可以和远程的其他人合作。我们可以把别人的内容拖下来,然后在别人的基础上工作。也可以把我们的工作分支发布出去,别人可以基于我们的分支工作。要使用这些特性,就需要用到git的远程分支。

git实战_第4张图片

本地的分支提交到远程分支的过程,本地和远程分支的变化。

从远程分支fetch更新下来内容,分支的变化。

git实战_第5张图片

远程分支情况下的工作流程:

1,本地做出修改。然后本地commit

2,获取远程分支的最新更新,使你的origin/master分支更新。

3,合并你的本地的commitorigin/master

4push你的更新到远程服务器。

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

你可能感兴趣的:(github,git,分布式,OpenSource)