抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

0 Git 的基本认知

Git 是现代软件版本管理系统,软件项目(包括开源与商业)依赖 Git 将进行版本管理。

Git 采用分散式架构 DVCS(Distributed Version Control System)。所有参与项目的开发者都拥有各自代码的完全拷贝

1 工作流

本地仓库由三棵树组成:工作目录(Working Dictionary) + 缓存区(Stage/Index) + 提交历史(History)

Git 本地仓库工作流
Git 本地仓库工作流

基本工作流模式为:编辑 → 缓存 → 提交

Working Dictionary → Index

git add --all           # 缓存区更新

Index → History

git commit -m 注释说明 # 提交到本地的最终结果

本地仓库 → 远端仓库

git push origin 分支名称
非克隆情况下需要手动连接到远端服务器
git remote add origin 远端服务器名称

origin 为远端服务器 colone 时使用默认别名,可以自定义。为保持一致主服务器一般都使用 origin。

1.1 Git 三区

项目的基本工作流由 git addgit commit 组成,基本模式为:编辑、缓存、提交。

首先,在工作目录下编辑文件。当需要备份项目状态时,使用 git add 缓存更改。当该缓存的副本已经就绪,需要进行一步版本更新或者记录时,使用 git commit 将其提交到项目历史。

使用 git reset 命令来撤销提交或者被缓存的快照。

最终,使用 git push 命令将提交到项目历史的最新版本发送到远端仓库,使其他项目成员也能看到这些更改。

image

工作目录(Working Dictionary)

工作目录(Working Dictionary)是开发者对具体代码进行编辑的地方,即所有的开发行为都在这个区域进行。该区显示在计算机文件夹中。

在提交历史区的 HEAD 指针指向的文件会显示在工作目录。

缓存区(Stage/Index)

缓存区(Stage/Index)在工作目录的上一层,在工作目录和提交历史区之间。经过缓存区的文件在 Git 中被追踪(tracked),这些文件可以进行版本控制。

缓存区相当于另外两个区的一个缓冲,工作区每次修改都可以执行 add 命令上传缓存,缓存区会保留上一次提交之后的所有合并,形成一个“快照”。

缓存区最终将合并的“快照”提交到提交历史区。“快照”保持了提交的原子性,有效避免了过多的提交浪费提交历史区的空间,也便于找到 bug 和执行最小代价撤销。

缓存区、缓冲区、暂存区、索引均为该区域别名,英文为 Stage 或 Index.

提交历史区(History)

提交历史区(History)是最终保存提交的地方,该区记录所有的提交和提交之间的分支关系。

提交具有版本号(b325a...)标签(v1.1.3)等标识,Git 会根据标识找到具体的提交。

在提交历史区有两种指针分支指针HEAD 指针

分支指针和分支名相同,指向该分支最顶端的最新提交。 HEAD 指针是 Git 的工作指针,该指针所在的位置会显示在工作目录当中,以供开发者查看或编辑。该指针所在的分支是 Git 工作的当前分支,一般也是未修改时显示在工作目录的提交版本。

1.2 仓库初始化

git init

该命令在本地创建一个新的 Git 仓库,或将已存在的文件夹转换成 Git 仓库。

cd 工作目录
git init

命令在当前目录下创建一个 .git 文件,该文件夹包含 Git 的必须元数据,默认隐藏。当前目录为 Git 的根目录。

git init <仓库名> 创建新仓库
git init 仓库名

创建一个文件夹为指定名称,并设为 Git 仓库。

git init --bare <仓库名> 创建裸仓库
git init --bare 仓库名

创建一个没有工作目录的裸仓库 目录名称 ,共享仓库应该如此创建。

共享仓库即中央仓库,其初始应该是一个裸仓库。向非裸仓库推送分支可能覆盖已有的代码变动,因此用 --bare 标记的仓库是储存设施,而非开发环境。对于 Git 工作流来说,中央仓库是裸仓库,开发者的本地仓库是非裸仓库。

使用命令在远端新建中央仓库

在远端创建仓库后,使用 git clone 拷贝到本地进行连接更为方便,因此 git init 常用于创建中央仓库。

ssh <user>@<host>   # SSH 连入中央仓库服务器
cd path/            # 定位到某个目录
git init --bare my-project.git  # --bare 标记创建中央存储仓库

开发者会将 my-project.git 克隆到本地的开发环境中。

实际上,Github 网页端交互式手动创建新仓库更为简单。

git clone

git clone 命令用于拷贝整个远端 Git 仓库,并且自动创建一个别名为 origin 的远程连接,指向被克隆的远端仓库。

# 工作目录中

git clone git@github.com:Lyrikp/cursor-LikeyArrow.git       # SSH 形式
git clone https://github.com/Lyrikp/cursor-LikeyArrow.git   # HTTPS 形式

该命令直接将远端仓库 cursor-LikeyArrow 克隆到本地的工作目录中,完成从远端到本地的初始同步。

git clone <远端仓库> <本地仓库> 克隆到指定仓库
git clone 远端仓库名 本地目录名

将远端仓库克隆到本地机器上的 本地目录名 目录。给出指定的形式不需要 cd 到工作目录,不过还是建议使用 cd 到工作目录,使用默认的形式。

远程到本地的工作流例子

需求:用SSH用户名 Lyrikp 连接到远端仓库 github.com:Lyrikp,获取远端服务器上的中央仓库副本,在本地工作区 CursorWorkPlace 中工作。

cd CursorWorkPlace
git clone git@github.com:Lyrikp/cursor-LikeyArrow.git
# 开始工作

git config

git config 命令用于定义本地用户的所有配置(全局或独立仓库均可),如用户信息、仓库行为等。

git config user.什么 <什么>
git config user.name 用户名             # 当前仓库提交的作者姓名
git config --global user.name 用户名    # 全局默认提交的作者姓名
git config --global user.email 邮箱地址  # 全局默认提交的作者邮箱

除此之外,git config 命令还可以定义 Git 本身的命令配置,如增加命令的别名。

git config 参数.参数 参数
git config --global alias.别名 命令原名 # 给某 命令原名 定义 别名
git config --global alias.co checkout # 给 checkout 赋别名 co

也可以打开全局配置文件来手动编辑。

git config --global --edit

该命令打开一个纯文本文件,其中记录了 Git 的全局配置,可以手动修改保存。因此,git config 命令可以视为一个方便的命令行接口,通常使用 --global 标记在一个设备上配置一次 Git,之后的工作中不用再更改。

Git 的配置项有 3 种,优先级依次降低:

  • /.git/config 某仓库的个性化配置
  • ~/.gitconfig 设备用户配置,--global 标记的设置存放在这里
  • $(prefix)/etc/gitconfig 系统层面设置

其配置项文件结构如下:

git 配置文件示例
git 配置文件示例

1.3 git add → 缓存区

