Skip to content
GitLab
Menu
Why GitLab
Pricing
Contact Sales
Explore
Why GitLab
Pricing
Contact Sales
Explore
Sign in
Get free trial
Changes
Page history
修改: unix/git.md
authored
Jan 13, 2017
by
Chan Chung kwong
Show whitespace changes
Inline
Side-by-side
unix/git.md
View page @
6a590fd8
...
...
@@ -2,65 +2,18 @@
# 基本概念
## 版本
管
制
## 版本
控
制
什么是版本控制?我为什么要关心它呢?版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订
情况的系统。在本书所展示的例子中,我们仅对保存着软件源代码的文本文件作版本控制管理,但实际上,你可以对
任何类型的文件进行版本控制。
版本控制就是记录一个或若干文件内容变化,以便将来查阅特定版本修订情况。最常见是对软件源代码作版本控制管理,但实际上可以对任何类型的文件进行版本控制。如果你是位图或网页设计师,可能会需要保存某一幅图片或页面布局文件的所有修订版本。
如果你是位图形或网页设计师,可能会需要保存某一幅图片或页面布局文件的所有修订版本(这或许是你非常渴望拥
有的功能)。采用版本控制系统(VCS)是个明智的选择。有了它你就可以将某个文件回溯到之前的状态,甚至将整
个项目都回退到过去某个时间点的状态。你可以比较文件的变化细节,查出最后是谁修改了哪个地方,从而找出导致
怪异问题出现的原因,又是谁在何时报告了某个功能缺陷等等。使用版本控制系统通常还意味着,就算你乱来一气把
整个项目中的文件改的改删的删,你也照样可以轻松恢复到原先的样子。但额外增加的工作量却微乎其微。
为了实现这效果,许多人习惯用复制整个项目目录的方式来保存不同的版本,或许还会改名加上备份时间以示区别,但手工流程既不方便又容易出。相比之下,采用版本控制系统(VCS)是个明智的选择。有了它你就可以:
-
将某个文件回溯到之前的状态,甚至将整个项目都回退到过去某个时间点的状态,从而可以更放心地进行试验
-
比较文件的变化细节,查出最后是谁修改了哪个地方,从而找出导致怪异问题出现的原因
当然这远不是现代版本控制系统的全部威力。早年的Unix工具SCCS和其GNU替代品RCS已经能做到这点。
本地版本控制系统
现代的版本控制系统还要让在不同系统上的开发者协同工作。传统的想法自然是客户端-服务端架构,在单一的服务器保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器以取出最新的文件或者提交更新,这就是集中式版本控制系统,常用的有CVS及其后继者Subversion,还有商业的Perforce。这种架构简单但有单点故障和效率瓶颈问题。
许多人习惯用复制整个项目目录的方式来保存不同的版本,或许还会改名加上备份时间以示区别。这么做唯一的好处
就是简单。不过坏处也不少:有时候会混淆所在的工作目录,一旦弄错文件丢了数据就没法撤销恢复。
为了解决这个问题,人们很久以前就开发了许多种本地版本控制系统,大多都是采用某种简单的数据库来记录文件的
历次更新差异(见图 1-1)。
图 1-1. 本地版本控制系统
其中最流行的一种叫做 rcs,现今许多计算机系统上都还看得到它的踪影。甚至在流行的 Mac OS X 系统上安装了开
发者工具包之后,也可以使用 rcs 命令。它的工作原理基本上就是保存并管理文件补丁(patch)。文件补丁是一种
特定格式的文本文件,记录着对应文件修订前后的内容变化。所以,根据每次修订后的补丁,rcs 可以通过不断打补
丁,计算出各个版本的文件内容。
集中化的版本控制系统
接下来人们又遇到一个问题,如何让在不同系统上的开发者协同工作?于是,集中化的版本控制系统( Centralized
Version Control Systems,简称 CVCS )应运而生。这类系统,诸如 CVS,Subversion 以及 Perforce 等,都有一
个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新
的文件或者提交更新。多年以来,这已成为版本控制系统的标准做法(见图 1-2)。
图 1-2. 集中化的版本控制系统
这种做法带来了许多好处,特别是相较于老式的本地 VCS 来说。现在,每个人都可以在一定程度上看到项目中的其
他人正在做些什么。而管理员也可以轻松掌控每个开发者的权限,并且管理一个 CVCS 要远比在各个客户端上维护本
地数据库来得轻松容易。
事分两面,有好有坏。这么做最显而易见的缺点是中央服务器的单点故障。如果宕机一小时,那么在这一小时内,谁
都无法提交更新,也就无法协同工作。要是中央服务器的磁盘发生故障,碰巧没做备份,或者备份不够及时,就会有
丢失数据的风险。最坏的情况是彻底丢失整个项目的所有历史更改记录,而被客户端偶然提取出来的保存在本地的某
些快照数据就成了恢复数据的希望。但这样的话依然是个问题,你不能保证所有的数据都已经有人事先完整提取出来
过。本地版本控制系统也存在类似问题,只要整个项目的历史记录被保存在单一位置,就有丢失所有历史更新记录的
风险。
分布式版本控制系统
于是分布式版本控制系统( Distributed Version Control System,简称 DVCS )面世了。在这类系统中,像 Git,
Mercurial,Bazaar 以及 Darcs 等,客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。这
么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。因为每一次的提
取操作,实际上都是一次对代码仓库的完整备份(见图 1-3)。
图 1-3. 分布式版本控制系统
更进一步,许多这类系统都可以指定和若干不同的远端代码仓库进行交互。籍此,你就可以在同一个项目中,分别和
不同工作小组的人相互协作。你可以根据需要设定不同的协作流程,比如层次模型式的工作流,而这在以前的集中式
系统中是无法实现的。
相反,在分布式版本控制系统如Git、Mercurial和Bazaar中,每个客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。这样不仅提高了容错性和一些常用操作的效率,而且可以根据需要设定不同的协作流程。
## Git
...
...
@@ -71,101 +24,24 @@ Linux 内核开源项目有着为数众广的参与者。绝大多数的 Linux
*
完全分布式
*
有能力高效管理类似 Linux 内核一样的超大规模项目(速度和数据量)
自诞生于 2005 年以来,Git 日臻成熟完善,在高度易用的同时,仍然保留着初期设定的目标。它的速度飞快,极其适合管理大项目,它还有着令人难以置信的非线性分支管理系统,可以应付各种复杂的项目开发需求。
那么,简单地说,Git 究竟是怎样的一个系统呢?请注意,接下来的内容非常重要,若是理解了 Git 的思想和基本
工作原理,用起来就会知其所以然,游刃有余。在开始学习 Git 的时候,请不要尝试把各种概念和其他版本控制系
统(诸如 Subversion 和 Perforce 等)相比拟,否则容易混淆每个操作的实际意义。Git 在保存和处理各种信息的
时候,虽然操作起来的命令形式非常相近,但它与其他版本控制系统的做法颇为不同。理解这些差异将有助于你准确
地使用 Git 提供的各种工具。
直接记录快照,而非差异比较
Git 和其他版本控制系统的主要差别在于,Git 只关心文件数据的整体是否发生变化,而大多数其他系统则只关心文
件内容的具体差异。这类系统(CVS,Subversion,Perforce,Bazaar 等等)每次记录有哪些文件作了更新,以及都
更新了哪些行的什么内容,请看图 1-4。
Git 并不保存这些前后变化的差异数据。实际上,Git 更像是把变化的文件作快照后,记录在一个微型的文件系统中。
每次提交更新时,它会纵览一遍所有文件的指纹信息并对文件作一快照,然后保存一个指向这次快照的索引。为提高
性能,若文件没有变化,Git 不会再次保存,而只对上次保存的快照作一链接。Git 的工作方式就像图 1-5 所示。
这是 Git 同其他系统的重要区别。它完全颠覆了传统版本控制的套路,并对各个环节的实现方式作了新的设计。Git
更像是个小型的文件系统,但它同时还提供了许多以此为基础的超强工具,而不只是一个简单的 VCS。稍后在第三章
讨论 Git 分支管理的时候,我们会再看看这样的设计究竟会带来哪些好处。
近乎所有操作都是本地执行
在 Git 中的绝大多数操作都只需要访问本地文件和资源,不用连网。但如果用 CVCS 的话,差不多所有操作都需要
连接网络。因为 Git 在本地磁盘上就保存着所有当前项目的历史更新,所以处理起来速度飞快。
举个例子,如果要浏览项目的历史更新摘要,Git 不用跑到外面的服务器上去取数据回来,而直接从本地数据库读取
后展示给你看。所以任何时候你都可以马上翻阅,无需等待。如果想要看当前版本的文件和一个月前的版本之间有何
差异,Git 会取出一个月前的快照和当前文件作一次差异运算,而不用请求远程服务器来做这件事,或是把老版本的
文件拉到本地来作比较。
用 CVCS 的话,没有网络或者断开 VPN 你就无法做任何事情。但用 Git 的话,就算你在飞机或者火车上,都可以非
常愉快地频繁提交更新,等到了有网络的时候再上传到远程仓库。同样,在回家的路上,不用连接 VPN 你也可以继
续工作。换作其他版本控制系统,这么做几乎不可能,抑或非常麻烦。比如 Perforce,如果不连到服务器,几乎什
么都做不了(译注:默认无法发出命令 p4 edit file 开始编辑文件,因为 Perforce 需要联网通知系统声明该文件
正在被谁修订。但实际上手工修改文件权限可以绕过这个限制,只是完成后还是无法提交更新。);如果是
Subversion 或 CVS,虽然可以编辑文件,但无法提交更新,因为数据库在网络上。看上去好像这些都不是什么大问
题,但实际体验过之后,你就会惊喜地发现,这其实是会带来很大不同的。
时刻保持数据完整性
在保存到 Git 之前,所有数据都要进行内容的校验和(checksum)计算,并将此结果作为数据的唯一标识和索引。
换句话说,不可能在你修改了文件或目录之后,Git 一无所知。这项特性作为 Git 的设计哲学,建在整体架构的最
底层。所以如果文件在传输时变得不完整,或者磁盘损坏导致文件数据缺失,Git 都能立即察觉。
Git 使用 SHA-1 算法计算数据的校验和,通过对文件的内容或目录的结构计算出一个 SHA-1 哈希值,作为指纹字符
串。该字串由 40 个十六进制字符(0-9 及 a-f)组成,看起来就像是:
24b9da6552252987aa493b52f8696cd6d3b00373
Git 的工作完全依赖于这类指纹字串,所以你会经常看到这样的哈希值。实际上,所有保存在 Git 数据库中的东西
都是用此哈希值来作索引的,而不是靠文件名。
多数操作仅添加数据
常用的 Git 操作大多仅仅是把数据添加到数据库。因为任何一种不可逆的操作,比如删除数据,都会使回退或重现
历史版本变得困难重重。在别的 VCS 中,若还未提交更新,就有可能丢失或者混淆一些修改的内容,但在 Git 里,
一旦提交快照之后就完全不用担心丢失数据,特别是养成定期推送到其他仓库的习惯的话。
这种高可靠性令我们的开发工作安心不少,尽管去做各种试验性的尝试好了,再怎样也不会弄丢数据。至于 Git 内
部究竟是如何保存和恢复数据的,我们会在第九章讨论 Git 内部原理时再作详述。
文件的三种状态
自诞生于 2005 年以来,Git 日臻成熟完善,在高度易用的同时,仍然保留着初期设定的目标。它的速度飞快,极其适合管理大项目,它还有着令人难以置信的非线性分支管理系统,可以应付各种复杂的项目开发需求。Git的特点包括:
好,现在请注意,接下来要讲的概念非常重要。对于任何一个文件,在 Git 内都只有三种状态:已提交(committed),
已修改(modified)和已暂存(staged)。已提交表示该文件已经被安全地保存在本地数据库中了;已修改表示修改
了某个文件,但还没有提交保存;已暂存表示把已修改的文件放在下次提交时要保存的清单中。
-
有效的压缩技术,节省存储空间
-
近乎所有操作都是本地执行,从而速度飞快且不用连网
-
使用SHA-1校验和而非文件名和时间戵作为数据的唯一标识和索引
-
多数操作仅添加数据到数据库而不会删除数据,减低丢失数据的可能性
由此我们看到 Git 管理项目时,文件流转的三个工作区域:Git 的工作目录,暂存区域,以及本地仓库。
图 1-6. 工作目录,暂存区域,以及本地仓库
每个项目都有一个 Git 目录(译注:如果 git clone 出来的话,就是其中 .git 的目录;如果 git clone --bare
的话,新建的目录本身就是 Git 目录。),它是 Git 用来保存元数据和对象数据库的地方。该目录非常重要,每次
克隆镜像仓库的时候,实际拷贝的就是这个目录里面的数据。
从项目中取出某个版本的所有文件和目录,用以开始后续工作的叫做工作目录。这些文件实际上都是从 Git 目录中
的压缩对象数据库中提取出来的,接下来就可以在工作目录中对这些文件进行编辑。
所谓的暂存区域只不过是个简单的文件,一般都放在 Git 目录中。有时候人们会把这个文件叫做索引文件,不过标
准说法还是叫暂存区域。
对于任何一个已跟踪的文件,在 Git 内为三种状态之一:
-
已提交,即文件已经被保存在本地仓库中
-
已暂存,即已经保存于暂存区(也称索引),在下次提交时会变成已提交
-
已修改,即只保存于工作树中
其中前两者放在
`.git`
目录中。
基本的 Git 工作流程如下:
1 在工作目录中修改某些文件。
2 对修改后的文件进行快照,然后保存到暂存区域。
3 提交更新,将保存在暂存区域的文件快照永久转储到 Git 目录中。
所以,我们可以从文件所处的位置来判断状态:如果是 Git 目录中保存着的特定版本文件,就属于已提交状态;如
果作了修改并已放入暂存区域,就属于已暂存状态;如果自上次取出后,作了修改但还没有放到暂存区域,就是已修
改状态。到第二章的时候,我们会进一步了解其中细节,并学会如何根据文件状态实施后续操作,以及怎样跳过暂存
直接提交。
1.
在工作目录中修改某些文件
2.
对修改后的文件进行快照,然后保存到暂存区域
3.
提交更新
# 安装和配置
...
...
@@ -203,6 +79,124 @@ Git 可以理解 kdiff3,tkdiff,meld,xxdiff,emerge,vimdiff,gvimdiff
要检查已有的配置信息,可以使用
`git config --list`
命令。有时候会看到重复的变量名,那就说明它们来自不同的配置文件,不过最终 Git 实际采用的是最后一个。也可以直接查阅某个环境变量的设定,只要把特定的名字跟在后面即可,例如:
`git config user.name`
# 基本使用
## 建立或获取仓库
想对一个项目开始使用Git进行版本管制,只用在项目的根目录运行:
```
git init
```
如果要从克隆现有仓库可以用下面的命令:
```
git clone <repository> [<directory>]
```
这会在当前目录下创建一个目录作为工作目录。
## 文件操作
工作树中的文件要纳入版本控制可用命令:
```
git add 路径
```
它会使文件变成已暂存,未跟踪的话也变成已跟踪。要删除则可用
`git rm`
(同时从工作树和暂存区删除)。
想知道当前各文件的状态,可用
`git status`
或者GUI的
`git gui`
。利用
`git diff`
可比较暂存区中版本与工作树中的版本。
要把暂存区的文件提交,可用:
```
git commit
```
想知道提交,可用
`git log`
或者GUI的
`gitk`
。
## 远程仓库
如果通过
`git clone`
创建仓库,则应有远程仓库origin。如果要增加远程仓库,可用
`git remote add `
想列出当前的远程仓库,用
`git remote -v`
。
# 主要命令概述
命令|用途
---|---
`git add [<pathspec>...]`
|跟踪文件
`git am [(<mbox> | <Maildir>)...]`
|解析邮件中补丁并应用到当前分支
`git archive [--format=<fmt>] [-o <file> | --output=<file>] <tree-ish> [<path>...]`
|创建归档
`git bisect`
|用二分查找定位导致bug的提交
`git branch [-r | -a] [<pattern>...]`
|列出分支(-r表示远程,-a表示所有)
`git branch [--set-upstream | --track | --no-track] [-l] [-f] <branchname> [<start-point>]|创建分支
`
git branch (--set-upstream-to=
<upstream>
| -u
<upstream>
) [
<branchname>
]|设置分支的上游
`git branch --unset-upstream [<branchname>]|取消设置分支的上游
`
git branch -m [
<oldbranch>
]
<newbranch>
`|重命名分支
`
git branch -d [-r]
<branchname>
...
`|删除分支(-r表示远程)
`
git branch --edit-description [
<branchname>
]
`|设置分支的描述
`
git bundle
`|通过归档移动对象和引用
`
git checkout
<branch>
`|检出分支
`
git checkout
<commit>
`|检出提交
`
git cherry-pick
<commit>
...
`|应用指定提交引入的变化
`
git citool
`|`
git commit
`的图形版本
`
git clean
`|删除未跟踪的文件
`
git clone
<repository>
[
<directory>
]
`|克隆仓库到一个新目录
`
git commit [-a] [-F
<file>
| -m
<msg>
]
`|把变化记录到仓库
`
git describe [
<commit-ish>
...]
`|用它前面的最近标签形容提交
`
git diff [--] [
<path>
...]
`|比较工作树与暂存区
`
git diff --no-index [--] [
<path>
...]
`|比较文件系统中两个树
`
git diff --cached [
<commit>
] [--] [
<path>
...]
`|比较暂存区与一个提交(默认HEAD)
`
git diff
<commit>
[--] [
<path>
...]
`|比较工作树与一个提交或分支
`
git diff
<commit>
<commit>
[--] [
<path>
...]
`|比较两个提交
`
git diff
<commit>
..
<commit>
[--] [
<path>
...]
`|比较两个提交,省略提交表示HEAD
`
git diff
<commit>
...
<commit>
[--] [
<path>
...]
`|比较第二个提交所在分支从两个提交共同祖先到第二个提交间变化,省略提交表示HEAD
`
git fetch [
<repository>
[
<refspec>
...]]
`|拉取另一仓库的数据
`
git format-patch [
<since>
|
<revision
range
>
]
`|把补丁格式化为邮件格式
`
git gc
`|清除不必要文件和优化仓库
`
git grep
<pattern>
[--and|--or|--not|(|)|-e
<pattern>
...] [ [--[no-]exclude-standard] [--cached | --no-index | --untracked] |
<tree>
...] [--] [
<pathspec>
...]
`|寻找匹配指定模式的行
`
git gui
`|Git的图形界面
`
git init
`|创建空Git仓库或重新初始化现有的仓库
`
git log
`|显示提交日志
`
git merge [-m
<msg>
] [
<commit>
...]
`|合并分支
`
git mv
<source>
<destination>
`|移动或重命名文件
`
git mv
<source>
...
<destination
directory
>
`|移动文件
`
git notes
`|阅读或增删对象的附加注记
`
git pull [
<repository>
[
<refspec>
...]]
`|拉取另一仓库的数据并与之合并
`
git push [
<repository>
[
<refspec>
...]]
`|把数据推入到远程分支
`
git rebase [--exec
<cmd>
] [--onto
<newbase>
] [
<upstream>
[
<branch>
]]
`|把本地变化回放到上游
`
git rebase [--exec
<cmd>
] [--onto
<newbase>
] --root [
<branch>
]
`|把本地变化回放到主分支
`
git reset [-q] [
<tree-ish>
] [--]
<paths>
...
`|把指定条目复制到暂存区
`
git reset (--patch | -p) [
<tree-ish>
] [--] [
<paths>
...]
`|把指定条目选择性复制到暂存区
`
git reset [--soft | --mixed [-N] | --hard | --merge | --keep] [-q] [
<commit>
]
`|把HEAD设为指定提交
`
git revert
`|[-m parent-number] <commit>...|回滾指定提交引入的变化
`
git rm [-r] [--cached] [--]
<file>
`|从工作目录(若没有--cached)和索引移除文件,-r表示递归
`
git shortlog
`|列出精简的日志
`
git show
<object>
...
`|显示对象
`
git stash
`|记录当前工作树
`
git status [
<pathspec>
...]
`|显示工作树状态(HEAD与暂存区间变化,暂存区与工作树间变化)
`
git submodule
`|管理子模块
`
git tag [-f] [-m
<msg>
| -F
<file>
]
<tagname>
[
<commit>
|
<object>
]
`|增加标签
`
git tag -d
<tagname>
...
`|删除标签
`
git tag [-n[
<num>
]] -l [
<pattern>
...]
`|列出标签
`
git worktree
`|支持多工作树
`
gitk
`|图形化的Git仓库浏览器
其中仓库通常用URL表示,形如以下之一:
- `
ssh://[user@]host.xz[:port]/path/to/repo.git/
`
- `
git://host.xz[:port]/path/to/repo.git/
`
- `
http[s]://host.xz[:port]/path/to/repo.git/
`
- `
ftp[s]://host.xz[:port]/path/to/repo.git/
`
- `
rsync://host.xz/path/to/repo.git/
`
- `
file:///path/to/repo.git/
`,通常也可省略`
file://
`
# 帮助
如有疑问,请用`
git help
`
命令查看文档。
\ No newline at end of file