一. 分支 (branch)
用于:
1. 由于debug或增加功能,从原有程序项目分出新项目。最终可以合并回原项目
分支常命名为:
bug/<bug_id>/<bug_说明>
feat/<新功能_说明>
2. 原项目(master)为release-to-user 分支,另分出一个项目,做develope。最终合并(提交)回原项目
分支常命名为:develop
这种用途,合并时由于master分支始终无人修改,会采用fast-forward合并模式 (master上将没有新commit节点产生)。所以,为了产生新commit节点,须: git merge --no-ff develop
工作原理:
创建新分支时,从当前分支的指定的commit节点,作以下动作:
1. 复制track list
2. 记录这个commit节点为新分支的HEAD (用于下次切换)
切换分支时,包扩了以下动作
1. 找到要切换分支的指定的commit节点 (默认是该分支的HEAD)
2. 比较这个commit节点的数据内容 和当前分支的在文档库里的对应版本,找出不同的文件
3. 取回在这些不同的文件到working directory
4. 覆盖 staging area数据
5. 替换 track list
6. 根据5删除working directory中不在track list上的文件
可见: 各分支,共享 repository+staging area+working directory+.gitignore;但各分支有自己的track list,且在分支生成后,各不相干。
管理命令:
A. git branch <新分支名字> [<起源分支>] [<起源的commit节点标识符或标签>] 创建新分支,但不切换
起源分支:默认是当前分支
起源commit节点:默认是起源分支的HEAD
B. git branch 察看文档库中所有分支
* gitk --all 能在GUI里显示出所有分支的演进图。如果不加-all参数,只显示当前分支的
* git log --graph --oneline --all --decorate 效果同上
C. git branch -d <待删分支名字> 删除分支。不能删除当前分支。只能删除已合并完的分支
支持参数:
1. -D 强制删除那个分支,放弃它的修改,不等合并
D. git checkout <分支名字> 切换分支。只有当前分支的修改已提交版本库,才有效
支持参数:
1. -b : 创建并切换到新分支
2. -f : 不判断当前分支修改是否已提交版本库,强制checkout的6个动作 (导致当前分支的修改丢失)
* 执行checkout之前,一定要做git status, 确保 :没提交的提交;当前分支的修改,不要误带入要切入的分支
* 正常的2种checkout的使用情况:
git checkout <分支> -->分支切换操作
git checout <commit节点> <文件名> -->取回当前分支某文件的历史版本
不正常的使用情况:
git checkout <commit节点> --> 这时 HEAD指向此commit节点形成detach head状态;当git add和git commit后,长出无名分支。
修复:
方法A: 删除此无名分支。 步骤:1. 命名当前分支 (git branch -m xxx),在这之前不要切换出去 2. 切换出去并删除此分支 (git checkout master; git branch -D xxx)
方法B: 合并无名分支到master上。步骤:1. 命名当前分支 (git branch -m xxx),在这之前不要切换出去 2. 切换出去,合并此分支 (git checkout master; git merge xxx)
E. git branch -m <新分支名字> 给当前分支改名字
F. git merge <分支名字B> 合并另一分支到当前分支,没冲突的情况下,动作包含了git add -A .和 git commit的动作 。
被合并分支B不会被删除,也不会有任何变化。当前分支A,会有新的HEAD, 他的父节点,HEAD^1是A自身的HEAD^, HEAD^2是B的HEAD。
因此,合并完成后,如果要撤销这个合并:不需要对分支B做任何操作;对分支A,消磁退到它的父节点:
git reset --hard HEAD^
合并没完成(由于冲突),撤销这个合并:
git merge --abort
* 相对分支生长点(叫base节点)的版本,没有修改发生的分支,无法作为被合并的分支角色。
* 两种合并机制:
1. fast-forward 即当前分支没有过修改。合并时,HEAD快进到被合并分支的HEAD。只有一个HEAD^
选项支持: --no-ff 放弃ff模式,为当前分支另生成HEAD,等效于3-way。
2. 3-way 即两个分支都发生过修改。合并室,生成新HEAD。3-way有两个HEAD^
G. git rebase <源分支> 把当前分支的base节点,推进到源分支的新HEAD节点。没冲突的情况下,动作包含了git add -A .和 git commit的动作 。
情景:
当前分支开发了太久,master分支都更新了。如果用git merge,会形成网状演进图,不清晰。所以使用rebase。
不是和此分支是团队共享的分支。
支持选项:
1. git rebase --skip 跳过某冲突文件
2. git rebase --abort 放弃未完成的演进
3. git rebase --continue fix冲突(同二)以后,手工git add ., 然后提交
二、冲突
原因:
3-way merge时,两个分支间存在修改同一个文件的情况
情景:
merge -- 两分支合并
rebase -- 源分支的新HEAD节点版本的更新运用到当前分支中,即当前分支base节点向前推进到源分支的新的HEAD
cherry-pick -- 指定commit节点 合并到working directory
revert -- 指定commit节点的前一节点版本 合并到working directory, 即让working directory的修改基于的base,向历史回退。是cherry-pick的原理一样,看上去和git reset --hard 一样,但reset不会创建新的commit节点,revert更不会删任何commit节点。
解决办法1 手工fix:
以上情景 探测到冲突时,会在冲突文件中加入<<<<<<<标识冲突开始,>>>>>>标识冲突结束。因此:
step 1. vi 这个冲突文件,fix共同修改的部分,删掉标识符。
step 2. git diff 对比确认
step 3. git add . 手工更新staging area
step 4. git commit -m “操作说明” 手工提交
解决办法2 mergetool:
前提条件:git配置mergetool --
git config --global merge.tool kdiff3
git config --global merge.tool.kdiff3.path “<安装路径>“
然后,git merge因为冲突失败后:
step 1. git mergetool --> kdiff3会自动作好上面方法1的step1,2
step 2. 人review kdiff的劳动成果,保存退出。mergetool会自动作上面方法1的step3.
step 3. git commit -m “操作说明” 手工提交
三、让合并来服务本地文件
这些动作,都会产生新的commit节点
操作命令:
A. git cherry-pick <commit节点的标识符或标签> 合并commit节点版本到本地的文件。没冲突的情况下,动作包含了git add -A .和 git commit的动作 。
支持选项::
1. git cherry-pick --abort 合并没完成(由于冲突),撤销这个合并
2. git cherry-pick --continue 合并冲突fix以后,手工做这个动作,等效于merge的冲突解决后的git commit。
冲突解决:
同merge,只是最后的手工git commit,被git cherry-pick --continue 替换。
B. git revert <commit节点的标识符或标签> 合并commit节点版本的前一个版本到本地的文件,动作包含了git add -A .和 git commit的动作 。
支持选项:
1. git revert --abort 合并没完成(由于冲突),撤销这个合并
冲突解决:
同merge。
四、如何反悔
对于merge、rebase (也可以是revert、cherry-pick)后,后悔想恢复版本库到操作之前。git reset --hard <commit节点标识符或标签>支持。
但关键是找到操作之前的那个commit节点标识符。这里,需要reflog
操作命令:
git reflog <HEAD或分支名称> 列出该分支的所有的commi节点和对它对应的操作, 靠上的是新的
git reset --hard <commit节点标识符或标签>