2009年11月2日星期一

[译文] git 简要教程 (适用于 1.5.1 或更新版本)1

原文链接:http://www.kernel.org/pub/software/scm/git/docs/tutorial.html

这个教程将介绍如何将一个新的项目导入到 git 之中,如何修改项目并如何将这些变更与其他开发者分享。

如果你更感兴趣如何用 git 取出一个项目,比如,测试软件的最新版本,你可能更应该看看The Git User’s Manual的前两章。

首先,记住你可以用 man 来获取 git 的文档,比如 "git diff" 的文档可以用如下命令察看:
$ man git-diff

在做任何改动之前,最好把自己的名字和 email 地址介绍给大家,最简单的方法就是:
$ git config --global user.name "Your Name Comes Here"$ git config --global user.email [email protected]
导入一个新项目

假设你有一个名为 project.tar.gz 的 tarball 作为项目的初始内容。你可以如下操作来把它至于 git 版本控制之下。
$ tar xzf project.tar.gz$ cd project$ git init

Git 将会如下回复:
Initialized empty Git repository in .git/

现在,你已经初始化了工作目录——你可能已经注意到了名为 ".git" 的一个新目录了。

下一步就是使用 git-add(1) 命令告诉 git 当前目录的所有文件 (注意这个点:.) 全是项目的一个快照:
$ git add .

这个快照目前存放在一个临时区域之中,在 git 中称为 "index"(索引)。使用 git-commit(1) 命令,你可以把 index 的所有内容永久性地存放到软件仓库之中:
$ git commit

这条命令会向你提示输入版本变更信息。这样,你的项目的第一个版本就已经存入 git 之中了。
进行修改

修改一些文件之后,你可以将更新这些内容到 index 之中:
$ git add file1 file2 file3

现在,你已经准备就绪,可以提交了。现在你可以使用 git-diff(1) 命令的 –cache 参数:
$ git diff --cached

(如果不使用 –cached 参数,git-diff(1)会显示所有还没添加进 index 的已经做出的改动。) 你也可以使用 git-status(1)来获得一些当前状况的概要信息:
$ git status# On branch master# Changes to be committed:# (use "git reset HEAD <file>..." to unstage)## modified: file1# modified: file2# modified: file3#

如果你需要进行更多改动,现在就可以进行,然后可以添加到 index 之中。最后,使用如下命令提交改动:
$ git commit

这将再次要求你输入关于这次改动内容的描述性信息,之后记录下的项目新版本。

此外,如果想省掉提交之前的git add命令,你可以直接用
$ git commit -a

这样会自动检测所有修改过的文件 (不包括新文件) ,并一气呵成地将它们添到 index 之中,并提交。

关于提交的描述信息: 虽然这个信息不是必须的,但提交信息描述最好以一行不超过50个字符的概要性信息来开头,在一个空行之后再进行更多的描述。比如那些将 commit 转化为 email 的工具就会把这个第一行作为邮件标题,其余的提交内容则放在邮件内部。
Git 跟踪内容而不是文件

很多版本控制系统提供了一个 "add" 命令用来记录一个新文件。而 git 的 "add" 命令更加简单也更加强大: git add既用于新文件也用于新近改动的文件,在所有这些情况下,它在 index 中对所有的文件与状态进行一次快照,这样就可以在下一次 commit 命令中进行提交。
Viewing project history

在任何时候,你都可以如下查看所有你进行过的改动
$ git log

你可能还想看到每一步改进中的所有完整的 diff ,这可以使用如下命令
$ git log -p

浏览改动的概要对于获得每一步修改的情况常常是比较有用的,可以使用如下命令
$ git log --stat --summary
管理分支

一个 git 仓库可以包含多个开发分支。使用如下命令可以建立一个称为 "experimental" 的新分支
$ git branch experimental

如果你运行命令
$ git branch

你将可以得到类似下面的已有分支的列表
experimental* master

"experimental" 就是你刚刚建立的那个分支,而 "master" 分支则是建立仓库的时候自动创建的缺省分支,里面的星号表示你当前所在的分支;输入命令
$ git checkout experimental

就可以切换到 experimental 分支。现在修改一个文件,并提交改变,然后重新回到 master 分支:
(edit file)$ git commit -a$ git checkout master

你会发现,刚才的变更已经不可见了,这是因为这个改变是发生于 experimental 分支的,而你现在已经回到 master 分支了。

现在,你可以在 master 分支上做一些不同的变更:
(edit file)$ git commit -a

这里,两个分支已经产生不同了,每个分支上都发生了不同的改动。要把 experimental 中的改变也合并到 master 之中,运行命令
$ git merge experimental

如果两者的改变并不冲突,那么就算是完成了。而如果这里有冲突,有问题的文件左边会显示出标记,以表明这个文件发生了冲突;
$ git diff

上述命令将会列出具体的冲突。一旦你编辑文件解决了冲突,
$ git commit -a

