什么是 Git?

Git — The stupid content tracker,傻瓜内容跟踪器。它是由 Linus Torvalds 开发的一个分布式版本控制/软件配置管理软件。

所谓版本控制,说白了就好像游戏存档一样。我们在玩游戏的时候总希望将当前进度存档,而且每到一些关键时刻(比如关键的选择),我们都希望另存一个新档,这样当我们发现选择错误了,我们可以读取那个关键的存档,而放弃在这之后的进度。版本控制做的就是这样的事情,当我们在写代码的时候,我们总希望能够备份当前的代码,并且当我们面临一些重要的更改的时候,为了当发现需要推倒重做的时候能够顺利回滚到更改之前,我们往往需要另外保存一份。版本控制系统就是帮我们处理这类工作的,它可以监视我们的代码,当代码发生更改,就提示我们将这个更改提交,作为一个版本。这样当我们后悔对代码所做的修改时,可以通过它回滚到我们之前所提交的任何一个版本。

早期Linux的开发人员是使用BitKeeper来管理版本控制和维护程式码。2005年的时候,开发BitKeeper的公司同Linux内核开源社区结束合作关系,并收回使用BitKeeper的权利。Torvalds开始着手开发Git来替代BitKeeper。Git的优点包括:

  1. 分布式。与常用的版本控制工具CVS、Subversion等不同,它采用了分布式版本库的方式,不必服务器端软件支持,使源代码的发布和交流极其方便。
  2. 速度快,体积小。以往的版本控制工具如SVN等在提交的时候都是提交整个文件的,速度慢,体积也比较大。而Git把内容按元数据方式存储,每次提交的时候都只是提交代码中变更的部分,因此速度非常快,体积也小很多。这个特性对于诸如Linux kernel这样的大项目来说非常重要。
  3. 合并跟踪。GIT的分支管理非常简单和有趣。用户可以从同一个工作目录下快速的在几个分支间切换,可以很容易发现未被合并的分支,并且能简单而快捷的合并这些文件。
  4. 高可靠性。GIT的内容存储使用的是SHA-1哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏。

实际上内核开发团队决定开始开发和使用Git来作为内核开发的版本控制系统的时候,世界开源社群的反对声音不少,最大的理由是Git太艰涩难懂,从Git的内部工作机制来说,的确是这样。但是随着开发的深入,Git的正常使用都由一些友好的脚本命令来执行,使Git变得非常好用,即使是用来管理我们自己的开发项目,Git都是一个友好,有力的工具。现在,越来越多的著名项目采用Git来管理项目开发,例如:wine、U-boot等。

安装Git

Git 已经集成在大部分 Linux 系统的源里,Ubuntu用户直接 apt-get ,Arch用户直接 pacman -S 即可。

如果想要从源码安装,或者使用的是 Windows操作系统 ,可以访问 Git 的官方主页 查看更多安装信息。

基本设置

用户信息

1
2
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com

--global 选项更改的配置文件就是位于你用户主目录下的那个,以后你所有的项目都会默认使用这里配置的用户信息。如果要在某个特定的项目中使用其他名字或者电邮,只要去掉 --global 选项重新配置即可,新的设定保存在当前项目的 .git/config 文件里。

文本编辑器

1
$ git config --global core.editor emacs

差异分析工具

例如改用 vimdiff:

1
$ git config --global merge.tool vimdiff

Git 可以理解 kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge 和 opendiff 等合并工具的输出信息。

查看配置信息

查看全部的配置信息

1
$ git config --list

查看某个环境变量的设定

把特定的名字跟在后面即可,像这样:

1
$ git config user.name

获取帮助

三个命令任选一:

1
2
3
$ git help <verb>
$ git <verb> --help
$ man git-<verb>

取得项目的 Git 仓库

初始化新仓库

1
$ git init

添加跟踪文件

1
2
3
$ git add *.c
$ git add README
$ git commit -m 'initial project version'

从现有仓库克隆

