2.Git使用教程
1|01. 起步
本章为Git入门。我们从介绍版本控制工具的背景知识开始,然后讲解如何在你的系统上运行Git,最后是关于如何设置Git以便开始工作。通过本章,你应该能了解为什么Git这么流行,为什么你应该使用Git以及你应该如何设置以便使用Git。
1|11. 1 关于版本控制
- 什么是"版本控制"?为什么要关心它呢?版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版修订情况的系统。
- 如果你是网页设计师,可能会需要保存某一副图片或页面布局文件的所有修订版本,采用版本控制系统(VCS)是个明智的选择。有了它你就可以将选定的文件回溯到之前的状态,甚至将整个项目都回退到过去某个时间点的状态,你可以比较文件的变化细节,查出最后是谁修改了哪个地方,从而找出导致怪异问题出现的原因,又是谁在何时报告了某个功能缺陷等等。使用版本控制系统通常还意味着,就算你乱来一气把整个项目中的文件改的改,删的删,你也能照样可以轻松恢复到原先的样子。
本地版本控制系统
-
许多人习惯用复制整个项目目录的方式来保存不同的版本,或许还会改名加上备份时间以示区别。这么做唯一的好处就是简单。但是特别容易犯错。有时候会混淆所在的工作目录,一不小心会写错文件或覆盖意想外的文件。
-
为了解决这个问题,人们很久以前就开发了许多种本地版本控制系统,大多都是采用某种简单的数据库来记录文件的历次更新差异。
-
其中最流行的一种叫做RCS,现今许多计算机系统上都还看得到它的踪影。RCS的工作原理是在硬盘上保存补丁集(补丁是指文件修订前后的变化);通过应用所有的补丁,可以重新计算出各个版本的文件内容。
集中化的版本控制系统
-
接下来人们又遇到一个问题,如何让在不同系统上的开发者协同工作?于是,集中化的版本控制系统(CVCS)应运而生。这类系统,诸如CVS、Subversion以及Perforce等,都有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们通过客户端连接到这台服务器,取出最新的文件或者提交更新。多年以来,这已成为版本控制系统的标准做法。
-
这种做法带来了许多好处,特别是相较于老式的本地VCS来说。现在,每个人都可以在一定程度上看到项目中的其他人正在做什么?而管理员也可以轻松掌控每个开发者的权限,并且管理一个CVCS要远比在各个客户端上维护本地数据库来得轻松容易。
-
事分两面,有好有坏。这么做最显而易见的缺点是中央服务器的单点故障。如果宕机一小时,那么在这一小时内,谁都无法提交更新,也就无法协同工作。如果中心数据库所在的磁盘发生损坏,又没做恰当备份,毫无疑问你将丢失所有数据--包括项目的整个变更历史,只剩下人们在各自机器上保留的单独快照。本地版本控制系统也存在类似问题,只要整个项目的历史记录被保存在单一位置,就有丢失所有历史更新记录的风险。
分布式版本控制系统
-
于是分布式版本控制系统(DVCS)面世了,在这类系统中,像Git、Mercurial、Bazaar以及Darcs等,客户端并不只提取最新的文件快照,而是把代码仓库完整地镜像下载下来,包括完整的历史记录。这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。因为每一次克隆操作,实际上都是一次对代码仓库的完整备份。
-
更进一步,许多这类系统都可以指定和若干不同的远端代码仓库进行交互。你可以在同一个项目中,分别和不同工作小组的人相互写作。你可以根据需要设定不同的协作流程,比如层次模型式的工作流,而这在以前的集中式系统中是无法实现的。
1|21.2 Git简史
- 同生活中的许多伟大事物一样,Git诞生于一个极富纷争大举创新的年代。
- Linux内核开源项目有着为数众多的参与者。绝大多数的Linux内核维护工作都花在了提交补丁和保存归档的繁琐事务上(1991年-2002年间)。到2002年,整个项目组开始启用一个专有的分布式版本控制系统BitKeeper来管理和维护代码。
- 到了2005年,开发BitKeeper的商业公司同Linux内核开源社区的合作关系结束,他们收回了Linux内核社区免费使用BitKeeper的权利。这就迫使Linux开源社区(特别是Linux的缔造者)基于使用BitKeeper时的经验教训,开发出自己的版本系统。他们对新的系统制订了若干目标;
- 速度
- 简单的设计
- 对非线性开发模式的强力支持(允许成千上万个并行开发的分支)
- 完全分布式
- 有能力高效管理类似Linux内核一样的超大规模项目(速度和数据量)
- 自诞生于2005年以来,Git日臻成熟完善,在高度易用的同时,仍然保留着初期设定的目标。它的速度飞快,极其适合管理大项目,有着令人难以置信的非线性分支管理系统。
1|31.3 Git是什么?
直接记录快照,而非差异比较
-
Git和其它版本控制系统(包括Subversion和近似工具)的主要差别在于Git对待数据的方式。从概念上来说,其它大部分系统以文件变更列表的方式存储信息,这类系统将他们存储的信息看作是一组基本文件和每个文件随时间逐步累积的差异(他们通常称作基于差异(delta-based)的版本控制)。
-
Git不按照以上方式对待或保存数据。反之,Git更像是把数据看作是对小型文件系统的一系列快照。在Git中,每当你提交更新或保存项目状态时,它基本上就会对当时的全部文件创建一个快照并保存这个快照的索引。
-
为了效率,如果文件没有修改,Git不再重新存储该文件,而是只保留一个链接指向之前存储的文件。Git对待数据更像是一个快照流。
-
这是Git与几乎所有其它版本控制系统的重要区别。因此Git重新考虑了以前每一代版本控制系统延续下来的诸多方面。Git更像是一个小型的文件系统,提供了许多以此为基础构建的超强工具,而不只是一个简单的VCS。
-
稍后我们在Git分支讨论Git分支管理时,将探究这种方式对待数据所能获得的益处。
近乎所有操作都是本地执行
- 在Git中的绝大多数操作都只需要访问本地文件和资源,一般不需要来自网络上其它计算机的信息。如果你习惯于所有操作都有网络延时开销的集中式版本控制系统,Git在这方面会让你感到速度之神赐给了Git超凡的能量。因为你在本地磁盘上就有项目的完整历史,所以大部分操作看起来瞬间完成。
- 举个例子,要浏览项目的历史,Git不需外连到服务器去获取历史,然后再显示出来--它只需直接从本地数据库中读取。你能立即看到项目历史。如果你想查看当前版本与一个月前的版本之间引入的修改,Git会查找到一个月前的文件做一次本地的差异计算,而不是由远程服务器处理或从远程服务器拉回旧版本文件再来本地处理。
- 这意味着你在离线或者没有VPN时,几乎可以进行任何操作。如你在飞机或火车上想做些工作,就能愉快地提交到本地库,直到有网络连接时再上传。如你回家后vpn客户端不正常,那么也仍能工作。使用其它系统的话,做到这些是不可能或很费力的。而用Subversion和CVS的话,你能修改文件,但不能向数据库提交修改(因为你的本地数据库离线了)。这样似乎问题不大,但是你可能会惊喜地发现它带来的巨大的不同。
Git保证完整性
- Git中所有的数据在存储前都计算校验,然后以校验和来引用。这意味着不可能在Git不知情时更改任何文件内容或目录内容。这个功能建构在Git底层,是构成Git哲学不可或缺的部分。若你在传送过程中丢失信息或损坏文件,Git就能发现。
- Git用以计算校验和的机制叫做SHA-1散列(hash,哈希)。这是一个由40个十六进制字符(0-9和a-f)组成的字符串,基于Git中文件的内容或目录结构计算出来。SHA-1哈希看起来是这样:
24b9da6552252987aa493b52f8696cd6d3b00373
- Git中使用这种哈希值的情况很多,你将经常看到这种哈希值。实际上,Git数据库中保存的信息都是以文件内容的哈希值来索引,而不是文件名。
Git一般只添加数据
- 你执行的Git操作,几乎只往Git数据库中添加数据。你很难使用Git从数据库中删除数据,也就是说Git几乎不会执行任何可能导致文件不可恢复的操作。同别的VCS一样,未提交更新时有可能丢失或弄乱修改的内容。但是一旦你提交快照到Git中,就难以再丢失数据,特别是如果你定期的推送数据库到其它仓库的话。
- 这使得我们使用Git称为一个安心愉悦的过程,因此我们深知可以尽情做各种尝试,而没有把事情弄糟的危险。更深度探讨Git如何保存数据及恢复丢失数据的话题。
三种状态
-
现在请注意,如果你希望后面的学习更顺利,请记住下面这些关于 Git 的概念。 Git 有三种状态,你的文件可能 处于其中之一: 已暂存(staged)、已修改(modified) 、已提交(committed)。
- 已修改:修改了文件,但还没保存到数据库中。
- 已暂存:对一个已修改文件的当前版本做了标记,使之包含在下次提交的快照中。
- 已提交:表示数据已经安全地保存在本地数据库中。
-
这会让我们的Git项目拥有三个阶段:
- 工作区
- 暂存区
- Git目录
-
工作区是对项目的某个版本独立提取出来的内容。 这些从 Git 仓库的压缩数据库中提取出来的文件,放在磁盘上
供你使用或修改。
-
暂存区是一个文件,保存了下次将要提交的文件列表信息,一般在 Git 仓库目录中。 按照 Git 的术语叫做“索
引”,不过一般说法还是叫“暂存区”。
-
Git 仓库目录是 Git 用来保存项目的元数据和对象数据库的地方。 这是 Git 中最重要的部分,从其它计算机克隆
仓库时,复制的就是这里的数据。
-
基本的 Git 工作流程如下:
- 在工作区中修改文件。
- 将你想要下次提交的更改选择性地暂存,这样只会将更改的部分添加到暂存区。
- 提交更新,找到暂存区的文件,将快照永久性存储到 Git 目录。
-
如果 Git 目录中保存着特定版本的文件,就属于 已提交 状态。 如果文件已修改并放入暂存区,就属于 已暂存 状 态。 如果自上次检出后,作了修改但还没有放到暂存区域,就是 已修改 状态。 在 Git 基础 一章,你会进一步了 解这些状态的细节, 并学会如何根据文件状态实施后续操作,以及怎样跳过暂存直接提交。
1|41.4 命令行
- Git 有多种使用方式。 你可以使用原生的命令行模式,也可以使用 GUI 模式,这些 GUI 软件也能提供多种功能。 在本书中,我们将使用命令行模式。 这是因为首先,只有在命令行模式下你才能执行 Git 的 所有 命令,而大多 数的 GUI 软件只实现了 Git 所有功能的一个子集以降低操作难度。 如果你学会了在命令行下如何操作,那么你 在操作 GUI 软件时应该也不会遇到什么困难,但是,反之则不成立。 此外,由于每个人的想法与侧重点不同, 不同的人常常会安装不同的 GUI 软件,但 所有 人一定会有命令行工具。
- 假如你是 macOS 用户,我们希望你懂得如何使用终端(Terminal);假如你是 Windows 用户,我们希望你懂 得如何使用命令窗口(Command Prompt)或 PowerShell。 如果你尚未掌握以上技能,我们建议你先停下来 快速学习一下,本书中的讲述和举例将用到这些技能。
1|51.5 安装Git
-
Windows安装教程:
-
-
MacOS安装教程:
-
-
Linux安装教程:
-
1|61.6 初次运行Git前的配置
-
既然已经在系统上安装了 Git,你会想要做几件事来定制你的 Git 环境。 每台计算机上只需要配置一次,程序升
级时会保留配置信息。 你可以在任何时候再次通过运行命令来修改它们。
-
Git 自带一个 git config 的工具来帮助设置控制 Git 外观和行为的配置变量。 这些变量存储在三个不同的位
置:
- /etc/gitconfig 文件: 包含系统上每一个用户及他们仓库的通用配置。 如果在执行 git config 时带上 --system 选项,那么它就会读写该文件中的配置变量。 (由于它是系统配置文件,因此你需要管理员或 超级用户权限来修改它。)
- ~/.gitconfig 或 ~/.config/git/config 文件:只针对当前用户。 你可以传递 --global 选项让 Git 读写此文件,这会对你系统上 所有 的仓库生效。
- 当前使用仓库的 Git 目录中的 config 文件(即 .git/config):针对该仓库。 你可以传递 --local 选 项让 Git 强制读写此文件,虽然默认情况下用的就是它。。 (当然,你需要进入某个 Git 仓库中才能让该选 项生效。)
-
每一个级别会覆盖上一级别的配置,所以 .git/config 的配置变量会覆盖 /etc/gitconfig 中的配置变量。
-
在 Windows 系统中,Git 会查找 $HOME 目录下(一般情况下是 C:\Users$USER )的 .gitconfig 文件。 Git 同样也会寻找 /etc/gitconfig 文件,但只限于 MSys 的根目录下,即安装 Git 时所选的目标位置。 如果 你在 Windows 上使用 Git 2.x 以后的版本,那么还有一个系统级的配置文件,Windows XP 上在 C:\Documents and Settings\All Users\Application Data\Git\config ,Windows Vista 及更新 的版本在C:\ProgramData\Git\config。此文件只能以管理员权限通过git config -f
来修 改。 -
你可以通过以下命令查看所有的配置以及它们所在的文件:
用户信息
-
安装完 Git 之后,要做的第一件事就是设置你的用户名和邮件地址。 这一点很重要,因为每一个 Git 提交都会使 用这些信息,它们会写入到你的每一次提交中,不可更改:
-
再次强调,如果使用了 --global 选项,那么该命令只需要运行一次,因为之后无论你在该系统上做任何事 情, Git 都会使用那些信息。 当你想针对特定项目使用不同的用户名称与邮件地址时,可以在那个项目目录下运 行没有 --global 选项的命令来配置。
-
很多 GUI 工具都会在第一次运行时帮助你配置这些信息。
检查配置信息
-
如果想要检查你的配置,可以使用git config --list命令来列出所有Git当时能找到的配置。
-
你可能会看到重复的变量名,因为 Git 会从不同的文件中读取同一个配置(例如:/etc/gitconfig 与 ~/.gitconfig)。 这种情况下,Git 会使用它找到的每一个变量的最后一个配置。
-
你可以通过输入 git config
: 来检查 Git 的某一项配置
2|02. Git基础
- 本章涵盖了你在使用 Git 完成各种工作时将 会用到的各种基本命令。 在学习完本章之后,你应该能够配置并初始化一个仓库(repository)、开始或停止跟 踪(track)文件、暂存(stage)或提交(commit)更改。 本章也将向你演示了如何配置 Git 来忽略指定的文 件和文件模式、如何迅速而简单地撤销错误操作、如何浏览你的项目的历史版本以及不同提交(commits)之间 的差异、如何向你的远程仓库推送(push)以及如何从你的远程仓库拉取(pull)文件。
2.1 获取Git仓库
- 通常有两种获取Git项目仓库的方式:
- 将尚未进行版本控制的本地目录转换为Git仓库;
- 从其它服务器克隆一个已存在的Git仓库。
- 两种方式都会在你的本地机器上得到一个工作就绪的Git仓库。
在已存在目录中初始化仓库
-
如果你有一个尚未进行版本控制的项目目录,想要用Git来控制它,那么首先需要进入该项目目录中。
-
-
如果在一个已存在文件的文件夹(而非空文件夹)中进行版本控制,你应该开始追踪这些文件并进行初始提交。 可以通过git add命令来指定所需的文件来进行追踪,然后执行git commit:
-
-
稍后我们再逐一解释这些指令的行为。 现在,你已经得到了一个存在被追踪文件与初始提交的 Git 仓库。
克隆现有的仓库
-
如果你想获得一份已经存在了的 Git 仓库的拷贝,比如说,你想为某个开源项目贡献自己的一份力,这时就要用 到 **git clone **命令。 如果你对其它的 VCS 系统(比如说 Subversion)很熟悉,请留心一下你所使用的命令 是"clone"而不是"checkout"。 这是 Git 区别于其它版本控制系统的一个重要特性,Git 克隆的是该 Git 仓库服务 器上的几乎所有数据,而不是仅仅复制完成你的工作所需要文件。 当你执行 **git clone **命令的时候,默认配置 下远程 Git 仓库中的每一个文件的每一个版本都将被拉取下来。 事实上,如果你的服务器的磁盘坏掉了,你通常 可以使用任何一个克隆下来的用户端来重建服务器上的仓库 (虽然可能会丢失某些服务器端的钩子(hook)设 置,但是所有版本的数据仍在)。
-
克隆仓库的命令是**git clone **
。 比如,要克隆 Git 的链接库 gockk,可以用下面的命令: -
这会在当前目录下创建一个名为 “libgit2” 的目录,并在这个目录下初始化一个 .git 文件夹, 从远程仓库拉 取下所有数据放入 .git 文件夹,然后从中读取最新版本的文件的拷贝。 如果你进入到这个新建的 libgit2 文 件夹,你会发现所有的项目文件已经在里面了,准备就绪等待后续的开发和使用。
-
如果你想在克隆远程仓库的时候,自定义本地仓库的名字,你可以通过额外的参数指定新的目录名:
- 这会执行与上一条命令相同的操作,但目标目录名变为了 my_project。
-
Git 支持多种数据传输协议。 上面的例子使用的是 https:// 协议,不过你也可以使用 git:// 协议或者使用 SSH 传输协议,比如 user@server:path/to/repo.git 。
2.2 记录每次更新到仓库
-
现在我们的机器上有了一个真实项目的git仓库,并从这个仓库中检出了所有文件的工作副本。通常,你会对这些文件做些修改,每当完成了一个阶段的目标,想要将记录下它时,就将它提交到仓库。
-
请记住,你工作目录下的每一个文件都不外乎这两种状态:已跟踪 或 未跟踪。 已跟踪的文件是指那些被纳入了
版本控制的文件,在上一次快照中有它们的记录,在工作一段时间后, 它们的状态可能是未修改,已修改或已
放入暂存区。简而言之,已跟踪的文件就是 Git 已经知道的文件。
-
工作目录中除已跟踪文件外的其它所有文件都属于未跟踪文件,它们既不存在于上次快照的记录中,也没有被放 入暂存区。 初次克隆某个仓库的时候,工作目录中的所有文件都属于已跟踪文件,并处于未修改状态,因为 Git 刚刚检出了它们, 而你尚未编辑过它们。
-
编辑过某些文件之后,由于自上次提交后你对它们做了修改,Git 将它们标记为已修改文件。 在工作时,你可以 选择性地将这些修改过的文件放入暂存区,然后提交所有已暂存的修改,如此反复。
检查当前文件状态
-
可以用git status命令查看哪些文件处于什么状态。如果在克隆仓库后立即使用此命令,会看到类似这样的输出:
-
这说明你现在的工作目录相当干净。换句话说,所有已跟踪文件在上次提交后都未被更改过。 此外,上面的信 息还表明,当前目录下没有出现任何处于未跟踪状态的新文件,否则 Git 会在这里列出来。 最后,该命令还显示 了当前所在分支,并告诉你这个分支同远程服务器上对应的分支没有偏离。 现在,分支名"master",这是默 认的分支名。 我们在 Git 分支 中会详细讨论分支和引用。
-
现在,让我们在项目下创建一个新的 README 文件。 如果之前并不存在这个文件,使用 git status 命令,你 将看到一个新的未跟踪文件:
-
在状态报告中可以看到新建的 README 文件出现在 Untracked files 下面。 未跟踪的文件意味着 Git 在之前 的快照(提交)中没有这些文件;Git 不会自动将之纳入跟踪范围,除非你明明白白地告诉它“我需要跟踪该文 件”。 这样的处理让你不必担心将生成的二进制文件或其它不想被跟踪的文件包含进来。 不过现在的例子中, 我们确实想要跟踪管理 README 这个文件。
跟踪新文件(git add)
-
使用命令 git add 开始跟踪一个文件。 所以,要跟踪 README 文件,运行:
-
-
此时再运行git status命令,会看到README文件已被跟踪,并处于暂存状态:
-
-
只要在 Changes to be committed 这行下面的,就说明是已暂存状态。 如果此时提交,那么该文件在你运 行git add时的版本将被留存在后续的历史记录中。你可能会想起之前我们使用git init后就运行了git add
命令,开始跟踪当前目录下的文件。git add命令使用文件或目录的路径作为参数;如果参 数是目录的路径,该命令将递归地跟踪该目录下的所有文件。
暂存已修改的文件
-
现在我们来修改一个已被跟踪的文件。 如果你修改了一个名为 CONTRIBUTING.md 的已被跟踪的文件,然后运 行git status命令,会看到下面内容:
-
-
文件CONTRIBUTING.md出现在Changes not staged for commit这行下面,说明已跟踪文件的内容发 生了变化,但还没有放到暂存区。 要暂存这次更新,需要运行 git add 命令。 这是个多功能命令:可以用它开 始跟踪新文件,或者把已跟踪的文件放到暂存区,还能用于合并时把有冲突的文件标记为已解决状态等。 将这 个命令理解为“精确地将内容添加到下一次提交中”而不是“将一个文件添加到项目中”要更加合适。 现在让 我们运行git add将“CONTRIBUTING.md”放到暂存区,然后再看看git status的输出:
-
-
现在两个文件都已暂存,下次提交时就会一并记录到仓库。 假设此时,你想要在 CONTRIBUTING.md 里再加条 注释。 重新编辑存盘后,准备好提交。 不过且慢,再运行 git status 看看:
-
-
怎么回事? 现在 CONTRIBUTING.md 文件同时出现在暂存区和非暂存区。 这怎么可能呢? 好吧,实际上 Git 只 不过暂存了你运行 git add 命令时的版本。 如果你现在提交,CONTRIBUTING.md 的版本是你最后一次运行 git add 命令时的那个版本,而不是你运行 git commit 时,在工作目录中的当前版本。 所以,运行了 git add之后又作了修订的文件,需要重新运行git add把最新版本重新暂存起来:
-
状态简览
-
git status 命令的输出十分详细,但其用语有些繁琐。 Git 有一个选项可以帮你缩短状态命令的输出,这样可
以以简洁的方式查看更改。 如果你使用 git status -s 命令或git status --short命令,你将得到一种
格式更为紧凑的输出。
-
新添加的未跟踪文件前面有 ?? 标记,新添加到暂存区中的文件前面有 A 标记,修改过的文件前面有 M 标记。 输
出中有两栏,左栏指明了暂存区的状态,右栏指明了工作区的状态。例如,上面的状态报告显示: README 文件
在工作区已修改但尚未暂存,而 lib/simplegit.rb 文件已修改且已暂存。 Rakefile 文件已修,暂存后又
作了修改,因此该文件的修改中既有已暂存的部分,又有未暂存的部分。
忽略文件
-
一般我们总会有些文件无需纳入 Git 的管理,也不希望它们总出现在未跟踪文件列表。 通常都是些自动生成的文
件,比如日志文件,或者编译过程中创建的临时文件等。 在这种情况下,我们可以创建一个名为 .gitignore
的文件,列出要忽略的文件的模式。 来看一个实际的** .gitignore** 例子:
-
-
第一行告诉 Git 忽略所有以 .o 或 .a 结尾的文件。一般这类对象文件和存档文件都是编译过程中出现的。 第二
行告诉 Git 忽略所有名字以波浪符(
~)结尾的文件,许多文本编辑软件(比如 Emacs)都用这样的文件名保存
副本。 此外,你可能还需要忽略 log,tmp 或者 pid 目录,以及自动生成的文档等等。 要养成一开始就为你的
新仓库设置好 .gitignore 文件的习惯,以免将来误提交这类无用的文件。
-
文件 .gitignore 的格式规范如下:
- 所有空行或者以 # 开头的行都会被 Git 忽略。
- 可以使用标准的 glob 模式匹配,它会递归地应用在整个工作区中。
- 匹配模式可以以(/)开头防止递归。
- 匹配模式可以以(/)结尾指定目录。
- 要忽略指定模式以外的文件或目录,可以在模式前加上叹号(!)取反。
-
所谓的 glob 模式是指 shell 所使用的简化了的正则表达式。 星号(*)匹配零个或多个任意字符;[abc] 匹配
任何一个列在方括号中的字符 (这个例子要么匹配一个 a,要么匹配一个 b,要么匹配一个 c); 问号(?)只
匹配一个任意字符;如果在方括号中使用短划线分隔两个字符, 表示所有在这两个字符范围内的都可以匹配 (比如 [0-9] 表示匹配所有 0 到 9 的数字)。 使用两个星号()表示匹配任意中间目录,比如 a//z 可以匹配 a/z 、 a/b/z 或 a/b/c/z 等。
-
我们再看一个 .gitignore 文件的例子:
-
-
GitHub 有一个十分详细的针对数十种项目及语言的 .gitignore 文件列表, 你可以在https://github.com/github/gitignore 找到它。
-
在最简单的情况下,一个仓库可能只根目录下有一个 .gitignore 文件,它递归地应用到整 个仓库中。 然而,子目录下也可以有额外的 .gitignore 文件。子目录中的 .gitignore文件中的规则只作用于它所在的目录中。 (Linux 内核的源码库拥有 206 个 .gitignore 文件。)
-
查看已暂存和未暂存的修改
-
如果 git status 命令的输出对于你来说过于简略,而你想知道具体修改了什么地方,可以用 git diff 命令。 稍后我们会详细介绍 git diff,你通常可能会用它来回答这两个问题:当前做的哪些更新尚未暂存? 有 哪些更新已暂存并准备好下次提交? 虽然 git status 已经通过在相应栏下列出文件名的方式回答了这个问题,但 git diff 能通过文件补丁的格式更加具体地显示哪些行发生了改变。
-
假如再次修改 README 文件后暂存,然后编辑 CONTRIBUTING.md 文件后先不暂存, 运行 status 命令将会看到:
-
-
要查看尚未暂存的文件更新了哪些部分,不加参数直接输入 git diff:
-
-
此命令比较的是工作目录中当前文件和暂存区域快照之间的差异。 也就是修改之后还没有暂存起来的变化内容。
-
若要查看已暂存的将要添加到下次提交里的内容,可以用** git diff --staged** 命令。 这条命令将比对已暂存文件与最后一次提交的文件差异:
-
-
请注意,git diff 本身只显示尚未暂存的改动,而不是自上次提交以来所做的所有改动。 所以有时候你一下子暂 存了所有更新过的文件,运行 git diff 后却什么也没有,就是这个原因。
-
像之前说的,暂存 CONTRIBUTING.md 后再编辑,可以使用 git status 查看已被暂存的修改或未被暂存的修
改。 如果我们的环境(终端输出)看起来如下:
-
-
现在运行 git diff 看暂存前后的变化:
-
-
然后用 git diff --cached 查看已经暂存起来的变化( --staged 和 **--cached **是同义词):
-
-
Git Diff 的插件版本,我们使用 git diff 来分析文件差异。 但是你也可以使用图形化的工具或外部diff 工具来比较差异。 可以使用 git difftool 命令来调用 emerge 或 vimdiff 等软件(包括商业软件)输出 diff 的分析结果。 使用 git difftool --tool-help 命令来看你的系统支持哪些 Git Diff 插件。
-
提交更新
-
现在的暂存区已经准备就绪,可以提交了。 在此之前,请务必确认还有什么已修改或新建的文件还没有 gitadd 过, 否则提交的时候不会记录这些尚未暂存的变化。 这些已修改但未暂存的文件只会保留在本地磁盘。 所以,每次准备提交前,先用git status 看下,你所需要的文件是不是都已暂存起来了, 然后再运行提交命令git commit:
-
-
编辑器会显示类似下面的文本信息(本例选用 Vim 的屏显方式展示):
-
-
可以看到,默认的提交消息包含最后一次运行 git status 的输出,放在注释行里,另外开头还有一个空行,供你输入提交说明。 你完全可以去掉这些注释行,不过留着也没关系,多少能帮你回想起这次更新的内容有哪些。
-
另外,你也可以在 commit 命令后添加 -m 选项,将提交信息与命令放在同一行,如下所示:
-
-
好,现在你已经创建了第一个提交! 可以看到,提交后它会告诉你,当前是在哪个分支(master)提交的,本次提交的完整 SHA-1 校验和是什么(463dc4f),以及在本次提交中,有多少文件修订过,多少行添加和删改过。
-
请记住,提交时记录的是放在暂存区域的快照。 任何还未暂存文件的仍然保持已修改状态,可以在下次提交时 纳入版本管理。 每一次运行提交操作,都是对你项目作一次快照,以后可以回到这个状态,或者进行比较。
跳过使用暂存区域
-
尽管使用暂存区域的方式可以精心准备要提交的细节,但有时候这么做略显繁琐。 Git 提供了一个跳过使用暂 存区域的方式, 只要在提交的时候,给 **git commit 加上 -a **选项,Git 就会自动把所有已经跟踪过的文件暂存起来一并提交,从而跳过 git add 步骤:
-
-
看到了吗?提交之前不再需要 git add 文件“CONTRIBUTING.md”了。 这是因为 -a 选项使本次提交包含了所有修改过的文件。 这很方便,但是要小心,有时这个选项会将不需要的文件添加到提交中。
移除文件
-
要从 Git 中移除某个文件,就必须要从已跟踪文件清单中移除(确切地说,是从暂存区域移除),然后提交。可以用 git rm 命令完成此项工作,并连带从工作目录中删除指定的文件,这样以后就不会出现在未跟踪文件清单中了。
-
如果只是简单地从工作目录中手工删除文件,运行 git status 时就会在 “Changes not staged for commit” 部分(也就是 未暂存清单)看到:
-
-
然后再运行 git rm 记录此次移除文件的操作:
-
-
下一次提交时,该文件就不再纳入版本管理了。 如果要删除之前修改过或已经放到暂存区的文件,则必须使用强制删除选项 -f(译注:即 force 的首字母)。 这是一种安全特性,用于防止误删尚未添加到快照的数据,这样的数据不能被 Git 恢复。
-
另外一种情况是,我们想把文件从 Git 仓库中删除(亦即从暂存区域移除),但仍然希望保留在当前工作目录中。 换句话说,你想让文件保留在磁盘,但是并不想让 Git 继续跟踪。 当你忘记添加 .gitignore 文件,不小心把一个很大的日志文件或一堆 .a 这样的编译生成文件添加到暂存区时,这一做法尤其有用。 为达到这一目的,使用 --cached选项:
-
-
git rm 命令后面可以列出文件或者目录的名字,也可以使用 glob 模式。比如:
-
-
注意到星号 * 之前的反斜杠 \, 因为 Git 有它自己的文件模式扩展匹配方式,所以我们不用 shell 来帮忙展开。此命令删除 log/ 目录下扩展名为 .log 的所有文件。 类似的比如:
-
-
该命令会删除所有名字以 ~ 结尾的文件。
移动文件
-
不像其它的 VCS 系统,Git 并不显式跟踪文件移动操作。 如果在 Git 中重命名了某个文件,仓库中存储的元数 据并不会体现出这是一次改名操作。 不过 Git 非常聪明,它会推断出究竟发生了什么,至于具体是如何做到的,我们稍后再谈。
-
既然如此,当你看到 Git 的 mv 命令时一定会困惑不已。 要在 Git 中对文件改名,可以这么做:
-
-
它会恰如预期般正常工作。 实际上,即便此时查看状态信息,也会明白无误地看到关于重命名操作的说明:
-
-
其实,运行 git mv 就相当于运行了下面三条命令:
-
-
如此分开操作,Git 也会意识到这是一次重命名,所以不管何种方式结果都一样。 两者唯一的区别在于,git mv 是一条命令而非三条命令,直接使用 git mv 方便得多。 不过在使用其他工具重命名文件时,记得在提交前git rm **删除旧文件名,再 git add 添加新文件名。
2.3 查看提交历史
-
在提交了若干更新,又或者克隆了某个项目之后,你也许想回顾下提交历史。 完成这个任务最简单而又有效的工具是 git log 命令。
-
我们使用一个非常简单的 “simplegit” 项目作为示例。 运行下面的命令获取该项目:
-
-
当你在此项目中运行 git log 命令时,可以看到下面的输出:
-
-
不传入任何参数的默认情况下,git log 会按时间先后顺序列出所有的提交,最近的更新排在最上面。 正如你所看到的,这个命令会列出每个提交的 SHA-1 校验和、作者的名字和电子邮件地址、提交时间以及提交说明。
-
git log 有许多选项可以帮助你搜寻你所要找的提交, 下面我们会介绍几个最常用的选项。
-
其中一个比较有用的选项是 -p 或 --patch ,它会显示每次提交所引入的差异(按 补丁 的格式输出)。 你也可以限制显示的日志条目数量,例如使用 -2 选项来只显示最近的两次提交:
-
-
该选项除了显示基本信息之外,还附带了每次提交的变化。 当进行代码审查,或者快速浏览某个搭档的提交所带来的变化的时候,这个参数就非常有用了。 你也可以为git log 附带一系列的总结性选项。 比如你想看到每次提交的简略统计信息,可以使用 --stat 选项:
-
-
正如你所看到的,--stat 选项在每次提交的下面列出所有被修改过的文件、有多少文件被修改了以及被修改过的文件的哪些行被移除或是添加了。 在每次提交的最后还有一个总结。
-
另一个非常有用的选项是** --pretty**。 这个选项可以使用不同于默认格式的方式展示提交历史。 这个选项有一些内建的子选项供你使用。 比如 oneline 会将每个提交放在一行显示,在浏览大量的提交时非常有用。 另外还有 short,full 和 fuller 选项,它们展示信息的格式基本一致,但是详尽程度不一:
-
-
最有意思的是 format ,可以定制记录的显示格式。 这样的输出对后期提取分析格外有用——因为你知道输出的格式不会随着 Git 的更新而发生改变:
-
-
git log --pretty=format 常用的选项 列出了 format 接受的常用格式占位符的写法及其代表的意义。
-
当 oneline 或 format 与另一个 log 选项 --graph 结合使用时尤其有用。 这个选项添加了一些 ASCII 字符串来形象地展示你的分支、合并历史:
-
-
这种输出类型会在我们下一章学完分支与合并以后变得更加有趣。
-
以上只是简单介绍了一些 git log 命令支持的选项。 git log的常用选项 列出了我们目前涉及到的和没涉及到的选项,以及它们是如何影响 log 命令的输出的:
限制输出长度
-
除了定制输出格式的选项之外,git log 还有许多非常实用的限制输出长度的选项,也就是只输出一部分的提 交。 之前你已经看到过 -2 选项了,它只会显示最近的两条提交, 实际上,你可以使用类似 -
的选项,其中的 n 可以是任何整数,表示仅显示最近的 n 条提交。 不过实践中这个选项不是很常用,因为 Git 默认会将所有的输出传送到分页程序中,所以你一次只会看到一页的内容。 -
但是,类似 --since 和 --until 这种按照时间作限制的选项很有用。 例如,下面的命令会列出最近两周的所有提交:
-
2.4 撤销操作
-
在任何一个阶段,你都有可能想要撤消某些操作。 这里,我们将会学习几个撤消你所做修改的基本工具。 注
意,有些撤消操作是不可逆的。 这是在使用 Git 的过程中,会因为操作失误而导致之前的工作丢失的少有的几个地方之一。
-
有时候我们提交完了才发现漏掉了几个文件没有添加,或者提交信息写错了。 此时,可以运行带有 --amend 选项的提交命令来重新提交:
-
-
这个命令会将暂存区中的文件提交。 如果自上次提交以来你还未做任何修改(例如,在上次提交后马上执行了此命令), 那么快照会保持不变,而你所修改的只是提交信息。
-
文本编辑器启动后,可以看到之前的提交信息。 编辑后保存会覆盖原来的提交信息。
-
例如,你提交后发现忘记了暂存某些需要的修改,可以像下面这样操作:
-
-
最终你只会有一个提交——第二次提交将代替第一次提交的结果。
- 当你在修补最后的提交时,与其说是修复旧提交,倒不如说是完全用一个 新的提交 替换旧的提交, 理解这一点非常重要。从效果上来说,就像是旧有的提交从未存在过一样,它并不会出现在仓库的历史中。
- 修补提交最明显的价值是可以稍微改进你最后的提交,而不会让“啊,忘了添加一个文件”或者 “小修补,修正笔误”这种提交信息弄乱你的仓库历史。
取消暂存的文件
-
接下来的两个小节演示如何操作暂存区和工作目录中已修改的文件。 这些命令在修改文件状态的同时,也会提示如何撤消操作。 例如,你已经修改了两个文件并且想要将它们作为两次独立的修改提交, 但是却意外地输入git add * 暂存了它们两个。如何只取消暂存两个中的一个呢? git status 命令提示了你:
-
-
在 “Changes to be committed” 文字正下方,提示使用 git reset HEAD
... 来取消暂存。 所以,我们可以这样来取消暂存 CONTRIBUTING.md 文件: -
-
这个命令有点儿奇怪,但是起作用了。 CONTRIBUTING.md 文件已经是修改未暂存的状态了。
- git reset 确实是个危险的命令,如果加上了 --hard 选项则更是如此。 然而在上述场景中,工作目录中的文件尚未修改,因此相对安全一些。
- 到目前为止这个神奇的调用就是你需要对 git reset 命令了解的全部。 我们将会在 重置揭密 中了解 reset 的更多细节以及如何掌握它做一些真正有趣的事。
撤销对文件的修改
-
如果你并不想保留对 CONTRIBUTING.md 文件的修改怎么办? 你该如何方便地撤消修改——将它还原成上次提交时的样子(或者刚克隆完的样子,或者刚把它放入工作目录时的样子)? 幸运的是,git status 也告诉了你应该如何做。 在最后一个例子中,未暂存区域是这样:
-
-
它非常清楚地告诉了你如何撤消之前所做的修改。 让我们来按照提示执行:
-
-
可以看到那些修改已经被撤消了。
- 请务必记得 git checkout --
是一个危险的命令。 你对那个文件在本地的任何修改都会消失——Git 会用最近提交的版本覆盖掉它。 除非你确实清楚不想要对那个文件的本地修改了,否则请不要使用这个命令。
- 请务必记得 git checkout --
-
如果你仍然想保留对那个文件做出的修改,但是现在仍然需要撤消,我们将会在 Git 分支 介绍保存进度与分支,这通常是更好的做法。
-
记住,在 Git 中任何 已提交 的东西几乎总是可以恢复的。 甚至那些被删除的分支中的提交或使用 --amend 选 项覆盖的提交也可以恢复 (阅读 数据恢复 了解数据恢复)。 然而,任何你未提交的东西丢失后很可能再也找不到了。
2.5 远程仓库的使用
- 为了能在任意 Git 项目上协作,你需要知道如何管理自己的远程仓库。 远程仓库是指托管在因特网或其他网络中的你的项目的版本库。 你可以有好几个远程仓库,通常有些仓库对你只读,有些则可以读写。 与他人协作涉及管理远程仓库以及根据需要推送或拉取数据。 管理远程仓库包括了解如何添加远程仓库、移除无效的远程仓库、管理不同的远程分支并定义它们是否被跟踪等等。 在本节中,我们将介绍一部分远程管理的技能。
远程仓库可以在你的本地主机上,你完全可以在一个“远程”仓库上工作,而实际上它在你本地的主机上。 词语“远程”未必
表示仓库在网络或互联网上的其它位置,而只是表示它在别处。 在这样的远程仓库上工作,仍然需要和其它远程仓库上一样的标准推送、拉取和抓取操作。
查看远程仓库
-
如果想查看你已经配置的远程仓库服务器,可以运行 git remote 命令。 它会列出你指定的每一个远程服务器的简写。 如果你已经克隆了自己的仓库,那么至少应该能看到 origin ——这是 Git 给你克隆的仓库服务器的默认 名字:
-
-
你也可以指定选项 -v,会显示需要读写远程仓库使用的 Git 保存的简写与其对应的 URL。
-
-
如果你的远程仓库不止一个,该命令会将它们全部列出。 例如,与几个协作者合作的,拥有多个远程仓库的仓库看起来像下面这样:
-
-
这表示我们能非常方便地拉取其它用户的贡献。我们还可以拥有向他们推送的权限,这里暂不详述。
-
注意这些远程仓库使用了不同的协议。我们将会在 在服务器上搭建 Git 中了解关于它们的更多信息。
添加远程仓库
-
我们在之前的章节中已经提到并展示了 git clone 命令是如何自行添加远程仓库的, 不过这里将告诉你如何自己来添加它。 运行 git remote ad
-
-
现在你可以在命令行中使用字符串 pb 来代替整个 URL。 例如,如果你想拉取 Paul 的仓库中有但你没有的信息,可以运行 git fetch pb:
-
-
现在 Paul 的 master 分支可以在本地通过 pb/master 访问到——你可以将它合并到自己的某个分支中, 或者如果你想要查看它的话,可以检出一个指向该点的本地分支。(我们将会在 Git 分支中详细介绍什么是分支以及如何使用分支。)
从远程仓库中抓取与拉取
-
就如刚才所见,从远程仓库中获得数据,可以执行:
-
-
这个命令会访问远程仓库,从中拉取所有你还没有的数据。 执行完成后,你将会拥有那个远程仓库中所有分支 的引用,可以随时合并或查看。
-
如果你使用 clone 命令克隆了一个仓库,命令会自动将其添加为远程仓库并默认以 “origin” 为简写。 所以,git fetch origin 会抓取克隆(或上一次抓取)后新推送的所有工作。 必须注意 git fetch 命令只会 将数据下载到你的本地仓库——它并不会自动合并或修改你当前的工作。 当准备好时你必须手动将其合并入你的工作。
-
如果你的当前分支设置了跟踪远程分支(阅读下一节和 Git 分支 了解更多信息), 那么可以用 git pull 命令来自动抓取后合并该远程分支到当前分支。 这或许是个更加简单舒服的工作流程。默认情况下,git clone 命 令会自动设置本地 master 分支跟踪克隆的远程仓库的 master 分支(或其它名字的默认分支)。 运行 git pull 通常会从最初克隆的服务器上抓取数据并自动尝试合并到当前所在的分支。
推送到远程仓库
-
当你想分享你的项目时,必须将其推送到上游。 这个命令很简单:git push
-
-
只有当你有所克隆服务器的写入权限,并且之前没有人推送过时,这条命令才能生效。 当你和其他人在同一时间克隆,他们先推送到上游然后你再推送到上游,你的推送就会毫无疑问地被拒绝。 你必须先抓取他们的工作并将其合并进你的工作后才能推送。 阅读 Git 分支 了解如何推送到远程仓库服务器的详细信息。
查看某个远程仓库
-
如果想要查看某一个远程仓库的更多信息,可以使用 git remote show
命令。 如果想以一个特定的缩写名运行这个命令,例如 origin,会得到像下面类似的信息: -
-
它同样会列出远程仓库的 URL 与跟踪分支的信息。 这些信息非常有用,它告诉你正处于 master 分支,并且如果运行 git pull, 就会抓取所有的远程引用,然后将远程 master 分支合并到本地 master 分支。 它也会列 出拉取到的所有远程引用。
-
这是一个经常遇到的简单例子。 如果你是 Git 的重度使用者,那么还可以通过git remote show看到更多的信息。
-
-
这个命令列出了当你在特定的分支上执行 git push 会自动地推送到哪一个远程分支。 它也同样地列出了哪些远程分支不在你的本地,哪些远程分支已经从服务器上移除了, 还有当你执行 git pull 时哪些本地分支可以与它跟踪的远程分支自动合并。
远程仓库的重命名与移除
-
你可以运行 git remote rename 来修改一个远程仓库的简写名。 例如,想要将 pb 重命名为 paul,可以用**git remote rename **这样做:
-
-
值得注意的是这同样也会修改你所有远程跟踪的分支名字。 那些过去引用 pb/master 的现在会引用paul/master。
-
如果因为一些原因想要移除一个远程仓库——你已经从服务器上搬走了或不再想使用某一个特定的镜像了, 又或者某一个贡献者不再贡献了——可以使用 git remote remove 或 git remote rm :
-
-
一旦你使用这种方式删除了一个远程仓库,那么所有和这个远程仓库相关的远程跟踪分支以及配置信息也会一起被删除。
2.6 打标签
- 像其他版本控制系统(VCS)一样,Git 可以给仓库历史中的某一个提交打上标签,以示重要。 比较有代表性的是人们会使用这个功能来标记发布结点( v1.0 、 v2.0 等等)。 在本节中,你将会学习如何列出已有的标签、 如何创建和删除新的标签、以及不同类型的标签分别是什么。
列出标签
-
在 Git 中列出已有的标签非常简单,只需要输入 git tag (可带上可选的 -l 选项 --list):
-
-
这个命令以字母顺序列出标签,但是它们显示的顺序并不重要。
-
你也可以按照特定的模式查找标签。 例如,Git 自身的源代码仓库包含标签的数量超过 500 个。 如果只对 1.8.5系列感兴趣,可以运行:
-
-
按照通配符列出标签需要 -l 或 --list 选项如果你只想要完整的标签列表,那么运行git tag 就会默认假定你想要一个列表,它会直接给你列出来, 此时的 -l 或 --list 是可选的。
-
然而,如果你提供了一个匹配标签名的通配模式,那么 -l 或 --list 就是强制使用的。
创建标签
- Git 支持两种标签:轻量标签(lightweight)与附注标签(annotated)。
- 轻量标签很像一个不会改变的分支——它只是某个特定提交的引用。
- 而附注标签是存储在 Git 数据库中的一个完整对象, 它们是可以被校验的,其中包含打标签者的名字、电子邮件地址、日期时间, 此外还有一个标签信息,并且可以使用 GNU Privacy Guard (GPG)签名并验证。 通常会建议创建附注标签,这样你可以拥有以上所有信息。但是如果你只是想用一个临时的标签, 或者因为某些原因不想要保存这些信息,那么也可以用轻量标签。
附注标签
-
在 Git 中创建附注标签十分简单。 最简单的方式是当你在运行 tag 命令时指定 -a 选项:
-
-
-m 选项指定了一条将会存储在标签中的信息。 如果没有为附注标签指定一条信息,Git 会启动编辑器要求你输入信息。
-
通过使用** git show** 命令可以看到标签信息和与之对应的提交信息:
-
-
输出显示了打标签者的信息、打标签的日期时间、附注信息,然后显示具体的提交信息。
轻量打签
-
另一种给提交打标签的方式是使用轻量标签。 轻量标签本质上是将提交校验和存储到一个文件中——没有保存任何其他信息。 创建轻量标签,不需要使用 -a、-s 或 -m 选项,只需要提供标签名字:
-
-
这时,如果在标签上运行 git show,你不会看到额外的标签信息。 命令只会显示出提交信息:
-
后期打标签
-
你也可以对过去的提交打标签。 假设提交历史是这样的:
-
-
现在,假设在 v1.2 时你忘记给项目打标签,也就是在updated rakefile提交。 你可以在之后补上标签。 要在那个提交上打标签,你需要在命令的末尾指定提交的校验和(或部分校验和):
-
-
可以看到你已经在那次提交上打上标签了:
-
删除标签
-
要删除掉你本地仓库上的标签,可以使用命令 git tag -d
。 例如,可以使用以下命令删除一个轻量标签: -
-
注意上述命令并不会从任何远程仓库中移除这个标签,你必须用 git push
-
:refs/tags/
来更新你的远程仓库: -
第一种变体是git push
:refs/tags/ : -
-
上面这种操作的含义是,将冒号前面的空值推送到远程标签名,从而高效地删除它。
-
第二种更直观的删除远程标签的方式是:
-
2.7 Git别名
-
在我们结束本章 Git 基础之前,正好有一个小技巧可以使你的 Git 体验更简单、容易、熟悉:别名。 我们不会在之后的章节中引用到或假定你使用过它们,但是你大概应该知道如何使用它们。
-
Git 并不会在你输入部分命令时自动推断出你想要的命令。 如果不想每次都输入完整的 Git 命令,可以通过 git config 文件来轻松地为每一个命令设置一个别名。 这里有一些例子你可以试试:
-
-
这意味着,当要输入 git commit 时,只需要输入 git ci。 随着你继续不断地使用 Git,可能也会经常使用其他命令,所以创建别名时不要犹豫。
-
在创建你认为应该存在的命令时这个技术会很有用。 例如,为了解决取消暂存文件的易用性问题,可以向 Git 中添加你自己的取消暂存别名:
-
-
这会使下面的两个命令等价:
-
-
这样看起来更清楚一些。 通常也会添加一个 last 命令,像这样:
-
-
这样,可以轻松地看到最后一次提交:
-
-
可以看出,Git 只是简单地将别名替换为对应的命令。 然而,你可能想要执行外部命令,而不是一个 Git 子命令。 如果是那样的话,可以在命令前面加入 ! 符号。 如果你自己要写一些与 Git 仓库协作的工具的话,那会很有用。 我们现在演示将 git visual 定义为 gitk 的别名:
-
3|03. Git分支
- 几乎所有的版本控制系统都以某种形式支持分支。 使用分支意味着你可以把你的工作从开发主线上分离开来,以免影响开发主线。 在很多版本控制系统中,这是一个略微低效的过程——常常需要完全创建一个源代码目录的副本。对于大项目来说,这样的过程会耗费很多时间。
- 有人把 Git 的分支模型称为它的“必杀技特性”,也正因为这一特性,使得 Git 从众多版本控制系统中脱颖而出。 为何 Git 的分支模型如此出众呢? Git 处理分支的方式可谓是难以置信的轻量,创建新分支这一操作几乎能在瞬间完成,并且在不同分支之间的切换操作也是一样便捷。 与许多其它版本控制系统不同,Git 鼓励在工作流 程中频繁地使用分支与合并,哪怕一天之内进行许多次。 理解和精通这一特性,你便会意识到 Git 是如此的强大而又独特,并且从此真正改变你的开发方式。
3.1 分支简介
-
为了真正理解 Git 处理分支的方式,我们需要回顾一下 Git 是如何保存数据的。
-
或许你还记得 起步 的内容, Git 保存的不是文件的变化或者差异,而是一系列不同时刻的 快照
-
在进行提交操作时,Git 会保存一个提交对象(commit object)。 知道了 Git 保存数据的方式,我们可以很自然的想到——该提交对象会包含一个指向暂存内容快照的指针。 但不仅仅是这样,该提交对象还包含了作者的姓 名和邮箱、提交时输入的信息以及指向它的父对象的指针。 首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象, 而由多个分支合并产生的提交对象有多个父对象,
-
为了更加形象地说明,我们假设现在有一个工作目录,里面包含了三个将要被暂存和提交的文件。 暂存操作会为每一个文件计算校验和(使用我们在 起步 中提到的 SHA-1 哈希算法),然后会把当前版本的文件快照保存到Git 仓库中 (Git 使用 blob 对象来保存它们),最终将校验和加入到暂存区域等待提交:
-
-
当使用 git commit 进行提交操作时,Git 会先计算每一个子目录(本例中只有项目根目录)的校验和, 然后在 Git 仓库中这些校验和保存为树对象。随后,Git 便会创建一个提交对象, 它除了包含上面提到的那些信息 外,还包含指向这个树对象(项目根目录)的指针。 如此一来,Git 就可以在需要的时候重现此次保存的快照。
-
现在,Git 仓库中有五个对象:三个 blob 对象(保存着文件快照)、一个 树 对象 (记录着目录结构和 blob 对象索引)以及一个 提交 对象(包含着指向前述树对象的指针和所有提交信息)。
-
图表 9. 首次提交对象及其树结构
-
做些修改后再次提交,那么这次产生的提交对象会包含一个指向上次提交对象(父对象)的指针。
-
Git 的分支,其实本质上仅仅是指向提交对象的可变指针。 Git 的默认分支名字是 master。 在多次提交操作之后,你其实已经有一个指向最后那个提交对象的 master 分支。 master 分支会在每次提交时自动向前移动。
Git 的 master 分支并不是一个特殊分支。 它就跟其它分支完全没有区别。 之所以几乎每一个仓库都有 master 分支,是因为 git init 命令默认创建它,并且大多数人都懒得去改动它。
分支创建
-
Git 是怎么创建新分支的呢? 很简单,它只是为你创建了一个可以移动的新的指针。 比如,创建一个 testing 分支, 你需要使用 git branch 命令:
-
-
这会在当前所在的提交对象上创建一个指针。
-
那么,Git 又是怎么知道当前在哪一个分支上呢? 也很简单,它有一个名为 HEAD 的特殊指针。 请注意它和许多其它版本控制系统(如 Subversion 或 CVS)里的 HEAD 概念完全不同。 在 Git中,它是一个指针,指向当前所在的本地分支(译注:将 HEAD 想象为当前分支的别名)。 在本例中,你仍然在master 分支上。 因为 git branch 命令仅仅 创建 一个新分支,并不会自动切换到新分支中去。
-
你可以简单地使用 git log 命令查看各个分支当前所指的对象。 提供这一功能的参数是 --decorate。
-
-
正如你所见,当前 master 和 testing 分支均指向校验和以 f30ab 开头的提交对象。
分支切换
-
要切换到一个已存在的分支,你需要使用git checkout 命令。 我们现在切换到新创建的 testing 分支去:
-
-
这样 HEAD 就指向 testing 分支了。
-
那么,这样的实现方式会给我们带来什么好处呢? 现在不妨再提交一次:
-
-
如图所示,你的 testing 分支向前移动了,但是 master 分支却没有,它仍然指向运行 git checkout 时所指的对象。 这就有意思了,现在我们切换回 master 分支看看:
-
-
这条命令做了两件事。 一是使 HEAD 指回 master 分支,二是将工作目录恢复成 master 分支所指向的快照内容。 也就是说,你现在做修改的话,项目将始于一个较旧的版本。 本质上来讲,这就是忽略 testing 分支所做 的修改,以便于向另一个方向进行开发。
-
分支切换会改变你工作目录中的文件在切换分支时,一定要注意你工作目录里的文件会被改变。 如果是切换到一个较旧的分支,你的工作目录会恢复到该分支最后一次提交时的样子。 如果 Git 不能干净利落地完成这个任 务,它将禁止切换分支。
-
我们不妨再稍微做些修改并提交
-
-
现在,这个项目的提交历史已经产生了分叉(参见 项目分叉历史)。 因为刚才你创建了一个新分支,并切换过去进行了一些工作,随后又切换回 master 分支进行了另外一些工作。 上述两次改动针对的是不同分支:你可以在不同分支间不断地来回切换和工作,并在时机成熟时将它们合并起来。 而所有这些工作,你需要的命令只有branch、checkout 和 commit。
-
你可以简单地使用 git log 命令查看分叉历史。 运行 git log --oneline --decorate --graph--all ,它会输出你的提交历史、各个分支的指向以及项目的分支分叉情况。
-
-
由于 Git 的分支实质上仅是包含所指对象校验和(长度为 40 的 SHA-1 值字符串)的文件,所以它的创建和销毁都异常高效。 创建一个新分支就相当于往一个文件中写入 41 个字节(40 个字符和 1 个换行符),如此的简单能不快吗?
-
这与过去大多数版本控制系统形成了鲜明的对比,它们在创建分支时,将所有的项目文件都复制一遍,并保存到一个特定的目录。 完成这样繁琐的过程通常需要好几秒钟,有时甚至需要好几分钟。所需时间的长短,完全取 决于项目的规模。 而在 Git 中,任何规模的项目都能在瞬间创建新分支。 同时,由于每次提交都会记录父对 象,所以寻找恰当的合并基础(译注:即共同祖先)也是同样的简单和高效。 这些高效的特性使得 Git 鼓励开发人员频繁地创建和使用分支。
-
接下来,让我们看看你为什么应该这样做。
- 创建新分支的同时切换过去通常我们会在创建一个新分支后立即切换过去,这可以用 git checkout -b
一条命令搞定。
- 创建新分支的同时切换过去通常我们会在创建一个新分支后立即切换过去,这可以用 git checkout -b
3.2 分支的新建与合并
- 让我们来看一个简单的分支新建与分支合并的例子,实际工作中你可能会用到类似的工作流。 你将经历如下步骤:
- 开发某个网站。
- 为实现某个新的用户需求,创建一个分支。
- 在这个分支上开展工作。
- 正在此时,你突然接到一个电话说有个很严重的问题需要紧急修补。 你将按照如下方式来处理:
- 切换到你的线上分支(production branch)。
- 为这个紧急任务新建一个分支,并在其中修复它。
- 在测试通过之后,切换回线上分支,然后合并这个修补分支,最后将改动推送到线上分支。
- 切换回你最初工作的分支上,继续工作。
新建分支
-
首先,我们假设你正在你的项目上工作,并且在 master 分支上已经有了一些提交。
-
现在,你已经决定要解决你的公司使用的问题追踪系统中的 #53 问题。 想要新建一个分支并同时切换到那个分支上,你可以运行一个带有 -b 参数的 git checkout 命令:
-
-
它是下面两条命令的简写:
-
-
你继续在 #53 问题上工作,并且做了一些提交。 在此过程中,iss53 分支在不断的向前推进,因为你已经检出到该分支 (也就是说,你的 HEAD 指针指向了 iss53 分支)
-
-
现在你接到那个电话,有个紧急问题等待你来解决。 有了 Git 的帮助,你不必把这个紧急问题和 iss53 的修改 混在一起, 你也不需要花大力气来还原关于 53# 问题的修改,然后再添加关于这个紧急问题的修改,最后将这 个修改提交到线上分支。 你所要做的仅仅是切换回 master 分支。
-
但是,在你这么做之前,要留意你的工作目录和暂存区里那些还没有被提交的修改, 它可能会和你即将检出的分支产生冲突从而阻止 Git 切换到该分支。 最好的方法是,在你切换分支之前,保持好一个干净的状态。 有一些方法可以绕过这个问题(即,暂存(stashing) 和 修补提交(commit amending)), 我们会在 贮藏与清 理 中看到关于这两个命令的介绍。 现在,我们假设你已经把你的修改全部提交了,这时你可以切换回 master分支了:
-
-
这个时候,你的工作目录和你在开始 #53 问题之前一模一样,现在你可以专心修复紧急问题了。 请牢记:当你切换分支的时候,Git 会重置你的工作目录,使其看起来像回到了你在那个分支上最后一次提交的样子。 Git 会自动添加、删除、修改文件以确保此时你的工作目录和这个分支最后一次提交时的样子一模一样。
-
接下来,你要修复这个紧急问题。 我们来建立一个 hotfix 分支,在该分支上工作直到问题解决:
-
-
你可以运行你的测试,确保你的修改是正确的,然后将 hotfix 分支合并回你的 master 分支来部署到线上。你可以使用 git merge 命令来达到上述目的:
-
-
在合并的时候,你应该注意到了“快进(fast-forward)”这个词。 由于你想要合并的分支 hotfix 所指向的提交 C4 是你所在的提交 C2 的直接后继, 因此 Git 会直接将指针向前移动。换句话说,当你试图合并两个分支时, 如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候, 只会简单的将指针向前推进 (指针右移),因为这种情况下的合并操作没有需要解决的分歧——这就叫做 “快进(fast-forward)”。
-
现在,最新的修改已经在 master 分支所指向的提交快照中,你可以着手发布该修复了。
-
关于这个紧急问题的解决方案发布之后,你准备回到被打断之前时的工作中。 然而,你应该先删除 hotfix 分支,因为你已经不再需要它了 —— master 分支已经指向了同一个位置。 你可以使用带 -d 选项的 git branch 命令来删除分支:
-
-
现在你可以切换回你正在工作的分支继续你的工作,也就是针对 #53 问题的那个分支(iss53 分支)。
-
-
hotfix 分支上所做的工作并没有包含到 iss53 分支中。 如果你需要拉取 hotfix 所做的修改,你可以使用 git merge master 命令将 master 分支合并入 iss53 分支,或者你也可以等到 iss53 分支完成其使命,再将其合并回 master 分支。
分支的合并
-
假设你已经修正了 #53 问题,并且打算将你的工作合并入 master 分支。 为此,你需要合并 iss53 分支到master 分支,这和之前你合并 hotfix 分支所做的工作差不多。 你只需要检出到你想合并入的分支,然后运行git merge 命令:
-
-
这和你之前合并 hotfix 分支的时候看起来有一点不一样。 在这种情况下,你的开发历史从一个更早的地方开始分叉开来(diverged)。 因为,master 分支所在提交并不是 iss53 分支所在提交的直接祖先,Git 不得不做一些额外的工作。 出现这种情况的时候,Git 会使用两个分支的末端所指的快照(C4 和 C5)以及这两个分支的公共祖先(C2),做一个简单的三方合并。
-
-
和之前将分支指针向前推进所不同的是,Git 将此次三方合并的结果做了一个新的快照并且自动创建一个新的提交指向它。 这个被称作一次合并提交,它的特别之处在于他有不止一个父提交。
-
既然你的修改已经合并进来了,就不再需要 iss53 分支了。 现在你可以在任务追踪系统中关闭此项任务,并删 除这个分支。
-
遇到冲突时的分支合并
-
有时候合并操作不会如此顺利。 如果你在两个不同的分支中,对同一个文件的同一个部分进行了不同的修改,Git 就没法干净的合并它们。 如果你对 #53 问题的修改和有关 hotfix 分支的修改都涉及到同一个文件的同一处,在合并它们的时候就会产生合并冲突:
-
-
此时 Git 做了合并,但是没有自动地创建一个新的合并提交。 Git 会暂停下来,等待你去解决合并产生的冲突。 你可以在合并冲突后的任意时刻使用 git status 命令来查看那些因包含合并冲突而处于未合并 (unmerged)状态的文件:
-
-
任何因包含合并冲突而有待解决的文件,都会以未合并状态标识出来。 Git 会在有冲突的文件中加入标准的冲突解决标记,这样你可以打开这些包含冲突的文件然后手动解决冲突。 出现冲突的文件会包含一些特殊区段,看起来像下面这个样子:
-
-
这表示 HEAD 所指示的版本(也就是你的 master 分支所在的位置,因为你在运行 merge 命令的时候已经检出到了这个分支)在这个区段的上半部分(======= 的上半部分),而 iss53 分支所指示的版本在 ======= 的 下半部分。 为了解决冲突,你必须选择使用由 ======= 分割的两部分中的一个,或者你也可以自行合并这些内容。 例如,你可以通过把这段内容换成下面的样子来解决冲突:
-
-
上述的冲突解决方案仅保留了其中一个分支的修改,并且 <<<<<<< , ======= , 和 >>>>>>> 这些行被完全删除了。 在你解决了所有文件里的冲突之后,对每个文件使用 git add 命令来将其标记为冲突已解决。 一旦暂存这些原本有冲突的文件,Git 就会将它们标记为冲突已解决。
-
如果你想使用图形化工具来解决冲突,你可以运行 git mergetool,该命令会为你启动一个合适的可视化合并工具,并带领你一步一步解决这些冲突:
-
-
如果你想使用除默认工具(在这里 Git 使用 opendiff 做为默认的合并工具,因为作者在 Mac 上运行该程序)外的其他合并工具,你可以在 “下列工具中(one of the following tools)” 这句后面看到所有支持的合并工具。 然后输入你喜欢的工具名字就可以了。
如果你需要更加高级的工具来解决复杂的合并冲突,我们会在 高级合并 介绍更多关于分支合并的内容。
-
等你退出合并工具之后,Git 会询问刚才的合并是否成功。 如果你回答是,Git 会暂存那些文件以表明冲突已解决: 你可以再次运行 git status 来确认所有的合并冲突都已被解决:
-
-
如果你对结果感到满意,并且确定之前有冲突的的文件都已经暂存了,这时你可以输入 git commit 来完成合并提交。 默认情况下提交信息看起来像下面这个样子:
-
-
如果你觉得上述的信息不够充分,不能完全体现分支合并的过程,你可以修改上述信息, 添加一些细节给未来检视这个合并的读者一些帮助,告诉他们你是如何解决合并冲突的,以及理由是什么。
3.3 分支管理
-
现在已经创建、合并、删除了一些分支,让我们看看一些常用的分支管理工具。
-
git branch 命令不只是可以创建与删除分支。 如果不加任何参数运行它,会得到当前所有分支的一列表:
-
-
注意 master 分支前的 * 字符:它代表现在检出的那一个分支(也就是说,当前 HEAD 指针所指向的分支)。这意味着如果在这时候提交,master 分支将会随着新的工作向前移动。 如果需要查看每一个分支的最后一次提交,可以运行 git branch -v 命令:
-
-
--merged 与 --no-merged 这两个有用的选项可以过滤这个列表中已经合并或尚未合并到当前分支的分支。如果要查看哪些分支已经合并到当前分支,可以运行 git branch --merged:
-
-
因为之前已经合并了 iss53 分支,所以现在看到它在列表中。 在这个列表中分支名字前没有 * 号的分支通常可以使用 git branch -d 删除掉;你已经将它们的工作整合到了另一个分支,所以并不会失去任何东西。
-
查看所有包含未合并工作的分支,可以运行 git branch --no-merged:
-
-
这里显示了其他分支。 因为它包含了还未合并的工作,尝试使用 git branch -d 命令删除它时会失败:
-
-
如果真的想要删除分支并丢掉那些工作,如同帮助信息里所指出的,可以使用 -D 选项强制删除它。
-
上面描述的选项 --merged和 --no-merged 会在没有给定提交或分支名作为参数时, 分别列出已合并或未合并到 当前 分支的分支。
-
你总是可以提供一个附加的参数来查看其它分支的合并状态而不必检出它们。 例如,尚未合并到 master 分支的有哪些?
-
-
3.4 分支开发工作流
- 现在你已经学会新建和合并分支,那么你可以或者应该用它来做些什么呢? 在本节,我们会介绍一些常见的利用分支进行开发的工作流程。而正是由于分支管理的便捷, 才衍生出这些典型的工作模式,你可以根据项目实 际情况选择一种用用看。
长期分支
-
因为 Git 使用简单的三方合并,所以就算在一段较长的时间内,反复把一个分支合并入另一个分支不是什么难事。 也就是说,在整个项目开发周期的不同阶段,你可以同时拥有多个开放的分支;你可以定期地把某些主题分支合并入其他分支中。
-
许多使用 Git 的开发者都喜欢使用这种方式来工作,比如只在 master 分支上保留完全稳定的代码——有可能仅仅是已经发布或即将发布的代码。 他们还有一些名为 develop 或者 next 的平行分支,被用来做后续开发或者测试稳定性——这些分支不必保持绝对稳定,但是一旦达到稳定状态,它们就可以被合并入 master分支了。 这样,在确保这些已完成的主题分支(短期分支,比如之前的 iss53 分支)能够通过所有测试,并且不会引入更多 bug 之后,就可以合并入主干分支中,等待下一次的发布。
-
事实上我们刚才讨论的,是随着你的提交而不断右移的指针。 稳定分支的指针总是在提交历史中落后一大截, 而前沿分支的指针往往比较靠前。
-
通常把他们想象成流水线(work silos)可能更好理解一点,那些经过测试考验的提交会被遴选到更加稳定的流水线上去。
-
你可以用这种方法维护不同层次的稳定性。 一些大型项目还有一个 proposed(建议) 或 pu: proposed updates(建议更新)分支,它可能因包含一些不成熟的内容而不能进入 next 或者 master 分支。 这么做的目的是使你的分支具有不同级别的稳定性;当它们具有一定程度的稳定性后,再把它们合并入具有更高级别稳定性的分支中。 再次强调一下,使用多个长期分支的方法并非必要,但是这么做通常很有帮助,尤其是当你在一个非常庞大或者复杂的项目中工作时。
主题分支
-
主题分支对任何规模的项目都适用。 主题分支是一种短期分支,它被用来实现单一特性或其相关工作。 也许你从来没有在其他的版本控制系统(VCS)上这么做过,因为在那些版本控制系统中创建和合并分支通常很费劲。然而,在 Git 中一天之内多次创建、使用、合并、删除分支都很常见。
-
你已经在上一节中你创建的 iss53 和 hotfix 主题分支中看到过这种用法。 你在上一节用到的主题分支 (iss53 和 hotfix 分支)中提交了一些更新,并且在它们合并入主干分支之后,你又删除了它们。 这项技术能使你快速并且完整地进行上下文切换(context-switch)——因为你的工作被分散到不同的流水线中,在不同的流水线中每个分支都仅与其目标特性相关,因此,在做代码审查之类的工作的时候就能更加容易地看出你做了 哪些改动。 你可以把做出的改动在主题分支中保留几分钟、几天甚至几个月,等它们成熟之后再合并,而不用在乎它们建立的顺序或工作进度。
-
考虑这样一个例子,你在 master 分支上工作到 C1,这时为了解决一个问题而新建 iss91 分支,在 iss91 分支上工作到 C4,然而对于那个问题你又有了新的想法,于是你再新建一个 iss91v2 分支试图用另一种方法解决那个问题,接着你回到 master 分支工作了一会儿,你又冒出了一个不太确定的想法,你便在 C10 的时候新建一个 dumbidea 分支,并在上面做些实验。 你的提交历史看起来像下面这个样子:
-
现在,我们假设两件事情:你决定使用第二个方案来解决那个问题,即使用在 iss91v2 分支中方案。 另外,你将 dumbidea 分支拿给你的同事看过之后,结果发现这是个惊人之举。 这时你可以抛弃 iss91 分支(即丢弃C5 和 C6 提交),然后把另外两个分支合并入主干分支。 最终你的提交历史看起来像下面这个样子:
-
我们将会在 分布式 Git 中向你揭示更多有关分支工作流的细节, 因此,请确保你阅读完那个章节之后,再来决定你的下个项目要使用什么样的分支策略(branching scheme)。
-
请牢记,当你做这么多操作的时候,这些分支全部都存于本地。 当你新建和合并分支的时候,所有这一切都只发生在你本地的 Git 版本库中 —— 没有与服务器发生交互。
3.5 远程分支
-
远程引用是对远程仓库的引用(指针),包括分支、标签等等。 你可以通过 git ls-remote
来显式地获得远程引用的完整列表, 或者通过 git remote show 获得远程分支的更多信息。 然而,一个更常见的做法是利用远程跟踪分支。 -
远程跟踪分支是远程分支状态的引用。它们是你无法移动的本地引用。一旦你进行了网络通信, Git 就会为你移动它们以精确反映远程仓库的状态。请将它们看做书签, 这样可以提醒你该分支在远程仓库中的位置就是你最后一次连接到它们的位置。
-
它们以
/ 的形式命名。 例如,如果你想要看你最后一次与远程仓库 origin 通信时master 分支的状态,你可以查看 origin/master 分支。 你与同事合作解决一个问题并且他们推送了一个iss53 分支,你可能有自己的本地 iss53 分支, 然而在服务器上的分支会以 origin/iss53 来表示。 -
这可能有一点儿难以理解,让我们来看一个例子。 假设你的网络里有一个在git.ourcompany.com 的 Git 服务器。 如果你从这里克隆,Git 的 clone 命令会为你自动将其命名为 origin,拉取它的所有数据, 创建一个指向它的 master 分支的指针,并且在本地将其命名为 origin/master。 Git 也会给你一个与 origin 的master 分支在指向同一个地方的本地 master 分支,这样你就有工作的基础。
-
“origin” 并无特殊含义远程仓库名字 “origin” 与分支名字 “master” 一样,在 Git 中并没有任何特别的含义一样。 同时 “master” 是当你运行 git init 时默认的起始分支名字,原因仅仅是它的广泛使用, “origin” 是当你运行 git clone 时默认的远程仓库名字。 如果你运行 git clone-o booyah,那么你默认的远程分支名字将会是 booyah/master。
-
如果你在本地的 master 分支做了一些工作,在同一段时间内有其他人推送提交到 git.ourcompany.com并且更新了它的 master 分支,这就是说你们的提交历史已走向不同的方向。 即便这样,只要你保持不与origin 服务器连接(并拉取数据),你的origin/master 指针就不会移动。
-
如果要与给定的远程仓库同步数据,运行 git fetch
命令(在本例中为 git fetch origin)。这个命令查找 “origin” 是哪一个服务器(在本例中,它是git.ourcompany .com), 从中抓取本地没有的数据,并且更新本地数据库,移动 origin/master 指针到更新之后的位置。 -
为了演示有多个远程仓库与远程分支的情况,我们假定你有另一个内部 Git 服务器,仅服务于你的某个敏捷开发团队。 这个服务器位于 git.team1.ourcompany.com。 你可以运行 git remote add 命令添加一个新的远程仓库引用到当前的项目,这个命令我们会在 Git 基础 中详细说明。 将这个远程仓库命名为 teamone,将其作为完整 URL 的缩写。
-
现在,可以运行 git fetch teamone 来抓取远程仓库 teamone 有而本地没有的数据。 因为那台服务器上现有的数据是 origin 服务器上的一个子集, 所以 Git 并不会抓取数据而是会设置远程跟踪分支teamone/master 指向 teamone 的 master 分支。
推送
-
当你想要公开分享一个分支时,需要将其推送到有写入权限的远程仓库上。 本地的分支并不会自动与远程仓库同步——你必须显式地推送想要分享的分支。 这样,你就可以把不愿意分享的内容放到私人分支上,而将需要和别人协作的内容推送到公开分支。
-
如果希望和别人一起在名为 serverfix 的分支上工作,你可以像推送第一个分支那样推送它。 运行 git push
: -
-
这里有些工作被简化了。 Git 自动将 serverfix 分支名字展开为refs/heads/serverfix:refs/heads/serverfix, 那意味着,“推送本地的 serverfix
分支来更新远程仓库上的 serverfix 分支。” 我们将会详细学习 Git 内部原理 的 refs/heads/ 部分, 但是现在可以先把它放在儿。你也可以运行 git push origin serverfix:serverfix, 它会做同样的事——也就是说“推送本地的 serverfix 分支,将其作为远程仓库的 serverfix 分支” 可以通过这种格式来推送本 地分支到一个命名不相同的远程分支。 如果并不想让远程仓库上的分支叫做 serverfix,可以运行 git push origin serverfix:awesomebranch 来将本地的 serverfix 分支推送到远程仓库上的 awesomebranch分支。- 如何避免每次输入密码,如果你正在使用 HTTPS URL 来推送,Git 服务器会询问用户名与密码。 默认情况下它会在终端中提示服务器是否允许你进行推送。
- 如果不想在每一次推送时都输入用户名与密码,你可以设置一个 “credential cache”。 最简单的方式就是将其保存在内存中几分钟,可以简单地运行 git config --global credential.helper cache 来设置它。
-
下一次其他协作者从服务器上抓取数据时,他们会在本地生成一个远程分支origin/serverfix,指向服务器的 serverfix 分支的引用:
-
-
要特别注意的一点是当抓取到新的远程跟踪分支时,本地不会自动生成一份可编辑的副本(拷贝)。 换一句话说,这种情况下,不会有一个新的 serverfix 分支——只有一个不可以修改的 origin/serverfix 指针。
-
可以运行 git merge origin/serverfix 将这些工作合并到当前所在的分支。 如果想要在自己的serverfix 分支上工作,可以将其建立在远程跟踪分支之上:
-
-
这会给你一个用于工作的本地分支,并且起点位于 origin/serverfix。
跟踪分支
-
从一个远程跟踪分支检出一个本地分支会自动创建所谓的“跟踪分支”(它跟踪的分支叫做“上游分支”)。跟踪分支是与远程分支有直接关系的本地分支。 如果在一个跟踪分支上输入 git pull,Git 能自动地识别去哪个服务器上抓取、合并到哪个分支。
-
当克隆一个仓库时,它通常会自动地创建一个跟踪 origin/master 的 master 分支。 然而,如果你愿意的话可以设置其他的跟踪分支,或是一个在其他远程仓库上的跟踪分支,又或者不跟踪 master 分支。 最简单的实例就是像之前看到的那样,运行 git checkout -b
/ 。 这是一个十分常用的操作所以 Git 提供了 --track 快捷方式: -
-
由于这个操作太常用了,该捷径本身还有一个捷径。 如果你尝试检出的分支 (a) 不存在且 (b) 刚好只有一个名字与之匹配的远程分支,那么 Git 就会为你创建一个跟踪分支:
-
-
如果想要将本地分支与远程分支设置为不同的名字,你可以轻松地使用上一个命令增加一个不同名字的本地分支:
-
-
现在,本地分支 sf 会自动从 origin/serverfix 拉取。
-
设置已有的本地分支跟踪一个刚刚拉取下来的远程分支,或者想要修改正在跟踪的上游分支, 你可以在任意时间使用 -u 或 --set-upstream-to 选项运行 git branch 来显式地设置。
-
-
上游快捷方式,当设置好跟踪分支后,可以通过简写 @{upstream} 或 @{u} 来引用它的上游分支。 所以在master 分支时并且它正在跟踪 origin/master 时,如果愿意的话可以使用 git merge @{u} 来取代 git merge origin/master。
-
如果想要查看设置的所有跟踪分支,可以使用 git branch 的 -vv 选项。 这会将所有的本地分支列出来并且包含更多的信息,如每一个分支正在跟踪哪个远程分支与本地分支是否是领先、落后或是都有。
-
-
这里可以看到 iss53 分支正在跟踪 origin/iss53 并且 “ahead” 是 2,意味着本地有两个提交还没有推送到服务器上。 也能看到 master 分支正在跟踪 origin/master 分支并且是最新的。 接下来可以看到serverfix 分支正在跟踪 teamone 服务器上的 server-fix-good 分支并且领先 3 落后 1, 意味着服务器上有一次提交还没有合并入同时本地有三次提交还没有推送。 最后看到 testing 分支并没有跟踪任何远程分支。
-
需要重点注意的一点是这些数字的值来自于你从每个服务器上最后一次抓取的数据。 这个命令并没有连接服务器,它只会告诉你关于本地缓存的服务器数据。 如果想要统计最新的领先与落后数字,需要在运行此命令前抓取所有的远程仓库。 可以像这样做:
-
拉取
- 当 **git fetch ** 命令从服务器上抓取本地没有的数据时,它并不会修改工作目录中的内容。 它只会获取数据然后让你自己合并。 然而,有一个命令叫作 git pull 在大多数情况下它的含义是一个 **git fetch ** 紧接着一个 **git merge ** 命令。 如果有一个像之前章节中演示的设置好的跟踪分支,不管它是显式地设置还是通过 clone或 checkout 命令为你创建的,git pull 都会查找当前分支所跟踪的服务器与分支, 从服务器上抓取数据然 后尝试合并入那个远程分支。
- 由于 git pull 的魔法经常令人困惑所以通常单独显式地使用 fetch 与 merge 命令会更好一些。
删除远程分支
-
假设你已经通过远程分支做完所有的工作了——也就是说你和你的协作者已经完成了一个特性, 并且将其合并到了远程仓库的 master 分支(或任何其他稳定代码分支)。 可以运行带有 --delete 选项的 git push 命令来删除一个远程分支。 如果想要从服务器上删除 serverfix 分支,运行下面的命令:
-
-
基本上这个命令做的只是从服务器上移除这个指针。 Git 服务器通常会保留数据一段时间直到垃圾回收运行,所以如果不小心删除掉了,通常是很容易恢复的。
3.6 变基
- 在 Git 中整合来自不同分支的修改主要有两种方法:merge 以及 rebase。 在本节中我们将学习什么是“变基”,怎样使用“变基”,并将展示该操作的惊艳之处,以及指出在何种情况下你应避免使用它。
变基的基本操作
-
请回顾之前在 分支的合并 中的一个例子,你会看到开发任务分叉到两个不同分支,又各自提交了更新。
-
之前介绍过,整合分支最容易的方法是 merge 命令。 它会把两个分支的最新快照(C3 和 C4)以及二者最近的共同祖先(C2)进行三方合并,合并的结果是生成一个新的快照(并提交)。
-
其实,还有一种方法:你可以提取在 C4 中引入的补丁和修改,然后在 C3 的基础上应用一次。 在 Git 中,这种操作就叫做 变基(rebase)。 你可以使用 rebase 命令将提交到某一分支上的所有修改都移至另一分支上,就好像“重新播放”一样。
-
在这个例子中,你可以检出 experiment 分支,然后将它变基到 master 分支上:
-
-
它的原理是首先找到这两个分支(即当前分支 experiment、变基操作的目标基底分支 master) 的最近共同祖先 C2,然后对比当前分支相对于该祖先的历次提交,提取相应的修改并存为临时文件, 然后将当前分支指向目标基底 C3, 最后以此将之前另存为临时文件的修改依序应用。 (译注:写明了 commit id,以便理解,下同)
-
现在回到 master 分支,进行一次快进合并。
-
-
此时,C4' 指向的快照就和 the merge example 中 C5 指向的快照一模一样了。 这两种整合方法的最终结果没 有任何区别,但是变基使得提交历史更加整洁。 你在查看一个经过变基的分支的历史记录时会发现,尽管实际的开发工作是并行的, 但它们看上去就像是串行的一样,提交历史是一条直线没有分叉。
-
一般我们这样做的目的是为了确保在向远程分支推送时能保持提交历史的整洁——例如向某个其他人维护的项目贡献代码时。 在这种情况下,你首先在自己的分支里进行开发,当开发完成时你需要先将你的代码变基到origin/master 上,然后再向主项目提交修改。 这样的话,该项目的维护者就不再需要进行整合工作,只需要快进合并便可。
-
请注意,无论是通过变基,还是通过三方合并,整合的最终结果所指向的快照始终是一样的,只不过提交历史不同罢了。 变基是将一系列提交按照原有次序依次应用到另一分支上,而合并是把最终结果合在一起。
更有趣的变基例子
-
在对两个分支进行变基时,所生成的“重放”并不一定要在目标分支上应用,你也可以指定另外的一个分支进行应用。 就像 从一个主题分支里再分出一个主题分支的提交历史 中的例子那样。 你创建了一个主题分支server,为服务端添加了一些功能,提交了 C3 和 C4。 然后从 C3 上创建了主题分支 client,为客户端添加了一些功能,提交了 C8 和 C9。 最后,你回到 server 分支,又提交了 C10。
-
假设你希望将 client 中的修改合并到主分支并发布,但暂时并不想合并 server 中的修改, 因为它们还需要经过更全面的测试。这时,你就可以使用 **git rebase ** 命令的 --onto 选项, 选中在 client 分支里但不在server 分支里的修改(即 C8 和 C9),将它们在 master 分支上重放:
-
-
以上命令的意思是:“取出 client 分支,找出它从 server 分支分歧之后的补丁, 然后把这些补丁在master 分支上重放一遍,让 client 看起来像直接基于 master 修改一样”。这理解起来有一点复杂,不过效果非常酷。
-
现在可以快进合并 master 分支了。
-
-
接下来你决定将 server 分支中的修改也整合进来。 使用 **git rebase
** 命令可以直接将主题分支 (即本例中的 server)变基到目标分支(即 master)上。 这样做能省去你先切换到server 分支,再对其执行变基命令的多个步骤。 -
-
如图 将 server 中的修改变基到 master 上 所示,server 中的代码被“续”到了 master 后面。
-
然后就可以快进合并主分支 master 了:
-
-
至此,client 和 server 分支中的修改都已经整合到主分支里了, 你可以删除这两个分支,最终提交历史会变成图 最终的提交历史 中的样子:
-
变基的风险
-
奇妙的变基也并非完美无缺,要用它得遵守一条准则:
- 如果提交存在于你的仓库之外,而别人可能基于这些提交进行开发,那么不要执行变基.
-
如果你遵循这条金科玉律,就不会出差错。 否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。
-
变基操作的实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。 如果你已经将提交推送至某个仓库,而其他人也已经从该仓库拉取提交并进行了后续工作,此时,如果你用 git rebase 命令重新整理了提交并再次推送,你的同伴因此将不得不再次将他们手头的工作与你的提交进行整合,如果接下来你还要拉取并整合他们修改过的提交,事情就会变得一团糟。
-
让我们来看一个在公开的仓库上执行变基操作所带来的问题。 假设你从一个中央服务器克隆然后在它的基础上进行了一些开发。 你的提交历史如图所示:
-
然后,某人又向中央服务器提交了一些修改,其中还包括一次合并。 你抓取了这些在远程分支上的修改,并将其合并到你本地的开发分支,然后你的提交历史就会变成这样:
-
接下来,这个人又决定把合并操作回滚,改用变基;继而又用 **git push --force ** 命令覆盖了服务器上的提交历史。 之后你从服务器抓取更新,会发现多出来一些新的提交。
-
结果就是你们两人的处境都十分尴尬。 如果你执行 **git pull ** 命令,你将合并来自两条提交历史的内容,生成一个新的合并提交,最终仓库会如图所示:
-
此时如果你执行 **git log ** 命令,你会发现有两个提交的作者、日期、日志居然是一样的,这会令人感到混乱。 此外,如果你将这一堆又推送到服务器上,你实际上是将那些已经被变基抛弃的提交又找了回来,这会令人感到更加混乱。 很明显对方并不想在提交历史中看到 C4 和 C6,因为之前就是他把这两个提交通过变基丢弃的。
用变基解决变基
-
如果你 真的 遭遇了类似的处境,Git 还有一些高级魔法可以帮到你。 如果团队中的某人强制推送并覆盖了一些你所基于的提交,你需要做的就是检查你做了哪些修改,以及他们覆盖了哪些修改。
-
实际上,Git 除了对整个提交计算 SHA-1 校验和以外,也对本次提交所引入的修改计算了校验和——即 “patch id”。
-
如果你拉取被覆盖过的更新并将你手头的工作基于此进行变基的话,一般情况下 Git 都能成功分辨出哪些是你的修改,并把它们应用到新分支上。
-
举个例子,如果遇到前面提到的 有人推送了经过变基的提交,并丢弃了你的本地开发所基于的一些提交那种情境,如果我们不是执行合并,而是执行 **git rebase teamone/master **, Git 将会:
- 检查哪些提交是我们的分支上独有的(C2,C3,C4,C6,C7)
- 检查其中哪些提交不是合并操作的结果(C2,C3,C4)
- 检查哪些提交在对方覆盖更新时并没有被纳入目标分支(只有 C2 和 C3,因为 C4 其实就是 C4')
- 把查到的这些提交应用在 **teamone/master ** 上面
-
从而我们将得到与 你将相同的内容又合并了一次,生成了一个新的提交 中不同的结果,如图 在一个被变基然后强制推送的分支上再次执行变基 所示。
-
要想上述方案有效,还需要对方在变基时确保 C4' 和 C4 是几乎一样的。否则变基操作将无法识别,并新建另一个类似 C4 的补丁(而这个补丁很可能无法整洁的整合入历史,因为补丁中的修改已经存在于某个地方了)。
-
在本例中另一种简单的方法是使用**git pull --rebas 命令而不是直接 **git pull **。 又或者你可以自己手动完成这个过程,先 **git fetch **,再 **git rebase teamone/master。 **
-
如果你习惯使用 git pull ,同时又希望默认使用选项 --rebase,你可以执行这条语句 **git config--global pull.rebase true **来更改 **pull.rebase ** 的默认配置。
-
如果你只对不会离开你电脑的提交执行变基,那就不会有事。 如果你对已经推送过的提交执行变基,但别人没有基于它的提交,那么也不会有事。 如果你对已经推送至共用仓库的提交上执行变基命令,并因此丢失了一些别人的开发所基于的提交, 那你就有大麻烦了,你的同事也会因此鄙视你。
-
如果你或你的同事在某些情形下决意要这么做,请一定要通知每个人执行 **git pull --rebase **命令,这样尽管不能避免伤痛,但能有所缓解。
变基 VS 合并
- 至此,你已在实战中学习了变基和合并的用法,你一定会想问,到底哪种方式更好。 在回答这个问题之前,让我们退后一步,想讨论一下提交历史到底意味着什么。
- 有一种观点认为,仓库的提交历史即是记录实际发生过什么。 它是针对历史的文档,本身就有价值,不能乱改。 从这个角度看来,改变提交历史是一种亵渎,你使用 谎言 掩盖了实际发生过的事情。 如果由合并产生的提交历史是一团糟怎么办? 既然事实就是如此,那么这些痕迹就应该被保留下来,让后人能够查阅。
- 另一种观点则正好相反,他们认为提交历史是 项目过程中发生的事。 没人会出版一本书的第一版草稿,软件维护手册也是需要反复修订才能方便使用。 持这一观点的人会使用 **rebase 及 filter-branch ** 等工具来编写故事,怎么方便后来的读者就怎么写。
- 现在,让我们回到之前的问题上来,到底合并还是变基好?希望你能明白,这并没有一个简单的答案。 Git 是一个非常强大的工具,它允许你对提交历史做许多事情,但每个团队、每个项目对此的需求并不相同。 既然你已经分别学习了两者的用法,相信你能够根据实际情况作出明智的选择。
- 总的原则是,只对尚未推送或分享给别人的本地修改执行变基操作清理历史, 从不对已推送至别处的提交执行变基操作,这样,你才能享受到两种方式带来的便利。
4|04.Git命令合集
4|14.1 设置与配置
Git config
- Git 做的很多工作都有一种默认方式。 对于绝大多数工作而言,你可以改变 Git 的默认方式,或者根据你的偏好 来设置。 这些设置涵盖了所有的事,从告诉 Git 你的名字,到指定偏好的终端颜色,以及你使用的编辑器。 此 命令会从几个特定的配置文件中读取和写入配置值,以便你可以从全局或者针对特定的仓库来进行设置。
- 本书几乎所有的章节都用到了git config命令。
- 在 初次运行 Git 前的配置 一节中,在开始使用 Git 之前,我们用它来指定我们的名字,邮箱地址和编辑器偏好。
- 在 Git 别名 一节中我们展示了如何创建可以展开为长选项序列的短命令,以便你不用每次都输入它们。
- 在变基一节中,执行git pull命令时,使用此命令来将--rebase作为默认选项
- 在 凭证存储 一节中,我们使用它来为你的 HTTP 密码设置一个默认的存储区域。
- 在 关键字展开 一节中我们展示了如何设置在 Git 的内容添加和减少时使用的 smudge 过滤器 和 clean 过滤器。
- 最后,基本上 配置 Git 整个章节都是针对此命令的。
git help
- git help 命令用来显示任何命令的 Git 自带文档。 但是我们仅会在此附录中提到大部分最常用的命令,对于每 一个命令的完整的可选项及标志列表,你可以随时运行git help
命令来了解。
4|24.2 获取与创建项目
- 有几种方式获取一个 Git 仓库。 一种是从网络上或者其他地方拷贝一个现有的仓库,另一种就是在一个目录中创建一个新的仓库。
git init
- 你只需要简单地运行git init就可以将一个目录转变成一个Git仓库,这样你就可以开始对它进行版本管理 了。
- 我们一开始在 获取 Git 仓库 一节中介绍了如何创建一个新的仓库来开始工作。
- 在 远程分支 一节中我们简单的讨论了如何改变默认分支。
- 在 把裸仓库放到服务器上 一节中我们使用此命令来为一个服务器创建一个空的祼仓库。
- 最后,我们在 底层命令与上层命令 一节中介绍了此命令背后工作的原理的一些细节。
git clone
- git clone 实际上是一个封装了其他几个命令的命令。 它创建了一个新目录,切换到新的目录,然后 git init 来初始化一个空的 Git 仓库, 然后为你指定的 URL 添加一个(默认名称为 origin 的)远程仓库(git remote add),再针对远程仓库执行git fetch,最后通过git checkout将远程仓库的最新提交检出到 本地的工作目录。
- git clone命令在本书中多次用到,这里只列举几个有意思的地方。
- 在 克隆现有的仓库 一节中我们通过几个示例详细介绍了此命令。
- 在 在服务器上搭建 Git 一节中,我们使用了 --bare 选项来创建一个没有任何工作目录的 Git 仓库副本。
- 在 打包 一节中我们使用它来解包一个打包好的 Git 仓库。
4|34.3 快照基础
- 对于基本的暂存内容及提交到你的历史记录中的工作流,只有少数基本的命令。
git add
- git add 命令将内容从工作目录添加到暂存区(或称为索引(index)区),以备下次提交。 当 git commit 命令执行时,默认情况下它只会检查暂存区域,因此git add是用来确定下一次提交时快照的样子的。
- 这个命令对于 Git 来说特别重要,所以在本书中被无数次地提及和使用。 我们将快速地过一遍一些可以看到的独 特的用法。
git status
- git status 命令会显示工作区及暂存区域中不同状态的文件。 其中包含了已修改但未暂存,或已经暂存但没 有提交的文件。 在一般的显示形式中,它会给你一些如何在这些暂存区之间移动文件的提示。
- 首先,我们在 检查当前文件状态 一节中介绍了 status 的基本及简单的形式。 虽然我们在全书中都有用到它, 但是绝大部分的你能用git status做的事情都在这一章讲到了。
git diff
- 当需要查看任意两棵树的差异时,可以使用 git diff 命令。 此命令可以查看你工作环境与你的暂存区的差异 (git diff默认的做法),你暂存区域与你最后提交之间的差异(git diff --staged),或者比较两个 提交记录的差异(git diff master branchB)。
- 首先,我们在查看已暂存和未暂存的修改一章中研究了git diff的基本用法,在此节中我们展示了如何查看 哪些变化已经暂存了,哪些没有。
- 在 提交准则 一节中,我们在提交前使用 --check 选项来检查可能存在的空白字符问题。
- 在确定引入了哪些东西一节中,了解了使用git diff A...B语法来更有效地比较不同分支之间的差异。
- 在 高级合并 一节中我们使用 -b 选项来过滤掉空白字符的差异,及通过 --theirs、--ours 和 --base 选项来 比较不同暂存区冲突文件的差异。
- 最后,在 开始使用子模块 一节中,我们使用此命令合 --submodule 选项来有效地比较子模块的变化。
git difftool
- 当你不想使用内置的git diff命令时。git difftool可以用来简单地启动一个外部工具来为你展示两棵树 之间的差异。
- 我们只在 查看已暂存和未暂存的修改 一节中简单的提到了此命令。
git commit
- git commit命令将所有通过git add暂存的文件内容在数据库中创建一个持久的快照,然后将当前分支上的 分支指针移到其之上。
- git commit命令将所有通过git add暂存的文件内容在数据库中创建一个持久的快照,然后将当前分支上的 分支指针移到其之上。
- 在分支简介,我们探讨了git commit的更多细节,及工作原理。
git reset
-
git reset 命令主要用来根据你传递给动作的参数来执行撤销操作。 它可以移动 HEAD 指针并且可选的改变 index 或者暂存区,如果你使用 --hard 参数的话你甚至可以改变工作区。 如果错误地为这个命令附加后面的 参数,你可能会丢失你的工作,所以在使用前你要确定你已经完全理解了它。
-
首先,我们在取消暂存的文件一节中介绍了git reset简单高效的用法,用来对执行过git add命令的文件 取消暂存。
-
在 重置揭密 一节中我们详细介绍了此命令,几乎整节都在解释此命令。
-
在中断一次合并一节中,我们使用git reset --hard来取消一个合并,同时我们也使用了git merge
--abort命令,它是git reset的一个简单的封装。
git rm
- git rm 是 Git 用来从工作区,或者暂存区移除文件的命令。 在为下一次提交暂存一个移除操作上,它与 git add 有一点类似。
- 我们在移除文件一节中提到了git rm的一些细节,包括递归地移除文件,和使用--cached选项来只移除暂 存区域的文件但是保留工作区的文件。
- 在本书的移除对象一节中,介绍了git rm仅有的几种不同用法,如在执行git filter-branch中使用和 解释了 --ignore-unmatch 选项。 这对脚本来说很有用。
git mv
- git mv 命令是一个便利命令,用于移到一个文件并且在新文件上执行
git add
命令及在老文件上执行git rm
命令。 - 我们只是在 移动文件 一节中简单地提到了此命令。
git clean
- git clean 是一个用来从工作区中移除不想要的文件的命令。 可以是编译的临时文件或者合并冲突的文件。
- 在 清理工作目录 一节中我们介绍了你可能会使用 clean 命令的大量选项及场景。
4|44.4 分支与合并
git branch
- git branch 命令实际上是某种程度上的分支管理工具。 它可以列出你所有的分支、创建新分支、删除分支及 重命名分支。
- Git 分支 一节主要是为 branch 命令来设计的,它贯穿了整个章节。 首先,我们在 分支创建 一节中介绍了它, 然后我们在 分支管理 一节中介绍了它的其它大部分特性(列举及删除)。
- 在跟踪分支一节中,我们使用git branch -u选项来设置一个跟踪分支。
- 最后,我们在 Git 引用 一节中讲到了它在背后做一什么
git checkout
- git checkout命令用来切换分支,或者检出内容到工作目录。
- 我们是在分支切换一节中第一次认识了命令及git branch命令。
- 在跟踪分支 一节中我们了解了如何使用 --track 标志来开始跟踪分支。
- 在 检出冲突 一节中,我们用此命令和 --conflict=diff3 来重新介绍文件冲突。
- 在重置揭密一节中,我们进一步了解了其细节及与git reset的关系。
- 最后,我们在 HEAD 引用 一节中介绍了此命令的一些实现细节。
git merge
- git merge 工具用来合并一个或者多个分支到你已经检出的分支中。 然后它将当前分支指针移动到合并结果 上。
- 我们首先在 新建分支 一节中介绍了 git merge 命令。 虽然它在本书的各种地方都有用到,但是 merge 命令只 有几个变种,一般只是git merge
带上一个你想合并进来的一个分支名称。 - 我们在派生的公开项目的后面介绍了如何做一个squashed merge(指Git合并时将其当作一个新的提交而不 是记录你合并时的分支的历史记录。)
- 在 高级合并 一节中,我们介绍了合并的过程及命令,包含 -Xignore-space-change 命令及 --abort 选项 来中止一个有问题的提交。
- 在 签署提交 一节中我们学习了如何在合并前验证签名,如果你项目正在使用 GPG 签名的话。
git mergetool
- 当你在Git的合并中遇到问题时,可以使用git mergetool来启动一个外部的合并帮助工具。
- 我们在 遇到冲突时的分支合并 中快速介绍了一下它,然后在 外部的合并与比较工具 一节中介绍了如何实现你自己的外部合并工具的细节。
git log
-
git log命令用来展示一个项目的可达历史记录,从最近的提交快照起。默认情况下,它只显示你当前所在分 支的历史记录,但是可以显示不同的甚至多个头记录或分支以供遍历。 此命令通常也用来在提交记录级别显示 两个或多个分支之间的差异。
-
在 查看提交历史 一节中我们介绍了此命令,并深入做了研究。 研究了包括 -p 和 --stat 选项来了解每一个提交引入的变更,及使用
--pretty
和 --online 选项来查看简洁的历史记录。 -
在 分支创建 一节中我们使用它加 --decorate 选项来简单的可视化我们分支的指针所在,同时我们使用--graph 选项来查看分叉的历史记录是怎么样的。
-
在私有小型团队和提交区间章节中,我们介绍了在使用git log命令时用branchA..branchB的语法来查看一个分支相对于另一个分支, 哪一些提交是唯一的。 在 提交区间 一节中我们作了更多介绍
-
在 <_merge_log>> 和 三点 章节中,我们介绍了 branchA...branchB 格式和 --left-right 语法来查看哪 些仅其中一个分支。 在 合并日志 一节中我们还研究了如何使用 --merge 选项来帮助合并冲突调试,同样也使 用 --cc 选项来查看在你历史记录中的合并提交的冲突。
-
在 引用日志 一节中我们使用此工具和 -g 选项 而不是遍历分支来查看 Git 的 reflog。
-
在 搜索 一节中我们研究了
-S
及 -L 选项来进行来在代码的历史变更中进行相当优雅地搜索,如一个函数的历史。 -
在签署提交一节中,我们了解了如何使用--show-signature来为每一个提交的git log输出中,添加一
个判断是否已经合法的签名的一个验证。
git stash
- git stash命令用来临时地保存一些还没有提交的工作,以便在分支上不需要提交未完成工作就可以清理工作 目录。
git tag
- git tag 命令用来为代码历史记录中的某一个点指定一个永久的书签。 一般来说它用于发布相关事项。
- 我们在 打标签 一节中介绍了此命令及相关细节,并在 为发布打标签 一节实践了此命令。
- 我也在 签署工作 一节中介绍了如何使用 -s 标志创建一个 GPG 签名的标签,然后使用 -v 选项来验证。
4|54.5 项目分享与更新
git fetch
- git fetch命令与一个远程的仓库交互,并且将远程仓库中有但是在当前仓库的没有的所有信息拉取下来然后 存储在你本地数据库中。
- 我们开始在 从远程仓库中抓取与拉取 一节中介绍了此命令,然后我们在 远程分支 中看到了几个使用示例。
- 我们在 向一个项目贡献 一节中有几个示例中也都有使用此命令。
- 在 合并请求引用 我们用它来抓取一个在默认空间之外指定的引用,在 打包 中,我们了解了怎么从一个包中获取 内容。
- 在引用规范章节中我们设置了高度自定义的refspec以便git fetch可以做一些跟默认不同的事情。
git pull
- git pull命令基本上就是git fetch和git merge命令的组合体,Git从你指定的远程仓库中抓取内容, 然后马上尝试将其合并进你所在的分支中。
- 我们在 从远程仓库中抓取与拉取 一节中快速介绍了此命令,然后在 查看某个远程仓库 一节中了解了如果你运行 此命令的话,什么将会合并。
- 我们也在 用变基解决变基 一节中了解了如何使用此命令来来处理变基的难题。
- 在 检出冲突 一节中我们展示了使用此命令如何通过一个 URL 来一次性的拉取变更。
- 最后,我们在 签署提交 一节中我们快速的介绍了你可以使用 --verify-signatures 选项来验证你正在拉取 下来的经过 GPG 签名的提交。
git push
- git push命令用来与另一个仓库通信,计算你本地数据库与远程仓库的差异,然后将差异推送到另一个仓库 中。 它需要有另一个仓库的写权限,因此这通常是需要验证的。
- 我们开始在 推送到远程仓库 一节中介绍了 git push 命令。 在这一节中主要介绍了推送一个分支到远程仓库的 基本用法。 在 推送 一节中,我们深入了解了如何推送指定分支,在 跟踪分支 一节中我们了解了如何设置一个 默认的推送的跟踪分支。 在 删除远程分支 一节中我们使用 --delete 标志和 git push 命令来在删除一个在 服务器上的分支。
- 在向一个项目贡献一整节中,我们看到了几个使用git push在多个远程仓库分享分支中的工作的示例。
- 在 共享标签 一节中,我们知道了如何使用此命令加 --tags 选项来分享你打的标签。
- 在 发布子模块改动 一节中,我们使用 --recurse-submodules 选项来检查是否我们所有的子模块的工作都已 经在推送子项目之前已经推送出去了,当使用子模块时这真的很有帮助。
- 在 其它客户端钩子 中我们简单的提到了 pre-push 挂钩(hook),它是一个可以用来设置成在一个推送完成之 前运行的脚本,以检查推送是否被允许。
- 最后,在 引用规范推送 一节中,我们知道了使用完整的 refspec 来推送,而不是通常使用的简写形式。 这对我们精确的指定要分享出去的工作很有帮助。
git remote
-
git remote 命令是一个是你远程仓库记录的管理工具。 它允许你将一个长的 URL 保存成一个简写的句柄,例 如origin,这样你就可以不用每次都输入他们了。你可以有多个这样的句柄,git remote可以用来添加, 修改,及删除它们。
-
此命令在 远程仓库的使用 一节中做了详细的介绍,包括列举、添加、移除、重命名功能。
-
几乎在此书的后续章节中都有使用此命令,但是一般是以git remote add
这样的标准格 式。
git archive
- git archive命令用来创建项目一个指定快照的归档文件。
git submodule
- git submodule 命令用来管理一个仓库的其他外部仓库。 它可以被用在库或者其他类型的共享资源上。 submodule 命令有几个子命令, 如(add、update、sync 等等)用来管理这些资源。
4|64.6 检查与比较
git show
- git show命令可以以一种简单的人类可读的方式来显示一个Git对象。你一般使用此命令来显示一个标签或一 个提交的信息。
- 我们在 附注标签 一节中使用此命令来显示带注解标签的信息。
- 然后,我们在 选择修订版本 一节中,用了很多次来显示不同的版本选择将解析出来的提交。
- 我们使用git show做的最有意思的事情是在手动文件再合并一节中用来在合并冲突的多个暂存区域中提取指 定文件的内容。
git shortlog
- git shortlog 是一个用来归纳 git log 的输出的命令。 它可以接受很多与 git log 相同的选项,但是此命 令并不会列出所有的提交,而是展示一个根据作者分组的提交记录的概括性信息
- 我们在 制作提交简报 一节中展示了如何使用此命令来创建一个漂亮的 changelog 文件。
git describe
- git describe 命令用来接受任何可以解析成一个提交的东西,然后生成一个人类可读的字符串且不可变。 这 是一种获得一个提交的描述的方式,它跟一个提交的 SHA-1 值一样是无歧义,但是更具可读性。
4|74.7 调试
- Git 有一些命令可以用来帮你调试你代码中的问题。 包括找出是什么时候,是谁引入的变更。
Git bisect
- git bisect工具是一个非常有用的调试工具,它通过自动进行一个二分查找来找到哪一个特定的提交是导致 bug 或者问题的第一个提交。
git blame
- git blame 命令标注任何文件的行,指出文件的每一行的最后的变更的提交及谁是那一个提交的作者。 当你要 找那个人去询问关于这块特殊代码的信息时这会很有用。
git grep
- git grep命令可以帮助在源代码中,甚至是你项目的老版本中的任意文件中查找任何字符串或者正则表达式。
4|84.8 补丁
- Git 中的一些命令是以引入的变更即提交这样的概念为中心的,这样一系列的提交,就是一系列的补丁。 这些命 令以这样的方式来管理你的分支。
git cherry-pick
- git cherry-pick命令用来获得在单个提交中引入的变更,然后尝试将作为一个新的提交引入到你当前分支 上。 从一个分支单独一个或者两个提交而不是合并整个分支的所有变更是非常有用的。
git rebase
- git rebase 命令基本是是一个自动化的 cherry-pick 命令。 它计算出一系列的提交,然后再以它们在其他 地方以同样的顺序一个一个的 cherry-picks 出它们。
- 在 变基 一章中详细提到了此命令,包括与已经公开的分支的变基所涉及的协作问题。
- 在 替换 中我们在一个分离历史记录到两个单独的仓库的示例中实践了此命令,同时使用了 --onto 选项。
- 在 Rerere 一节中,我们研究了在变基时遇到的合并冲突的问题。
- 在 修改多个提交信息 一节中,我们也结合 -i 选项将其用于交互式的脚本模式。
git revert
- git revert 命令本质上就是一个逆向的 git cherry-pick 操作。 它将你提交中的变更的以完全相反的方式 的应用到一个新创建的提交中,本质上就是撤销或者倒转。
- 我们在 还原提交 一节中使用此命令来撤销一个合并提交。
4|94.9 邮件
- 很多 Git 项目,包括 Git 本身,基本是通过邮件列表来维护的。 从方便地生成邮件补丁到从一个邮箱中应用这些 补丁,Git 都有工具来让这些操作变得简单。
git apply
- git apply 命令应用一个通过 git diff 或者甚至使用 GNU diff 命令创建的补丁。 它跟补丁命令做了差不多 的工作,但还是有一些小小的差别。
git am
- git am 命令用来应用来自邮箱的补丁。特别是那些被 mbox 格式化过的。 这对于通过邮件接受补丁并将他们 轻松地应用到你的项目中很有用。
- 我们在 使用 am 命令应用补丁 命令中提到了它的用法及工作流,包括使用 --resolved、-i 及 -3 选项。
- 我们在电子邮件工作流钩子也提到了几条hooks,你可以用来辅助与git am相关工作流。
- 在 邮件通知 一节中我们也将用此命令来应用 格式化的 GitHub 的推送请求的变更。
git format-patch
- git format-patch命令用来以mbox的格式来生成一系列的补丁以便你可以发送到一个邮件列表中。
git imap-send
- git imap-send 将一个由 git format-patch 生成的邮箱上传至 IMAP 草稿文件夹。 我们在 通过邮件的公 开项目一节中见过一个通过使用git imap-send工具向一个项目发送补丁进行贡献的例子。
git send-email
- git send-mail命令用来通过邮件发送那些使用git format-patch生成的补丁。
git request-pull
- git request-pull 命令只是简单的用来生成一个可通过邮件发送给某个人的示例信息体。 如果你在公共服务 器上有一个分支,并且想让别人知道如何集成这些变更,而不用通过邮件发送补丁,你就可以执行此命令的输出 发送给这个你想拉取变更的人。
4|104.10 外部系统
- Git 有一些可以与其他的版本控制系统集成的命令。
git svn
- git svn 可以使 Git 作为一个客户端来与 Subversion 版本控制系统通信。 这意味着你可以使用 Git 来检出内 容,或者提交到 Subversion 服务器。
git fast-import
- 对于其他版本控制系统或者从其他任何的格式导入,你可以使用git fast-import快速地将其他格式映射到 Git 可以轻松记录的格式。
4|114.11 管理
- 如果你正在管理一个 Git 仓库,或者需要通过一个复杂的方法来修复某些东西,Git 提供了一些管理命令来帮助 你
git gc
- git gc 命令在你的仓库中执行 “garbage collection”,删除数据库中不需要的文件和将其他文件打包成一种 更有效的格式。
git fsck
- git fsck命令用来检查内部数据库的问题或者不一致性。
git reflog
-
git reflog命令分析你所有分支的头指针的日志来查找出你在重写历史上可能丢失的提交。
-
我们主要在引用日志一节中提到了此命令,并在展示了一般用法,及如何使用git log -g来通过git log
的输出来查看同样的信息。
git filter-branch
- git filter-branch命令用来根据某些规则来重写大量的提交记录,例如从任何地方删除文件,或者通过过 滤一个仓库中的一个单独的子目录以提取出一个项目。
- 在 从每一个提交中移除一个文件 一节中,我们解释了此命令,并探究了其他几个选项,例如 --commit -filter,--subdirectory-filter 及 --tree-filter 。
__EOF__

本文链接:https://www.cnblogs.com/gockk/p/16165523.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。您的鼓励是博主的最大动力!
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)