git add 命令将工作目录的更改添加到缓存区,不会更改仓库以及仓库的提交历史,该步骤相给提交进入提交历史区之前做一个缓冲。

git add 全部缓存

不考虑单个细节缓存全部工作目录的所有内容是最快的缓存方式,也最为常用。Git 提供了 3 种全部缓存的方式。

未缓存过的文件称为 untrack 文件,即“未跟踪文件”。新文件(new)和被修改文件(modified)均属于 untrack,被删除文件(deleted)除外。

git add --all   # 缓存所有变更,有过滤

git add .       # 缓存所有文件,不删除,有过滤

git add *       # 缓存所有文件,不删除,不过滤
git add --all 缓存区与工作目录一致
git add --all
git add -A # --all 缩写为 -A

全部一致
将所有的本地更改进行缓存,同时删除工作目录不存在的文件。

会根据 .gitignore 文件进行忽略。

git add . 有忽略不删除
git add .

只加改不删除
将所有的本地更改都进行缓存,但在工作目录删除的文件不会在缓存区删除。

会根据 .gitignore 文件进行忽略。

git add * 不忽略不删除
git add *

全加不减
添加所有文件,包括被忽略的文件,且不删除不存在的文件。

git add --update 更新已有文件
git add --update
git add -u # --update 缩写为 -u

只更删已有文件
添加 deleted 和 modified,不包括 new.

git add 细节缓存

git add 命令可以指定缓存的文件或目录,从而再次划分工作细节,方便撤销更改。

git add 文件 # 缓存该文件的更改

git add 目录 # 缓存该目录下的所有更改

另外也可以进行手动选择提交的细节。

git add -p
git add -p

交互式缓存,可以将某文件的某一处更改就加入到缓存。Git 会显示一系列更改并等待用户的进一步输入命令。

y 缓存某一处更改,记 yes.

n 忽略某一处更改,记 no.

s 将某一处分割为更小的几份,记 separate.

e 手动编辑某一处更改,记 edit.

q 退出编辑。

1.4 git commit → 提交历史区

git commit 将缓存区中的文件快照进入提交历史区,形成一个提交。commit 命令总是将缓存区的快照提交到 Git 的工作分支,大多数情况下都带有一个 -m 标记来附带一个注释。

注释是对这个提交的简要描述,整齐简洁的提交要求开发者为每一个提交编写注释,在查看项目历史的时候才方便知道每一个提交都做了些什么。

Gitmoji 规范注释

Gitmoji 是 Github 的一个开源项目,用 :表情代码: 将 emoji 添加在 Github 的注释当中。

该项目也规定了一个的注释规范:emoji 功能(范围): 注释详情

例如 🎨 规范(blog): 优化文件结构

git commit -m "注释"
git commit --amend 重新提交

使用 --amend 标记,Git 会用新提交覆盖掉上一个提交,是修改小 bug 或重新编写注释的快速方法。

git commit --amend -m "注释" # 该提交会覆盖掉上一个提交

1.5 检出历史版本

git checkout 的作用是查看提交的项目历史版本的具体文件,即将历史版本同步到工作区中。

若检出的指定项目为分支,则直接切换到工作分支;若检出制定项目具体到文件名,等同于回滚文件的历史。

git checkout 版本号         # 工作区变为 版本号版本

git checkout 版本号 文件名   # 工作区 文件名文件 变为 版本号版本

git checkout 分支名         # 工作区切换到 分支名分支

checkout 原理

git checkout 的本质是修改 HEAD 指针,该指针永远指向当前 Git 的工作区,大部分情况下也会显示到工作目录中。

git checkout <提交号> 查看之前版本的提交

在切换版本之前,需要先确定提交号(提交ID),使用 git log 命令:

git log --oneline # 只需要版本号,用短形式检索
目的是回溯到 776d17e “去掉like-link背景光”
目的是回溯到 776d17e “去掉like-link背景光”
git checkout 776d17e

工作目录显示 776b17e 提交的状态,此时可以进行自由查看和编辑,如果不提交重新切回分支顶端的话,将不会影响最新的项目状态。

为了继续开发,需要回到项目原来的最新状态:

git checkout master # 用最新的版本号也可以
git checkout <版本号> <文件名> 查看指定版本的文件

git checkout 的检出方式可以更为细腻,只检出某个版本号的某个文件,而不用修改其他文件。

git checkout 版本号 文件名

具体到文件名的检出会被视为一次“修改”,Git 会认为这个文件是被更改过的,等待被添加进缓存。检出到 HEAD 指向的最新版本就可以回复到原来的最新版:

git checkout HEAD 文件名 # 恢复文件版本

1.6 版本回溯

检出文件名

git checkout 版本号 文件名

精确到文件名的 checkout 命令会确实修改文件内容,使 Git 认为其为“需要提交的更改”。因此可以直接用这个命令回滚某个确切的文件。

git revert 撤销提交

git revert 命令撤销了一个提交,并将此更改视为一个新提交。在项目历史中依次提交了 a、b、c 三个版本,最新的版本为 c,使用 git revert 后将新增一个 d,其内容与 b 相同。

# 以下三种方式的效果相同
# 均新建一个和其祖父内容一致的提交

git revert

git revert HEAD

git revert c的id
git revert HEAD 变化
git revert HEAD 变化

git revert 的原理是**反做(undo)**某次提交的动作。因此,在撤销某提交时,只会修改当时与父提交不同的文件,此后提交产生的新更改不受影响。

git revert b版本号
revert 撤销以前版本
revert 撤销以前版本

该命令只撤销了 b 版本所做的操作:b 版本新增文件 2.txt,因此删除 2.txt,并创建新提交 d 版本。其中,3.txt 的新增并不发生在 b 版本,不受影响。

撤销(revert)应用在某个历史版本操作需要被整个移除时。例如,当追踪到一个 bug,发现它是由某个提交造成,于是撤销到这个提交的所有操作并创建新提交,其他的文件将不受影响。

git reset 相比,git revert 不会移除除了撤销的提交外的其他提交,也不会更改项目历史(只是添加了一个新的版本)。而 git reset 会将整个历史和全部文件都回溯到某个版本。

git reset 重置

git reset 会重设整个工作区或缓存区,其产生“破坏性修改”,无法进行 undo,是少数的可能会造成工作丢失的命令之一。

命令通常被用来移除提交重置缓存/工作目录。但无论是哪种情况,它都应该只在本地修改永远都不应该重设和其他开发者共享的快照

reset 的原理是同时移动 HEAD 指针和分支指针,默认同步缓存区,添加 --hard 标记同时同步工作目录,--soft 标记都不同步。

git reset 只应用于本地,不要随便去重设已经共享或者其他开发者的项目历史。

