12 KiB
12 KiB
Git Branch
-
Git原理
- 在git中,并不通过修改集或者差异来存储数据,而是按照快照的方式来存储数据。
- 当在执行一次commit时,git会存储commit object。commit object中包含了一个指向快照的指针。
- commit object包含如下信息:
- 作者名称和email地址
- 为commit指定的提交信息(comment)
- 指向快照(snapshot)的指针
- 指向之前提交的指针(parent或parents):
- 对于正常的提交,有0个(init commit)或者一个parent
- 对于合并多个分支的commit,有多个parent
- git 提交的具体细节:
- 对于将文件stage的操作,git会为每个staged的文件计算一个checksum,并且将文件的版本存储到git仓库中(文件版本将会被当做blob访问),并且将checksum添加到staging area中
- 当调用git commit时,git为项目的每个子目录计算checksum,,并且将它们作为树形对象存储到git仓库中。
- git稍后会创建一个commit object,commit object中包含元数据和指向项目树对象的指针,树结构中指明了目录下文件指向哪个blob。由于快照只是指向树对象的指针,故而快照可以被重复创建。
- snapshot会包含一个指向它前面快照的指针
- git branch的本质:
- 在git中,git branch实际上只是一个指向commmit的可移动指针。
- 对于每次提交,master branch的指针都会自动移动向最新的提交。
- HEAD指针:
- HEAD指针指向当前所在的分支,可以通过git log指令查看HEAD指针当前和哪些分支的指针相同
- 在git中,并不通过修改集或者差异来存储数据,而是按照快照的方式来存储数据。
-
Git Branch Operation
- 创建新的branch
- 创建新的分支实际上是创建了一个可供移动的新指针,指向了当前所在的提交(HEAD指针所指向的提交)
- 可以通过git branch来创建一个新的分支
# 创建一个指向当前快照的新branch指针 $ git branch <branch-name> or # 创建一个新分支并且移动到该分支 $ git checkout -b <new-branch-name> # 创建一个新分支并移动到该分支 $ git switch -c <new-branch-name>
- 在现有的分支之间进行切换
- 在分支之间进行切换实际上是把HEAD指针指向别的branch指针
- 可以通过git checkout来实现分支之间的切换
- 在调用git checkout在分支之间进行切换时,工作目录中的内容也会被还原到目标分支的版本,故而在切换之前需要对尚未提交的任务进行提交,否则未保存的任务会丢失
# 在分支之间进行切换 # * 在切换分支时,会做如下事情 # * 将HEAD指针指向想要切换到的分支 # * 将working directory中的文件还原到目标分支的快照状态 $ git checkout <branch-name> or $ git switch <branch-name> # 移动到上一个分支 $ git switch -
- 在分支下调用git log
- git log只会显示当前分支下的提交历史,不会显示其他分支下的提交历史。
- 如果要显示指定分支的提交历史,为git log指定branch-name
$ git log <branch-name> - 如果想要显示所有分支下的提交历史,可以为git log添加-all选项
$ git log --all
- 如果要显示指定分支的提交历史,为git log指定branch-name
- git log只会显示当前分支下的提交历史,不会显示其他分支下的提交历史。
- 显示git分支状态图
- 通过为git log指定--graph选项,可以输出git分支的状态图
$ git log --all --pretty=oneline --graph
- 通过为git log指定--graph选项,可以输出git分支的状态图
- 合并分支
- 可以通过git merge操作来合并两个分支
- 如果想要让当前分支合并一个分支,并且当前分支对于目标分支来说在commit history中是可达的,那么git会直接让当前分支的指针移动到目标分支的最后一次提交位置。这被称之为“fast-forward”机制。
# 可以通过git merge命令来对分支进行合并 # 如果合并的目标分支的git log记录中当前分支是可达的,那么 # 会直接将当前分支的指针移动到目标分支指向的位置 $ git merge <branch-name>- 如果当前分支的last commit并不是目标分支的祖先,那么合并分支需要通过创建一个新的snapshot,新的snapshot存储合并分支的结果,并且创建一个commit指向snapshot。新的commit具有两个parent。
- 在合并后,如果被合并的分支也不再被需要,那么同样可以通过git branch -d操作来删除旧的分支
- 可以通过git merge操作来合并两个分支
- 删除不再被需要的分支
- 通常,当创建一个hotfix分支来修复bug时,如果bug已经被修复,切换到master分支进行merge操作后,hotfix分支不再被需要,可以调用git branch -d来删除分支
# 删除不再被需要的分支 # * 当删除已经被合并的分支时,可使用-d选项 # * 如果待删除分支没有被合并,那么需要指定-D选项来强制删除 $ git branch -d <branch-name>
- 通常,当创建一个hotfix分支来修复bug时,如果bug已经被修复,切换到master分支进行merge操作后,hotfix分支不再被需要,可以调用git branch -d来删除分支
- 分支合并冲突
- 在合并时,相较于两个分支的共同祖先,如果两个分支修改了同一个文件的同一部分,那么文件的合并会发生冲突。
- 在发生合并冲突后,可以通过git status来检测冲突的细节
# 通过git status来检测合并冲突的细节 $ git status
- git分支管理
- 查看当前项目的分支,可以用git branch命令
# 查看当前项目的分支 $ git branch or # 查看当前项目的分支,并且列出每个分支的最后一次提交 $ git branch -v # 可以通过--merged或者--no-merged选项来对已经或者还未合并到当前 # 分支的分支进行过滤 # 列出已经合并到当前分支的分支 $ git branch --merged # 列出尚未被合并到当前分支的分支 $ git branch --no-merged- 对于git branch --merged列出的分支,除了当前分支外都可以删除,因为已经对其进行了合并操作,可以无丢失的删除该类分支
- 对分支进行重命名
- 可以通过git branch --move来对本地分支进行重命名
# 重命名本地分支 $ git branch --move <old-branch-name> <new-branch-name>- 对远程仓库的分支进行重命名操作
- 可以通过git push --set-upstream origin new-name来对远程仓库的分支进行重命名操作
- 执行该操作后,远程仓库中旧分支名仍然会存在,需要手动的删除
# 重命名远程分支 $ git push --set-upstream <remote> <new-name> # 删除旧分支 $ git push <remote> --delete <old-name> - 可以通过git push --set-upstream origin new-name来对远程仓库的分支进行重命名操作
- 查看当前项目的分支,可以用git branch命令
- 创建新的branch
-
远程分支
- 远程tracking分支
- 远程tracking分支是对远程分支状态的引用。远程tracking分支虽然是本地分支,但是你并不能对其进行移动。只有每次当你与服务器进行网络连接时,远程tracking分支才会移动。
- 远程tracking分支名字按照remote/branch-name的形式组成
- 在调用git clone方法时,会自动创建一个origin/master的远程tracking分支和一个本地的master分支。origin/master分支在之后没有和服务器通信的时间段内都不会被修改。
- 当调用git fetch origin操作后,位于服务端的信息被拉取到本地,并且origin/master也会被移动到和服务端相同的位置,此时origin/master和本地master分支进行合并可能会出现冲突的问题
- 如果有多个远程项目,那么属于不同项目的远程tracking分支通过remote shortname区分
# remote tracking branch in different remote server # * such as remote-1/ProjectName # * remote-2/ProjectName - 将本地分支推送到远程仓库
- 可以用git push操作将本地分支推送到远程分支
# 推送本地分支 $ git push <remote> <local-branch-name:remote-branch-name> # 如果远程分支名称和本地分支相同,那么可以简化为如下形式 $ git push <remote> <branch-name> - 将远程分支拉取到本地
- 当调用git fetch方法将远程仓库拉取到本地时,对于远程的分支hotfix,拉取到本地后只会有一个remote/hotfix的远程tracking分支,并不会创建一个名为hotfix的本地分支。
- 如果想要基于远程tracking分支的基础上创建一个本地分支,可以调用如下命令
# 创建一个本地分支,并且将开始位置设置为remote/branch $ git checkout -b 'branch-name' remote/branch or $ git switch -c 'branch-name' remote/branch - 跟踪分支
- 根据远程tracking分支创建分支时,会自动的创建一个跟踪分支。
- 跟踪分支是一个和远程分支相关联的本地分支,当位于跟踪分支时,调用git pull时,会自动知道应该从哪个远程服务器进行拉取,并且知道整合哪个分支
- 当调用git clone时,会自动创建一个本地的跟踪分支master,master分支和远程分支origin/master分支相关联
- 创建一个跟踪分支,除了使用git checkout -b 'branch-name' 'remote/branch'外,还可以使用--track来指定
# 创建跟踪分支并且切换到该分支 $ git checkout --track remote/branch # 甚至,当checkout指定的名称 # * 本地仓库中并不存在该名字的分支 # * 该名字的分支在远程仓库中存在 # 那么,调用git checkout <branch-name>就会直接创建跟踪分支 $ git checkout <branch-name> - 如果已经存在一个本地分支,并且想将其跟踪远程分支;或者想要对一个本地分支跟踪的远程分支进行修改,可以使用git branch -u来完成
# 添加或者修改本地分支绑定到的远程分支 # * 通过指定-u选项或者--set-upstream-to选项 $ git branch -u remote/branch [local-branch] or $ git branch --set-upstream-to remote/branch [local-branch] - 查看各个本地分支绑定的远程分支
- 在git remote show中查看
# 通过git remote show查看 $ git remote show <remote-name> - 通过git branch -vv查看
# 通过git branch -vv查看 # * git branch -vv并不会连接服务器,而是会从上次连接服务器缓存的 # 数据中读取 # * 如果想要获得实时的值,在执行该命令前最好加上git fetch --all $ git branch -vv
- 在git remote show中查看
- git pull和git fetch区别
- git fetch只是从远程服务端拉取数据,但是并不会自动合并,需要自己手动合并
- git pull,从服务端拉取数据后会自动合并
- git删除远程仓库的分支
- 可以通过git push remote --delete branch来删除远程的分支
# 删除位于远程服务端的分支 $ git push <remote> --delete <branch-name>
- 可以通过git push remote --delete branch来删除远程的分支
- 远程tracking分支
-
Git Rebasing
- 和merge操作不同,对于rebase操作,如果想把A快照基于B快照rebase,此时会找到A和B的最近公共祖先快照,并且把快照A基于祖先快照的修改记录下来,并且将该修改基于快照B再进行执行产生一个新的快照C,并且将C的parent设置为B。之后会将指向快照B的指针通过fast-forward机制修改指向快照C。
- 相对于merge操作,rebase操作产生的结果和merge操作合并后快照的结果完全相同。但是,相对于merge操作,rebase操作所产生的git log是一条直线,其 历史会更加的简洁。
- git rebase操作:
# git rebase <target-branch> # 其会找到当前分支和目标分支的公共祖先快照,并且将当前分支的快照 # 相对于公共祖先的快照变化基于target分支的最后提交replay # 然后将当前分支的节点指向到产生的新快照 $ git rebase <target-branch>- 在调用git rebase时,和git merge一样,可能会出现冲突的问题。故而,在出现冲突后,需要手动解决冲突,并且调用git rebase --continue命令。
- git rebase复杂操作
# 调用git rebase,可以将一个分支基于另一个分支的改变replay到第三分支上 $ git rebase --onto target-branch base-branch topic-branch