这个命令将把合并的结果提交。最终,
$ gitk

会显示出漂亮的图标以展示历史变革。

这里你可以使用如下命令删除 experimental 分支。
$ git branch -d experimental

这个命令会确定 experimental 中的所有改动已经在当前分支当中了。

如果你在 crazy-idea 分支中进行开发,然后又后悔了,你可以用如下命令删除分支
$ git branch -D crazy-idea

分支操作十分简单而且代价低廉,所以适合于尝试一些东西。
使用 git 进行协作

假设 Alice 在 /home/alice/project 中的 git 仓库启动了一个新项目,而在本机中也拥有 home 目录的 Bob 想要贡献一些代码。

他可以以如下工作开始:
$ git clone /home/alice/project myrepo

这会新建一个名为 "myrepo" 的目录,里面包含了 Alice 的仓库的一份克隆。这份克隆与原始项目完全一致,可以处理自己的一份原始项目历史。

之后,Bob 进行了一些变更并提交了这些变动:
(edit files)$ git commit -a(repeat as necessary)

当他完成的时候,他告诉 Alice 将 /home/bob/myrepo 之中的变动导入到原始仓库之中。她使用如下命令来完成这一工作:
$ cd /home/alice/project$ git pull /home/bob/myrepo master

这会合并 Bob 的 "master" 分支到 Alice 的当前分支。如果 Alice 也已经修改了某些内容,她需要手工修复冲突。(注意,"master" 参数实际上并不是必要的,因为这是缺省分支。)

"pull" 命令包括两个操作: 从远端分支中取出改动,然后合并到当前分支之中。

当你只在一个很小的小组里工作的时候,通常不会频繁地访问同一个仓库。通过定义仓库的快捷方式,可以让访问远程仓库更方便一些:
$ git remote add bob /home/bob/myrepo

这样,Alice 可以如下用 "git fetch" 命令仅取出改动,而不把它们合并到当前分支之中:
$ git fetch bob

和长格式不同,当 Alice 使用 git remote设置的快捷方式从 Bob 的仓库中获取内容的时候,取出的内容存储在一个 remote tracking 分支之中,在本例中是 bob/master。所以,如下操作:
$ git log -p master..bob/master

将会列出从 Bob 从 Alice 的主分支中分支出去以后的所有改动。

检查了这些变动之后,Alice 可以将这些变动合并到自己的 master 分支中:
$ git merge bob/master

这个合并也可以通过从自己的 remote tracking 分支中 pull 来做到,如:
$ git pull . remotes/bob/master

注意,git pull 总是合并进当前的分支,不论命令行给出的是什么。

之后,Bob 可以如下使用 Alice 的最近改动更新自己的仓库
$ git pull

这里,他不需要给出 Alice 的仓库的位置;当 Bob 克隆了 Alice 的仓库的时候,git 在仓库设置中保存了她的仓库的位置,即 pull 所使用的位置:
$ git config --get remote.origin.url/home/alice/project

(git-clone 创建的完整配置信息可以用 "git config -l" 获得,git-config(1)的 man page 解释了所有选项的含义。)

Git 也在 "origin/master" 分支保存了一份 Alice 的主分支的原始拷贝:
$ git branch -r origin/master

如果其后 Bob 决定转到另一台主机上工作,他还可以通过 ssh 来克隆原始仓库:
$ git clone alice.org:/home/alice/project myrepo

此外,git 本身也有远程协议,并且可以使用 rsync 或 http,详细情况可以查看 git-pull(1)的 man page。

Git 也可以使用类似 CVS 的工作方式,使用一个中心仓库,所有用户将改动推送到仓库之中,相关内容可以查阅 git-push(1)的手册页或git for CVS users。
浏览历史

Git 的历史是通过一系列相互关联的 commit 构成的。我们已经通过 git log 命令看到了这些提交的列表。注意,每个 git log 条目的第一行是那次提交的名称:
$ git logcommit c82a22c39cbc32576f64f5c6b3f24b99ea8149c7Author: Junio C Hamano <[email protected]>Date: Tue May 16 17:18:22 2006 -0700

merge-base: Clarify the comments on post processing.

把这个名称用于 git show 命令,可以得到提交的详情。
$ git show c82a22c39cbc32576f64f5c6b3f24b99ea8149c7

不过还有其他办法来指代这次提交。你可以只使用名称的开始部分,只要它足够长,保证在所有提交中是惟一的就行了:
$ git show c82a22c39c # the first few characters of the name are # usually enough$ git show HEAD # the tip of the current branch$ git show experimental # the tip of the "experimental" branch

每一次提交通常都有一次提交作为 "parent" ,它是项目的前一个状态:
$ git show HEAD^ # to see the parent of HEAD$ git show HEAD^^ # to see the grandparent of HEAD$ git show HEAD~4 # to see the great-great grandparent of HEAD

