Linux内核开发的版本控制工具Git中文教程

 

本文将以 Git 官方文档 Tutorial, core-tutorial 和 Everyday GIT 作为蓝本翻译整理,但是暂时去掉了对 Git 内部工作机制的阐述, 力求简明扼要,并加入了作者使用 Git 的过程中的一些心得体会,注意事项,以及更多的例子。 建议你最好通过你所使用的 Unix / Linux 发行版的安装包来安装 Git, 你可以在线浏览本文 ,也可以通过下面的命令来得到本文最新的版本库,并且通过后面的学习用 Git 作为工具参加到本文的创作中来。

 

$ git-clone  http://www.bitsun.com/git/gittutorcn.git

 

创建一个版本库:git-init-db

 

创建一个 Git 版本库是很容易的,只要用命令 git-init-db 就可以了。 现在我们来为本文的写作创建一个版本库:

 

$ mkdir gittutorcn
$ cd gittutorcn
$ git-init-db

 

git 将会作出以下的回应

 

defaulting to local storage area

 

这样,一个空的版本库就创建好了,并在当前目录中创建一个叫 .git 的子目录。 你可以用 ls -a 查看一下,并请注意其中的三项内容:

 

    *

 

      一个叫 HEAD 的文件,我们现在来查看一下它的内容:

 

      $ cat .git/HEAD

 

      现在 HEAD 的内容应该是这样:

 

      ref: refs/heads/master

 

      我们可以看到,HEAD 文件中的内容其实只是包含了一个索引信息, 并且,这个索引将总是指向你的项目中的当前开发分支。
    *

 

      一个叫 objects 的子目录,它包含了你的项目中的所有对象,我们不必直接地了解到这些对象内容, 我们应该关心是存放在这些对象中的项目的数据。
      Note
       关于 git 对象的分类,以及 git 对象数据库的说明, 请参看 [Discussion]
    *

 

      一个叫 refs 的子目录,它用来保存指向对象的索引。

 

具体地说,子目录 refs 包含着两个子目录叫 heads 和 tags, 就像他们的名字所表达的意味一样:他们存放了不同的开发分支的头的索引, 或者是你用来标定版本的标签的索引。

 

请注意:master 是默认的分支,这也是为什么 .git/HEAD 创建的时候就指向 master 的原因,尽管目前它其实并不存在。 git 将假设你会在 master 上开始并展开你以后的工作,除非你自己创建你自己的分支。

 

另外,这只是一个约定俗成的习惯而已,实际上你可以将你的工作分支叫任何名字, 而不必在版本库中一定要有一个叫 master 的分支,尽管很多 git 工具都认为 master 分支是存在的。

 

现在已经创建好了一个 git 版本库,但是它是空的,还不能做任何事情,下一步就是怎么向版本库植入数据了。
植入内容跟踪信息:git-add

 

为了简明起见,我们创建两个文件作为练习:

 

$ echo "Hello world" > hello
$ echo "Silly example" > example

 

我们再用 git-add 命令将这两个文件加入到版本库文件索引当中:

 

$ git-add hello example

 

git-add 实际上是个脚本命令,它是对 git 内核命令 git-update-index 的调用。 因此上面的命令和下面的命令其实是等价的:

 

$ git-update-index --add hello example

 

如果你要将某个文件从 git 的目录跟踪系统中清除出去,同样可以用 git-update-index 命令。例如:

 

$ git-update-index --force-remove foo.c

 

Note
 git- add 可以将某个目录下的所有内容全都纳入内容跟踪之下,例如: git-add ./path/to/your/wanted 。但是在这样做之前, 应该注意先将一些我们不希望跟踪的文件清理掉, 例如,gcc 编译出来的 *.o 文件,vim 的交换文件 .*.swp 之类。

 

应该建立一个清晰的概念就是,git-add 和 git-update-index 只是刷新了 git 的跟踪信息,hello 和 example 这两个文件中的内容并没有提交到 git 的内容跟踪范畴之内。
提交内容到版本库:git-commit

 