git reset 重设缓存区
git reset       # 重设整个缓存区,与最新提交相同
git reset 文件名 # 从缓存区移除特定文件,取消该文件的缓存
git reset 版本号 # 重设缓存区为该版本,分支末端设为该版本,之后的项目历史快照会被移除
git reset --hard 重设缓存区和工作目录
git reset --hard        # 重设缓存区和工作目录,与最新提交相同
git reset --hard 版本号  # 分支末端设为版本号提交,缓存区和工作目录重设到该提交,之后的所有提交被清除

简单来说,带有 --hard 标记的 git reset 会同时重设缓存区与工作目录,对于作了大死之后的重新来过非常方便。

使用 git reset 取消文件缓存

场景:在本地工作目录编辑了两份文件 1.py2.py,在全部缓存了的时候,发现 2.py 的修改不用提交,需要撤销 2.py 的缓存再提交。

git add .       # 缓存区同时存在 1.py 2.py
git reset 2.py  # 缓存区只有 1.py
git commit -m "1.py修改" # 提交了 1.py 的修改
image
作大死后重设工作目录

场景:编写了一个 作死.py 并提交到了项目历史 1.2 当中。后续继续修改,提交了 1.3 版本。发现在将来的 2.0 版本中均不需要这些更改,需要返回 1.1 版本再进行工作。

git add 作死.py
git commit -m "v1.2"

继续修改,提交了 1.3 版本

git add --all
git commit -m "v1.3"

为了更新 2.0 版本,决定从 1.1 版本重新开始修改

git reset --hard HEAD^2 # 工作区和缓存区回退 2 个版本,从 1.3 回到 1.1
image

2 整理提交

Git 中进行版本控制的中心是提交历史区,要尽可能保持整齐和简洁。因此,在进行正式提交之前,最好先查看工作分支的状态并进行整理。

2.1 查看状态

查看状态主要有 3 个命令 :

  • git status 对比工作目录和缓存区
  • git log 所有提交日志
  • git reflog 分支提交日志

status 工作/缓存区对比

git status 命令会显示工作目录和缓存区之间的差异,以查看哪些更改已经被缓存,哪些还未被缓存,哪些还是 untrack 文件。

这个命令和项目历史是完全无关的,项目历史的查看使用 git log 命令。

git status
status 示例
status 示例

从上至下分别为已缓存、未缓存、未追踪。

未追踪文件(untrack)指产生更改的或新增的文件。通常编译之后的二进制文件(.pyc .obj .exe 等)也不需要进行追踪,可以在 .gitignore 忽略规则文件中写入在该文件中写入 *.pyc *.obj *.exe 来忽略它们。

log 提交日志

git log 命令显示已经提交到提交历史的快照。可以用不同的命令进行筛选和搜索等操作。

git log # 默认格式完整输出项目历史
log日志示例
log日志示例

命令显示完整的项目历史,用 空格 翻到下一页,用 q 退出。

git log 标记
git log -n 3        # 只显示最近的 3 个提交
git log --oneline   # 每个提交显示压缩到一行
git log --stat      # 被更改的文件及增删行数
git log -p          # 显示全部差异,最详细视图
git log --author="作者" # 搜索指定作者的提交
git log --grep="信息"   # 搜索匹配信息的提交
git log 始..末          # 搜索始末之间的提交,可以为提交 ID、分支、HEAD或任何一种引用
git log 文件            # 搜索包含特定文件的提交
git log --graph     # 绘制辅助线

另外,可以使用 .. 句法对分支进行比较

分支日志比较
git log --oneline master..some-feature

对 master 主分支和 some-feature 分支进行比较,且在一行内显示。

git reflog 分支日志

Git 用引用日志的机制来记录分支顶端的更新,它允许回到不被任何分支或者标签引用的更改。在重写历史之后,引用日志包含了分支旧状态的信息,课可以根据需要回到这些状态。

reflog 更侧重于显示某个分支下的提交记录,而 log 命令更着重显示提交信息。

git reflog # 显示本地仓库的分支日志
分支日志
分支日志
--relative-date 标签显示相对日期
git reflog --relative-date # 用相对日期显示引用日志
分支日志(相对日期)
分支日志(相对日期)

日志显示的格式为 提交ID HEAD@{倒数第几个}: 操作: 注释

采用 --relative-date 标签则以“相对最新的日期”形式显示提交,可以直观地查看当时编辑时的时间。

在有了该日志提供的安全网下,使用 git reset --hard 对时间线进行修改是较为常用的操作。

引用日志只对提交到本地仓库的更改有效,且只记录移动操作。

2.2 整理提交历史

提交历史区通常由 Git 自动维护,在进行手动整理的时候 Git 会警告这些命令可能会丢失内容。

commit –amend 重新提交

git commit --amend 命令用来修复最新提交。它的作用是将缓存的修改和之前的提交合并到一起,即修改(替换)上一次提交,而不是创建一个新的提交。

# 3.py 已再次被编辑
git add .
git commit --amend
amend 标记修改而非新增提交
amend 标记修改而非新增提交

简单来说,--amend 标记给开发者一次重新提交的机会,用于修复小问题或者搞错了注释的小意外。

不要修复公共提交

rebase 变基

git rebase 将分支移到一个新的基提交,用于保持一个线性的项目历史。

指的是两个分支的公共祖先提交。基提交有多个子提交,是产生分叉的地方。主分支不谈基,非主分支一定存在一个基提交。

变基命令变的是当前工作分支的基,指定变到的目的位置。

git rebase ID/分支/tag/HEAD^ # 将当前分支并到给出分支后面
git rebase 变到的 被变的
rebase图示
rebase图示

图示中有两个分支 masterfeaturefeature 的基底为 1.1 ,需要将这个基底改为 master 最新所在的 1.2

git checkout feature    # 工作分支切换到 feature 上
git rebase master       # 将工作分支变基至 master 上

# 上述命令等价于
git rebase master feature

因此,git rebase 可以很好地让提交从多分支变成一条单线,从而获得简洁的线性项目历史。

git rebase 是将上游更改合并进本地仓库的通常方法。即云端仓库有更新时,为了追上更新且放弃本地的旧版本,直接用 git rebase 把更新拉到自己本地的历史版本后面。该命令会将更改建立在其他的进展之上

不要 rebase 公共历史

git rebase + git merge 保持线性历史

场景:主分支为 master ,开发了新的功能分支 new-feature. 在新功能发开过程中,发现项目中有一个漏洞,于是开发了快速修复分支 hotfix. 将修复分支合并回主分支,再整合 new-feature 分支。

git checkout -b new-feature master # 基于 master 创建 new-feature 分支,并切换至该分支

编辑新功能

git commit -a -m "新功能添加" # -a 标签自动添加已追踪入缓存(修改、删除),不添加新文件,用来省略一步 add

发现了一个安全漏洞,基于 master 分支创建 hotfix 快速修复分支

git checkout -b hotfix master # 基于 master 创建 hotfix 分支,并切换至该分支

编辑修复功能

