Git 分支详解

mac2022-06-30  12

目录

分支操作

分支合并

merge:Fast forward、no-ff、squash

rebase

cherry-pick

分支冲突

简单分支管理的冲突解决

多人协作时的冲突解决

远程分支

高级合并

分支开发工作流:长期分支、特性分支


分支:本质是指向提交对象的可变指针,实质上是包含所指提交对象校验和的文件。创建分支即为相应的提交对象创建一个可以移动的新指针,相当于往一个文件中写入41个字节(40个字符的哈希值和1个换行符)

HEAD指针:指向当前所在的本地分支(指向指针的指针或别名)

分支操作

创建分支:git branch <name>

查看分支:git branch

-a:查看所有分支(本地、远程)

-r:查看远程分支

       -v:查看每个分支的最后一次提交

       --merged或--no-merge:过滤已经或者尚未合并到当前分支的分支

       -f:强制移动分支位置

切换分支:git checkout <name>

创建+切换分支:git checkout -b <name>

合并指定分支到当前分支:git merge <name>

删除分支:git branch -d <name>

分支重命名:git branch -m oldname newname

➜ clone_repository git:(master) git branch //先查看当前分支 ➜ clone_repository git:(master) git checkout -b dev //创建分支dev并转换到dev Switched to a new branch 'dev' ➜ clone_repository git:(dev) git branch //再次查看分支 ➜ clone_repository git:(dev) echo "change file1 on branch dev" >> file1 //在dev修改file1 ➜ clone_repository git:(dev) ✗ git add file1 //在dev分支add ➜ clone_repository git:(dev) ✗ git commit -m "change file1 on dev" //在dev提交 [dev f415b36] change file1 on dev 1 file changed, 1 insertion(+) ➜ clone_repository git:(dev) git checkout master //回到master分支 Switched to branch 'master' Your branch is up to date with 'origin/master'. //origin与master是一致的(因为是在dev修改,master没有修改) ➜ clone_repository git:(master) cat file1 //可以看到master分支中file1没变 new file in the clone repository ➜ clone_repository git:(master) git merge dev //将dev分支与master合并 Updating d87fc27..f415b36 Fast-forward file1 | 1 + 1 file changed, 1 insertion(+) ➜ clone_repository git:(master) git branch -d dev //删除dev分支 Deleted branch dev (was f415b36).

 

分支合并

在git中合并分支有三种方法:merge,rebase,cherry-pick

merge:Fast forward、no-ff、squash

Fast forward:当你试图合并两个分支时,如果顺着一个分支走下去能够到达另一个分支,则合并时直接将指针向前推进(指针右移),因为没有需要解决的分歧——这就叫做 “快进(fast-forward)”

三方合并:虽然出现分叉,但是各自改动的内容不冲突时,git会使用两个分支的末端所指的快照以及这两个分支的共同祖先,做一个简单的三方合并,即做一个新的快照并自动创建一个新的合并提交指向它

no-ff:禁用快速合并。接受被合并的分支上的所有工作,在master分支新建一个commit节点完成合并

squash:在当前分支新建一个commit节点,与no-ff唯一区别是不保留对合入分支的引用

git merge --no-ff branch:no-ff模式合并

git merge --squash branch:--squash选项接受被合并的分支上的所有工作,并将其压缩至一个变更集,使仓库变成一个真正的合并发生的状态,不会真的生成一个合并提交(即没有第二父提交)

git merge --abort:退出合并,尝试恢复到运行合并命令前到状态,当工作目录中有未储藏、未提交当修改时,不能完美处理

➜ clone_repository git:(master) git merge dev // fast-forward Updating bb3bca3..d4ed2c2 Fast-forward file | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 回退 ➜ clone_repository git:(master) git reset --hard HEAD^ ... ➜ clone_repository git:(master) ✗ git merge --no-ff dev // no-ff,//需要交互式地输入commit信息 Merge made by the 'recursive' strategy. file | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) ➜ clone_repository git:(master) git merge --no-ff -m "merge with no-ff" dev //带上-m选项在merge同时创建commit节点 Merge made by the 'recursive' strategy. file | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)

 

 