既然我们刷新了 Git 的跟踪信息,现在我们看看版本库的状态:

 

$ git-status

 

我们能看到 git 的状态提示:

 

#
# Initial commit
#
#
# Updated but not checked in br />#   (will commit)
#
#       new file: example
#       new file: hello
#

 

提示信息告诉我们版本库中加入了两个新的文件,并且 git 提示我们提交这些文件, 我们可以通过 git-commit 命令来提交:

 

$ git-commit -m "Initial commit of  gittutor reposistory"

 

查看当前的工作:git-diff

 

git-diff 命令将比较当前的工作目录和版本库数据库中的差异。 现在我们编辑一些文件来体验一下 git 的跟踪功能。

 

$ echo "It's a new day for git" >> hello

 

我们再来比较一下,当前的工作目录和版本库中的数据的差别。

 

$ git-diff

 

差异将以典型的 patch 方式表示出来:

 

diff --git a/hello b/hello
index a5c1966..bd9212c 100644
--- a/hello
+++ b/hello
@@ -1 +1,2 @@
 Hello, world
+It's a new day for git

 

此时,我们可以再次使用组合命令 git-update-index 和 git-commit 将我们的工作提交到版本库中。

 

$ git-update-index hello
$ git-commit -m "new day for git"

 

实际上,如果要提交的文件都是已经纳入 git 版本库的文件,那么不必为这些文件都应用 git-update-index 命令之后再进行提交,下面的命令更简捷并且和上面的命令是等价的。

 

$ git-commit -a -m "new day for git"

 

管理分支:git-branch

 

直至现在为止,我们的项目版本库一直都是只有一个分支 master。 在 git 版本库中创建分支的成本几乎为零,所以,不必吝啬多创建几个分支。 下面列举一些常见的分支策略,仅供大家参考:

 

    *

 

      创建一个属于自己的个人工作分支,以避免对主分支 master 造成太多的干扰, 也方便与他人交流协作。
    *

 

      当进行高风险的工作时,创建一个试验性的分支,扔掉一个烂摊子总比收拾一个烂摊子好得多。
    *

 

      合并别人的工作的时候,最好是创建一个临时的分支,关于如何用临时分支合并别人的工作的技巧, 将会在后面讲述。 

 

创建分支

 

下面的命令将创建我自己的工作分支,名叫 robin,并且将以后的工作转移到这个分支上开展。

 

$ git-branch robin
$ git-checkout robin

 

删除分支

 

要删除版本库中的某个分支,使用 git-branch -D 命令就可以了,例如:

 

$ git-branch -D branch-name

 

查看项目的发展变化和比较差异

 

这一节介绍几个查看项目的版本库的发展变化以及比较差异的很有用的命令:
git-show-branch
git-diff
git-whatchanged

 

我们现在为 robin, master 两个分支都增加一些内容。

 

$ git-checkout robin
$ echo "Work, work, workd" >> hello
$ git-commit -m "Some workd" -i hello

 

$ git-checkout master
$ echo "Play, play, play" >> hello
$ echo "Lots of fun" >> example
$ git-commit -m "Some fun" -i hello example

 

git-show-branch 命令可以使我们看到版本库中每个分支的世系发展状态, 并且可以看到每次提交的内容是否已进入每个分支。

 

$ git-show-branch 

 

这个命令让我们看到版本库的发展记录。

 

* [master] Some fun
 ! [robin] some work
--
*  [master] Some fun
 + [robin] some work
*+ [master^] a new day for git

 

譬如我们要查看世系标号为 master^ 和 robin 的版本的差异情况, 我们可以使用这样的命令:

 

$ git-diff master^ robin

 

我们可以看到这两个版本的差异:

 

diff --git a/hello b/hello
index 263414f..cc44c73 100644
--- a/hello
+++ b/hello
@@ -1,2 +1,3 @@
 Hello World
 It's a new day for git
+Work, work, work

 

Note
 关于 GIT 版本世系编号的定义,请参看 git-rev-parse 。

 

我们现在再用 git-whatchanged 命令来看看 master 分支是怎么发展的。

 

