1. 版本控制类型

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

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

What is version control: centralized vs. DVCS
分散式版本控制

2. 术语

版本控制系统 / 源代码管理器
版本控制系统(简称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)
分支是从主开发流程中分支出来的新的开发流程。这种分支开发流程可以在不更改主流程的情况下继续延伸下去。

回到之前关于游戏保存点的示例,你可以将分支看做在游戏中设立保存点后,尝试一个有风险的招式。如果有风险的招式不奏效,则回到保存的位置。令分支非常强大的关键之处是你可以在一个分支上设定保存点,然后切换到另一个分支并继续设定保存点。

3. git设置

1
2
3
4
5
6
7
8
9
10
11
12
13
# 设置你的 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

Atom Editor 设置

git config --global core.editor "atom --wait"

3. git init

运行 git init 命令会初始化 Git 跟踪所有内容会用到的所有必要文件和目录。所有这些文件都存储在叫做 .git(隐藏目录)的目录下。这个.git 目录是一个库!Git 会将所有 commit 记录在这里,并跟踪所有内容!

.git 目录内容

  • 配置文件 - 存储了所有与项目有关的配置设置。
  • description 文件 - 此文件仅用于 GitWeb 程序,因此可以忽略
  • hooks 目录 - 我们会在此处放置客户端或服务器端脚本,以便用来连接到 Git 的不同生命周期事件
  • info 目录 - 包含全局排除文件
  • objects 目录 - 此目录将存储我们提交的所有 commit
  • refs 目录 - 此目录存储了指向 commit 的指针(通常是“分支”和“标签”)

Git 内部原理 - 底层命令和高层命令

自定义 Git - Git Hooks

在现有目录中初始化仓库
git init 文档
git init 教程

4. git clone

验证终端位置
提示:在克隆任何内容之前,确保命令行工具已定位于正确的目录下。克隆项目会新建一个目录,并将克隆的 Git 仓库放在其中。问题是无法创建嵌套的 Git 仓库。因此,确保终端的当前工作目录没有位于 Git 仓库中。如果当前工作目录没有在 shell 的提示符中显示,输入 pwd 输出工作目录。

git clone 命令用于创建一个与现有仓库完全相同的副本。

$ git clone <path-to-repository-to-clone>

该命令:

  • 会获取现有仓库的路径
  • 默认地将创建一个与被克隆的仓库名称相同的目录
  • 可以提供第二个参数,作为该目录的名称
  • 将在现有工作目录下创建一个新的仓库

克隆现有仓库
git 克隆文档
git 克隆教程

5. git status

git status 是了解 Git 的核心所在。它将告诉我们 Git 正在考虑什么,以及 Git 所看到的我们仓库的状态。

$ git status

证明此仓库尚无任何 commit,此处fatal为git程序正在退出

$ git log
fatal: your current branch 'master' does not have any commits yet

查看文件的状态
git status 文档
git status 教程

6. git log

git log 命令用于显示仓库中所有 commit 的信息。
默认情况下,该命令会显示仓库中每个 commit 的:SHA、作者、日期、消息

$ git log

在命令行上使用分页器了解日志内容:

  • 要向下滚动,按下
  1. j 一次向下移动一行
  2. d按照一半的屏幕幅面移动
  3. f 按照整个屏幕幅面移动
  • 要 向上滚动,按下
  1. k 或 ↑ 一次向上移动一行
  2. u 按照一半的屏幕幅面移动
  3. b 按照整个屏幕幅面移动
  • 按下 q 可以退出日志(返回普通的命令提示符)

ls命令将列出当前目录下的所有文件。ls -l会显示 long 格式的信息(-l 表示 long)。

只显示7位SHA和commit消息:

$ git log --oneline

显示 commit 中更改的文件以及添加或删除的行数:

$ git log --stat

显示对文件作出实际更改的选项。该选项是 –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-diff文档

7.git show

提供 commit 的 SHA 作为最后一个参数,例如:

$ git log -p fdf5493

通过提供 SHA,git log -p 命令将从这条 commit 开始!无需滚动并逐条查阅!注意,它还会显示在所提供的 SHA 之前提交的所有 commit 信息。

显示特定的一个 commit 的命令是 git show:

$ git show

运行上述示例命令将仅显示最近的 commit。通常,将 SHA 作为最后一个参数提供给命令:

$ git show fdf5493

默认情况下,git show 会显示:
commit、作者、日期、commit 消息、补丁信息

但是,git show 可以与我们了解过的大部分其他选项一起使用:

  • --stat 显示更改了多少文件,以及添加/删除的行数

  • -p--patch 显示默认补丁信息,但是如果使用了

  • --stat将不显示补丁信息,因此传入 -p 以再次添加该信息

  • -w 忽略空格变化

