(参考Udacity-侵删)
“版本控制”即“控制版本”!版本控制系统是帮助你控制(或管理)某个事物(通常是源代码)的不同版本。
- 深入研究
版本控制系统信息
有很多版本控制系统可供我们选择。单凭这一点就证明版本控制非常重要。以下是三大最热门的版本控制系统:
版本控制系统模型包括两大主要类型:
- 集中式模型 - 所有用户都连接到一个中央的主仓库(master repository)
- 分布式模型 - 每个用户都在自己的计算机上拥有完整的仓库
深入研究
- 集中式与分布式版本控制系统的比较(Atlassian 博文 - 英)
- 分布式版本控制:维基百科链接 | 百度百科链接
版本控制系统的主要目的是帮助你保留项目的详细历史记录,并且能够在不同的版本上进行工作。保留详细的项目历史记录很重要,因为这样可以看出一段时间内项目的进度。如果需要,你还可以回到项目的某个阶段,并恢复数据或文件。
一、术语
版本控制系统 / 源代码管理器
版本控制系统(简称VCS)是一个管理源代码不同版本的工具。源代码管理器(简称SCM)是版本控制系统的另一个名称。
Git 是一个 SCM(因此也是 VCS!)。Git 网站的 URL 是 https://git-scm.com/ (注意它的域名中直接包含“SCM”!)。
提交(Commit)
Git 将数据看做微型文件系统的一组快照。每次 commit(在 Git 中保持项目状态),它都对文件当时的状况拍照,并存储对该快照的引用。你可以将其看做游戏中的保存点,它会保存项目的文件和关于文件的所有信息。
你在 Git 中的所有操作都是帮助你进行 commit,因此 commit 是 Git 中的基本单位。
仓库(Repository / repo)
仓库是一个包含项目内容以及几个文件(在 Mac OS X 上默认地处于隐藏状态)的目录,用来与 Git 进行通信。仓库可以存储在本地,或作为远程副本存储在其他计算机上。仓库是由 commit 构成的。
工作目录 / 工作区(Working Directory)
工作目录是你在计算机的文件系统中看到的文件。当你在代码编辑器中打开项目文件时,你是在工作目录中处理文件。
与这些文件形成对比的是保持在仓库中(在 commit 中!)的文件。
在使用 Git 时,工作目录与命令行工具的 current working directory(当前工作目录)不一样,后者是 shell 当前正在查看的目录。
检出(Checkout)
检出是指将仓库中的内容复制到工作目录下。
暂存区 / 暂存索引 / 索引(Staging Area / Staging Index / Index)
Git 目录下的一个文件,存储的是即将进入下个commit 内容的信息。可以将暂存区看做准备工作台,Git 将在此区域获取下个commit。暂存索引中的文件是准备添加到仓库中的文件。
SHA
SHA 是每个 commit 的 ID 编号。以下是 commit 的 SHA 示例:e2adf8ae3e2e4ed40add75cc44cf9d0a869afeb6。
它是一个长 40 个字符的字符串(由 0–9 和 a–f 组成),并根据 Git 中的文件或目录结构的内容计算得出。SHA 的全称是"Secure Hash Algorithm"(安全哈希算法)。
分支(Branch)
分支是从主开发流程中分支出来的新的开发流程。这种分支开发流程可以在不更改主流程的情况下继续延伸下去。
二、安装 与 配置
自行下载安装
初次配置 Git
在命令行工具中运行以下每行,确保所有选项都已被配置好。
# 设置你的 Git 用户名
git config --global user.name "<Your-Full-Name>"
# 设置你的 Git 邮箱
git config --global user.email "<your-email-address>"
# 确保 Git 输出内容带有颜色标记
git config --global color.ui auto
# 对比显示原始状态
git config --global merge.conflictstyle diff3
git config --list
Git 与代码编辑器
以下是三个最热门的代码编辑器。如果你使用的是其他编辑器,则在 Google (国内请用梯子)中搜索“修改 Git 默认编辑器为 X 编辑器”(将 X替换为你的代码编辑器的名称)。
Atom Editor 设置
git config --global core.editor "atom --wait"
Sublime Text 设置
git config --global core.editor "'/Applications/Sublime Text 2.app/Contents/SharedSupport/bin/subl' -n -w"
VSCode 设置
git config --global core.editor "code --wait"
三、创建 Git 仓库
git init
在对 Git 仓库进行 commit 或执行任何其他操作之前,需要一个实际存在的仓库。要使用 Git 新建一个仓库,我们将使用 git init 命令。
简单来说:git init 可以在计算机上从头创建全新仓库。
init 子命令是"initialize"(初始化)的简称,这个命令很有用,因为它将进行所有仓库初始设置。
运行 git init 命令会初始化 Git 跟踪所有内容会用到的所有必要文件和目录。所有这些文件都存储在叫做 .git(注意开头有个 .它是一个隐藏目录)的目录下。这个 .git 目录是一个库!Git 会将所有 commit 记录在这里,并跟踪所有内容!
警告:请勿直接修改.git目录下的任何文件。这是仓库的核心。如果你更改了文件名或文件内容,Git 可能就无法跟踪你保存在仓库中的文件,你可能会丢失很多内容!可以查看这些文件,但是请勿编辑或删除这些文件。
.git 目录内容
(对git的使用并不重要,因此不用记住任何东西)
-
config 文件 -存储了所有与项目有关的配置设置。
Git 会查看 Git 目录下你当前所使用仓库对应的配置文件(.git/config)中的配置值。这些值仅适用于当前仓库。
例如,假设你将 Git 全局配置为使用你的个人电子邮箱。如果你想针对某个项目使用你的工作邮箱,则此项更改会被添加到该文件中。
description 文件 - 此文件仅用于 GitWeb 程序,因此可以忽略
hooks 目录 - 我们会在此处放置客户端或服务器端脚本,以便用来连接到 Git 的不同生命周期事件(hooks 目录可以用来连接到 Git 工作流的不同部分或事件)
info 目录 - 包含全局排除文件
objects 目录 - 此目录将存储我们提交的所有 commit
refs 目录 - 此目录存储了指向 commit 的指针(通常是“分支”和“标签”)
总结: 运行此命令可以创建隐藏 .git 目录。此 .git 目录是仓库的核心/存储中心。它存储了所有的配置文件和目录,以及所有的 commit。
深入研究
- Git 内部原理 - 底层命令和高层命令 : 英 | 中
- 自定义 Git - Git Hooks - 英 | 中
实用链接
git clone
git clone 可以将一个现有仓库从其他地方克隆或复制到本地计算机。
输入命令 git clone,然后输入你要克隆的 Git 仓库的路径。(一般个人项目或开源项目在github或码云上,自行注册并创建仓库)
git clone [仓库url]
//clone并重命名
git clone [仓库url] [new_name]
总结:
该命令:
- 会获取现有仓库的路径
- 默认地将创建一个与被克隆的仓库名称相同的目录
- 可以提供第二个参数,作为该目录的名称
- 将在现有工作目录下创建一个新的仓库
实用链接
git status
git status 查看仓库状态。
git status 是了解 Git 的核心所在。它将告诉我们 Git 正在考虑什么,以及 Git 所看到的我们仓库的状态。当你第一次使用 Git 时,你应该一直都要使用 git status 命令!说真的,你应该习惯于运行任何其他命令之后,都运行下该命令。这样可以帮助你了解 Git 的工作原理,并避免你对文件 / 仓库状态做出不正确的推论。
git status 命令将显示很多信息,具体取决于你的文件状态、工作目录和仓库。但是你不需要过于关心这些内容…
总结: 该命令:
- 告诉我们已在工作目录中被创建但 Git 尚未开始跟踪的新文件
- Git 正在跟踪的已修改文件
- 其他信息
实用链接
四、查看仓库的历史纪录
git log 显示有关现有提交的信息。
默认情况下,该命令会显示仓库中每个 commit 的:
- SHA
- 作者
- 日期
- 消息
但是有些信息我们不关心,所以可以使用:
<!--只显示简略版SHA(7位数)和commit消息-->
$ git log --oneline
<!--【简约信息】-->
git 使用命令行分页器 less 浏览所有信息。以下是 less 的重要快捷键:
- 要向下滚动,按下
- j 或 ↓ 一次向下移动一行
- d 按照一半的屏幕幅面移动
- f 按照整个屏幕幅面移动
- 要按页向下滚动,使用空格键或 Page Down 按钮
- 要向上滚动,按上
- k 或 ↑ 一次向上移动一行
- u 按照一半的屏幕幅面移动
- b 按照整个屏幕幅面移动
- 要按页向下滚动,使用 b 或 Page Up 按钮
- 按下 q 可以退出日志(返回普通的命令提示符)
git log --stat
显示 commit 中更改的文件以及添加或删除的行数。stat(stat 是“【统计信息】 statistics”的简称)
$ git log --stat
git log -p/--patch
显示对文件作出实际更改的选项。该选项是 --patch,可以简写为 -p。【补丁信息】
$ git log -p
带注释的 git log -p 输出:
- 🔵 - 正在显示的文件
- 🔶 - 文件第一版的哈希值和第二版的哈希值
- 通常不重要,因此可以忽略
- ❤️ - 文件的旧版本和当前版本
- 🔍 - 添加的行所在的位置以及添加了多少行
- -15,83 表示旧版本(用 - 表示)从第 15 行开始,显示了 83 行
- +15,85 表示当前版本(用 + 表示)从第 15 行开始,现在变成了 85 行...这 85 行显示在下方
- ✏️ - 在 commit 中实际进行的更改
- 用红色标示并以减号 (-) 开头的行是位于文件原始版本中,但是被 commit 删除的行
- 用绿色标示并以加号 (+) 开头的行是 commit 新加的行
链接:
使用 -p 生成补丁(英)
以上命令可以组合使用:
- git log -p --stat 将在补丁信息上方显示统计信息。实际上,顺序并不重要。
- git log --stat -p 也会在补丁信息上方显示统计信息。
- git log --stat --oneline 简约及统计信息。
- git log -p -w 将显示补丁信息,但是不会突出显示仅更改了空格的行。
另外,如果知道SHA值,可以直接查找:
$ git log [SHA]
$ git log -p fdf5493
...
git show
git show 显示有关给定提交的信息,但需要向其提供提交ID也被称为SHA,该命令就会显示有关这一提交的信息。若仅运行 git show 则只显示最近的commit。
$ git show
$ git show fdf5493
作用:
git show 命令将仅显示一个 commit。因此,你看不到任何其他 commit,
git show 命令的输出和 git log -p 命令的完全一样。因此默认情况下,git show 会显示:
- commit
- 作者
- 日期
- commit 消息
- 补丁信息
但是,git show 可以与我们了解过的大部分其他选项一起使用:
- --stat - 显示更改了多少文件,以及添加/删除的行数
- -p 或 --patch - 显示默认补丁信息,但是如果使用了 --stat,将不显示补丁信息,因此传入 -p 以再次添加该信息
- -w - 忽略空格变化
五、向仓库添加commit
git add
将文件从工作目录添加到暂存区中。
暂存文件
在终端上运行以下命令,使用 git add 将 index.html 添加到暂存区:
$ git add index.html
注意: 这里仅添加了 index.html 文件。稍后我们将添加 CSS 和 JavaScript 文件。
Changes to be committed
输出结果中现在出现了全新的区域:"Changes to be committed"区域!这一新的"Changes to be committed"区域显示了位于暂存区的文件!目前只显示了 index.html 文件,因此暂存区只有这个文件。继续这一思路,如果我们现在提交 commit,则只有 index.html 文件会被提交。
提示:你注意到"Changes to be committed"下方的帮助文本了吗?它提示 (use "git rm --cached <file>..." to unstage),也就是当你不小心运行了 git add 并提供了错误文件,它会提示你应该怎么操作。
顺便提下,git rm --cached 与 shell 的 rm 命令不同。git rm --cached 不会破坏任何属于你的文件,它只是从暂存区删掉了文件。
此外,帮助文本中出现了"unstage"(撤消暂存)字眼。将文件从工作目录移到暂存区叫做"staging"(暂存)。如果已移动文件,则叫做"staged"(已暂存)。从暂存区将文件移回工作目录将"unstage"(撤消暂存)。如果你阅读的文档中提示“stage the following files”,则表明你应该使用 git add 命令。
暂存剩余的文件
index.html 文件已暂存。我们再暂存另外两个文件。现在我们可以运行以下命令:
$ git add css/app.css js/app.js
但是要输入的内容好多啊。我们可以使用一个特殊的命令行字符:
句点 .
句点指代当前目录,可以用来表示所有文件和目录(包括所有嵌套文件和目录!)。
$ git add css/app.css js/app.js
# 等同于
$ git add .
唯一要注意的是,你可能会不小心包含多余的文件。现在,我们希望同时暂存 css/app.css 和 js/app.js,因此运行该命令没问题。现在假设你向 img 目录添加了一些图片,但是暂时不想暂存这些图片。运行 git add . 将暂存这些图片。如果你暂存了不想暂存的文件,git status 会告诉你撤消暂存需要用到的命令。
暂存剩余的文件
$ git add .
小结
git add 命令用于将文件从工作目录移到暂存区。
$ git add <file1> <file2> … <fileN>
此命令:
- 可接受多个文件名(用空格分隔)
- 此外,可以使用句点 . 来代替文件列表,告诉 git 添加当前目录至暂存区(以及所有嵌套文件)
- 其实也可以用 git add --all
提交 Commit
git commit
git commit 将文件从暂存区取出并保存到本地仓库区,也就是你实际将要提交的地方。
尽量不要用此语句,运行这条命令将会打开你在初始配置时的代码编辑器(如未配置,则默认打开Vim编辑器,Vim 很受 Unix 或 Linux 系统用户的欢迎,但是对新用户来说,并不太好用。如何退出 Vim )。
配置自己的编译器:
$ git config --global core.editor <your-editor's-config-went-here> <!--Using Notepad++ as your editor--> $ git config --global core.editor "'C:/Program Files (x86)/Notepad++/notepad++.exe' -multiInst -notabbar -nosession -noPlugin"
假如配置了自己的编译器,当你快速切回终端,会看到终端冻结了,并等待你在弹出的代码编辑器完成编辑。不用担心。当我们向代码编辑器添加必要的内容,并最终关闭代码编辑器窗口后,终端将不再冻结,并回到正常状况。
代码编辑器 Commit 消息解释说明
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
# On branch master
#
# Initial commit
#
# Changes to be committed:
# new file: css/app.css
# new file: index.html
# new file: js/app.js
#
第一段精确地告诉了我们需要执行的操作 - 我们需要为该 commit 提供一条消息。此外 ,任何以字符 # 开头的行将被忽略。在后面还提示:这将是初始 commit。最后,给出了将提交 commit 的文件列表。
因为这是存储库的第一个 commit,我们将使用 commit 消息 "Initial commit"。文本 "Initial commit" 并不特殊,只是第一个 commit 的常用消息。如果你想使用其他消息,完全可以!
在代码编辑器的第一行输出 commit 消息:(随便)如:first initial ---这是你提交的文件修改的信息
完成提交
(若是配置的自定义的编辑器)现在保存文件并关闭编辑器窗口(只关闭面板/标签页还不够,你还需要关闭 git commit 命令打开的代码编辑器窗口)。
(若是Vim编辑器)wq 是退出。
(注:以下是我自己的git bash窗口)
-
进入Vim编辑器
-
insert信息
-
退出
回到终端:
终于提交了第一个 commit(注:只是提交到本地仓库)。
使用 -m 选项绕过编辑器
提示:如果你要编写的提交说明很简短,不想等打开代码编辑器后再输入信息,可以直接在命令行中使用 -m 选项传入信息:
<!--常用这条语句-->
$ git commit -m "Initial commit"
注意,要提交 commit,待提交的文件必须位于暂存区。要将文件从工作目录移到暂存区用 git add。
所以当你修改文件后就执行 git commit 则输出:"no changes added to commit"。这是因为你没有使用 git add 将文件从工作目录移到暂存区。
那么何时进行commit呢?
这种情况事自身情况而定,一般情况下是每写完一个功能提交一下,这样当一个更改有bug,你需要撤消该更改时,则不用同时撤消另一个更改。
深入研究
- 将文本编辑器与 git 相关联 - 英
- 起步 - 初次运行 Git 前的配置 (git book):英 | 中
编写良好的提交说明:
参考:
git diff
git diff 显示文件两个版本之间的差异,输出与git log -p 输出一样。"显示尚未 commit 的更改"。
<!--git diff 命令可以用来查看已被加入但是尚未提交的更改。-->
$ git diff
git log -p 其实就是在后台使用了 git diff
让git忽略某些文件
创建 .gitignore 文件,并添加到项目跟目录。你只需列出希望 git ignore(忽略,不跟踪)的文件名,git 将忽略这些文件。
通配符允许你使用特殊的字符来表示某些格式/字符。在 .gitignore 文件中,你可以使用:
- 空白行作为空格
- # - 将行标记为注释
- * - 与 0 个或多个字符匹配(如:*.jpg将忽略所有后缀为jpg的文件)
- ? - 与 1 个字符匹配(如:git 忽略"be?rs”,则将忽略bears、beers,但是beavers不能忽略)
- [abc] - 与 a、b 或 c 匹配
- ** - 与嵌套目录匹配 - a/**/z 与以下项匹配
- a/z
- a/b/z
- a/b/c/z
六、标签、分支 和 合并
标签 git tag
git tag 为特定提交添加标签,标签是提交的额外标记,可以指示有用信息。
$ git tag -a v1.0
上述命令将打开代码编辑器,并等待你为标签输入信息。
注意:在上述命令 (git tag -a v1.0) 中,使用了 -a 选项。该选项告诉 git 创建一个带注释的标签。如果你没有提供该选项(即 git tag v1.0),那么它将创建一个轻量级标签。
建议使用带注释的标签,因为它们包含了大量的额外信息,例如:
- 标签创建者
- 标签创建日期
- 标签消息
因此,你应该始终使用带注释的标签。
验证标签
保存并退出编辑器后,命令行上什么也不会显示。那么如何知道已经向项目中添加了标签呢?只需输入 git tag,命令行会显示仓库中的所有标签。
这时利用git log 将看不到添加的标签。所以需要用到 git log --decorate 。--decorate 选项将显示默认视图隐藏起来的一些详情。
在 2.13 版 git 中,log 命令已改为自动启用 --decorate 选项。这意味着,你不需要在命令中包含 --decorate 选项,因为它已经自动包含了!因此下面的命令输出结果完全一样:
$ git log --decorate $ git log
输出结果显示的 tag: v1.0 了吗?这就是标签!标签与 commit 相绑定。因此,该标签与 commit 的 SHA 位于同一行。
删除标签
如果将标签消息中的某个字打错了,或标签名称打错了(输入 v0.1,而不是 v1.0),如何修正这个错误?最简单的方法是删除这个标签并重新创建。
可以通过输入 -d 选项 (表示 delete 删除!)加上标签名称来删除 git 标签:
$ git tag -d v1.0
向以前的 commit 添加标签
运行 git tag -a v1.0 将为最近的 commit 添加标签。但是如果你想向仓库中很久之前的 Commit 添加标签呢?
只需提供要添加标签的 commit 的 SHA 即可!
$ git tag -a v1.0 a87984
深入研究
- 添加标签:英 | 中 git 图书
- git tag - 英 git文档
分支 git branch
git branch 创建分支,用于并行开发项目的不同功能。而不会对哪些提交属于哪个功能感到困惑。
注:gti branch [dev] [SHA] 在某个SHA值处创建一个分支dev
$ git branch
//1.列出仓库中的所有分支名称
//2.创建新的分支 git branch [name]
//3.删除分支 git branch -d [name]
创建分支
//创建一个叫做"sidebar"的分支
$ git branch sidebar
//补充:创建分支并切换到这个分支
$ git checkout -b dev
切换分支
git checkout 可以在不同的分支和标签之间进行切换。
注意,在进行 commit 时,该 commit 将添加到当前分支上。
虽然我们创建了新的 sidebar 分支,但是并没有在这个分支上。如果我们现在进行 commit 的话,该 commit 将添加到 master 分支,而不是 sidebar 分支。所以要在分支之间进行切换,我们需要使用以下命令。
$ git checkout sidebar
运行该命令将:
- 从工作目录中删除 git 跟踪的所有文件和目录
- (git 跟踪的文件存储在仓库中,因此什么也不会丢失)
- 转到仓库,并提取分支指向的 commit 所对应的所有文件和目录
查看具体分支信息:git log --oneline --decorate
活跃分支(当前分支)
- 当前提示符上就有当前分支(如绿色的单词)
- 通过git branch查看,前面有 * 号的即为当前分支
删除分支
分支用来进行开发或对项目进行修正,不会影响到项目(因为更改是在分支上进行的)。在分支上做出更改后,你可以将该分支组合到 master 分支上(这种“分支组合过程”叫做“合并”(merge)。
合并了分支的更改后,你可能不再需要该分支了。如果你想删除分支,可以使用 -d 选项。
//删除"sidebar"分支
$ git branch -d sidebar
注意,无法删除当前所在的分支。因此要删除 sidebar 分支,你需要切换到 master 分支,或者创建并切换到新的分支。
补充:如果某个分支上有任何其他分支上都没有包含的 commit(也就是这个 commit 是要被删除的分支独有的),git 不会删除该分支。如果你创建了 sidebar 分支,向其添加了 commit,然后尝试使用 git branch -d sidebar 删除该分支,git 不会让你删除该分支,因为你无法删除当前所在的分支。如果你切换到 master 分支并尝试删除 sidebar 分支,git 也不会让你删除,因为 sidebar 分支上的新 commit 会丢失!要强制删除,你需要使用大写的 D 选项 - git branch -D sidebar。
同时查看所有分支
我们在 git log 输出结果中看不到其他分支,触发切换到某个分支。如果能在 git log 输出结果中看到所有分支,是不是很棒?
我们将使用新的 --graph 和 --all 选项:
$ git log --oneline --decorate --graph --all
--graph 选项将条目和行添加到输出的最左侧。显示了实际的分支。--all 选项会显示仓库中的所有分支。
总结:
// 列出所有分支
$ git branch
// 创建新的"dev"分支
$ git branch dev
// 重命名当前分支
$ git branch -m [new_name]
// 切换到"dev"分支
$ git checkout dev
// 创建并切换到"side"分支
$ git checkout -b side
// 删除"side"分支
$ git branch -d side
// 强制删除"side"分支
$ git branch -D side
// 创建新的分支 并切换,且与master分支同节点
$ git checkout -b footer master
// 查看log信息 包含标签
$ git log --oneline --decorate
// 查看所有log信息 包含标签 及所有分支
$ git log --oneline --decorate --graph --all
深入研究
合并 git merge
git merge 将不同分支上的更改自动合并在一起。
当你要合并分支时,务必知道当前位于哪个分支上。注意,合并分支会提交 commit。如在mater分支则合并后将把更改合并到master上,如在dev分支上则合并后将更改合并到了dev分支。
如果你在错误的分支上进行了合并,可以使用以下命令撤消合并:
git reset --hard HEAD^
(确保包含 ^ 字符!它属于“相对 commit 引用”并表示“父 级 commit”。)
合并指令
$ git merge <name-of-branch-to-merge-in>
发生合并时,git 将:
- 查看将合并的分支
- 查看分支的历史记录并寻找两个分支的 commit 历史记录中都有的单个 commit
- 将单个分支上更改的代码行合并到一起
- 提交一个 commit 来记录合并操作
注:分支名是自定义的,以下所说的分支都是自己命名的。
快进合并
在我们的项目中,我们检出了 master 分支,我希望它拥有 footer 分支上的更改。用语言描述的话就是“我想要合并 footer 分支。”。注意表述“合并…”;在进行合并时,另一个分支上的更改将出现在当前检出的分支上。
我再强调下,当我们合并时,我们将其他分支合并到当前(检出的)分支上。我们不是将两个分支合并到一个新的分支上。也不是将当前分支合并到其他分支上。
因为 footer 直接在 master 前面,因此这种合并最简单。将 footer 合并到 master 中将导致快进合并(Fast-forward merge)。快进合并将使当前检出的分支向前移动,直到它指向与另一个分支(这里是 footer)指向的 commit 一样为止。
要合并 footer 分支,运行:
$ git merge footer
普通合并
要合并 sidebar 分支,确保你位于 master 分支上,并运行:
$ git merge sidebar
因为合并的是两个完全不一样的分支,因此将提交 commit。在进行 commit 时,需要提供 commit 消息。因为这是合并 commit,因此已经提供了默认消息。你也可以更改消息,但通常都会直接使用默认的合并 commit 消息。因此当你 "的代码编辑器打开" 并包含该消息时,直接关闭编辑器以确认使用该 commit 消息。
小节:
HEAD 指针所指向的分支将具有合并 commit。
$ git merge <other-branch>
合并有以下两种类型:
- 快进合并 – 要合并的分支必须位于检出分支前面。检出分支的指针将向前移动,指向另一分支所指向的同一 commit。
- 普通类型的合并
- 两个完全不同的分支被合并
- 创建一个合并 commit
深入研究
- 分支合并:英 | 中 git 图书
- git-merge git 文档 (英)
- git 合并 Atlassian 博客 (英)
合并冲突
大部分情况下,git 将能够成功地合并分支。但是,有时候 git 无法完全自动地进行合并。合并失败时,就称为合并冲突。
如果出现合并冲突,git 将尝试尽可能合并多的内容,然后将留下特殊选项(例如 >>> 和 <<<),告诉你(没错,告诉作为程序员的你!)需要从何处手动修复。
什么导致了合并冲突
正如你所知道的,git 会跟踪文件中的代码行。如果完全相同的行在不同的文件中更改了,将产生合并冲突。例如,你在两个分支上都更改了标题,因此 git 根本不知道你要保留哪个标题。它肯定不会随机选择一个标题!
小知识:在最近修改 master 分支的 commit 前面创建一个 heading-update 分支。
创建一个不是从 master 分支上分叉的分支。如果我们在从 master 分支上分叉的分支上做出更改,那么该更改将在此更改前面,git 将直接使用该更改,而不是使用我们刚刚在 master 上做出的更改。因此我们需要将该分支“放在过去”。
我们创建一个位于最近 commit 之前的 commit 上的分支。使用 git log 获取上一个 commit 的 SHA,并在该 commit 上创建一个分支。
- 首先获取SHA值
git log --oneline --decorate --graph --all- 创建分支
git branch heading-update 25cdbb6- 切换到分支
git checkout heading-update
在创建 heading-update 分支后,我的 git log 输出结果如下所示:
git log --oneline --decorate --graph --all
修改并提交:
合并后,将发生冲突:
进入代码进行手动合并即可。
合并冲突指示符解释
- <<<<<<< HEAD 此行下方的所有内容(直到下个指示符)显示了当前分支上的行 或者 原始内容
- ======= 表示原始行内容的结束位置,之后的所有行(直到下个指示符)是被合并的当前分支上的行的内容,可以说 被修改的内容
- >>>>>>> heading-update 是要被合并的分支(此例中是 heading-update 分支)上的行结束指示符
注意在删除代码解决冲突时(提示符也要删除),自己想保留哪些代码,然后保存文件,暂存文件(add),提交commit
深入研究
七、 撤销与更改
更改最后一个 commit
git commit --amend 可以更改最近的提交
- 编辑文件
- 保存文件(编辑器一般会自动保存Ctrl+S)
- 暂存文件(git add .)
- 运行 git commit --amend
当然你也可以执行新的 commit 即git commit,但是这样就会出现两个 commit 执行完全相同的任务
还原 revert
git revert [SHA] 撤销在该提交中做出的更改,同时在该提交中添加的行将被删除。
运行 git revert [SHA](随即弹出代码编辑器,以便编辑/确认提供的 commit 消息).输出结果显示了被还原的commit 中提交的说明,注意它还创建了一个新的commit来记录这一更改。
深入研究
- git-revert git 文档 (英)
- git revert Atlassian 教程 (英)
重置 reset
git reset 按顺序删除提交,潜在危险:它将会从仓库中删除项目。
重置(reset) 似乎和 还原(revert) 相似,但它们实际上差别很大。还原会创建一个新的 commit,并还原或撤消之前的 commit。但是重置会清除 commit!
好消息是:git 会在完全清除任何内容之前,持续跟踪大约 30 天。要调用这些内容,你需要使用 git reflog 命令。请参阅以下链接以了解详情:
深度研究
- 来自 Git 文档的 git-reset (英)
- 来自 Git Blog 的 Reset Demystified (英)
- 来自 Git Book 的 祖先引用 英 | 中
以下内容了解即可:
相关 commit 引用
有时候你可能需要引用相对于另一个 commit 的 commit。例如,有时候你需要告诉 git 调用当前 commit 的前一个 commit,或者是前两个 commit。我们可以使用特殊的“祖先引用”字符来告诉 git 这些相对引用。这些字符为:
- ^ – 表示父 commit
- ~ – 表示第一个父 commit
我们可以通过以下方式引用之前的 commit:
- 父 commit – 以下内容表示当前 commit 的父 commit
- HEAD^
- HEAD~
- HEAD~1
- 祖父 commit – 以下内容表示当前 commit 的祖父 commit
- HEAD^^
- HEAD~2
- 曾祖父 commit – 以下内容表示当前 commit 的曾祖父 commit
- HEAD^^^
- HEAD~3
^ 和 ~ 的区别主要体现在通过合并而创建的 commit 中。合并 commit 具有两个父级。对于合并 commit,^ 引用用来表示第一个父 commit,而 ^2 表示第二个父 commit。第一个父 commit 是当你运行 git merge 时所处的分支,而第二个父 commit 是被合并的分支。
如:
* 9ec05ca (HEAD -> master) Revert "Set page heading to "Quests & Crusades""
* db7e87a Set page heading to "Quests & Crusades"
* 796ddb0 Merge branch 'heading-update'
|\
| * 4c9749e (heading-update) Set page heading to "Crusade"
* | 0c5975a Set page heading to "Quest"
|/
* 1a56a81 Merge branch 'sidebar'
|\
| * f69811c (sidebar) Update sidebar with favorite movie
| * e6c65a6 Add new sidebar content
* | e014d91 (footer) Add links to social media
* | 209752a Improve site heading for SEO
* | 3772ab1 Set background color for page
|/
* 5bfe5e7 Add starting HTML structure
* 6fa5f34 Add .gitignore file
* a879849 Add header to blog
* 94de470 Initial commit
我们来看看如何引用一些之前的 commit。因为 HEAD 指向 9ec05ca commit:
- HEAD^ 是 db7e87a commit
- HEAD~1 同样是 db7e87a commit
- HEAD^^ 是 796ddb0 commit
- HEAD~2 同样是 796ddb0 commit
- HEAD^^^ 是 0c5975a commit
- HEAD~3 同样是 0c5975a commit
- HEAD^^^2 是 4c9749e commit(这是曾祖父的 (HEAD^^) 第二个父 commit (^2))
那么 HEAD~6 引用的是哪个commit?
答案是:209752a
(提示利用最左边的*号)
那么 HEAD~4^2 引用的是哪个commit?
答:f69811c
(HEAD~4引用的是当前分支的第四个父commit,然后^2告诉我们他是合并commit的第二个父commit(被合并的那个commit!))
git reset 命令
$ git reset <reference-to-commit>
git reset 的选项
- git reset --mixed HEAD~1 (默认项=git reset HEAD~1)
- 将HEAD移到前一个提交,当前提交所作的更改将会存留于工作区,这时在暂存文件(add)并提交(commit)将会获得相同的提交内容,只是SHA将改变,因为提交的时间戳不同。
- git reset --soft HEAD~1
- 将HEAD移到前一个提交,当前提交所作的更改将会存留于暂存区,这时只需重新提交(commit)将会获得相同的提交,SHA改变。
- git reset --hard HEAD~1
- 将HEAD移到前一个提交,当前提交所作的更改将会删除。
其实我们在进行任何重置操作之前,可以在最近的 commit 上创建一个 backup 分支,因此如果出现错误,可以返回这些 commit:
git branch backup
再次声明:参考 Udacity -侵删