git commit -a -m "修复安全漏洞" # 添加修改入缓存区并提交入 hotfix 分支
此时存在 3 个分支
此时存在 3 个分支
合并回 master```bashgit checkout master # 切换到主分支``````bashgit merge hotfix # 合并 master 和 hotfix 分支``````bashgit branch -d hotfix # 删除 hotfix 分支```
hotfix 分支合并至 master ,剩下 2 个分支
hotfix 分支合并至 master ,剩下 2 个分支
整合 hotfix 分支之后就有了一个分叉的项目历史,用 rebase 整合 new-feature 分支,获得一个线性的历史```bashgit rebase master new-feature```
rebase 后
rebase 后

这样 new-feature 分支到了 master 分支的末端,就可以在 master 分支上将进行标准的快速向前(线性)合并了:

git checkout master
git merge new-feature
快速向前合并
快速向前合并

rebase -i 整理历史

-i 标记用来开启交互式 rebase 命令。交互式的变基可以在过程中修改单个提交,而不是将全部所有提交移动到新的基上。在此基础上,可以移除、分割、更改提交的顺序。

带有交互式标记的变基命令可理解为整理提交历史的命令。

git rebase -i 基

将当前分支用可交互的方式变基到“基”上。该命令会打开一个编辑器,可以给每个被编辑的提交输入命令。

  • p pick 使用该提交
  • r reword 使用该提交并重新编辑提交信息
  • e edit 使用该提交,但停止到该提交
  • s squash 将该提交与前一个提交合并
  • f fixup 将该提交和前一个提交合并,但不保留该提交的注释
  • d drop 丢弃该提交
pick a129a12 v1.1版本   # 更改前面的 pick 所在位置即可控制
pick 99cc21d v1.2版本
pick 6b8c312 v1.3版本

# 下面的注释是操作提示

操作同 vim 编辑器。i 进入编辑模式,esc + :wq 保存并退出编辑界面,之后进入 commit message 提交信息编辑界面。

# This is the 1st commit message:
v1.1版本
# This is the commit message #2:
v1.2版本
# This is the commit message #3:
v1.3版本

在这个界面可以修改每一个提交注释,操作方式同上。保存并退出后,一个 git rebase 流程完成。

在上面的例子中,将 v1.2版本 前面的命令更改为 squash,则第二个提交会和第一个提交合并,之后将再看不见 v1.2版本(合并到的 v1.1版本 的 ID 会重新生成)。对之后的所有人来说,版本树是从 1.1 跳到了 1.3 版本。这样的交互式提交可以在一个分支在变基到别的分支上时更为干净。

clean 清除未跟踪

git clean 命令将未跟踪的文件从工作目录里面移除,属于破坏性编辑,不可撤销。

该命令通常与 git reset --hard 一起使用。reset 影响被 Git 跟踪的文件,clean 影响未跟踪的文件,共同使用可以使工作区彻底变回某个提交状态。

git clean 标记
git clean -n    # 查看哪些文件会被移除,并不执行删除
git clean -f    # 强制移除未跟踪文件,不包括 .gitignore 忽略的
git clean -f 路径 # 限制路径的强制移除
git clean -df   # 移除未跟踪的文件和目录
git clean -xf   # 移除所有未跟踪的文件,包括忽略

在本地仓库作死之后进行毁尸灭迹,git reset --hard + git clean -f 是最好的选择,这两个命令共同使用可以使工作目录和最近的提交完全匹配,从而在干净的状态下继续工作。

git clean 命令还常用来清理 build 之后的工作目录。它可以方便地删除编译生成的、不需要进行提交的二进制文件。git clean -xf 通常是打包发布前必要的一步。

git clean 命令与 git reset 同为仅有的几个可以永久删除提交的命令之一,要小心使用。该命令是被一般禁止的,所以 -f 强制标记是基本操作,以避免错删。

3 远程协作管理

Git 给每一个开发者一份自己的仓库拷贝,都拥有各自完整的本地历史和分支结构。用户共享的是一系列的提交,即允许共享时同步整个分支。

3.1 远端仓库同步

最基础的远程与本地之间联系的操作是推送(push)拉取(fetch),将本地仓库的分支上传至远端仓库,或者将远端仓库的共享下载到本地仓库来查看使用。

git remote 连接

git remote 命令用来创建、查看和删除与其他仓库之间的连接。在显示的时候采用类似 origin 的别名来代替远程仓库完整的 url.

git remote 显示远端连接信息
git remote      # 列出本地仓库与远端仓库连接
git remote -v   # 显示远端连接同时显示连接的 url
remote命令示例
remote命令示例
git remote add <别名> <连接> 添加远端连接
git remote add 别名 远端仓库url

加上 add 命令就可以添加本地仓库与远端仓库的连接,在添加的时候需要给远端仓库的 url 附上 别名。该别名方便在其他 Git 命令中引用。

git remote rm <远端库> 删除远端连接
git remote rm 别名

添加 rm 命令以移除已有的远端仓库连接。

git remote rename <旧名> <新名> 重命名别名
git remote rename 旧别名 新别名

重新给远端连接赋别名。

Git 为每个开发者都提供完全隔离的开发环境,信息并不会自动在仓库之间流转,而是需要可开发者手动提交到云端,或者手动从上游提交拉取到本地。

关于 origin 的远程连接:

git clone 克隆仓库时创建的远程连接默认别名为 origin ,因此大多数中央仓库的取名均统一为此。

远程仓库的 url 连接最简单的方式是 HTTPS 协议或 SSH 协议。HTTPS 协议允许匿名、只读访问;SSH 协议方便非匿名读写,需要在托管的服务器上拥有有权限的 SSH 账户。

最终这些 url 都会指向一个 xxx.git 文件,该文件是负责连接同步的 Git 文件。

添加与别的开发者仓库的远程连接

场景:开发者 OceanCat 在 dev.arknights.com/OceanCat.git 上维护了一个仓库,自己的本地仓库需要与这个仓库连接。

git remote add OceanCat https://dev.arknights.com/OceanCat.git

这种方式可以访问每一个开发者的仓库,充分发挥 Git 分布式项目管理的特性,从而不再局限于单一的一个中央仓库,使得中央仓库外的协作变的可能。这种特性有利于在维护大项目的时候分成的小团队进行协作。

git fetch 拉取

git fetch 命令用来将远程仓库的分支导入本地仓库

git fetch 远端别名          # 拉取该远端仓库的全部分支

git fetch 远端别名 分支名    # 拉取该远端仓库的指定分支

fetch 下来的内容会单独表示成一个远程分支,不影响本地开发的提交,需要手动 merge 整合。这种安全的方式可以再整合前检查那些远程分支上的提交,看到中央仓库的历史进展,且不会强制让你合并或使用这些提交。

远程分支

