Git分支
使用分支意味着你可以把你的工作从开发主线上分离开来, 以免影响开发主线。
- 分支被称为Git的“必杀技特性”
- Git 处理分支的方式可谓是难以置信的轻量
- Git 鼓励在工作流 程中频繁地使用分支与合并
分支简介
Git 保存的不是文件的变化或者差异,而是一系列不同时刻的快照 。
在进行提交操作时,Git 会保存一个提交对象(commit object)。这个提交对象包含一个指向暂存内容快照的指针、作者的姓名和邮箱、提交时输入的信息以及指向它的父对象的指针。
- 首次提交产生的提交对象没有父对象,普通提交操作产生的提交对象有一个父对象, 而由多个分支合并产生的提交对象有多个父对象。
当使用 git commit 进行提交操作时,Git 会先计算每一个子目录的校验和, 然后在 Git 仓库中将这些校验和保存为树对象。随后,Git 便会创建一个提交对象, 它除了包含上面提到的那些信息外,还包含指向这个树对象(项目根目录)的指针。
暂存操作会为每一个文件计算校验和(SHA-1 哈希算法),然后会把当前版本的文件快照保存到 Git 仓库中 (Git 使用 blob 对象来保存文件快照),最终将校验和加入到暂存区域等待提交。
- blob对象:保存文件快照。
- 树对象:记录目录结构和blob对象索引。
- 提交对象:包含指向树对象的指针和所有提交信息。


分支创建和删除
分支创建,即创建一个可以移动的新的指针,这个指针指向某个提交对象。
# 在当前所在的提交对象上创建一个指针
# 只创建,并不会自动切换
git branch 分支名
# 创建分支并切换
git checkout -b <newbranchname>
# git init 操作会默认创建一个 master 分支
# 删除分支
git branch -d 分支名
查看分支

- HEAD:指向当前所在的本地分支(当前分支的别名)。
# 查看各个分支指向的提交对象(--oneline选项可选)
git log --oneline --decorate
# f30ab (HEAD -> master, testing) add feature
# 说明 master、testing 分支都指向校验和以 f30ab 开头的提交对象
# HEAD 指向 master,是master的别名。
# 查看仓库的分支列表
git branch
# 带 * 表示是HEAD指向的分支
# 查看本地仓库是否存在特定远程分支
git branch -a | grep <remote>/<branch>
# 存在则输出 remotes/<remote>/<branch>
# 不存在则无输出
切换分支
分支切换,即更改 HEAD 的指向。
# checkout :检出
git checkout 分支名HEAD 表明了当前的分支,提交后,HEAD 所指的分支会纳入新的提交对象并将指针后移(类链表),其余的分支指向不变。

- 分支指针指向了下一个提交对象的父提交对象。不同的分支上的提交会将链表岔开,形成分支。

由于 Git 的分支实质上仅是包含所指对象校验和(长度为 40 的 SHA-1 值字符串)的文件,所以它的创建和销毁 都异常高效。 创建一个新分支就相当于往一个文件中写入 41 个字节(40 个字符和 1 个换行符),能不快吗?
切换分支前,要留意你的工作目录和暂存区里那些还没有被提交的修改, 它可能会和你即将检出的 分支产生冲突从而阻止 Git 切换到该分支。 最好的方法是,在你切换分支之前,保持好一个干净的状态。 有一 些方法可以绕过这个问题(即,暂存(stashing) 和 修补提交(commit amending))。
当你切换分支的时候,Git 会重置你的工作目录。 Git 会自动添加、删除、修改文件以确保此时你的工作目录和这个分支最后一次提交时的样子一模一样。
分支的合并
分支为开发提供了单独的测试环境,将维护、修bug、拓展等独立于生成环境。一般的做法,是在生产环境的提交上新建一个分支,在这个分支上进行测试,ok了再合并到生成环境所在的分支。这样生产环境就被独立开来,不受后期开发的影响。
Git 的提交记录了当时的快照,可以很方便地进行定位追踪,再新建分支、解决、合并,一气呵成。

步进
# 将当前分支合并至目标分支
git merge 目标分支名
## eg;
$ git checkout master
$ git merge hotfix
Updating f42c576..3a0874c
Fast-forward
index.html | 2 ++
1 file changed, 2 insertions(+)
# 切换回master分支,并合并至hotfix分支
# 快进 Fast-forward :目标分支作为当前分支的直接/间接后继,无需解决分歧
当你试图合并两个分支 时, 如果顺着一个分支走下去能够到达另一个分支,那么 Git 在合并两者的时候, 只会简单的将指针向前推进(无分歧需要解决)。这就叫做“快进”。
*合并