1
$ git clone git://github.com/schacon/grit.git

这会在当前目录下创建一个名为 grit 的目录,其中包含一个 .git 的目录,用于保存下载下来的所有版本记录,然后从中取出最新版本的文件拷贝。

如果希望在克隆的时候,自己定义要新建的项目目录名称,可以在上面的命令末尾指定新的名字:

1
$ git clone git://github.com/schacon/grit.git mygrit

记录每次更新到仓库

检查当前文件状态

1
$ git status

跟踪新文件/暂存已修改文件

1
$ git add <file> ...

忽略某些文件

可以创建一个名为 .gitignore 的文件,列出要忽略的文件模式。来看一个实际的例子:

1
2
3
$ cat .gitignore
*.[oa]
*~

文件 .gitignore 的格式规范如下:

  • 所有空行或者以注释符号 # 开头的行都会被 Git 忽略。
  • 可以使用标准的 glob 模式 [1] 匹配。
  • 匹配模式最后跟反斜杠(/)说明要忽略的是目录。
  • 要忽略指定模式以外的文件或目录,可以在模式前加上惊叹号(!)取反。

查看已暂存和未暂存的更新

git status 的显示比较简单,仅仅是列出了修改过的文件。

如果要查看具体修改了什么地方,可以用 git diff 命令:

1
$ git diff

此命令比较的是 工作目录中当前文件和暂存区域快照之间的差异 ,也就是修改之后 还没有暂存 起来的变化内容。

若要看 已经暂存 起来的文件和 上次提交 时的快照之间的差异,可以用 git diff --cached 命令 或 git diff --staged 命令,两条命令等效,但后者只在 Git 1.6.1 及更高版本才有。)

1
$ git diff --cached

1
$ git diff --staged

提交更新

应该养成的习惯:每次准备提交前,先用 git status 看下,是不是都已暂存起来了,然后再运行提交命令 git commit

1
$ git commit

几个有用的参数:

  • -v: 将修改差异的每一行都包含到注释中来;
  • -m <提交说明>: 在一行命令中提交更新;
  • -a: 自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add 步骤。

移除文件

1
$ git rm <file> ...

如果删除之前修改过并且已经放到暂存区域的话,则必须要用强制删除选项 -f (译注:即 force 的首字母),以防误删除文件后丢失修改的内容。

递归匹配删除文件:

1
$ git rm \*~

会递归删除当前目录及其子目录中所有 ~ 结尾的文件,注意 * 号前必须得加上反斜杠,否则不会递归匹配。

仅是从跟踪清单中删除文件

把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。换句话说,仅是从跟踪清单中删除。比如一些大型日志文件或者一堆 .a 编译文件,不小心纳入仓库后,要移除跟踪但不删除文件,以便稍后在 .gitignore 文件中补上,用 –cached 选项即可

1
$ git rm --cached <file> ...

移动 / 重命名文件

1
$ git mv file_from file_to

其实,运行 git mv 就相当于运行了下面三条命令:

1
2
3
$ mv README.txt README
$ git rm README.txt
$ git add README

查看提交历史

1
$ git log

常用选项