远程分支实质上和其他任何本地分支相同,其仅代表这些提交行程的分支来源并非本地而是其他的仓库。远程分支相当于一个只读的本地分支。查看远程分支时,需要额外传入 -r 参数。

git branch -r

# origin/master
# origin/develop

远程分支会有 remote 别名的前缀,如 origin/分支名 或者其他创建连接时起的别名。

可以使用 git checkoutgit log 命令来检查这些分支。如果接受这些远程分支包含的更改,就使用 git merge 命令将它并入本地分支。

同步本地仓库和远程仓库是一个分两步的过程:git fetch 将远程仓库拉取到本地,git merge 将更改与本地合并(同时改变工作区和项目历史)。git pull 是这两个步骤的快捷方式。

同步远程仓库的例子

场景:将别名 origin 的远程仓库同步至本地仓库。

git fetch origin # 拉取远端仓库到本地

该命令显示被下载的的分支:

a1e8fb5..45e66a4 master -> origin/main
a1e8fb5..9e8ab1c develop -> origin/develop
* [new branch] new-feature -> origin/new-feature

即本地的 master develop new-feature 三个分支存在更新,但是 fetch 并不会自动更新。需要使用 git merge 进行合并来手动更新。

git checkout master     # 工作分支切换到 master
git merge origin/main   # 将工作分支与 origin/main 合并
合并后(快速合并)
合并后(快速合并)

因为本地在拉取前没有产生新的提交,因此是一个线性的快速合并,使得 master 指向 origin/main 所在的位置即可。

此时本地仓库的项目历史和工作区都保持了与上游更新的同步。

git pull 拉取+合并

git pull = git fetch + git merge,即拉取后合并。

git pull 远程库             # 拉取当前分支对应的远程副本中的更改,并立即合并。

git pull --rebase 远程库    # 使用 rebase 合并

默认的合并方式是 merge,通过 --rebase 标签改为用变基的形式合并。

git push 推送

git push 是推送操作,即将本地仓库的提交转移到远程仓库中(并视情况在远程仓库进行合并)。push 操作可能会覆盖已有的更改,在推送的时候要小心使用。

git push 远程库 本地分支    # 将指定的本地分支推送到远程库

git push 远程库            # 推送当前工作的本地分支

push 操作会推送所有指定的提交和提交对象,其会在目标仓库中创建一个新分支来记录本地的分支。为了防止有提交覆盖的情况,如果无法在这个新分支上进行快速合并时,Git 不会允许 push 操作的发生。

git push --force 强制推送
git push 远程库 --force

强制推送本地仓库,且在非快速合并情况下的仍然会推送。使用该标记时要非常确定远程仓库的更改需要被强制覆盖。

git push --all 推送所有本地分支
git push 远程库 --all  # 推送所有的本地分支

将本地仓库的所有分支都推送到远程仓库。

git push --tags 推送本地标签
git push 远程库 --tags

--tags 标记使推送时同时推送本地的标签。

git push --delete 分支名 删除远程库中的分支
git push origin --delete new-feature

该指令删除了 origin 远程库当中的 new-feature 分支。

git push 用来将本地更改发布到中央仓库。在本地工作后积累了一些本地提交,准备和其他开发者共享的时候,就可以使用交互式的 rebase 先整理本地项目历史,后推送到中央仓库。

当本地的 master 分支进展超过中央仓库的 master 分支时,运行 git push origin master 指令会在推送完本地的 master 分支提交后将中央仓库的提交更新为最新版本。即相当于在远程仓库内部运行了 git merge master 指令。

因为 push 指令的自动合并特性会覆盖中央仓库的提交历史,Git 会拒绝导致非快速向前合并的推送请求。所以在远程历史和本地历史出现分叉的情况下,要先 pull 下来远程分支,在本地进行整理之后再尝试进行推送。

若出现分叉,要先与中央仓库同步,整理成线性后再进行推送。

--force 标记会覆盖这个行为,强制让远程仓库的分支符合本地仓库的分支,删除上游更改,即让远程仓库的项目历史与本地完全相同。因此,在使用该标记的时候要慎之又慎,保证该操作不会影响到其他开发者。

推送的标准流程例子

首先确保本地的 master 分支和中央仓库的副本是一只的,提前 fetch 拉取中央仓库的副本到本地并使用 rebase -i 交互式整理。之后再用 git push 命令进行推送。

git checkout master     # 工作分支切换到 master
git fetch origin master # 拉取远端仓库的 master 分支到本地
git rebase -i origin/master # 整理拉取来的远端 master
# 在交互式文本窗口中,用 Squash 合并提交

此时,本地的 master 分支将会是最新的,推送到远端仓库后会导致快速向前的合并,因此不会出现复杂分支的问题。

git push origin master  # 进行推送

3.2 提交 Pr

PR 是 Pull Request 的简称,是开发者使用 GitHub 进行协作的利器。该功能在线上提供了友好的可交互页面,直接通过 Github 完成。

PR界面
PR界面

开发者在开发了一个新模块之后,准备将自己的新功能并入中央仓库以便后续进行“官方发布”,就要用到 Pull Request 功能。

开发者使用自己的 Github 账号从自己的远端仓库中选择一个提交给出一个 PR,它通知所有参与的开发者:有一个新代码需要被审查。

开发者们可以在该 PR 携带的讨论版下进行讨论和反馈,以及推送后续的提交来修改这个 PR,所有的相关这个新功能的活动都在这个 Pull Request 中追踪。

PR 的 3 个作用
PR 的 3 个作用

Pull Request 的协作模型会让共享提交的解决方案形成更为线性的工作流,并且有一个更加方便的线上讨论合作的网页空间。

Pull Request 原理

提交 Pull Request 值得是“拉取的请求”,即请求(request)项目维护者拉取(pull)自己提交的分支到项目仓库。也就是说这个请求需要提供 4 个信息:源仓库、源分支、目标仓库、目标分支。

PR的 4 个信息
PR的 4 个信息

Github 会自动设置例如上图的默认值。当协作工作流有变化时。团队可能需要设定不同的值,即可以自由设置被提交的分支和提交到的分支。

PR 的工作方式

Pull Request 可以和 feature 分支工作流、GitFlow 工作流或者 Fork 工作流一起使用。PR 需要在两个不同的分支中进行交互,因此不存在中心化工作流的应用。

PR 的大致流程

  1. 开发者在本地仓库中为新功能新建一个专门的分支
  2. 开发者将新功能分支推送到自己账号的 Github 仓库
  3. 开发者用 Github 对项目发起一个 Pull Request
  4. 团队成员审查代码,进行讨论并做出修改
  5. 项目维护者将该功能并入官方仓库,然后关闭这个 PR
Feature 分支工作流中的 PR

Feature 分支工作流使用共享的 GitHub 仓库管理协作。开发者在单独的 feature 功能分支中添加新功能或对该功能进行迭代。