$ git checkout master
$ git merge iss53对于叉开(diverged)的两个分支,进行合并时,Git回做一些额外工作:
找到分支所指向提交结点(提交对象)的公共祖先 C2。(找到参与合并的三方)

image-20230507165707466 合并提交:创建一个新的快照进行三分合并。

image-20230507165727809
遇到冲突时的分支合并:
不同分支都涉及对同一份文件的相同位置的修改时,会产生合并冲突。
此时 Git 做了合并,但是没有自动地创建一个新的合并提交。 Git 会暂停下来,等待你去解决合并产生的冲突。 你可以在合并冲突后的任意时刻使用 git status 命令来查看那些因包含合并冲突而处于未合并 (unmerged)状态的文件。
任何因包含合并冲突而有待解决的文件,都会以未合并状态标识出来。 Git 会在有冲突的文件中加入标准的冲突解决标记,这样你可以打开这些包含冲突的文件然后手动解决冲突。
冲突标记:(行1、3(分割)、7)
<<<<<<< HEAD:index.html
<div id="footer">contact : [email protected]</div>
=======
<div id="footer">
please contact us at [email protected]
</div>
>>>>>>> iss53:index.html- 删除冲突标记(手动编辑最终的文件内容,并删除冲突标记)后,使用 git add 命令标记其冲突已解决,之后重新提交即可(手动提交 合并提交对象,完成分支合并)。
- 冲突文件都已解决并以暂存后才能成功提交。
使用可视化工具来解决冲突:
# 启动一个合适的可视化合并工具,并带领你一步一步解决这些冲突
git mergetool
# 提示信息中 use one of the following tools: 后的都可以用
$ git mergetool
This message is displayed because 'merge.tool' is not configured.
See 'git mergetool --tool-help' or 'git help config' for more details.
'git mergetool' will now attempt to use one of the following tools:
opendiff kdiff3 tkdiff xxdiff meld tortoisemerge gvimdiff diffuse
diffmerge ecmerge p4merge araxis bc3 codecompare vimdiff emerge
Merging:
index.html
Normal merge conflict for 'index.html':
{local}: modified file
{remote}: modified file
Hit return to start merge resolution tool (opendiff):- 退出合并工具之后,Git 会询问刚才的合并是否成功。如“是,则Git自动进行暂存以表明冲突已解决。
分支管理
# 查看仓库的分支列表
git branch
# 带 * 表示是HEAD指向的分支
# 附加查看每一个分支的最后一次提交
git branch -v
# 过滤选项:
--merged:过滤已经合并到当前分支的分支。通常可以安全删除而不会失去任何东西。
--no-merged:过滤尚未合并到当前分支的分支。包含未合并的工作
使用 git branch -d 删除时会失败,需要使用 -D 强制删除。
# --merged 和 --no-merged 也可以接受一个分支参数,过滤出 已合并/未合并到这个分支 的分支
# git branch --no-merged testing分支开发工作流
介于Git便捷高效的分支管理,衍生出的典型工作模式。
长期分支:流水线 work silos

- master:安全稳定的代码
- develop / next:平行分支,进行后续开发和稳定性测试等。时机成熟时将master并入。
- topic:主题分支,针对性地解决某个问题的分支,如 iss53。

- 类似于这种方法,维护不同层次的稳定性,不必拘泥于这三个部分。
使用多个长期分支的方法并非必要,但是这么做通常很有帮助,尤其是当你在一 个非常庞大或者复杂的项目中工作时。
主题分支
主题分支对任何规模的项目都适用。 主题分支是一种短期分支,它被用来实现单一特性或其相关工作。

- 抛弃 iss91 ,合并dumbidea、iss91v2到 master

远程分支
远程引用:对远程仓库的引用(指针),包括分支、标签等等。
# 获得完成的远程引用列表
git ls-remote <remote>
# 获得远程分支的更多信息
git remote show <remote>远程跟踪分支:远程分支状态的引用。是用户无法移动的本地引用,一旦你进行了网络通信, Git 就会为你移动它们以精确反映远程仓库的状态。
- 该分支在远程仓库中的位置就是你最后一次连接到它们的位置。
- 以
<remote>/<branch>的形式命名。
不同用户对同一远程分支的提交会导致用户间的提交历史走向不同步。

同步远程仓库,使用fetch、pull命令皆可。

本地分支名和拉取的远程仓库分支名可以不同,但是它们通常是相同的。
可以使用 -b 选项来指定本地分支名,如:
git checkout -b <local_branch_name> <remote_branch_name>其中 <remote_branch_name> 是远程分支的名字,<local_branch_name> 是您想要创建的本地分支的名字。
在此过程中,如果您没有指定本地分支的名称,则 Git 会使用与远程分支相同的名称来创建本地分支。
多仓库下的远程跟踪分支:

推送到远程分支
# 推送 HEAD 到 <remote>/<branch>
git push <remote> <branch>
# 推送 local_branch 到 remote/remote_branch
git push <remote> <local_branch>.<remote_branch>如果使用HTTPS进行推送,Git 服务器会询问用户名和密码,默认情况下它会在终 端中提示服务器是否允许你进行推送。
如果不想在每一次推送时都输入用户名与密码,你可以设置一个 “credential cache”。 最 简单的方式就是将其保存在内存中几分钟,可以简单地运行 git config --global credential.helper cache 来设置它。
从远程分支抓取
见 fetch pull 。
当抓取到新的远程跟踪分支时,本地不会自动生成一份可编辑的副本(拷贝)。 只有一个不可以修改的 <remote>/<branch> 分支指针,使用 merge 进行合并。
# 检出一个起点于 <remote>/<branch> 的本地分支
# 创建一个跟踪分支
git checkout -b <local_branch> <remote>/<branch>由于 git pull 的魔法经常令人困惑所以通常单独显式地使用 fetch 与 merge 命令会更好一些。
跟踪分支
从一个远程跟踪分支检出一个本地分支会自动。是与远程分支有直接关系的本地分支。
上游分支:跟踪分支所”跟踪“的远程分支。
- 在一个跟踪分支上输入 git pull,Git 能自动地对上游分支进行拉取并合并到本地的跟踪分支。
# 创建一个跟踪分支
git checkout -b <local_branch> <remote>/<branch>
# 创建并检出与上游分支同名的跟踪分支
git checkout --track <remote>/<branch>
# 本地无同名分支时,自动从远程仓库创建并检出同名跟踪分支
git checkout <remote_branch>
# 要查看设置的所有跟踪分支,同时包含是否领先、落后等信息
git branch -vv
# 并不会联网查询,使用的是本地缓存的数据,需要先抓取以获得最新的统计信息
git fetch --all; git branch -vv运行 git branch 命令设置上游分支:
# 二者等价,设置当前分支的上游分支
git branch -u <remote>/<branch>
git branch --set-upstream-to <remote>/<branch>上游快捷方式:
# 使用 @{upstream} 或 @{u} 表示当前分支的上游分支
## eg:
git merge @{u}
## 等价于: git merge origin/master删除远程分支
# 从远程仓库中删除分支
git push <remote> --delete <branch>基本上这个命令做的只是从服务器上移除这个指针。 Git 服务器通常会保留数据一段时间直到垃圾回收运行,所以如果不小心删除掉了,通常是很容易恢复的。
变基
对于分叉的提交,merge会进行一个三方合并,生成一个新的快照(合并提交)。
使用变基(rebase),可以将某一分支的修改”嫁接“至另一分支。
变基完,通常需要配合 快速合并 完成最终的合并。
# 将当前分支变基到目标基底分支
git rebase 目标基底分支
$ git checkout experiment
$ git rebase master
快速合并:
$ git checkout master
$ git merge experiment
原理:
- 找到共同祖先 C2
- 对比当前分支相对于祖先的历次提交,提取相应的修改并存为临时文件
- 将当前分支指向目标基底 C3
- 将另存为临时文件的修改依序应用
- 变基使得提交历史更加整洁,提交历史是一条直线没有分叉。

仅合并分支修改:
# 在分支branch中抽出不与ex-branch共有的提交
# 然后将这些提交变基到 M-branch上
git rebase --onto <M-brach> <ex-branch> <branch>
快速合并:
$ git checkout master
$ git merge client
二次变基:
git rebase master server
变基的风险:
如果提交存在于你的仓库之外,而别人可能基于这些提交进行开发,那么不要执行变基。
变基操作的实质是丢弃一些现有的提交,然后相应地新建一些内容一样但实际上不同的提交。 如果你已经将提 交推送至某个仓库,而其他人也已经从该仓库拉取提交并进行了后续工作,此时,如果你用 git rebase 命令 重新整理了提交并再次推送,你的同伴因此将不得不再次将他们手头的工作与你的提交进行整合,如果接下来你 还要拉取并整合他们修改过的提交,事情就会变得一团糟。
- 真的出现了这种情况,也可以提供变基来以毒攻毒。
总的原则是,只对尚未推送或分享给别人的本地修改执行变基操作清理历史, 从不对已推送至别处的提交执行 变基操作,这样,你才能享受到两种方式带来的便利。