选项 说明
-p 按补丁格式显示每个更新之间的差异。
--stat 显示每次更新的文件修改统计信息。
--shortstat 只显示 --stat 中最后的行数修改添加移除统计。
--name-only 仅在提交信息后显示已修改的文件清单。
--name-status 显示新增、修改、删除的文件清单。
--abbrev-commit 仅显示 SHA-1 的前几个字符,而非所有的40个字符。
--relative-date 使用较短的相对时间显示(比如,“2 weeks ago" )。
--graph 显示ASCII图形表示的分支合并历史。
--pretty 使用其他格式显示历史提交信息。

使用 --pretty 选项,可以指定使用完全不同于默认格式的方式展示提交历史。比如用 oneline 将每个提交放在一行显示,这在提交数很大时非常有用。可选值包括:

  • oneline
  • short
  • full
  • fuller
  • format: 可以定制要显示的记录格式,这样的输出便于后期编程提取分析,常用的格式占位符写法及其代表的意义:
选项 说明
%H 提交对象(commit)的完整哈希字串
%h 提交对象的简短哈希字串
%T 树对象(tree)的完整哈希字串
%t 树对象的简短哈希字串
%P 父对象(parent)的完整哈希字串
%p 父对象的简短哈希字串
%an 作者(author)的名字
%ae 作者的电子邮件地址
%ad 作者修订日期(可以用 -date=选项定制格式)
%ar 作者修订日期,按多久以前的方式显示
%cn 提交者(committer)的名字
%ce 提交者的电子邮件地址
%cd 提交日期
%cr 提交日期,按多久以前的方式显示
%s 提交说明

限制输出长度

git log 还有许多非常实用的限制输出长度的选项:

选项 说明
-(n) 仅显示最近的n条提交
--since--after 仅显示指定时间之后的提交。
--until--before 仅显示指定时间之前的提交。
--author 仅显示指定作者相关的提交。
--committer 仅显示指定提交者相关的提交。

示例 查看 Git 仓库中,2008 年 10 月期间,Junio Hamano 提交的但未合并的测试脚本(位于项目的 t/ 目录下的文件):

1
$ git log --pretty="%h - %s" --author=gitster --since="2008-10-01" --before="2008-11-01" --no-merges -- t/

撤销操作

修改最后一次提交

1
$ git commit --amend

撤销文件修改

1
$ git checkout <file> ...

回退以前的版本

reset是指将当前head的内容重置,不会留任何痕迹。

1
git reset --hard HEAD~3

会将最新的3次提交全部重置,就像没有提交过一样。

远程仓库的使用

查看当前的远程库

1
git remote

如果加上 -v 选项(译注:此为 --verbose 的简写,取首字母),还可以显示对应的克隆地址:

1
$ git remote -v

添加远程仓库

1
$ git remote add <shortname> <url>

从远程仓库抓取数据

1
$ git fetch [remote-name]

此命令会到远程仓库中拉取所有你本地仓库中还没有的数据。运行完成后,你就可以在本地访问该远程仓库中的所有分支,将其中某个分支合并到本地,或者只是取出某个分支,一探究竟。

如果是克隆了一个仓库,此命令会自动将远程仓库归于 origin 名下。所以,git fetch origin 会抓取从你上次克隆以来别人上传到此远程仓库中的所有更新(或是上次 fetch 以来别人提交的更新)。有一点很重要,需要记住,fetch 命令只是将远端的数据拉到本地仓库,并不自动合并到当前工作分支,只有当你确实准备好了,才能手工合并。

如果设置了某个分支用于跟踪某个远端仓库的分支(参见下节及第三章的内容),可以使用 git pull 命令自动抓取数据下来,然后将远端分支自动合并到本地仓库中当前分支。在日常工作中我们经常这么用,既快且好。

推送数据到远程仓库

1
$ git push [remote-name] [branch-name]

查看远程仓库信息

1
$ git remote show [remote-name]

远程仓库的删除和重命名

1
2
3
4
5
6
7
$ git remote rename <remote-name-old> <remote-name-new>
```

### 移除对应的远程仓库 ###

``` sh
$ git remote rm [remote-name]

实用技巧

Linux使用git的时候,如果添加的文件是中文名字,会显示为转义符,虽然不影响上传的结果,但是看着很不方便。这个时候可以使用

1
$ git config --global core.quotepath false

这个命令,来禁用对于大于0x80的字符进行转义的功能,这样就可以显示出中文文件名了。


  1. 所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。

    • 星号(*)匹配零个或多个任意字符;
    • [abc] 匹配任何一个列在方括号中的字符(这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c);
    • 问号(?)只匹配一个任意字符。

    如果在方括号中使用短划线分隔两个字符,表示所有在这两个字符范围内的都可以匹配(比如 [0-9] 表示匹配所有 0 到 9 的数字)。 ↩︎

Comments