Git 分支详解

目录

分支操作

分支合并

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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值