Git 是 Linux Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的分布式版本控制软件,它不同于Subversion、CVS这样的集中式版本控制系统。在集中式版本控制系统中只有一个仓库(repository),许多个工作目录(working copy),而像Git这样的分布式版本控制系统中(其他主要的分布式版本控制系统还有BitKeeper、Mercurial、GNU Arch、Bazaar、Darcs、SVK、Monotone等),每一个工作目录都包含一个完整仓库,它们可以支持离线工作,本地提交可以稍后提交到服务器上。分布式系统理论上也比集中式的单服务器系统更健壮,单服务器系统一旦服务器出现问题整个系统就不能运行了,分布式系统通常不会因为一两个节点而受到影响。
官方网站: http://git-scm.com/
官方下载: http://git-scm.com/download
Pro Git网站: http://progit.org/book/
中文版: http://progit.org/2010/06/09/pro-git-zh.html
本站下载: progit-zh.epub.tar.gz 要阅读此文件,需要在firefox下装个阅读 ePub 电子书插件
抛开服务器、帐户、SSH、读写权限等等干扰,在本地相对安全的实现一个干净清洁的 Git 使用环境。
sudo apt-get install git-core
安装Git程序,或者到新立得软件包里面 输入 git然后安装。
一般在新的系统上,我们都需要先配置下自己的 Git 工作环境。配置工作只需一次,以后升级时还会沿用现在的配置。当然,如果需要,你随时可以用相同的命令修改已有的配置。
Git 提供了一个叫做 git config 的工具(译注:实际是 git-config 命令,只不过可以通过 git 加一个名字来呼叫此命令。),专门用来配置或读取相应的工作环境变量。而正是由这些环境变量,决定了 Git 在各个环节的具体工作方式和行为。这些变量可以存放在以下三个不同的地方:
第一个要配置的是你个人的用户名称和电子邮件地址。这两条配置很重要,每次 Git 提交时都会引用这两条信息,说明是谁提交了更新,所以会随更新内容一起被永久纳入历史记录:
$ git config --global user.name "Your Name Comes Here" $ git config --global user.email you@pub.admon.org
如果用了 –global 选项,那么更改的配置文件就是位于你用户主目录下的那个,以后你所有的项目都会默认使用这里配置的用户信息。如果要在某个特定的项目中使用其他名字或者电邮,只要去掉 –global 选项重新配置即可,新的设定保存在当前项目的 .git/config 文件里。
接下来要设置的是默认使用的文本编辑器。Git 需要你输入一些额外消息的时候,会自动调用一个外部文本编辑器给你用。默认会使用操作系统指定的默认编辑器,一般可能会是 Vi 或者 Vim。如果你有其他偏好,比如 Emacs 的话,可以重新设置:
$ git config --global core.editor emacs
还有一个比较常用的是,在解决合并冲突时使用哪种差异分析工具。比如要改用 vimdiff 的话:
$ git config --global merge.tool vimdiff
Git 可以理解 kdiff3,tkdiff,meld,xxdiff,emerge,vimdiff,gvimdiff,ecmerge,和 opendiff 等合并工具的输出信息。当然,你也可以指定使用自己开发的工具。
要检查已有的配置信息,可以使用 git config –list 命令:
$ git config --list user.name=Scott Chacon user.email=schacon@gmail.com color.status=auto color.branch=auto color.interactive=auto color.diff=auto ...
有时候会看到重复的变量名,那就说明它们来自不同的配置文件(比如 /etc/gitconfig 和 ~/.gitconfig),不过最终 Git 实际采用的是最后一个。
想了解 Git 的各式工具该怎么用,可以阅读它们的使用帮助,方法有三:
$ git help <verb> $ git <verb> --help $ man git-<verb>
比如,要学习 config 命令可以怎么用,运行:
$ git help config
我们随时都可以浏览这些帮助信息而无需连网。
Git 的共享仓库,都放在使用 TrueCrypt 加密后的虚拟分区中:
/Volumes/"TrueCrypt Volume"/Git
而 TrueCrypt 的虚拟磁盘镜像文件存储在移动硬盘上。当然,如果没有加密需要,可以将共享仓库直接放在移动硬盘中,比如:
/mnt/projects/Git
当前项目为 Diary 不用加密
存放在下边位置:
$ makdir (Project) #创建工作项目目录 $ cd (project-directory) $ mkdir (~/Projects/Diary) #在项目中建一个今天的目录 $ cd Diary #要对现有的某个项目开始用 Git 管理,只需到此项目所在的目录
$ git init
从当前目录初始化,初始化后,在当前目录下会出现一个名为 .git 的目录,所有 Git 需要的数据和资源都存放在这个目录中。不过目前,仅仅是按照既有的结构框架初始化好了里边所有的文件和目录,但我们还没有开始跟踪管理项目中的任何一个文件。
如果当前目录下有几个文件想要纳入版本控制,需要先用 git add 命令告诉 Git 开始对这些文件进行跟踪,然后提交:
$ git add * $ git add README
将工作树下所有文档(包含子目录)生成快照。所生成的快照被存放到一个临时的存储区域,Git 称该区域为索引。使用git-commit 命令可将索引提交至仓库中,这个过程称为提交,每一次提交都意味着版本在进行一次更新。
$ git commit -m 'initial project version' #你的版本更新信息
这样,Diary 项目已经是一个带有 Git 版本控制的目录了,并且该仓库已经进行了第一次成功的提交。
对于简短的版本更新信息,可以使用git-commit 的“-m”选项。
用这个已经变成带有 Git 版本控制功能的 Diary 项目目录,生成一个纯净的不带任何项目文件,只有 .git 目录的 Git 仓库:
$ cd ~/Projects $ git clone --bare Diary Diary.git
将该纯净仓库复制到我们的加密虚拟磁盘中:
$ mkdir /Volumes/"TrueCrypt Volumes"/Git $ cp ~/Projects/Diary.git /Volumes/"TrueCrypt Volume"/Git/
这样,我们 Diary 项目的原始纯净仓库,就已经被安全的保护在了 TrueCrypt 虚拟磁盘中的 Git 目录中了。
接下来,也是最后一步,删掉原来的 Diary 项目,重新从纯净仓库中 clone 出新的副本:
$ cd ~/Projects $ rm -f -r Diary $ git clone file:///Volumes/"TrueCrypt Volume"/Git/Diary.git
大功告成,目前的项目状态就是:
当前工作副本是 ~/Projects/Diary
原始仓库是 /Volumes/”TrueCrypt Volume”/Git/
当你愉快的工作了一个星期,进行了若干次 commit 提交操作,到了周末需要备份项目的时候(虽然一个星期备份一次是很不好的习惯,最好一天一次),只需要挂载上 TrueCrypt 分区,然后:
$ cd ~/Projects/Diary $ git commit -m "Last commit and then push" $ git push
你一周的辛苦工作,就已经被推送(push)到 TrueCrypt 分区中位于 Git/Diary.git 的原始仓库中去了。
当然,上边删除 Diary 原始目录并重新 clone,仅仅是为了方便快捷,和作为说明来用,另外一个可能会因为 Diary 中文件被更改而被 Git 拒绝推送的操作是:
view sourceprint? $ cd ~/Projects/Diary $ git remote add origin file:///Volumes/"TrueCrypt Volume"/Git/Diary.git $ git commit -m "Last commit and then push" $ git push origin master 或者 $ git push
二、在生成文档内容快照时,工作树中有一些文档是你不希望接受Git 管理的,譬如程序编译时生成的中间文件,对于这样的文件如何避免为之生成快照?譬如在工作树中存在以下子目录:
doc tmp ipc drivers fs
其中的tmp 目录存放着文档编译时生成的中间文件,因此该目录不应该被Git 所管理。为解决此类问题,Git 提供了文档忽略机制,可以将工作树中你不希望接受Git 管理的文档信息写到同一目录下的.gitignore 文件中。对于本例中的tmp 目录,采用如下操作可将其排除仓库之外,然后再对project 生成快照即可。
$ cd project $ echo "tmp" > .gitignore $ git add .
有关gitignore 文件的诸多细节知识可阅读其使用手册:
$ man gitignore
Git 仓库就是那个.git 目录,其中存放的是我们所提交的文档索引内容,Git 可基于文档索引内容对其所管理的文档进行内容追踪,从而实现文档的版本控制。工作树是包含.git 的目录,在前文示例中即project 目录。 为了更加明确仓库与工作树的概念,下面做一个实验:
$ cp -R project/.git /tmp/test.git $ cd /tmp $ git clone test.git test-copy
首先,我们将project 目录中的.git 目录复制到/tmp 目录下并进行重命名为test.git ,然后使用git-clone 命令从test.git 中生成test-copy 目录。若进入test-copy 目录观察一下,就会发现该目录所包含的内容是等同于project 目录的。
上述实验意味着,只要我们拥有仓库,即test.git ,那么就可以很容易地生成工作树,而这个工作树又包含着一个仓库,即test-copy/.git 。所以,我们可以这样理解:在Git 中,仓库与工作树之间无需分的很清楚。
在工作树中,我们日常所进行的工作无非是对Git 仓库所管理的文档进行修改,或者添加/删除一些文件。这些操作与采用Git 管理我们的文档之前没有任何差异,只是在你认为一个工作阶段完成之时,要记得通知Git,命令它记下你所进行更新,这一步骤是通过生成文档快照并将其加入到索引中来实现的。下面举例说明。
譬如我向project 目录添加了一个新文件 fs/binfmt_hwt.c ,我需要通知Git 记住我的这一更新:
$ cd project $ git add fs/binfmt_hwt.c
这样,Git 就会将有关fs/binfmt_hwt.c 的更新添加到索引中。然后我又对其它文档进行了一些修改,譬如修改了ipc/msg.c ,继续使用git-add 命令将它们的更新添加到索引中:
$ git add ipc/msg.c
这里也可以使用以下命令:
$ git-update-index
晚上,这一天的工作告以段落,我觉得有必要将今天所做的提交到仓库中,于是执行git-commit 操作,将索引内容添加到仓库中。
可能一天下来,你对工作树中的许多文档都进行了更新(文档添加、修改、删除),但是我忘记了它们的名字,此时若将所做的全部更新添加到索引中,比较轻省的做法就是:
1 工作树克隆命令,在后文中将会对其详细讲述。
$ cd project $ git add . $ git commit -a ... 输入日志信息...
最后这一步-a是通用的方法,我个人比较喜欢使用 git-commit –m “版本信息” –a ,这样就不用对版本文件操作了。
git-add 命令通常能够判断出当前目录(包括其子目录)下用户所添加的新文档,并将其信息追加到索引中。git-commit 命令的-a 选项可将所有被修改的文档或者已删除的文档的当前状态提交倒仓库中。记住,如果只是修改或者删除了已被Git 管理的文档,是没必要使用git-add 命令的。
本节并未讲述新的Git 命令,完全是前面所讲过的一些命令的重复介绍,只是它们出现的场景有所区别而已。另外,要注意的问题是,Git 不会主动记录你对文档进行的更新,除非你对它发号施令。
在工作树中,使用git-log 命令可以查看当前项目的日志,也就是你在使用git-commit 向仓库提交新版本时所属如的版本更新信息。
$ git log
如果你想看一下每一次版本的大致变动情况,可使用以下命令:
$ git log --stat --summary
下面分析一下git-log 命令的回应信息。如下是我对内核修改后提交的几个版本,版本标记分别为first、second、third ,最下面的那个为原始版本。
每一个版本都对应着一次项目版本更新提交。在项目日志信息中,每条日志的首行(就是那一串莫名奇妙的数字)为版本更新提交所进行的命名,我们可以将该命名理解为项目版本号。项目版本号应该是唯一的,默认由Git 自动生成,用以标示项目的某一次更新。如果我们将项目版本号用作git-show 命令的参数,即可查看该次项目版本的更新细节:
$ git show 版本号(比较长我就不输了)
除了使用完整的版本号查看项目版本更新细节之外,也还可以使用以下方式:
$ git show ddea091 # 一般只使用版本号的前几个字符即可 $ git show HEAD # 显示当前分支的最新版本的更新细节
每一个项目版本号通常都对应存在一个父版本号,也就是项目的前一次版本状态。可使用如下命令查看当前项目版本的父版本更新细节:
$ git show HEAD^ # 查看HEAD 的父版本更新细节 $ git show HEAD^^ # 查看HEAD 的祖父版本更新细节 $ git show HEAD~4 # 查看HEAD 的祖父之祖父的版本更新细节
版本控制系统的一个重要任务就是提供撤销和恢复某一阶段工作的功能。
git-reset 命令就是为这样的任务而准备的,它可以将项目当前版本定位到之前提交的任何版本中。
git-reset 命令有三个选项:--mixed 、–soft 和–hard 。我们在日常使用中仅使用前两个选项;第三个选项由于杀伤力太大,容易损坏项目仓库,需谨慎使用。
–mixed
仅是重置索引的位置,而不改变你的工作树中的任何东西(即,文件中的所有变化都会被保留,也不标记他们为待提交状态),并且提示什么内容还没有被更新了。这个是默认的选项。
–soft
既不触动索引的位置,也不改变工作树中的任何内容,我们只是要求这些内容成为一份好的内容(之后才成为真正的提交内容)。这个选项使你可以将已经提交的东西重新逆转至“已更新但未提交(Updated but not Check in)”的状态。就像已经执行过 git-update-index 命令,但是还没有执行 git-commit 命令一样。
–hard
将工作树中的内容和头索引都切换至指定的版本位置中,也就是说自 <commit-ish> 之后的所有的跟踪内容和工作树中的内容都会全部丢失。因此,这个选项要慎用,除非你已经非常确定你的确不想再看到那些东西了。关于git-reset 命令的具体如何使用可留作本章的练习题,你可以随便创建一个Git 仓库并向其提交一些版本更新,然后测试–mixed 与–soft 选项的效果。
如果欲查看git-reset 命令对工作树的影响,可使用git-status 命令。这是我们工作中的重点和难点!
直至现在为止,我们的项目版本库一直都是只有一个分支 master。在 git 版本库中创建分支的成本几乎为零,所以不必吝啬多创建几个分支。下面列举一些常见的分支策略,仅供大家参考:
创建一个属于自己的个人工作分支,以避免对主分支 master 造成太多的干扰,也方便与他人交流协作。
当进行高风险的工作时,创建一个试验性的分支,扔掉一个烂摊子总比收拾一个烂摊子好得多。
合并别人的工作的时候,最好是创建一个临时的分支,关于如何用临时分支合并别人的工作的技巧,将会在后面讲述。
下面的命令将创建我自己的工作分支,名叫 litary,并且将以后的工作转移到这个分支上开展。
$ git-branch litary $ git-checkout litary
要删除版本库中的某个分支,使用 git-branch -D 命令就可以了,例如:
$ git-branch -D branch-name
运行下面的命令可以得到你当前工作目录的分支列表:
$ git-branch
输出的分支中前面带*的就是你现在所在的分支,如果你忘记了你现在工作在哪个分支上,可以这样查看,而且运行下面的命令也可以告诉你:
$ cat .git/HEAD
查看项目的发展变化和比较差异
这一节介绍几个查看项目的版本库的发展变化以及比较差异的很有用的命令:
git-show-branch git-diff git-whatchanged
git-show-branch 命令可以使我们看到版本库中每个分支的世系发展状态,并且可以看到每次提交的内容是否已进入每个分支。让我们看到版本库的发展记录。
譬如我们要查看世系标号为 master^ 和 litary 的版本的差异情况,我们可以使用这样的命令:
$ git-diff master^ litary
合并两个分支:git-merge
既然我们为项目创建了不同的分支,那么我们就要经常地将自己或者是别人在一个分支上的工作合并到其他的分支上去。现在我们看看怎么将 litary 分支上的工作合并到 master 分支中。现在转移我们当前的工作分支到 master,并且将 litary 分支上的工作合并进来。
$ git-checkout master $ git-merge "Merge work in litary" HEAD litary合并两个分支,还有一个更简便的方式,下面的命令和上面的命令是等价的。 $ git-checkout master $ git-pull . litary
但是,此时 git 会出现合并冲突提示,就要根据具体的情况和需求对它修改。