在从游戏存档聊聊Git版本管理一文中,我们最后得到了两个存档路径。
m00
/ \
m01 m00-1
那我们为什么要两个存档路径呢?
考虑这么一个场景,马里奥到某个地点可以从地面上走,也可以从水管走。这时候我可能想看看从地面走和从水管走有什么区别,此时我就可以在这个分岔路口先存档一下。比如上面的m00,然后我从地面走,保存m01。然后,我再回到m00,从水管走,保存m00-1。
分支
我们分别按照这两条路径继续往下玩,我们先通过下面的指令进入m01存档:
git checkout master
checkout指令前文中我们已经使用过了,它的作用就是用来切换分支或恢复工作树文件的。后面的master就是分支名。当我们初始化了git目录后,默认会创建一个master分支。
因为之前我们的m01存档是在master分支上的,所以我们通过上面的命令切换到master分支,而m01是最新存档,所以目录中的信息就是和m01相同的内容。
我们从这个存档开始玩游戏,假设我们玩到了1-1关:
********
************
####....#.
#..###.....##....
###.......######
...........
##*####### 1 - 1
####*******######
...#***.****.*###....
....**********##.....
....**** *****....
#### ####
###### ######
我们通过下面的命令进行存档:
git add player.txt
git commit -m 'm02'
存档完成后,我们再进入到m00-1对应的存档:
git checkout load-00
load-00是另一个分支,是我们在前文中通过switch命令创建的。因为m00-1存档是load-00分支的最新存档,所以默认就读取到了m00-1的存档内容。
我们同样的开始玩游戏,玩了一段时间后,发现也到了1-1关。
此时,我们通过如下命令进行存档:
git add player.txt
git commit -m 'm00-2'
现在我们就得到了下面这样的存档列表:
m00
/ \
m01 m00-1
/ \
m02 m00-2
现在,就出现了一个问题。原来我分别从地面上和管道里进行了游戏,但是最后都到了1-1关。如果我继续按照两个存到路径进行游戏的话,相同的关卡我要玩两次。一般玩家肯定不会乐意的,那该怎么办呢?
此时,我们需要将这两条存档路径合并,即到1-1关,两条存档又回到了一条上。git提供了merge来完成这样的需求。
merge
目前我们在load-00分支m00-2存档处,而目前m00-2和m-02存档的关卡实际都是1-1。我们可以通过如下指令将存档合并:
git merge master
merge指令的作用就是将目标分支的内容与当前分支内容进行合并。此处就是将master分支的内容合并到load-00分支上。
执行之后,会出现类似这样的信息:
Merge branch 'master' into load-00
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
输入:wq退出后,即完成了合并。此时,存档结构看起来像这样:
m00
/ \
m01 m00-1
/ \
m02 m00-2
\ \
---Merge branch 'master' into load-00
master分支和load-00分支在「Merge branch ‘master’ into load-00」处合并了。但是两边分支还有有差异的:
- load-00分支的最新提交指向的是「Merge branch ‘master’ into load-00」
- 而master分支的最新提交指向的是m02
要使得master最新提交也指向「Merge branch ‘master’ into load-00」,你需要执行如下指令:
git checkout master
git merge load-00
你会看到类似下面的信息:
Updating 0266015..a1321a3
Fast-forward
Git直接将游标指向到了「Merge branch ‘master’ into load-00」。
Fast-forward
如果你使用过其它的VCS,比如SVN,你就会发现这里的差别。像SVN在处理合并时,每次都会新增一个提交,如果是这样的话,两个分支永远都不会被合并,一直在执行下面的循环:
Merge branch 'load-00' into master
Merge branch 'master' into load-00
Merge branch 'load-00' into master
Merge branch 'master' into load-00
......
而Git为了避免此类问题,当两边内容一致时,直接移动游标,而不是新增一个提交。
冲突解决
上面合并时并没有出现冲突,当合并时,Git发现无法自动进行合并时,就会提示类似下面的错误:
Auto-merging player.txt
CONFLICT (content): Merge conflict in player.txt
Automatic merge failed; fix conflicts and then commit the result.
打开player.txt,我们可能会看到如下内容:
********
************
####....#.
#..###.....##....
<<<<<<< HEAD
###.......######
........... 1 - 1
##*#######
####*******######
...#***.****.*###....
....**********##.....
....**** *****....
=======
###.......######
...........
##*#######
####*******######
...#***.****.*###.... 1 - 3
....**********##.....
....**** *****....
>>>>>>> master
#### ####
###### ######
上面和下面的内容出现了冲突,我们需要进行解决。我们需要自行确定需要保留哪些内容,假设我们要保留1-3,那么我们可以选择下面的内容:
********
************
####....#.
#..###.....##....
###.......######
...........
##*####### 1 - 3
####*******######
...#***.****.*###....
....**********##.....
....**** *****....
#### ####
###### ######
处理完,通过下面的指令进行存档即可:
git add player.txt
git commit -m 'm00-3'
Cherry-Pick
与merge比较类似的操作是Cherry-Pick。
我们通过reset命令回退一下:
# 回退操作后面细说
git reset --hard HEAD^
然后我们执行如下命令来演示一下Cherry-Pick:
git cherry-pick master
如果出现冲突,通过上面的方法解决一下冲突,提交即可:
git add player.txt
git commit -m 'm00-3'
提交完成后,我们也能类似下面的存档结构:
m00
/ \
m01 m00-1
/ \
m02 m00-2
\
m00-3
从结构上,我们可以看到明显的差别:
- merge后两个分支是汇聚到了一点
- 而Cherry-Pick只是将一个分支的提交拉到了目标分支,你可以理解为又在目标分支进行了对应的提交
总结
本文通过对存档的管理简单梳理了Git的分支及对应的指令操作。
下面,我们来聊一聊存档的回滚。