$ git-checkout master
$ git-whatchanged 

 

diff-tree 1d2fa05... (from 3ecebc0...)
Author: Vortune.Robin 
Date:   Tue Mar 21 02:24:31 2006 +0800

 

    Some fun

 

:100644 100644 f24c74a... 7f8b141... M  example
:100644 100644 263414f... 06fa6a2... M  hello

 

diff-tree 3ecebc0... (from 895f09a...)
Author: Vortune.Robin 
Date:   Tue Mar 21 02:17:23 2006 +0800

 

    a new day for git

 

:100644 100644 557db03... 263414f... M  hello

 

从上面的内容中我们可以看到,在 robin 分支中的日志为 "Some work" 的内容, 并没有在 master 分支中出现。
合并两个分支:git-merge

 

既然我们为项目创建了不同的分支, 那么我们就要经常地将自己或者是别人在一个分支上的工作合并到其他的分支上去。 现在我们看看怎么将 robin 分支上的工作合并到 master 分支中。 现在转移我们当前的工作分支到 master,并且将 robin 分支上的工作合并进来。

 

$ git-checkout master
$ git-merge "Merge work in robin" HEAD robin

 

合并两个分支,还有一个更简便的方式,下面的命令和上面的命令是等价的。

 

$ git-checkout master
$ git-pull . robin

 

但是,此时 git 会出现合并冲突提示:

 

Trying really trivial in-index merge...
fatal: Merge requires file-level merging
Nope.
Merging HEAD with d2659fcf690ec693c04c82b03202fc5530d50960
Merging br />1d2fa05b13b63e39f621d8ee911817df0662d9b7 Some fun
d2659fcf690ec693c04c82b03202fc5530d50960 some work
found 1 common ancestor(s) br />3ecebc0cb4894a33208dfa7c7c6fc8b5f9da0eda a new day for git
Auto-merging hello
CONFLICT (content): Merge conflict in hello

 

Automatic merge failed; fix up by hand

 

git 的提示指出,在合并作用于文件 hello 的 'Some fun' 和 'some work' 这两个对象时有冲突, 具体通俗点说,就是在 master, robin 这两个分支中的 hello 文件的某些相同的行中的内容不一样。 我们需要手动解决这些冲突,现在先让我们看看现在的 hello 文件中的内容。

 

$ cat hello

 

此时的 hello 文件应是这样的,用过其他的版本控制系统的朋友应该很容易看出这个典型的冲突表示格式:

 

Hello World
It's a new day for git
<<<<<<< HEAD/hello
Play, play, play
=======
Work, work, work
>>>>>>> d2659fcf690ec693c04c82b03202fc5530d50960/hello

 

我们用编辑器将 hello 文件改为:

 

Hello World
It's a new day for git
Play, play, play
Work, work, work

 

现在可以将手动解决了冲突的文件提交了。

 

$ git-commit -i hello

 

以上是典型的两路合并(2-way merge)算法,绝大多数情况下已经够用。 但是还有更复杂的三路合并和多内容树合并的情况。详情可参看: git-read-tree, git-merge 等文档。
逆转与恢复:git-reset
项目跟踪工具的一个重要任务之一,就是使我们能够随时逆转(Undo)和恢复(Redo)某一阶段的工作。

 

git-reset 命令就是为这样的任务准备的。 它将当前的工作分支的 头 定位到以前提交的任何版本中,它有三个重置的算法选项。
命令形式:

 

git-reset [--mixed | --soft | --hard] [<commit-ish>]
命令的选项:

 

--mixed
    仅是重置索引的位置,而不改变你的工作树中的任何东西(即,文件中的所有变化都会被保留, 也不标记他们为待提交状态),并且提示什么内容还没有被更新了。这个是默认的选项。 
--soft
    既不触动索引的位置,也不改变工作树中的任何内容,我们只是要求这些内容成为一份好的内容 (之后才成为真正的提交内容)。这个选项使你可以将已经提交的东西重新逆转至“已更新但未提交(Updated but not Check in)”的状态。 就像已经执行过 git-update-index 命令,但是还没有执行 git-commit 命令一样。 