虽然是在同一个仓库中,但开发者不能擅自将新功能分支并入 master 分支。在新功能需要更新时,开发者需要提出一个 PR 来启动对这个功能的讨论,最终由项目维护者决定是否进行合并。

Feature 分支 PR 工作流
Feature 分支 PR 工作流

这个工作流中只有一个仓库,即官方发布的中央仓库。PR 是在这个中央仓库内提出的,目的是将新功能加入主分支。中央仓库的各 feature 分支为源分支,master 为目标分支。

一个 PR 也可以是未完成的。当开发者在实现一个特殊需求时遇到了问题,同样可以发布一个包含工作进展的 PR,由其他开发者在这个 PR 下提供建议,甚至帮你完成作业。

GitFlow 工作流中的 PR

GitFlow 工作流定义了围绕项目发布的分支模型。其 PR 和 feature 分支工作流是完全一致的,开发者在功能、发布以及修复分支需要提交审查的时候发布一个 PR,Github 来通知其他团队成员。

GitFlow PR 工作流
GitFlow PR 工作流

GitFlow 工作流和 Feature 分支工作流最大的区别在于,其仓库中有 master 分支和 develop 分支:master 分支负责接收确定的发布版本(Release)和快速修复(Hotfix);develop 分支负责接收开发者 PR 的新功能(feature),同时也应接收修复和版本更新。

Fork 工作流中的 PR

Fork 工作流基于 Github 的 “Fork 其他开发者的公共项目”功能。

在 Fork 工作流中,开发者将完整的 功能/版本/修复 推送到开发者自己的仓库,之后再进行 Pull Request,告诉项目维护者有新的代码需要审查。

Fork 工作流区分了各个开发者,使得每个开发者都有各自的云端仓库,Pull Request 的源仓库和目标仓库不再是同一个。源仓库是开发者自己的仓库,源分支时包含提交修改的那个分支;目标仓库是中央仓库。

Fork PR 工作流
Fork PR 工作流

Fork 工作流还可以用来和官方项目之外的开发者进行协作:开发者 A 和开发者 B 一同开发一个功能,他们可以向对方的 GitHub 仓库发起 PR,在正式对中央仓库提交修改申请之前进行个人之间的小合作。

Fork 工作流的一个例子

场景:开源项目贡献代码/小团队开发中使用 Pull Request 和 Fork 工作流。 admin 为项目维护者,A 为参与项目的开发者。

1 开发者先 fork 官方项目

首先参与进项目,要做一份官方项目的副本到 A 自己的云端仓库中。在 admin 维护的官方仓库的 Github 界面里点击 Fork 按键,在接下来的界面中选择保存的位置和项目名称后保存。

fork 他人项目
fork 他人项目

2 开发者 clone 项目到本地

A 需要将项目保存到本地以进行工作。

git clone SSH/HTTPS的.git # 克隆自己仓库的

该命令自动创建了名为 origin 的远程连接,指向 A 的仓库。

3 工作中

A 将进行开发前,需要为该种能新创建一个分支,用来隔离新功能和原项目。

git checkout -b new-feature # 创建并切换到新分支

对代码进行编辑。

git commit -a -m "新功能草稿" # 进缓存并提交

在新分支中进行提交不会影响到原来的项目主分支,可以自由工作。当需要整理 feature 分支的历史时,使用交互式的 rebase -i 整理即可。在大项目中,提交前的整理是必要的。

4 开发者推送分支到个人云端

在功能完成后,A 使用简单的 git push 将 feature 分支推送到自己的 Github 仓库上

git push origin 分支名

这一步骤为接下来的 PR 做准备,也公开了自己的更改,使得有权限的协作者都可以查看当前的修改。

5 创建 PR

在个人的 Github 仓库中有了 feature 分支之后,就可以进行 PR 的提交。

在 fork 到个人账号下的云端仓库中,找到 PR 分页下的 New Pull Request 按键,用当前的 Github 账号创建一个新 PR。

个人仓库的 PR 分页提交 PR
个人仓库的 PR 分页提交 PR

进入到第二个界面,设定源分支(compare)、目标仓库(base fork)、目标分支(base),同时可以查看合并提示和具体更改信息。

具体的 PR 提交界面
具体的 PR 提交界面

最终提交前,还需要进行一个简单的说明,和 commit 的注释一样,用以告知维护者这次更改的大致内容,Github 会给 维护者 admin 发送通知。

6 维护者审查 PR

维护者可以在自己的 Github 仓库(官方仓库)的 Pull requests 分页中看到所有的 PR. 在每个 PR 中会显示建军节、提交历史和包含的更改。

当 admin 认为该提交分支可以进行合并了,点击 Merge Pull Request 按键来来通过 PR,该提交就会并入官方仓库的指定分支。

提交后的 PR 界面
提交后的 PR 界面

若该 PR 有需要更改的地方时,可以在讨论区进行讨论告知修复。A 在自己的 feature 分支后面增添了一个新的提交,并推送到了自己的 Github 仓库中,该更新会自动添加到原来的 PR 后面,admin 可以继续审查并进行讨论。

7 PR 被接受

admin 接受了 A 提交的 PR,A 的 feature 分支并入了官方仓库的 master 分支,该 PR 随后被关闭。功能整合进项目中,在其他 master 分支上进行工作的开发者可以使用 git pull 命令将修改拉取到自己的本地仓库,更新工作目录。

4 Git 分支

Git 分支和 SVN 分支不同,后者只用来记录大规模的开发效果,前者则在日常开发工作流中处处被使用。

分支代表了一条独立开发的流水线,可以看做是请求一个新的「工作目录/缓存区/项目历史」集合。新的提交会被放入新的分支的历史当中,而不影响其他分支的历史。

初始化分支 branch

git branch 命令用来创建、列出、重命名和删除分支,此命令不会主动切换分支和更改分支内的内容。

git branch          # 列出仓库内所有的分支
git branch 分支名    # 创建 分支名 分支,工作区不切换
git branch -m 新命名 # 重命名当前分支为 新命名
git branch -d 分支名 # 删除指定分支
git branch -D 分支名 # 强制删除制定分支

当开发者想要给当前代码添加新的功能或者修复 bug 时,都应该新建一个分支以封装新的修改,这确保了不稳定的代码永远不会被提交到主代码库中,也方便在新修改并入主分支前清理 feature 分支的历史,从而减少无用的提交版本,保持简洁。

分支实现的具体原理

Git 分支没有在不同分支之间复制文件,不会修改仓库内的文件,只是新建了一个指针指向最新的提交。

不同的指针即代表了不同的分支,Git 通过提交历史的关系来推断当前的提交属于哪一个指针,从而确定隔离不同的分支。

这个特性使得 Git 的合并模型为动态的,在进行分支合并时只需要操控指针即可,项目历史的合并只需要将两个独立的提交历史连接起来,而不需要修改仓库内容。