要注意,合并提交可能会有多个 "parent":
$ git show HEAD^1 # show the first parent of HEAD (same as HEAD^)$ git show HEAD^2 # show the second parent of HEAD

你还可以给你的提交一个名字; 命令
$ git-tag v2.5 1b2e1d63ff

让你可以使用 "v2.5" 来指代 1b2e1d63ff。如果你想把这个名字与他人共享 (比如标记一个发布版本),你应该建立一个 "tag" 对象,可能还需要签署它; 详情请查看 git-tag(1)的 man page。

任何 git 命令都可以使用上述任何一种名字。比如:
$ git diff v2.5 HEAD # compare the current HEAD to v2.5$ git branch stable v2.5 # start a new branch named "stable" based # at v2.5$ git reset --hard HEAD^ # reset your current branch and working # directory to its state at HEAD^

小心使用上述最后一个命令: 这将丢失工作目录中的所有改动,他还会清除本分支内随后的所有提交。如果这个分支是包含这些提交的惟一分支,它们将永远地丢失了。此外,不要对一个公众可见的、有其他开发者从中 pull 内容的分支使用 "git reset" 命令,这将导致一些不必要的合并来清除其他开发者的历史信息。如果你需要取消已经推送的改动,可以使用git-revert(1)命令。

git grep 可以在项目的所有版本历史中寻找字符串,如下命令
$ git grep "hello" v2.5

会在版本 v2.5 中寻找所有 "hello" 的踪迹。

如果你不提供 commit 名称,git grep 会在你当前的目录中搜索所有由 git 管理的文件。于是,如下命令
$ git grep "hello"

是搜索 git 跟踪的所有文件的便捷的方式。

很多 git 命令可以处理一组提交,可以通过多种方式来指定版本。这里是一些 git log 的例子:
$ git log v2.5..v2.6 # commits between v2.5 and v2.6$ git log v2.5.. # commits since v2.5$ git log --since="2 weeks ago" # commits from the last 2 weeks$ git log v2.5.. Makefile # commits since v2.5 which modify # Makefile

你给出的范围的上下边界不一定是严格的时间先后关系,比如,"stable-release" 分支可能会在 "master" 分支之后相当长一段时间才会引入同一个提交内容,这样
$ git log stable..experimental

将会列出 expermental 分支之中已经有的,而 stable 分支却还没有的提交,而命令
$ git log experimental..stable

将会列出 stable 中已有、但 experimental 却没有的提交。

"git log" 命令有一个弱点:必须将所有提交在一个列表中呈现出来。当项目历史中有多个不同开发分支并最终合并到一起时,"git log" 中呈现出来的顺序可能没什么意义。

大部分有大量开发者的项目 (比如 linux kernel 或 git 本身) 都经常合并分支,gitk 可以更好地将这些合并变化展示出来。比如,
$ gitk --since="2 weeks ago" drivers/

这个命令允许你浏览过去两个星期中在 "drivers" 目录之中的任意提交。(注意: 你可以按住 ctrl 键然后用 "-" 和 "+" 来调整 gitk 的字体大小。)

最后,大部分命令可以带有文件名,这可以用于指定某次提交中的某个文件,从而指定某个文件的某个版本:
$ git diff v2.5:Makefile HEAD:Makefile.in

你还可以用 "git show" 命令去查看任意文件的任意版本:
$ git show v2.5:Makefile
下一步学习

这个教程应该足够你的项目进行基本的项目发布版本管理。不过,要完全深入地理解 git 的强大功能可能还需要理解两个简单的概念:

对象数据库是个相当阳春的系统,用于存储你的项目的历史,包括文件、目录以及各次提交。

索引文件是目录树的状态的缓存,用于建立提交、取出工作目录并保存一次合并中包含的不同的目录树。

本教程的第二部分解释了对象数据库、索引文件以及一些其他你在使用 git 中所需要了解的零七八碎的概念。

如果你不想在这条路上继续下去,还有一些细枝末节的东西可能十分有趣:

git-format-patch(1), git-am(1): 用于将一系列 git 提交转化成 email 发送的补丁或反之,对于 linux 内核这样的高度依赖于邮件发送的补丁的项目来说十分有用。

git-bisect(1): 当你的项目发生问题 (功能或性能上的退步) 的时候,一个跟踪发现错误的方法就是通过历史发现那个罪魁祸首的提交。Git bisect 可以帮助你进行二分查找发现那个提交。它能在一个有很多分支合并的具有复杂非线性历史的项目中十分灵巧地进行接近最优的搜索。

Everyday GIT with 20 Commands Or So

git for CVS users.

原文最后更新 25-Aug-2007 03:53:15 UTC

译文初稿:2007年7月,王旭 (gnawux<at>gmail.com)

译文最后更新:2008年5月2日,王旭 (gnawux<at>gmail.com)

没有评论:

发表评论