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 | # 设置你的 Git 用户名 |
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 init 文档
git init 教程
4. git clone
验证终端位置
提示:在克隆任何内容之前,确保命令行工具已定位于正确的目录下。克隆项目会新建一个目录,并将克隆的 Git 仓库放在其中。问题是无法创建嵌套的 Git 仓库。因此,确保终端的当前工作目录没有位于 Git 仓库中。如果当前工作目录没有在 shell 的提示符中显示,输入 pwd 输出工作目录。
git clone 命令用于创建一个与现有仓库完全相同的副本。
$ git clone <path-to-repository-to-clone>
该命令:
- 会获取现有仓库的路径
- 默认地将创建一个与被克隆的仓库名称相同的目录
- 可以提供第二个参数,作为该目录的名称
- 将在现有工作目录下创建一个新的仓库
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
在命令行上使用分页器了解日志内容:
- 要向下滚动,按下
j
或↓
一次向下移动一行d
按照一半的屏幕幅面移动f
按照整个屏幕幅面移动
- 要 向上滚动,按下
- k 或 ↑ 一次向上移动一行
- u 按照一半的屏幕幅面移动
- b 按照整个屏幕幅面移动
- 按下 q 可以退出日志(返回普通的命令提示符)
ls
命令将列出当前目录下的所有文件。ls -l
会显示 long 格式的信息(-l 表示 long)。
只显示7位SHA和commit消息:
$ git log --oneline
显示 commit 中更改的文件以及添加或删除的行数:
$ git log --stat
显示对文件作出实际更改的选项。该选项是 –patch,可以简写为 -p:
$ git log -p
🔵 - 正在显示的文件
🔶 - 文件第一版的哈希值和第二版的哈希值
通常不重要,因此可以忽略
❤️ - 文件的旧版本和当前版本
🔍 - 添加的行所在的位置以及添加了多少行
- -15,83 表示旧版本(用 - 表示)从第 15 行开始,文件有 83 行
- +15,85 表示当前版本(用 + 表示)从第 15 行开始,现在有 85 行…这 85 行显示在下方
✏️ - 在 commit 中实际进行的更改
- 用红色标示并以减号 (-) 开头的行是位于文件原始版本中,但是被 commit 删除的行
- 用绿色标示并以加号 (+) 开头的行是 commit 新加的行
统计信息显示在补丁上方
git log -p --stat
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
一样
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 会跟踪文件中的代码行。如果完全相同的行在不同的文件中更改了,将产生合并冲突。
冲突指示符(例如 >>>
和 <<<
)
要解决文件中的冲突:
- 找到并删掉存在合并冲突指示符的所有行
- 决定保留哪些行
- 保存文件
- 暂存文件
- 提交 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