8. git add

git add 命令用于将文件从工作目录移到暂存区。

$ git add <file1> <file2> … <fileN>

Changes to be committed下方的帮助文本提示git rm --cached, 不会破坏任何属于你的文件,它只是从暂存区删掉了文件。

句点.指代当前目录,可以用来表示所有文件和目录(包括所有嵌套文件和目录

$ git add .

9. git commit

git commit 命令会取出暂存区的文件并保存到仓库中:

$ git commit

直接在命令行中使用 -m 选项传入信息:

$ git commit -m "Initial commit"

在上述示例中,文本”Initial commit”被作为提交说明信息。但是注意,不能为 commit 提供信息的描述(description),只能提供信息部分(message)。

将文本编辑器与 git 相关联 GitHub 帮助文档
使用入门 - 第一次设置 git git 图书

10. git diff

查看已经执行但是尚未 commit 的更改:

$ git diff

运行结果和git log -p一样

git diff文档

11. .gitignore

将某个文件保留在项目的目录结构中,确保它不会意外地提交到项目中。
可以使用名称特殊的文件 .gitignore(注意文件名开头的点,很重要!)。将此文件添加到项目中隐藏的 .git 目录。你只需列出希望 git ignore(忽略,不跟踪)的文件名,git 将忽略这些文件。

通配符glob
通配符允许你使用特殊的字符来表示某些格式/字符。在 .gitignore 文件中,你可以使用:

  • 空白行作为空格
  • # 将行标记为注释
  • * 与 0 个或多个字符匹配
  • ? 与 1 个字符匹配
  • [abc] 与 a、b 或 c 匹配
  • ** 与嵌套目录匹配 a/**/z 与以下项匹配
    a/z
    a/b/z
    a/b/c/z

因此如果所有 50 个图片都是 JPEG 图片,并且位于”samples”文件夹中,那么我们可以向 .gitignore 中添加以下行,使 git 忽略所有这 50 个图片。

samples/*.jpg

忽略文件 git 图书
gitignore git 文档
忽略文件 GitHub 文档
gitignore.io

12. git tag

与仓库的标签进行交互:

$ git tag -a v1.0

-a 选项告诉 git 创建一个带注释的标签。如果是git tag v1.0,那么它将创建一个轻量级标签。

应该始终使用带注释的标签,因为它们包含了大量的额外信息,例如:

  • 标签创建者
  • 标签创建日期
  • 标签消息

git log 的 –decorate 选项
--decorate 选项将显示默认视图隐藏起来的一些详情。
log 命令已改为自动启用 –decorate 选项。

删除 git 标签:

$ git tag -d v1.0

向以前的 commit 添加标签
运行 git tag -a v1.0 将为最近的 commit 添加标签。
提供要添加标签的 commit 的 SHA 即可向仓库中很久之前的 Commit 添加标签。

$ git tag -a v1.0 a87984

git 基础知识 - 添加标签 git 图书
git tag git 文档

13. git branch

与 git 的分支进行交互。

列出所有分支

$ git branch

创建分支,只需使用 git branch 并提供要创建的分支对应的名称。
例如:创建一个叫做”sidebar”的分支:

$ git branch sidebar

在分支之间进行切换,我们需要使用 git 的 checkout 命令。

$ git checkout sidebar

运行该命令将:

  • 从工作目录中删除 git 跟踪的所有文件和目录(git 跟踪的文件存储在仓库中,因此什么也不会丢失)
  • 转到仓库,并提取分支指向的 commit 所对应的所有文件和目录

因此此命令将删除 master 分支中的 commit 引用的所有文件。它会将这些文件替换为 sidebar 分支中的 commit 引用的文件。

判断活跃分支的最快速方式是查看 git branch 命令的输出结果。活跃分支名称旁边会显示一个星号。

删除给出的分支(这里是”sidebar”分支):

$ git branch -d sidebar

注意,无法删除当前所在的分支。因此要删除 sidebar 分支,你需要切换到 master 分支,或者创建并切换到新的分支。

强制删除,需要使用大写的 D 选项

$ git branch -D sidebar

git checkout 命令也可以创建一个新的分支
如果你添加 -b 选项,则能够用一个命令创建分支并切换到该分支。添加 master 让此 footer 分支的起点位置与 master 分支的一样。

$ git checkout -b newbranchname master

同时查看所有分支

$ git log --oneline --decorate --graph --all

--graph 选项将条目和行添加到输出的最左侧。显示了实际的分支。–all 选项会显示仓库中的所有分支。

git 分支 - 分支的新建与合并 git 文档
了解 git 分支
git 分支教程 Atlassian 博客

14. git merge

合并分支

$ git merge <other-branch>

将分支组合到一起称为合并。

git 可以自动将不同分支上的更改合并到一起。这种分支和合并功能正是 git 的强大之处!你可以在分支上做出小的或大的更改,然后使用 git 合并这些更改。

发生合并时,git 将:

  • 查看将合并的分支
  • 查看分支的历史记录并寻找两个分支的 commit 历史记录中都有的单个 commit
  • 将单个分支上更改的代码行合并到一起
  • 提交一个 commit 来记录合并操作

撤消合并

$ git reset --hard HEAD^

(确保包含 ^ 字符!它属于“相对 commit 引用”并表示“父 级 commit”)

快进合并

快进合并将使当前检出的分支向前移动,直到它指向与另一个分支(这里是 footer)指向的 commit 一样为止。

要合并 footer 分支,运行:

$ git merge footer

分支合并 git 图书
git-merge git 文档
git 合并 Atlassian 博客

15. 合并冲突

git 会跟踪文件中的代码行。如果完全相同的行在不同的文件中更改了,将产生合并冲突。

冲突指示符(例如 >>><<<

要解决文件中的冲突:

  1. 找到并删掉存在合并冲突指示符的所有行
  2. 决定保留哪些行
  3. 保存文件
  4. 暂存文件
  5. 提交 commit

遇到冲突时的分支合并 git 图书
冲突如何被显示 git 文档

16. commit更改、还原、重置

更改最近的 commit包含忘记包含的文件(或文件更改)

$ git commit --amend

如果你的工作目录没有内容(也就是仓库中没有任何未 commit 的更改),那么运行 git commit –amend 将使你能够重新提供 commit 消息。代码编辑器将打开,并显示原始 commit 消息。只需纠正拼错的单词或重新表述即可!然后保存文件并关闭编辑器,以便采用新的 commit 消息。

可以运行 git commit –amend 来更新最近的 commit,而不是创建新的 commit。

还原(revert) 具体的 commit

$ git revert <SHA-of-commit-to-revert>

此命令:将撤消目标 commit 所做出的更改,创建一个新的 commit 来记录这一更改。

git-revert git 文档
git revert Atlassian 教程

重置(清除)commit

$ git reset <reference-to-commit>

可以用来:

  • 将 HEAD 和当前分支指针移到目标 commit
  • 清除 commit
  • 将 commit 的更改移到暂存区
  • 取消暂存 commit 的更改

还原(revert) 会创建一个新的 commit,并还原或撤消之前的 commit。但是重置(reset)会清除 commit!

一定要谨慎使用 git 的重置功能。这是少数几个可以从仓库中清除 commit 的命令。如果某个 commit 不再存在于仓库中,它所包含的内容也会消失。

为了减轻你的压力,澄清下,git 会在完全清除任何内容之前,持续跟踪大约 30 天。要调用这些内容,你需要使用 git reflog 命令。请参阅以下链接以了解详情:
git-reflog
重写历史记录
reflog,你的安全屏障

备份分支
注意,使用 git reset 命令将清除当前分支上的 commit。因此,如果你想跟着操作接下来出现的所有重置操作,需要在当前 commit 上创建一个分支,以便用作备份。

在进行任何重置操作之前创建一个 backup 分支:

$ git branch backup

回到正常状况:

$ git checkout -- index.html
$ git merge backup

从工作目录中删除未 commit 的更改
将 backup 合并到 master(这将导致快进合并并使 master 向上移到和 backup 一样的点)

* 9ec05ca (HEAD -> master) Revert "Set page heading to "Quests & Crusades""
* db7e87a Set page heading to "Quests & Crusades"
* 796ddb0 Merge branch 'heading-update'

HEAD 指向 9ec05ca 上的 master。
运行 git reset --mixed HEAD^ 会把 commit 9ec05ca 中做出的更改移至工作目录中。
运行 git reset --soft HEAD^ 会把 commit 9ec05ca 中做出的更改直接移至暂存区。
运行 git reset --hard HEAD^ 将清除 commit 9ec05ca 中做出的更改。

来自 Git 文档的 git-reset
来自 Git Blog 的 Reset Demystified
来自 Git Book 的祖先引用

17. 相关 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 是被合并的分支。

我们来看一个示例,这样更好理解。这是我的 git log 当前的显示结果:

* 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))

创建一个仓库来记录你的计算机设置 https://dotfiles.github.io/

18. 向github上传代码

$ git init

$ git add .

$ git commit -m ""

$ git remote add origin https://github.com/仓库url地址

$ git pull --rebase origin master

$ git push -u origin master