这也是 Git 相较于其他的版本控制系统最大的优势所在。

切换分支 switch/checkout

git checkoutgit switch 命令都可以用来切换 Git 的工作分支。

git switch 分支名       # 切换到 分支名 分支
git checkout 分支名     # 切换到 分支名 分支
git switch -c 新分支    # 以当前分支为基创建新分支,并切换到新分支
git switch -c 新分支 旧分支  # 以旧分支为基,创建新分支并切换

git checkout -b 新分支  # 以当前分支为基创建新分支,并切换到新分支
git checkout -b 新分支 旧分支 # 以旧分支为基,创建新分支并切换
git checkout 原理

git checkout 命令用来对某个实体的不同版本之间进行切换,实体分三种:文件,提交,分支。

对于文件的 checkout 会将别的版本的文件视为新的修改;提交的 checkout 会移动 HEAD 指针的位置;分支的 checkout 则是切换 Git 的工作分支。

HEAD 指针的具体含义为:指向当前快照的引用。git checkout 命令的本质是移动头指针 HEAD 的位置,从而改变显示在工作区的内容。

而当工作区的内容和最新快照的引用不一致时,会进入“分离HEAD”状态。在 checkout 提交(commit)的时候会出现这种情况。

分离HEAD
分离HEAD

此时 HEAD 所指向的提交游离在工作流程之外,不属于任何一个分支,如果在这个状态下对提交进行修改,这些修改不会被允许合并回任何分支,也无法带回到任何一个新的分支。

因此,在分离 HEAD 的情况下,不能进行开发,而只是查看旧的提交状态。

switch 和 checkout

switch 命令出现前,Git 使用 checkout 命令来进行分支切换。当然现在也是可以的,二者在实现分支切换功能时没有任何区别。

switch 命令只用于切换,限制了 HEAD 指针移动的条件,让命令更加清晰;checkout 更着重于移动 HEAD 指针来执行更多底层的命令。这也能避免使用 checkout 时类似于“feature.cpp”可以是分支也可以是文件的歧义。

切换分支的命令非常直白,就是将 HEAD 指针指向指定的分支顶端,Git 会再 reflog 中记录 checkout 的操作历史,执行 git reflog 查看。

远端分支的切换与本地分支是相同的。

合并分支 merge

git merge 合并命令将多条分支合并为同一个,在版本控制和协作方面有重要作用。命令默认将被指定的分支并入当前 Git 选中工作的分支,当前工作的分支会被更新,指定分支不受影响。

通常使用 git checkout 选中需要保留的分支,git merge 被合并分支 将被合并分支并入保留的分支,再使用 git branch -d 被合并分支 删除已经无用的分支。

git checkout master     # 工作分支选定到 master
git merge feature       # 将 feature 合并入工作分支 master
git branch -d feature   # 删除 feature 分支

在合并分支时,项目历史的结构有很多种,Git 会根据结构自动进行不同的合并,以保证合并不会产生冲突,或者在产生冲突的时候提示用户选择保留文件。

快速向前合并

快速向前合并的结构要求合并的两个分支是线性结构的,即被合并的目标分支完全在当前工作分支的后面。快速向前合并只用移动当前分支的顶端到目标分支的顶端,而不再创建新的提交

快速向前合并
快速向前合并

这种情况通常出现在 master 分支没有出现新的提交,而只存在一个 feature 分支的更新时。此时 master 分支的所有提交都早于 feature 的提交,因此在项目历史的结构上,master 整体在 feature 的前面,是线性关系。

在快速向前合并中,不会出现冲突的情况。

三路合并

分支产生分叉时,就只能进行三路合并。三路合并会创建一个新提交来合并两个分支的历史,三路指的就是:两个分支的顶端提交 + 两个分支的共同祖先提交。

三路合并可能会产生冲突,即两个分支的顶端文件产生了不一样的修改。此时 Git 会将没有冲突的文件正常合并且存入缓存区,并提示开发者哪些文件产生了冲突需要进行手动修改,不自动创建新提交。

开发者修改后需要对冲突文件进行 git addgit commit 手动生成一个新的合并提交即可。

git merge 命令通常会创造出一个新的提交来保存合并之后的工作区。

三路合并
三路合并

master 分支和 feature 分支产生来了分叉,即 master 分支在 feature 分支的基后仍有新的提交更新,此时只能进行三路合并。merge 会创建一个新的提交来保存二者的更改。若产生冲突,则会在等待开发者手动解决冲突后自行创建新提交。

5 子模块

子模块是 Git 普通仓库下的一个独立仓库,有普通仓库仓库的全部功能,完全按照普通仓库的命令进行管理。

一个仓库下可以有多个子仓库,即子模块。

初始化

在仓库目录中使用 git submodule add 仓库链接 路径 命令创建新的子模块。

git submodule add git@github.com:volantis-x/hexo-theme-volantis.git themes/volantis

此时在根目录的 themes/volantis 路径下添加了远端仓库链接作为子模块。

父仓库/子模块分开拉取

# 本地工作目录中
git clone 父仓库地址     # 先克隆下来远端仓库

git submodule init      # 子模块初始化
git submodule update    # 子模块更新

后面两个命令也可以用下面的命令替代:

git submodule update --init # 初始化子模块并更新

git submodule update --init --recursive # 对全部子模块生效

子模块管理文件

添加子模块后,在父仓库的目录下会产生一个 .gitmodules 文件:

.gitmodules 文件
.gitmodules 文件

该仓库下包含了两个子模块,是分别从不同的远端仓库拉取下来的。

子模块操作

更新

在父目录中:

git submodule update                # 与主仓库中的子模块大马同步

git submodule update --remote       # 同步所有子模块
git submodule update --remote 指定  # 指定同步的子模块

或者直接进入到子模块的工作目录里面:

git pull

删除

  1. 删除 .gitmodules 管理文件的条目 git submodule deinit -f 子模块路径
  2. 删除 .git/config 文件中的条目
  3. 清除缓存 git rm --cache 子模块路径
  4. 删除子模块文件
  5. 删除 .git/modules 下目录

查看内容

这两个命令的作用是一样的

# 父仓库目录中
git submodule
git submodule status

6 指令图解

三区交互

Git 的基本工作流包含三个区:工作区、缓存区、提交历史区。这三区之间的交互命令如下:

三区交互
三区交互
  • git add 工作目录 → 缓存区

  • git commit -m 注释 缓存区 → 项目历史

  • git commit -a -m 注释 工作区 → 缓存区 和 项目历史,且不包含 untracked 文件(即新增文件)

  • git reset 最新提交 → 缓存区

  • git checkout 版本号 文件名 将工作区某文件回归到该版本提交的文件状态

  • git checkout 版本号 工作区变为该版本状态

  • git reset --hard 同时将 工作区 和 缓存区 变为 项目历史顶端的最新提交

commit 提交原理