rebase

将当前分支的修改应用到变基操作的目标基底分支上,操作实质上丢弃一些现有的提交,然后相应地建立一些内容一样但是实际上不同的提交

变基的原则:不要对在你的仓库外有副本的分支执行变基。(只在从未推送至公共仓库的提交上执行变基命令)

因为别人可能基于这个分支的提交进行了后续工作,若此时应用变基会丢弃对方开发所基于的某些提交,若出现此情况,可以利用变基解决,即将自己的分支变基到对方变基后到分支上,或者用git pull --rebase命令

(1)正常rebase:没有冲突,比如两个分支不修改同一个文件,比如各自分支建立新文件或修改不同文件

➜ clone_repository git:(dev) touch dev_file ➜ clone_repository git:(dev) ✗ git add dev_file ➜ clone_repository git:(dev) ✗ git commit -m "dev add dev_file" [dev 0083bea] dev add dev_file 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 dev_file ➜ clone_repository git:(dev) vi dev_file ➜ clone_repository git:(dev) ✗ git add dev_file ➜ clone_repository git:(dev) ✗ git commit -m "dev change dev_file" [dev ff56952] dev change dev_file 1 file changed, 1 insertion(+) ➜ clone_repository git:(dev) git checkout master Switched to branch 'master' ➜ clone_repository git:(master) touch master_file ➜ clone_repository git:(master) ✗ git add master_file ➜ clone_repository git:(master) ✗ git commit -m "master_file" [master 89deaa4] master_file 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 master_file ➜ clone_repository git:(master) git log --graph --oneline --all //分支出现分叉 ➜ clone_repository git:(master) git checkout dev Switched to branch 'dev' ➜ clone_repository git:(dev) git rebase master //到dev变基 First, rewinding head to replay your work on top of it... Applying: dev add dev_file Applying: dev change dev_file ➜ clone_repository git:(dev) git log --graph --oneline --all //变基后的分支 ➜ clone_repository git:(dev) git checkout master Switched to branch 'master' ➜ clone_repository git:(master) git merge dev //快进合并将master移动到最新提交 Updating a7eaae4..98f52bb Fast-forward dev_file | 1 + 1 file changed, 1 insertion(+) create mode 100644 dev_file ➜ clone_repository git:(master) git log --graph --oneline --all //merge后的master

 

(2)有冲突的rebase:

➜ clone_repository git:(master) git checkout -b dev //在dev修改file:改了最后一行 Switched to branch 'dev' ➜ clone_repository git:(dev) vi file ➜ clone_repository git:(dev) ✗ git add file ➜ clone_repository git:(dev) ✗ git commit -m "dev change file" [dev 29eaa63] dev change file 1 file changed, 1 insertion(+), 1 deletion(-) ➜ clone_repository git:(dev) git checkout master //在master修改file:删了最后一行 Switched to branch 'master' Your branch is ahead of 'origin/master' by 4 commits. (use "git push" to publish your local commits) ➜ clone_repository git:(master) vi file ➜ clone_repository git:(master) ✗ git add file ➜ clone_repository git:(master) ✗ git commit -m "master change file" [master 9de3c25] master change file 1 file changed, 1 deletion(-) ➜ clone_repository git:(master) git log --oneline --decorate --graph --all //查看分支可知分叉了 ➜ clone_repository git:(master) git checkout dev //回到dev变基 Switched to branch 'dev' ➜ clone_repository git:(dev) git rebase master First, rewinding head to replay your work on top of it... Applying: dev change file Using index info to reconstruct a base tree... M file Falling back to patching base and 3-way merge... Auto-merging file CONFLICT (content): Merge conflict in file //提示出现冲突 error: Failed to merge in the changes. Patch failed at 0001 dev change file hint: Use 'git am --show-current-patch' to see the failed patch //提示用此命令查看失败的补丁(修改) Resolve all conflicts manually, mark them as resolved with "git add/rm <conflicted_files>", then run "git rebase --continue". //手动解决冲突然后git add或者git rm删除冲突文件后再git rebase --continue You can instead skip this commit: run "git rebase --skip". To abort and get back to the state before "git rebase", run "git rebase --abort". ➜ clone_repository git:(9de3c25) ✗ git am --show-current-patch //查看failed patch ➜ clone_repository git:(9de3c25) ✗ vi file //手动解决冲突 ➜ clone_repository git:(9de3c25) ✗ git add file //mark them as resolved ➜ clone_repository git:(9de3c25) ✗ git rebase --continue //解决冲突后继续rebase Applying: dev change file ➜ clone_repository git:(dev) git log --oneline --decorate --graph --all //查看变基后的分支图 ➜ clone_repository git:(dev) git checkout master Switched to branch 'master' ➜ clone_repository git:(master) cat file //回到master分支可知file停留在rebase的状态(对应pro git page 84的C3状态) new file in the clone repository ➜ clone_repository git:(master) git merge dev //快进合并后,master于dev的提交同步,见下图4 Updating 9de3c25..6ab9a0b Fast-forward file | 1 + 1 file changed, 1 insertion(+)

 

