在入门篇我们简单地讲解了Git的基本使用方法。在高级篇呢,我们首先要讲解一下分支的使用方法和操作。
在开发软件时,可能有多人同时为同一个软件开发功能或修复BUG,可能存在多个Release版本,并且需要对各个版本进行维护。
所幸,Git的分支功能可以支持同时进行多个功能的开发和版本管理。
什么是分支?
分支是为了将修改记录的整体流程分叉保存。分叉后的分支不受其他分支的影响,所以在同一个数据库里可以同时进行多个修改。
下面是使用分支进行作业的图示。
为了不受其他开发人员的影响,您可以在主分支上建立自己专用的分支。完成工作后,将自己分支上的修改合并到主分支。因为每一次提交的历史记录都会被保存,所以当发生问题时,定位和修改造成问题的提交就容易多了。
master分支
在数据库进行最初的提交后, Git会创建一个名为master的分支。因此之后的提交,在切换分支之前都会添加到master分支里。
分支的运用
在Git您可以自由地建立分支。但是,要先确定运用规则才可以有效地利用分支。
这里我们会介绍两种分支 (“Merge分支”和 “Topic分支” ) 的运用规则。
Merge分支
Merge分支是为了可以随时发布release而创建的分支,它还能作为Topic分支的源分支使用。保持分支稳定的状态是很重要的。如果要进行更改,通常先创建Topic分支,而针对该分支,可以使用Jenkins之类的CI工具进行自动化编译以及测试。
通常,大家会将master分支当作Merge分支使用。
Topic分支
Topic分支是为了开发新功能或修复Bug等任务而建立的分支。若要同时进行多个的任务,请创建多个的Topic分支。
Topic分支是从稳定的Merge分支创建的。完成作业后,要把Topic分支合并回Merge分支。
事前预备
首先建立一个新目录,并在里面建立一个空数据库。这里我们创建一个名为haha
的目录。
$ mkdir haha
$ cd haha
$ git init
Initialized empty Git repository in C:/Users/32923/Desktop/workspace/haha/.git/
在 haha
目录中创建一个名为myfile.txt 的档案 , 然后提交
$ echo '学习Git' >> myfile.txt
$ git add myfile.txt
$ git commit -m "first commit"
[master (root-commit) dcddfdc] first commit
1 file changed, 1 insertion(+)
create mode 100644 myfile.txt
目前的历史记录是这样的。
建立分支
创建名为issue1的分支
你可以通过 branch
命令来创建分支。
$ git branch issue1
不指定参数直接执行branch命令的话,可以显示分支列表。前面有*的就是现在的分支。
$ git branch
issue1
* master
目前的历史记录是这样的。
分支的切换
若要切换作业的分支,就要进行checkout操作。进行checkout时,git会从工作树还原向目标分支提交的修改内容。checkout之后的提交记录将被追加到目标分支。
HEAD
HEAD指向的是现在使用中的分支的最后一次更新。通常默认指向master分支的最后一次更新。通过移动HEAD,就可以变更使用的分支。
切换分支
若要在新建的issue1分支进行提交,需要切换到issue1分支。
要执行checkout命令以退出分支。
$ git checkout issue1
Switched to branch 'issue1'
目前的历史记录是这样的。
提示
在checkout命令指定 -b选项执行,可以创建分支并进行切换。
$ git checkout -b <branch>
在切换到issue1分支的状态下提交,历史记录会被记录到issue1分支。在myfile.txt添加add命令的说明后再提交。
$ echo 'add把变更录入到索引中' >> myfile.txt
$ git add myfile.txt
$ git commit -m "添加add的说明"
[issue1 149ff63] 添加add的说明
1 file changed, 1 insertion(+)
目前的历史记录是这样的。
stash 暂存
还未提交的修改内容以及新添加的文件,留在索引区域或工作树的情况下切换到其他的分支时,修改内容会从原来的分支移动到目标分支。
但是如果在checkout的目标分支中相同的文件也有修改,checkout会失败的。这时要么先提交修改内容,要么用stash暂时保存修改内容后再checkout。
stash是临时保存文件修改内容的区域。stash可以暂时保存工作树和索引里还没提交的修改内容,您可以事后再取出暂存的修改,应用到原先的分支或其他的分支上。
有未提交的更改时切换分支
如果当前工作树中的有未提交的到数据库的内容
而此时又需要切换到其他的分支进行操作, 但是当前分支的工作又没有完成, 不能提交到数据库
如果直接切换到其他分支, 会带着当前分支未提交的内容一起切换到其他分支
来看一下,修改 myfile.txt
的内容:
$ echo 'stash' >> myfile.txt
切换分支:
$ git checkout master
error: Your local changes to the following files would be overwritten by checkout:
myfile.txt
Please commit your changes or stash them before you switch branches.
Aborting
暂存未提交的修改
正确的做法是什么? 使用stash把当前分支的工作树中的更改内容暂存:
$ git stash
再次切换到其他分支, 并查看状态:
$ git checkout master
Switched to branch 'master'
$ git status
可以看到, 其他分支中未提交的内容的, 并没有跟随过来了
恢复暂存区未提交的修改
此时, 再次回到issue1分支, 如果恢复之前暂存的内容呢?执行命令即可:
$ git checkout issue1
Switched to branch 'issue1'
$ git stash pop
可以看到之前暂存的内容就恢复了, 可以继续开发了
最后, 如果不想要工作树中未提交的更改, 可以使用checkout命令撤销:
$ git checkout myfile.txt
Updated 1 path from the index
查看工作树状态:
$ git status
工作树是干净的, 已经没有任何改动了
分支的合并
完成作业后的topic分支,最后要合并回merge分支。合并分支有2种方法:使用merge或rebase。使用这2种方法,合并后分支的历史记录会有很大的差别。
merge
使用merge可以合并多个历史记录的流程。
如下图所示,bugfix分支是从master分支分叉出来的。
合并 bugfix分支到master分支时,如果master分支的状态没有被更改过,那么这个合并是非常简单的。 bugfix分支的历史记录包含master分支所有的历史记录,所以通过把master分支的位置移动到bugfix的最新分支上,Git 就会合并。这样的合并被称为fast-forward(快进)合并。
但是,master分支的历史记录有可能在bugfix分支分叉出去后有新的更新。这种情况下,要把master分支的修改内容和bugfix分支的修改内容汇合起来。
因此,合并两个修改会生成一个提交。这时,master分支的HEAD会移动到该提交上。
注意
执行合并时,如果设定了non fast-forward选项,即使在能够fast-forward合并的情况下也会生成新的提交并合并。
执行non fast-forward后,分支会维持原状。那么要查明在这个分支里的操作就很容易了。
rebase
跟merge的例子一样,如下图所示,bugfix分支是从master分支分叉出来的。
如果使用rebase方法进行分支合并,会出现下图所显示的历史记录。现在我们来简单地讲解一下合并的流程吧。
首先,rebase bugfix分支到master分支, bugfix分支的历史记录会添加在master分支的后面。如图所示,历史记录成一条线,相当整洁。
这时移动提交X和Y有可能会发生冲突,所以需要修改各自的提交时发生冲突的部分。
rebase之后,master的HEAD位置不变。因此,要合并master分支和bugfix分支,即是将master的HEAD移动到bugfix的HEAD这里。
注意
Merge和rebase都是合并历史记录,但是各自的特征不同。
merge
保持修改内容的历史记录,但是历史记录会很复杂。
rebase
历史记录简单,是在原有提交的基础上将差异内容反映进去。
因此,可能导致原本的提交内容无法正常运行。
您可以根据开发团队的需要分别使用merge和rebase。
例如,想简化历史记录,
- 在topic分支中更新merge分支的最新代码,请使用rebase。
- 向merge分支导入topic分支的话,先使用rebase,再使用merge。
合并分支
向master分支合并issue1分支的修改。
执行merge命令以合并分支。
$ git merge <commit>
该命令将指定分支导入到HEAD指定的分支。先切换master分支,然后把issue1分支导入到master分支。
$ git checkout master
Switched to branch 'master'
打开myfile.txt档案以确认内容,然后提交。
学习Git
已经在issue1分支进行了编辑上一页的档案,所以master分支的myfile.txt的内容没有更改。
$ git merge issue1
Updating dcddfdc..149ff63
Fast-forward
myfile.txt | 1 +
1 file changed, 1 insertion(+)
master分支指向的提交移动到和issue1同样的位置。这个是fast-forward(快进)合并。
打开myfile.txt档案,确认内容。
学习Git
add 把变更录入到索引中
已添加「add 把变更录入到索引中」。
删除分支
既然issue1分支的内容已经顺利地合并到master分支了,现在可以将其删除了。
在branch命令指定-d选项执行,以删除分支。
$ git branch -d <branchname>
执行以下的命令以删除issue1分支。
$ git branch -d issue1
Deleted branch issue1 (was b2b23c4).
issue1分支被删除了。您可以用branch命令来确认分支是否已被删除。
$ git branch
* master
注意:您必须离开你要删除的分支,才能删除这个分支。也就是不能删除当前所在的分支
提示
如果因一些原因不能删除分支, 可以使用-D参数强制删除分支:
$ git branch -D <branchname>
比如,您要删除的分支的历史家里和当前所在分支的历史纪录不一致的时候,就不能删除分支
会提示您,分支没有合并到当前分支
如果确定要删除分支, 可以使用 branch -D
强制删除
$ git checkout -b test
$ echo 111 >> myfile.txt
$ git add .
$ git commit -m 'text'
$ git checkout master
$ git branch -d test
error: The branch 'test' is not fully merged.
If you are sure you want to delete it, run 'git branch -D test'.
$ git branch -D test
Deleted branch test (was fd2fd46).
并行操作
接下来,创建2个分支来尝试并行操作吧。
首先创建issue2分支和issue3分支,并切换到issue2分支。
$ git branch issue3
$ git checkout -b issue2
Switched to branch 'issue2'
$ git branch
* issue2
issue3
master
在issue2分支的myfile.txt添加commit命令的说明后提交。
学习Git
add 把变更录入到索引中
commit 记录索引的状态
$ git add myfile.txt
$ git commit -m "添加commit的说明"
[issue2 8f7aa27] 添加commit的说明
1 files changed, 2 insertions(+), 0 deletions(-)
接着,切换到issue3分支。
$ git checkout issue3
Switched to branch 'issue3'
打开myfile.txt档案。由于在issue2分支添加了commit命令的说明,所以issue3分支的myfile.txt里只有add命令的说明。
添加pull命令的说明后提交。
学习Git
add 把变更录入到索引中
pull 取得远端数据库的内容
$ git add myfile.txt
$ git commit -m "添加pull的说明"
[issue3 e5f91ac] 添加pull的说明
1 files changed, 2 insertions(+), 0 deletions(-)
这样,添加commit的说明的操作,和添加pull的说明的操作就并行进行了。
解决合并的冲突
把issue2分支和issue3分支的修改合并到master。
切换master分支后,与issue2分支合并。
$ git checkout master
Switched to branch 'master'
$git merge issue2
Updating 149ff63..4f2fb5f
Fast-forward
myfile.txt | 1 +
1 file changed, 1 insertion(+)
执行fast-forward(快进)合并。
接着合并issue3分支。
$ git merge issue3
Auto-merging myfile.txt
CONFLICT (content): Merge conflict in myfile.txt
Automatic merge failed; fix conflicts and then commit the result.
自动合并失败。由于在同一行进行了修改,所以产生了冲突。这时myfile.txt的内容如下:
学习Git
add把变更录入到索引中
<<<<<<< HEAD
commit 记录索引的状态
=======
pull取得远端数据库的内容
>>>>>>> issue3
在发生冲突的地方,Git生成了内容的差异。请做以下修改:
学习Git
add 把变更录入到索引中
commit 记录索引的状态
pull 取得远端数据库的内容
修改冲突的部分,重新提交。
$ git add myfile.txt
$ git commit -m "合并issue3分支"
[master 77fd56e] 合并issue3分支
历史记录如下图所示。因为在这次合并中修改了冲突部分,所以会重新创建合并修改的提交记录。这样,master的HEAD就移动到这里了。这种合并不是fast-forward合并,而是non fast-forward合并。
用rebase合并
合并issue3分支的时候,使用rebase可以使提交的历史记录显得更简洁。
现在暂时取消刚才的合并。
$ git reset --hard HEAD~
HEAD is now at 4f2fb5f 添加commit的说明
切换到issue3分支后,对master执行rebase。
$ git checkout issue3
Switched to branch 'issue3'
$ git rebase master
error: could not apply 09c0a52... 添加pull的说明
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "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".
Could not apply 09c0a52... 添加pull的说明
Auto-merging myfile.txt
CONFLICT (content): Merge conflict in myfile.txt
和merge时的操作相同,修改在myfile.txt发生冲突的部分。
学习Git
add 把变更录入到索引中
<<<<<<< HEAD
commit 记录索引的状态
=======
pull 取得远端数据库的内容
>>>>>>> issue3
rebase的时候,修改冲突后的提交不是使用commit命令,而是执行rebase命令指定 --continue选项。若要取消rebase,指定 --abort选项。
$ git add myfile.txt
$ git rebase --continue
这样,在master分支的issue3分支就可以fast-forward合并了。切换到master分支后执行合并。
$ git checkout master
$ git merge issue3
Updating 8f7aa27..96a0ff0
Fast-forward
myfile.txt | 1 +
1 files changed, 1 insertions(+), 0 deletions(-)
myfile.txt的最终内容和merge是一样的,但是历史记录如下。
远程数据库相关命令
查看本地和远程库的所有分支
$ git branch -a
有
remotes/origin
开头的就是远程库分支
远程库创建分支
$ git push <origin> <Branch name>
Branch name 为分支名称
远程库删除分支
$ git push <origin> --delete <Branch name>
查看当前远程库
执行命令git remote会列出每个远程库的简短名字,例如下面的执行结果:
$ git remote
github
origin
如果想查看仓库的详细地址信息,可以添加参数-v。例如执行命令git remote -v,就可以看到类似下面的内容:
$ git remote -v
github git@github.com:HaungKS/demo.git (fetch)
github git@github.com:HaungKS/demo.git (push)
origin git@gitee.com:little-cute-invincible/homework.git (fetch)
origin git@gitee.com:little-cute-invincible/homework.git (push)
设置远程仓库的地址
命令格式 git remote set-url [--push] <name> <newurl> [<oldurl>]
,或者 git remote set-url -add <name> <newurl>
,或者 git remote set-url --delete <name> <url>
。第一条命令是用新的仓库地址替换旧的仓库地址,其他两条命令分别对应添加一个仓库和删除一个仓库。
例如:
$ git remote set-url origin git@gitee.com:little-cute-invincible/homework.git
查看远程仓库信息
命令格式git remote show <仓库名>
,查看某个远程仓库的详细信息,比如:
$ git remote show origin
* remote origin
Fetch URL: git@gitee.com:little-cute-invincible/homework.git
Push URL: git@gitee.com:little-cute-invincible/homework.git
HEAD branch: master
Remote branches:
huang tracked
master tracked
Local branch configured for 'git pull':
master merges with remote master
Local refs configured for 'git push':
huang pushes to huang (up to date)
master pushes to master (up to date)