Git 使用缓存区中的文件创建新的提交,在当前的提交节点后创建一个新节点,填充提交内容,并让当前分支的顶端指针指向新节点。

基本 commit 原理
基本 commit 原理

当出现分支情况时,commit 命令会在当前的工作分支上执行基本的提交操作,从而出现分叉或者分叉分支的延伸。

分支 commit 原理
分支 commit 原理

提交产生了一些小问题,又没有必要再创建新提交修复而让提交历史变得复杂,可以使用 git commit --amend 标记来用新提交替换掉分支顶端。

替换修改提交
替换修改提交

checkout 检出/切换原理

git checkout 命令用于从提交历史区或者缓存区检出文件到工作目录中,以及重要的分支切换。

checkout 的基本原理是更改 HEAD 指针的位置,而 HEAD 指针是 Git 工作的分支,同时也是显示在工作目录的提交。

给定文件名的情况下,git checkout 文件名 命令会将文件从提交历史区同时拷贝到缓存区和工作区当中,当前分支不会发生变化。

git checkout 检出文件到工作区与缓存区

指定到具体文件。同时更新工作目录和缓存区的该文件,当前分支不会发生变化。

git checkout HEAD~ miao.cpp
checkout <file>
checkout <file>

如果命令没有指定是哪一个提交,只给定了具体文件名,则该文件的更新方向为缓存区 → 工作目录。

HEAD 的位置没有改变,在修改后进行提交的历史是线性正常延长的,而非修改以前的历史记录。

git checkout <branch> 切换分支

指定到分支名。

git checkout stable
 切换工作分支
切换工作分支

可以直接认为是切换了 Git 当前的工作分支。原理是更改了 HEAD 所在的位置,同时更新工作目录和缓存区。

git checkout <commit> 检出提交与分离 HEAD 原理

指定到提交,会产生分离 HEAD 情况,得到一个匿名分支。匿名分支应该被仅用作查看,其修改无法合并回原提交历史。

被指定的提交可以是:提交号(b325c)、标签(v1.2.1)、相对位置(HEAD~)的任何一个。

git checkout master~2
检出提交
检出提交

检出一个具体的提交,该示例是用分支头指针的相对位置寻址的。命令将某个提交的所有文件同步到工作目录和缓存区。

这种情况会出现分离 HEADHEAD指针 指向了 Git 工作的地方。在分离情况下,HEAD指针 不在某个分支的顶端。此时 Git 单独工作在一个提交或者说未命名的分支上。

分离 HEAD 时进行提交会以当前检出的提交为基延伸出一条新的分支,该分支仍是未命名的。这种情况非常危险,如果此时切换到其他分支上,将无法通过简记重新回来(只能用长且复杂没有规律的提交号)。

通常分离 HEAD 只用来查看以前的版本而不进行修改,如果一定要在查看后修改并重新延伸一个分支,需要在 Git 工作于未命名分支上的情况下新建一个分支。

git checkout -b new
分离HEAD 下保留修改提交必须创建新分支
分离HEAD 下保留修改提交必须创建新分支

diff 比较原理

git diff 命令用来查看两个提交之间的变动,在下图中为:箭头终点提交 相较于起点提交 有哪些变动。

diff图解
diff图解

在工作目录进行工作没有进缓存时,缓存区储存的是最新提交的内容,此时使用不加标签的 git diff 命令查看的是工作目录和缓存区的区别,即最新工作进展和上一次保存的提交之间的区别。

单地址时,查看工作目录的和指定地址的区别。

一般来说,diff 指令地址的逻辑是 git diff 旧 新 和 “相对于工作目录的最新进展”。

reset 重设原理

git reset 命令将当前的分支指向指定位置,默认地址 HEAD,更新缓存区为分支顶端版本。在添加 --hard 标签下同时更新工作目录,--soft 标签下缓存区和工作目录都不变(HEAD 指针位置不变)。

reset 命令的基本原理是同时变更 HEAD 指针和分支顶端指针,使缓存区(–hard 带工作目录)与指定提交相同。

reset 图解
reset 图解

不给地址的情况下直接使用 git reset,默认的重设地址是 HEAD,等同于直接重置缓存区。在添加 --hard 标签情况下,将放弃工作目录和缓存区的所有更改,重新回到最新提交的情况。在取消错误更改的时候很有用。

默认重设用于回到上个提交
默认重设用于回到上个提交

具体到文件名的 reset --hard 命令工作效果几乎同 checkout,都可以用来查看该文件的以前版本。二者的差别在于 reset 命令会同时更改分支的顶端。

merge 合并原理

git merge 命令用来合并不同的分支,以做到版本更新、补丁等功能。merge 命令移动的是当前工作分支的顶端指针和 HEAD 指针。

当一个分支之间是线性关系——在一条直线上时,以更早的分支为工作分支(移动该分支顶端和 HEAD),执行合并命令,会发生快速向前合并(fast-forward merge)

快速向前合并
快速向前合并

快速向前合并不会产生冲突,也是最快最方便的合并方式。

当分支之间产生分叉,并非线性关系时,需要进行三路合并(3-way merge),产生一个新的提交来解决可能得冲突合并。

三路合并
三路合并

如果不存在冲突,merge 命令会自动创建一个新提交,并移动当前工作分支的顶端和 HEAD 指针到新提交上。另一个分支顶端不变,且因为已经合流,可以使用 git branch -d 分支名 来删除顶端指针以废弃该分支。

如果存在冲突,merge 命令会将不冲突的部分存入缓存区,并告知开发者那些文件产生了冲突,需要手动决定保留版本。

手动解决需要重新在某一个分支上创建一个新的提交来解决冲突后,再次尝试三路合并。此时可使用 git commit --amend 来避免产生过多提交。也可以使用可视化工具解决冲突。

cherry-pick 复制提交原理

git cherry-pick 提交号 命令会复制该提交内容,在当前工作分支上创建内容一致的新提交。

cherry-pick图解
cherry-pick图解

同时同步工作目录与缓存区。相当于是将某个提交拉到了当前工作分支上,成为一个新提交。

rebase 变基原理

git rebase 分支名 命令将工作分支的基变到指定分支的顶端,形成一个线性关系。

在该示例中,工作分支为 topic 分支,运行 git rebase master 后,相当于 topic 分支两个独立于主分支的提交接到了主分支的最后面,成为最新的提交,同时工作分支的指针也移动到最新。此时原本的分支没有指针指向,是被废弃的。

rebase图解
rebase图解

可以说,rebase 是线性化的自动 cherry-pick 命令。需要注意的是,cherry-pick 和 rebase 都工作在保留的分支上。

添加 git rebase --onto 分支名 基提交号 标记,可以指定基的位置,在上图中如果将基提交号设置为 169a6 ,则只会将该提交之后的提交接到指定分支的后面,即 2c33a 提交的内容在 da985 后形成一个新提交。

评论