cherry-pick

"复制"一个提交节点并在当前分支做一次完全一样的新提交

git cherry-pick <commit id>:单独合并一个提交

git cherry-pick -x <commit id>:同上,不同点为保留原提交者信息

git cherry-pick <start-commit-id>..<end-commit-id>:把<start-commit-id>到<end-commit-id>之间(左开右闭,不包含start-commit-id)的提交cherry-pick到当前分支

git cherry-pick <statr-commit-id>^..<end-commit-id>:有"^"标志的表示把<start-commit-id>到<end-commit-id>之间(闭区间,包含start-commit-id)的提交cherry-pick到当前分支

 

分支冲突

出现冲突:在两个不同分支中,对同一个文件的同一个部分进行了不同的修改,此时git无法自动干净地合并分支,就必须先解决冲突,然后再提交,合并完成。

解决冲突:把Git合并失败的文件手动编辑为我们希望的内容,再提交。

简单分支管理的冲突解决

当我们在master和feature1两个分支上都对同一个文件进行了修改提交后,

解决办法:

A.  在文件中选择一个版本保留,删除有冲突不需要保存的部分内容后,保存文件

B.  再次提交(add,commit)

多人协作时的冲突解决

当你的小伙伴将本地dev上的一个文件push到origin/dev上后,而碰巧你也修改了同一个文件打算push到远程,但此时会和你的小伙伴的提交有冲突。

解决办法:

A.用git pull把最新的提交从origin/dev上抓取下来(第一次pull可能会失败,git可能会提示你没有指定本地的dev和origin/dev的链接,一般根据提示采用$ git branch--set-upstream dev origin/dev 建立链接,再次使用gtit pull)

B.现在你已经成功的拉取了远程最新的提交,但是合并有冲突,需要像上一例中手动解决,在本地合并再次提交(commit)

C.提交成功后就可以push到远程啦