--hard
    将工作树中的内容和头索引都切换至指定的版本位置中,也就是说自 <commit-ish> 之后的所有的跟踪内容和工作树中的内容都会全部丢失。 因此,这个选项要慎用,除非你已经非常确定你的确不想再看到那些东西了。 

 

一个重要技巧--逆转提交与恢复

 

可能有人会问,--soft 选项既不重置头索引的位置,也不改变工作树中的内容, 那么它有什么用呢?现在我们介绍一个 --soft 选项的使用技巧。 下面我们用例子来说明:

 

$ git-checkout master
$ git-checkout -b softreset
$ git-show-branch

 

这里我们创建了一个 master 的拷贝分支 softreset, 现在我们可以看到两个分支是在同一起跑线上的。

 

! [master] Merge branch 'robin'
 ! [robin] some work
  * [softreset] Merge branch 'robin'
---
- - [master] Merge branch 'robin'
+ * [master^] Some fun
++* [robin] some work

 

我们为 文件增加一些内容并提交。

 

$ echo "Botch, botch, botch" >> hello
$ git-commit -a -m "some botch"
$ git-show-branch

 

我们可以看到此时 softreset 比 master 推进了一个版本 "some botch" 。

 

! [master] Merge branch 'robin'
 ! [robin] some work
  * [softreset] some botch
---
  * [softreset] some botch
- - [master] Merge branch 'robin'
+ * [master^] Some fun
++* [robin] some work

 

现在让我们来考虑这样的一种情况,假如我们现在对刚刚提交的内容不满意, 那么我们再编辑项目的内容,再提交的话,那么 "some botch" 的内容就会留在版本库中了。 我们当然不希望将有明显问题的内容留在版本库中,这个时候 --soft 选项就很有用了。 为了深入了解 --soft 的机制,我们看看现在 softreset 分支的头和 ORIG_HEAD 保存的索引。

 

$ cat .git/refs/heads/softreset .git/ORIG_HEAD

 

结果如下:

 

5e7cf906233e052bdca8c598cad2cb5478f9540a
7bbd1370e2c667d955b6f6652bf8274efdc1fbd3

 

现在用 --soft 选项逆转刚才提交的内容:

 

git-reset --soft HEAD^

 

现在让我们再看看 .git/ORIG_HEAD 的中保存了什么?

 

$ cat .git/ORIG_HEAD

 

结果如下:

 

5e7cf906233e052bdca8c598cad2cb5478f9540a

 

看!现在的 .git/ORIG_HEAD 等于逆转前的 .git/refs/heads/softreset 。 也就是说,git- reset --soft HEAD^ 命令逆转了刚才提交的版本进度, 但是它将那次提交的对象的索引拷贝到了 .git/ORIG_HEAD 中。

 

我们再编辑 hello 文件成为下面的内容:

 

Hello World
It's a new day for git
Play, play, play
Work, work, work
Nice, nice, nice

 

我们甚至可以比较一下现在的工作树中的内容和被取消了的那次提交的内容有什么差异:

 

$ git-diff ORIG_HEAD

 

结果如下:

 

diff --git a/hello b/hello
index f978676..dd02c32 100644
--- a/hello
+++ b/hello
@@ -2,4 +2,4 @@ Hello World
 It's a new day for git
 Play, play, play
 Work, work, work
-Botch, botch, botch
+Nice, nice, nice

 

接着,我们可以恢复刚才被取消了的那次提交了。

 

$ git-commit -a -c ORIG_HEAD

 

注意,这个命令会打开默认的文本编辑器以编辑原来提交的版本日志信息,我们改为 "nice work" 。 大家可以自行用 git-show-branch 命令来查看一下现在的分支状态。 并且我们还可以不断地重复上述的步骤,一直修改到你对这个版本进度满意为止。

 

git-reset 命令还有很多的用途和技巧,请参考 git-reset ,以及 Everyday GIT with 20 commands or So 。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值