Git-版本控制

(参考Udacity-侵删)

“版本控制”即“控制版本”!版本控制系统是帮助你控制(或管理)某个事物(通常是源代码)的不同版本。

版本控制系统信息

有很多版本控制系统可供我们选择。单凭这一点就证明版本控制非常重要。以下是三大最热门的版本控制系统:

版本控制系统模型包括两大主要类型:

  • 集中式模型 - 所有用户都连接到一个中央的主仓库(master repository)
  • 分布式模型 - 每个用户都在自己的计算机上拥有完整的仓库

深入研究

版本控制系统的主要目的是帮助你保留项目的详细历史记录,并且能够在不同的版本上进行工作。保留详细的项目历史记录很重要,因为这样可以看出一段时间内项目的进度。如果需要,你还可以回到项目的某个阶段,并恢复数据或文件。

一、术语

版本控制系统 / 源代码管理器

版本控制系统(简称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 用户名
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 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 与 git log --stat对比

git log -p/--patch

显示对文件作出实际更改的选项。该选项是 --patch,可以简写为 -p。【补丁信息】

$ git log -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 文件。

git status

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窗口)

  1. 进入Vim编辑器


    进入Vim编辑器
  2. insert信息

    insert信息

  3. 退出

    退出

回到终端:



终于提交了第一个 commit(注:只是提交到本地仓库)。

使用 -m 选项绕过编辑器

提示:如果你要编写的提交说明很简短,不想等打开代码编辑器后再输入信息,可以直接在命令行中使用 -m 选项传入信息:

<!--常用这条语句-->
$ git commit -m "Initial commit"

注意,要提交 commit,待提交的文件必须位于暂存区。要将文件从工作目录移到暂存区用 git add。

所以当你修改文件后就执行 git commit 则输出:"no changes added to commit"。这是因为你没有使用 git add 将文件从工作目录移到暂存区。

那么何时进行commit呢?

这种情况事自身情况而定,一般情况下是每写完一个功能提交一下,这样当一个更改有bug,你需要撤消该更改时,则不用同时撤消另一个更改。

深入研究
编写良好的提交说明:

参考:

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 图书

六、标签、分支 和 合并

标签 git tag

git tag 为特定提交添加标签,标签是提交的额外标记,可以指示有用信息。

$ git tag -a v1.0

上述命令将打开代码编辑器,并等待你为标签输入信息。


打开自配置代码编译器(VS Code)

注意:在上述命令 (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 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

活跃分支(当前分支)
  1. 当前提示符上就有当前分支(如绿色的单词)
  2. 通过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 无法完全自动地进行合并。合并失败时,就称为合并冲突。

如果出现合并冲突,git 将尝试尽可能合并多的内容,然后将留下特殊选项(例如 >>> 和 <<<),告诉你(没错,告诉作为程序员的你!)需要从何处手动修复。

什么导致了合并冲突

正如你所知道的,git 会跟踪文件中的代码行。如果完全相同的行在不同的文件中更改了,将产生合并冲突。例如,你在两个分支上都更改了标题,因此 git 根本不知道你要保留哪个标题。它肯定不会随机选择一个标题!

小知识:在最近修改 master 分支的 commit 前面创建一个 heading-update 分支。

创建一个不是从 master 分支上分叉的分支。如果我们在从 master 分支上分叉的分支上做出更改,那么该更改将在此更改前面,git 将直接使用该更改,而不是使用我们刚刚在 master 上做出的更改。因此我们需要将该分支“放在过去”。

我们创建一个位于最近 commit 之前的 commit 上的分支。使用 git log 获取上一个 commit 的 SHA,并在该 commit 上创建一个分支。

  1. 首先获取SHA值
    git log --oneline --decorate --graph --all
  2. 创建分支
    git branch heading-update 25cdbb6
  3. 切换到分支
    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来记录这一更改。

深入研究
重置 reset

git reset 按顺序删除提交,潜在危险:它将会从仓库中删除项目。

重置(reset) 似乎和 还原(revert) 相似,但它们实际上差别很大。还原会创建一个新的 commit,并还原或撤消之前的 commit。但是重置会清除 commit!

好消息是:git 会在完全清除任何内容之前,持续跟踪大约 30 天。要调用这些内容,你需要使用 git reflog 命令。请参阅以下链接以了解详情:

深度研究
以下内容了解即可:
相关 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 -侵删



最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,271评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,725评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,252评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,634评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,549评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,985评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,471评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,128评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,257评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,233评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,235评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,940评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,528评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,623评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,858评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,245评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,790评论 2 339

推荐阅读更多精彩内容