➜ clone_repository git:(master) git branch feature1 //新建分支feature1 ➜ clone_repository git:(master) git checkout feature1 //切换分支 Switched to branch 'feature1' ➜ clone_repository git:(feature1) echo "Creating a new branch is quick AND simple." >> file1 //在feature1修改并提交 ➜ clone_repository git:(feature1) ✗ git add file1 ➜ clone_repository git:(feature1) ✗ git commit -m "AND simple" [feature1 18a91fc] AND simple 1 file changed, 1 insertion(+) ➜ clone_repository git:(feature1) git checkout master //回到master Switched to branch 'master' Your branch is up to date with 'origin/master'. ➜ clone_repository git:(master) echo "Creating a new branch is quick & simple." >> file1 //在master修改 ➜ clone_repository git:(master) ✗ git add file1 ➜ clone_repository git:(master) ✗ git commit -m "& simple" [master 6f99fd1] & simple 1 file changed, 1 insertion(+) ➜ clone_repository git:(master) git merge feature1 //试图合并 Auto-merging file1 CONFLICT (content): Merge conflict in file1 //产生冲突 Automatic merge failed; fix conflicts and then commit the result. ➜ clone_repository git:(master) ✗ git status On branch master Your branch is ahead of 'origin/master' by 1 commit. (use "git push" to publish your local commits) You have unmerged paths. (fix conflicts and run "git commit") (use "git merge --abort" to abort the merge) Unmerged paths: (use "git add <file>..." to mark resolution) both modified: file1 //两边都修改了的file1 no changes added to commit (use "git add" and/or "git commit -a") ➜ clone_repository git:(master) ✗ vi file1 //这种情况可以根据需求选择master上的内容或者feature1的内容,或者是用全新的内容来代替,然后再add和commit ➜ clone_repository git:(master) ✗ git add file1 ➜ clone_repository git:(master) ✗ git commit -m "conflict fixed" [master 8db0d94] conflict fixed ➜ clone_repository git:(master) git log --graph --pretty=oneline --abbrev-commit 显示分支合并情况

 

 

远程分支

远程引用:对远程仓库对引用(指针),包括分支、标签等

远程分支相关命令:

新建远程分支

git push origin br1:br2(先新建本地分支,然后推送至远程分支,不过此时br1并没有跟踪远程的br2)

查看分支:git branch(以下选项可组合)

-a:所有分支

-r:远程分支

-vv:本地分支及其跟踪的分支

删除远程分支

git branch -d -r [remote/branch] //eg:origin/test

git push origin --delete [branch] //删除远程分支。从服务器上移除这个指针,服务器一般会保留数据一段时间直到垃圾回收,因此容易恢复

git push origin :[branch] //借用引用规格删除远程分支(origin后面有个空格)

git fecth:拉取远程仓库有而本地没有的数据(拉取到本地的远程跟踪分支remote/branch上),不会自动修改、合并当前工作目录的内容,需要手动合并

git pull:自动抓取然后合并远程分支到当前分支,大多数情况下其作用为git fetch紧跟一个git merge。查找当前分支所跟踪的远程分支,抓取数据尝试合并

git push

git push [remote] [branch]:将当前本地分支推送到远程仓库

git push [remote] [branch1]:[branch2] :将本地的 branch分支推送到远程仓库上的 branch2 分支

 

远程跟踪分支:以remote/branch命名的本地分支,是远程分支状态的引用,记录的是上一次与远程仓库通信时,各个远程分支的状态。当本地与远程进行通信操作(fetch,push,pull)时,它会自行移动。clone远程仓库时,git的clone命令会自动将仓库命名为origin,拉取它的所有数据,创建一个在本地名为origin/master的远程跟踪指针,指向origin仓库的master分支,同时git也会给你一个本地master分支,与origin的master分支指向同处。

跟踪分支:从一个远程跟踪分支检出一个本地分支会自动创建“跟踪分支”,它是与远程分支有直接关系的本地分支,在跟踪分支上使用git pull命令时,git能自动识别去哪个服务器上抓取、合并到那个分支。当克隆一个仓库时,它通常会自动地创建一个跟踪 origin/master 的 master 分支

git checkout -b [bra] [remote/brb]:创建一个跟踪remote/brb的分支bra

git branch --set-upstream-to=[remote/brb] [bra] : 显式地设置已有的本地分支bra 跟踪远程分支brb,若不指定bra则设置当前分支跟踪brb

git branch -u [remote/brb] [bra]:修改bra正在跟踪的上游分支为brb,若不指定bra则修改当前分支的上游分支

git branch -vv:查看设置的所有分支,会列出所有本地分支,给出其所跟踪的远程分支,以及二者之间领先落后关系,由于对比的是从服务器最后一次抓取的数据,所以使用此命令前需要抓取所有远程仓库:git fetch --all; git branch -vv

➜ test git:(master) git branch -a //当前所有分支 ➜ test git:(master) git branch -vv //当前本地分支及其所跟踪分支的情况(下图2:master跟踪远程master,teset没有跟踪远程分支) ➜ test git:(master) git checkout -b dev origin/dev //创建跟踪origin/dev的本地dev分支 Branch 'dev' set up to track remote branch 'dev' from 'origin'. Switched to a new branch 'dev' ➜ test git:(dev) git branch -vv //新增dev跟踪origin/dev ➜ test git:(dev) git branch --set-upstream-to=origin/master test //设置test的上游分支 Branch 'test' set up to track remote branch 'master' from 'origin'. ➜ test git:(dev) git branch -vv //test跟踪origin/master且落后一个提交 ➜ test git:(dev) git branch -u origin/master //修改dev跟踪origin/master Branch 'dev' set up to track remote branch 'master' from 'origin'. ➜ test git:(dev) git branch -u origin/dev test //修改test跟踪origin/dev Branch 'test' set up to track remote branch 'dev' from 'origin'. ➜ test git:(dev) git branch -vv //下图5:可见dev落后 ➜ test git:(dev) git fetch //从origin/master拉取更新到dev的远程跟踪分支origin/master ➜ test git:(dev) git merge origin/master //本地dev分支merge远程跟踪分支origin/master拉取到到内容 Updating 9f16864..c2b4224 Fast-forward file | 1 + 1 file changed, 1 insertion(+) ➜ test git:(dev) git branch -a -vv ➜ test git:(dev) git branch -d -r origin/dev //删除远程分支dev Deleted remote-tracking branch origin/dev (was 9f16864). ➜ test git:(dev) git branch -a -vv

 

➜ test git:(dev) echo "1" >> file //在dev修改file并提交,此时dev比其远程分支提前一个提交,需要push ➜ test git:(dev) ✗ git commit -a -m "1" [dev 50aa179] 1 1 file changed, 1 insertion(+) ➜ test git:(dev) git push //直接git push发生错误 fatal: The upstream branch of your current branch does not match //错因:上游分支和当前分支名字不匹配 the name of your current branch. To push to the upstream branch on the remote, use git push origin HEAD:master //要按照此格式push To push to the branch of the same name on the remote, use git push origin HEAD To choose either option permanently, see push.default in 'git help config'. ➜ test git:(dev) git push origin dev:master ... ➜ test git:(dev) git status On branch dev Your branch is up to date with 'origin/master'. nothing to commit, working tree clean ------------------------------------------------------ ➜ test git:(dev) git checkout master //现在回到master分支,注意dev和master都跟踪远程都master分支 Switched to branch 'master' Your branch is behind 'origin/master' by 1 commit, and can be fast-forwarded. //这里提示本地master落后了 (use "git pull" to update your local branch) ➜ test git:(master) echo "2" >> file //先不管master落后的事实继续修改file并提交 ➜ test git:(master) ✗ git commit -a -m "2" [master 08ad215] 2 1 file changed, 1 insertion(+) ➜ test git:(master) git push //master推送失败,因为当前分支behind远程部分,要git pull To ssh://git.sankuai.com/~tangxiaoling02/test.git ! [rejected] master -> master (non-fast-forward) error: failed to push some refs to 'ssh://git@git.sankuai.com/~tangxiaoling02/test.git' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Integrate the remote changes (e.g. hint: 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details. ➜ test git:(master) git pull //由于dev和master都修改了file,pull发生冲突,需要手动解决 Auto-merging file CONFLICT (content): Merge conflict in file Automatic merge failed; fix conflicts and then commit the result. ➜ test git:(master) ✗ vi file //修改冲突并提交 ➜ test git:(master) ✗ git commit -a -m "file" [master b11b337] file ➜ test git:(master) git push //再次push成功 ...

 

 

高级合并

做一次可能有冲突的合并前,尽量保证工作目录是干净的(有未完成的工作可以提交临时分支或者储藏起来)

忽略空白

git merge -Xignore-all-space:忽略所有已有的空白

git merge -Xignore-space-change:忽略空白修改

 

手动处理文件后再合并

进入合并冲突状态后,得到文件的自己版本(ours)、对方版本(theirs)、共同版本(base)的拷贝,想手动修复其中一个,并为其重新合并。

Git 在索引中存储了文件的自己版本、对方版本、共同版本,在 “stages” 下每一个都有一个数 字与它们关联。 Stage 1是它们共同的祖先版本(分叉开始的位置),stage 2是你的版本,stage 3来自于 MERGE_HEAD,即你将要合并入的版本(“theirs”)

git show :1:file > filecommon:可用此类似命令将三个版本的文件拷贝出来

git ls-files -u:得到这三个版本文件的Git glob对象的SHA-1值

(1)对要合并入的文件在真正合并前运行dos2unix程序:dos2unix their(因为待合入的分支对文件做了unix2dos修改,所以这里手动修复以消除空白字符的改动)

(2)手动修复后重新合并文件:git merge-file -p ourfile commonfile theirfile > mergefile

git diff --ours:比较合并结果与在你分支的文件版本的区别

git diff --theirs:比较合并结果与在对方分支的文件版本的区别

git diff --base:查看文件在两边是如何改动的

一下三幅图片在书中的场景是:在whitespace分支将Unix换行符改为Dos换行符(都是空白字符)并把“world”改为“mundo”;在master分支加了“# prints out a greeting”

这里的-b用来去除空白

 

检出冲突

git checkout --conflict:重新检出文件并替换合并冲突标记

git checkout --conflict=diff3 filename:给出ours、theirs、base版本的冲突标记,如下图(--conflict=merge为默认选项)

git checkout --ours:无需合并,仅留下ours的修改版本

git checkout --theirs:无需合并,仅留下theirs的修改版本

 

 

合并日志

利用日志回顾历史找出不同分支修改同一文件的原因。git log找出某一分支的所有独立提交,详见前面的双点、多点、三点

git log --oneline --left-right --merge:只显示任何一边接触了合并冲突文件的提交

git log -p:按补丁格式显示各个更新之间的差异

 

撤销合并

合并之后不想要引入的修改了

方法1:修复引用

git reset --hard HEAD~:重置分支指向。因为会改写历史,所以若有人已经使用了这个合并提交,或者合并后又有新的提交,不建议使用

方法2:还原提交

git revert:撤销某个提交的修改

此时若又后悔了,想再合并topic分支就找不到C3、C4,无法引入其修改了,即使在C4后面再有新提交C7,合并topic也只能引入C7的修改

解决这个问题:用 git revert ^M 撤销^M这个提交,然后再git merge topic合并分支。如下图2,此图中M和^M抵消了,^^M引入了C3、C4的修改,C8在^^M基础上引入了C7的修改

其他类型的合并

(1)发生冲突时直接选择一边

git merge -Xours branch

git merge -Xtheirs branch

(2)ours合并策略:记录一个以两边分支作为父节点的合并提交,但实质上不关心合并入的分支,简单的把当前分支的代码当作合并结果记录下来

(3)子树合并:git read-tree 读取一个分支的根目录树到当前到暂存区和工作目录中

 

 

分支开发工作流:长期分支、特性分支

长期分支:在整个项目开发周期的不同阶段,可以拥有多个开放的分支,可以定期地把某些特性分支合并入其他分支中。比如在master分支上保留完全稳定的代码,然后用develop或next等平行分支做后续开发或者稳定性测试,等这些分支达到稳定状态就可以合并入master分支。不同分支具有不同级别的稳定性

特性分支:短期分支,用来实现单一特性或其相关工作。将工作分散到不同流水线中,不同流水线中的每个分支都仅与其目标特性相关——使你能快速并完整地进行上下文切换

 

最新回复(0)