Git小游戏

1

Git小游戏

  在两年前,写过一篇Git基础教学的blog,那个时候还在上学,没有接触过太多大型项目,对Git的理解不够深入。现在在公司里,接触到的代码量都是上亿级别的,甚至几万人合作开发,因此必须要熟练运用Git命令,很多小伙伴感觉到困惑,只从网络上查询到Git的基本用法,但是经常会忘记,如何才能有效记忆呢?这里我给大家推荐一个可视化的Git练习网址,小伙伴们可以一边玩游戏一边掌握Git的使用。

前言

这个博客里面的图片和题库都是来自上面那个网站,目的是和大家一起练习。Git小游戏将Git操作分为七个部分,这篇博客也会分成七个段落去介绍每一道题目的解法和原理。小伙伴们在学习时,可以看图先进行练习,然后再和答案进行对比。这里答案是不唯一的,如果有更好的答案,也可以和我进行沟通交流,希望你我都可以从中学习到更多的知识。

基础篇

Git Commit

game1

这个题目非常容易了,我们一般开发完代码后,都需要三步走,add - commit - push,这里是教给大家如何commit。但是一般git commit 后面要加一些注释,所以常用git commit -m操作。这里也一样,我们就对C2节点加”C2”注释。

1
2
3
4
// C2节点提交
git commit -m "C2"
// C3节点提交
git commit -m "C3"

Git Branch

game2

分支是Git中的一个重要组成部分,一般在代码开发阶段都会创建多个分支,在dev上面进行开发,main上面进行提交。本题难度也不大,只是简单的创建分支和切换分支。

1
2
3
4
创建bugFix分支
git branch bugFix
切换到bugFix分支上
git checkout bugFix

本题的最优解法是分支的创建和切换合并成一步操作。

1
2
创建分支,并切换到该分支上
git checkout -b bugFix

git checkout -b branchName nodeName也可以在某个节点上创建分支,nodeName默认为当前节点。

Git Merge

game3

Merge操作是将两个分支进行合并,比如你在dev分支上进行开发,开发完成后要在main上面提交,此时就需要将分支进行合并。本题也很简单,只是最普通的合并操作即可。

此时要审题,因为C2节点上面的分支是bugFix,所以要先创建一个bugFix的分支,并且进行一次提交。而main上面有C3和C4节点,说明main也进行了一次提交,然后合并了bugFix分支。

1
2
3
4
5
6
7
8
9
10
// 创建bugFix分支
git checkout -b bugFix
// bugFix提交C2节点
git commit -m "C2"
// 切换回main分支
git checkout main
// main分支提交C3节点
git commit -m "C3"
// main分支合并bugFix分支
git merge bugFix

此时要注意一件事,有时候要将dev和main的节点保持一致,所以也要讲dev移动要main上面去,此时可以切换回dev分支,并使用git merge main,因为此时main分支已经和dev分支合并过了,所以执行合并操作后,会直接将dev移动到main节点上。

Git Rebase

game4

Rebase操作算是一个比较进阶的操作了,也是一种合并分支的操作,只不过它和merge的区别在于,merge直接将有分叉的两个分支合并,而rebase是将一个分支的后面逐个增加另一个分支的节点,保持一种线性的合并关系。

本题可以看出我们需要先创建一个bugFix分支,然后进行一次提交。然后在main分支上进行一次提交。最后将bugFix的节点追加在main的后面,可以理解为换基,也就是说C2节点是基于C1节点进行的一次提交,现在换成基于C3节点的一次提交。这也就是rebase的含义,re代表重新,再一次,base表示基准。

1
2
3
4
5
6
7
8
9
10
11
12
// 创建bugFix分支
git checkout -b bugFix
// bugFix提交C2节点
git commit -m "C2"
// 切换回main分支
git checkout main
// main分支提交C3节点
git commit -m "C3"
// 切换到bugFix分支上
git checkout bugFix
// 将bugFix换基到main
git rebase main

上面的代码和merge非常相似,小伙伴们可以将两种操作进行比较。本题最后两步可以合并为一步。git rebase main bugFix,这个目的就是将bugFix分支换基到main上面。

高级篇

分离HEAD

game5

我们知道,其实Git的本质是一个链表,一次commit就相当于在头节点HEAD前面追加一个节点,并且移动HEAD的位置。因此HEAD总是指向当前分支上最近一次的提交记录。

git checkout检出操作,可以调整HEAD当前的指向。本题非常简单,因为要显式表示HEAD的位置,因此虽然HEAD默认在bugFix最近一次提交,但是也需要检出。值得注意的是,如果检出了HEAD节点,再次提交时只有HEAD节点发生移动,分支不会移动了。如果没有检出,再次提交时,HEAD节点和分支会同时向前移动,这也就是为什么称为分离HEAD

检出命令指定节点的哈希值,不能指定分支名,因为指定分支名是切换分支的命令。

1
2
// 在C4节点上检出HEAD
git checkout C4

既然聊到了checkout,我们已经知道git checkout的两种用法,一个是切换分支,另一个是检出HEAD节点。其实checkout还有一种用法,就是将工作区的内容恢复到暂存区的状态。说的简单点就是如果某个文件进行了修改,但是还没有add,就可以使用git checkout – filename进行回退。如果想回退所有文件,可以使用git checkout .

扩展一些,如果某个文件刚刚添加,还是untracked的状态,因为git checkout .仅仅是将工作区恢复到暂存区,因为文件都还没有被跟踪,所以新添加的文件不会被回退,此时应该如何操作呢?git clean这个命令要慎用,git clean -f是删除untracked的文件,git clean -df是删除untracked的文件和文件夹。git clean -x是指删除.gitignore中的文件,因此一般使用git clean -xdf删除所有untracked的文件。

再扩展一些,如果某个文件已经add了,如何将其删除呢?可以使用git reset HEAD filename。此时可以将加入到暂存区的文件恢复到工作区。关于reset的更多用法,后面会进行介绍。

相对引用(^)

game6

相对引用^是指回到某个节点的上一个节点,在本题中想要到C3节点上去,可以使用节点哈希值直接检出,更推荐使用相对引用解决本题。

^可以出现在某个分支后面,也可以出现在某个节点的哈希值后面。

1
2
// 在bugFix分支的前一次提交上检出HEAD节点
git checkout bugFix^

也可以写git checkout C4^,因为本题不推荐使用哈希值,所以这里不进行演示。

相对引用(~)

game7
相对引用就更简单了,是解决相对引用^的复杂问题。比如我想回到某个节点的前10个节点,按照上面的做法需要^操作10次,而使用只需要在后面加上一个数字,就可以直接回到前10个节点。

我们来看一下这个题目,这个题目又可以让我们学到新知识了,这里不是简单的检出操作,而是要将分支进行修改,git branch -f branch_name node_name可以将分支修改到某个节点上。本题需要将main分支移动到C6节点,将bugFix分支移动到C0节点,将HEAD检出到C1节点。

我们可以通过绝对引用将main分支移动到C6节点,然后将bugFix使用相对引用移动到前面4个节点,将HEAD移动到前面3个节点即可。虽然bugFix和HEAD也可以使用绝对引用,但是这里还是推荐相对引用。

1
2
3
4
5
6
// 将main分支切换到C6节点上
git branch -f main C6
// 将bugFix分支切换到main分支的前4个节点上
git branch -f bugFix main~4
// 在main分支的前3个节点上检出HEAD
git checkout main~3

撤销变更

game8
我们常常需要进行回退操作,比如写了一段代码,发现了一些bug,想回到以前的状态。这应该如何操作呢?首先我们需要明确一件事情,我们是想回到以前,看一下以前的代码,并进行更改后提交还是想回到以前提交以前的代码?

可能这段逻辑说的有一些拗口,假如在节点C1上面提交了a,然后又在节点C2上面提交了b,现在想回到节点C1,在a的基础上提交c,这时候就是第一种情况,回到以前的代码,进行更改后提交。此时使用git reset C1操作,可以将节点移动到C1上面,此时进行修改、add、commit即可。

注意git reset还有三种常用的模式。

  • git reset –hard 这种是最粗暴的方式,将所有跟踪到的,无论是工作区还是暂存区全部清空。
  • git reset 这种是平滑的方式,将工作区、暂存区以及reset导致的差异都放进工作区中,并清空暂存区。
  • git reset –soft 这种是最柔弱的方式,保留工作区,将暂存区以及reset导致的差异都放进暂存区中。

上面说的都是第一种情况,如果是第二种情况,想回到以前的代码,直接进行提交,不修改任何操作。此时使用git revert HEAD操作,会直接生成一个新的节点,和上一个节点的内容完全相同,并且会直接跳转到commit message页面。

值得注意的是git revert操作尽量不要跳跃多步操作,因为这样需要我们去手动解决冲突。

还有一点是git revert与git reset的差异,git revert HEAD是指新建一个节点与上一个节点内容相同。而git reset HEAD^是指回到上一个节点。注意revert是没有^符号的。

讲了这么多,终于可以讲本题了。。。本题就非常简单了,local指向C3节点,没有创建新节点,只是希望指针移动到C1节点,那么就是用git reset操作。pushed指向C2节点,希望创建一个新的用于提交的C1节点,那么就使用git revert操作。

1
2
3
4
5
6
// 将HEAD回退到上一个节点
git reset HEAD^
// 切换到pushed分支
git checkout pushed
// 创建和pushed分支上一次提交一样的节点
git revert HEAD

移动提交记录

Git Cherry-pick

game9
git cherry-pick是将一些节点复制到当前所在的HEAD下面,并将HEAD移动到新的节点上。注意如果此时当前节点和cherry-pick的节点有冲突,则需要解冲突,然后git add,git cherry-pick –continue才是一个完整的cherry-pick操作。如果中途不想cherry-pick,则使用git cherry-pick –abort终止。

本题看起来很多节点,其实非常简单。而且cherry-pick操作可以一次性指定多个节点或分支名,非常方便。

1
2
// 在HEAD下面追加C3 C4 C7节点
git cherry-pick C3 C4 C7

交互式Rebase

game10
rebase操作在上面已经介绍过了,其中还有一些进阶的用法,其中rebase -i可以交互式的对提交进行合并、删除和交换顺序,这一点是非常非常重要的。

举个实用的例子,我们向开发分支上面进行了1次代码开发,9次bugFix。此时觉得功能已经开发完成了,现在想要合入主干代码。这个时候如果进行cherry-pick,需要做10次,因为每一次提交都要进行cherry-pick。现在我们可以使用rebase进行代码合并,将开发分支合并为一次提交,此时只需要cherry-pick一次到主干即可。

git rebase -i nodeName可以以某个节点为基,将其后面的节点进行rebase。比如想合并10次提交,git rebase -i HEAD~10。此时会进入到一个交互式界面,此时所有的提交都会出现pick的状态,pick代表选择,squash代表合并,也可以将某一行删去代表删除。比如合并10次,则将开发的提交写为pick或者p,将其他9次bugFix的提交写为squash或者s,然后保存退出,此时会进入到commit message界面,对提交信息进行调整,保存退出后会新生成一个节点,里面包含了所有10次的提交。

说了这么多,小伙伴们可以在本地建立一个git仓库实验一下,我们回到本题上面来。这个小游戏将枯燥的交互界面进行了优化,不用输入代码,仅仅需要鼠标拖动即可。但是在本地是需要用命令来可视化操作的。

因为新的分支上面只有C3 C5 C4三个节点,因此可以认为是删除了C2节点,并调整了C4和C5的顺序。

1
2
// 对前4次提交进行交互式调整
git rebase -i HEAD~4

虽然本题很简单,但是在本地应该如何去做这个题目呢?
第一句话和本题一样,git rebase -i HEAD~4,然后在交互式界面中删除第一句话,将最后两句交换位置,保存退出即可。如果有冲突则需要解决冲突,git add,git rebase –continue,此时到commit message界面,同样保存退出即可。

杂项

只取一个提交记录

game11
本题没有什么新鲜的技巧,就是使用前面刚刚学习的git cherry-pick或者git rebase -i操作。

本题要将bugFix节点单独拉出来一条分支,可以使用git cherry-pick操作,首先切换到main分支,然后cherry-pick bugFix节点即可。

1
2
3
4
// 切换到main分支
git checkout main
// main分支下面追加bugFix节点
git cherry-pick bugFix

也可以使用git rebase 操作,删除其他两个提交,只将bugFix提交保留,并且将当前main分支修改到bugFix分支即可。注意此时不是切换,而是修改git branch -f操作。

1
2
3
4
// 以前三个节点为基,删除C2和C3节点
git rebase -i HEAD~3
// 将main分支修改到bugFix分支
git branch -f main bugFix

提交的技巧1

game12
在我们的开发阶段,比如修改了某个功能,以本题为例,修改了某个图片的分辨率,然后进行了一次提交,节点为C2。后面有进行了其他功能的开发和提交,节点为C3。此时如果发现C2节点分辨率有改动,想进行修改,此时可以先使用rebase将C2节点移动到前面,然后进行git commit –amend操作进行修改,修改完成后再次使用rebase操作将C2的节点移动回去。

这里git commit –amend操作,就是在某一次提交后面打补丁,进行进一步修改提交。会产生一个新的commit id,但是该commit id会覆盖原来的,而不是在后面追加。如果是git commit则会在后面追加新的commit id。

此题的答案就如同上面的解析一样,看懂了解释就会解答本题。

1
2
3
4
5
6
7
8
// 以前两个节点为基,调整C2和C3节点的顺序
git rebase -i HEAD~2
// 在当前caption分支后面追加一次提交
git commit --amend
// 以前两个节点为基,调整C3''和C2'节点的顺序
git rebase -i HEAD~2
// 将main分支修改到caption分支
git branch -f main caption

提交的技巧2

game13
和上面的场景一样,但是两次使用rebase操作,可能会产生大量的冲突,我们还可以用另一种方式进行解决。

我们能否先cherry-pick第一个节点,进行git commit -amend操作后再cherry-pick另外一个节点呢?

1
2
3
4
5
6
7
8
// 切换到main分支
git checkout main
// 在main分支下面追加newImage节点
git cherry-pick newImage
// 在main分支后面追加一次提交
git commit --amend
// 在main分支下面追加caption节点
git cherry-pick caption

Git Tag

game14
做标记,往往我们进行了一次开发,如果时间较长,可能提交次数非常多,有时候重要的特性会被遗忘在提交海中。这时候我们可以对某些重要的节点进行标记,git tag tagName nodeName。非常简单就是给节点名加上标记。可以使用这些节点名进行指针跳跃,注意不要给自己找麻烦,将B节点名作为A节点的tag。结果是想跳转到B,会跳转到A。

如果想要删除某个tag,使用git tag -d tagName。

本题就非常简单了,直接给C1节点标记为v1,C2节点标记为v2,并检出v1即可。

1
2
3
4
5
6
// 给C1节点标记为v1
git tag v0 C1
// 给C2节点标记为v2
git tag v1 C2
// 在标记为v1的节点上检出HEAD
git checkout v1

Git Describe

game15
这里有一个误导,本题是有问题,git describe是查询带有annotation的tag,如果是查询不带有annotation的tag,则需要使用git describe –tags nodeName。这个命令是查找某个节点之前距离最近的一个带有tag的节点。

运行该代码后如果没有找到在该节点之前,并且带有tag的节点,则会报错。如果找到了,如果该节点自身就带有tag,则会显示该tagName,否则会以下面这个方式显示。tagName-distance-gnodeName。

这里说明一点,tagName是指找到的某个节点的tag名,distance表示该节点距离所查找的节点距离,gnodeName是指节点的哈希值前面加一个字符g。

比如A-B-C的提交顺序,B的tag为tagB。

  • git describe –tags A结果会报错,因为A之前没有含有tag的节点
  • git describe –tags B会显示tagB,因为B本身就含有tag,直接打印出tagName
  • git describe –tags C会显示tagB-1-gC,因为B距离C为1

本题是一个福利局,仅仅做一个git commit就可以通过。

1
2
// 进行一次提交
git commit -m "C7"

高级话题

多次Rebase

game16
这一节的标题是多次rebase,从题目中就可以看出来,我们要产生一条新的分支,将C3一直到C7都rebase到C2节点上。其中的操作我们在rebase已经讲过,难度不大,本题可以作为rebase的练习题。

1
2
3
4
5
6
7
8
// 以main为基,追加bugFix上的节点
git rebase main bugFix
// 以bugFix为基,追加side上的节点
git rebase bugFix side
// 以side为基,追加another上的节点
git rebase side another
// 将main分支修改到another上
git branch -f main another

两个父节点

game17
我们再说回到merge操作,merge是合并两条分支,因此后面的提交就会出现两个父节点,那么如何指定回到某一个父节点呢?

之前说过的相对引用,相对引用^其实是回到第一个父节点,如果有多个父节点,也是回到第一个父节点。那么如何回到第二个父节点呢?^2就可以回到第二个父节点了,同理^n是回到第n个父节点。

那么如何看哪一个父节点是第一个父节点呢?交给大家一个非常好用的方法
git log --oneline --all --graph
这是用图形化的方式显示提交记录,从左到右是父节点的顺序。而本题又有一个误导,因为本题的图形化界面并不是log的形式,而制作这个游戏的人可能也没有注意到这一点,这个题目第二个父节点在左边。

本题明确父节点的位置就非常简单了,而且要注意的是相对引用的^和~可以连续。关于如何连续使用,可以先自己使用常规方法解答本题后,看一下下面的答案。

1
2
// 创建bugWork分支,指向HEAD的父节点的第二个父节点的父节点
git branch bugWork HEAD^^2^

纠缠不清的分支

game18
下面我们再来一个练习,现在有一个main分支,和one、two、three三个分支,main分支进行了多次提交,但是另外三个分支在较早的节点上。希望达到如上图的效果。

其实方法很简单,我们要明确在哪个节点进行岔路,可以看到在C1节点岔路。因此我们要在C1上面cherry-pick其他main分支上的其他提交。而恰巧one、two、three都在C1节点上,所以本题的最优解是使用cherry-pick操作。

1
2
3
4
5
6
7
8
9
10
// 切换到one分支
git checkout one
// 在one分支后面追加C4 C3 C2节点
git cherry-pick C4 C3 C2
// 切换到two分支
git checkout two
// 在two分支后面追加C5 C4 C3 C2节点
git cherry-pick C5 C4 C3 C2
// 将three分支修改到C2节点
git branch -f three C2

使用rebase操作也可以达到相同的结果,只不过需要多花一些步骤,既然是练习,这里也进行演示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 将one分支修改到main节点
git branch -f one main
// 切换到one分支
git checkout one
// 以前4个节点为基,删除节点C5,并调整节点顺序为C4 C3 C2
git rebase -i HEAD~4
// 将two分支修改到main节点
git branch -f two main
// 切换到two分支
git checkout two
// 以前4个节点为基,调整节点顺序为C5 C4 C3 C2
git rebase -i HEAD~4
// 将three分支修改到C2节点
git branch -f three C2

Push & Pull ——Git远程仓库!

Git Clone

game19
我们之前聊的都是git的本地操作,现在要学习如何与远程分支打交道,这也是我们学习git的根本原因。因为在公司进行编码时,主干、商用、开发分支等都会在远端,不可能在本地,所以学习远程git是非常非常重要的。

首先介绍git clone remoteBranchName,这个操作是最基础的,常被人称为拉分支。相当于在本地拉一条远端的分支。clone这个单词顾名思义了,其实就是将远端的分支克隆到本地,其中节点信息都可以在本地看到。

我们可以先了解到这里,随着后面的题目训练,我们会更有感悟。本题对新手非常不友好,虽然就是很简单的git clone操作,但是会给同学们造成很深的误解,都是先有远程分支,然后clone到本地,而UI显示的却是将本地的分支clone到远端,小伙伴们不要在意就好。

1
2
// 克隆一条远端分支
git clone

远程分支

game20
往往我们拉分支都会选择一个空的文件夹去git clone,拉下来会默认创建一个分支,该分支与远端的分支同名。但是HEAD指向本地的克隆分支,如何区别本地和远端的分支呢?

在git bash中本地的分支名为绿色的,且前面会有HEAD指针,当然如果改变了指向就没有HEAD指针了。而远端的分支名为红色的,且前面会有origin/的标识,表示远端分支。

注意本题又是一个陷阱,本题的目的是教会我们如何区分本地分支和远程分支。其实我们在本地并不能切换到远程分支上,因此题目的描述是错误的,这个我们理解即可。要想做好本题,需要在本地分支和远程分支各做一次提交,代码非常简单。

1
2
3
4
5
6
// 在本地分支上进行一次提交
git commit -m "C2"
// 将HEAD节点检出到远端分支上
git checkout o/main
// 在远端分支上进行一次提交
git commit -m "C3"

Git Fetch

game21
根据前面的学习,我们已经会克隆远程分支并进行提交了,那么如果在多人的开发过程中,有人在远端进行了提交,我们如何进行同步更新呢?下面就要介绍git fetch操作,这个操作是从远端下载本地缺少的提交记录,并且把远程分支的指针更新到最新。

注意,此时是不会有冲突的,因为这一步操作仅仅是更新了远程的分支,并不影响本地的HEAD。有的小伙伴会有疑问,如果本地修改的和远程修改的地方相同,这应该怎么办呢?我们把这个问题先放一放,先把这道题解决~因为本题有两个远程分支,因此两个远程分支都需要fetch,因为clone的时候已经关联了远程分支,因此此时不需要再指定远程分支的全名了,直接git fetch即可。

1
2
// 将远程分支更新到本地
git fetch

Git Pull

game22
下面介绍git pull操作,在讲到git fetch的时候,抛出了一个问题。当本地分支已经进行了提交,此时远程分支和本地分支修改了同一个文件,此时git fetch不会产生冲突。如果此时执行git merge origin/main操作,就会产生冲突,因为当你提交的分支在main上面,而git fetch是更新远程分支origin/main,此时两个分支互不影响,而此时执行git merge origin/main,就会产生冲突。

而git pull就等价于git fetch + git merge origin/main操作,将远程分支更新,并进行合并。因此git pull是可能产生冲突的。

本题也很简单,游戏里面不需要解冲突,我们只需要关心git pull操作做了哪些事情即可。

1
2
// 将远程分支更新到本地并合并为一个新的提交
git pull

模拟团队合作

game23
本题又是一个误导,根本没有git fakeTeamwork这个命令,只是为了模拟远程提交而做的一些trick。我们按照提示完成本题即可,完全可以忽略本题。

1
2
3
4
5
6
7
8
// 克隆一条远端分支
git clone
// 模拟两次远程main分支的提交
git fakeTeamwork main 2
// 在本地main分支进行一次提交
git commit -m "C4"
// 将远程分支更新到本地并合并为一个新的提交
git pull

Git Push

game24
我们终于讲到了最后一个重要的操作,git push,可以明显的看出git pull和git push是相反的一组操作。git pull是从远端拉取最新的提交,而git pull则是将本地最新的提交上传到远端。尤其是我们在和别人协作开发代码的时候,更是要经常使用git push操作,因为当我们的代码开发完成之后,总要让其他开发人员也看得到,因此要上传到远端,就是git push命令。

注意git push命令除了将本地代码提交到远端以外,还会将远端分支进行更新,也更新到最新。前提是我们在push之前要进行一次pull操作,先将代码更新到最新,然后才能进行push,这个也很好理解,如果代码都不是最新的,如何push上去,在当前最新的远端分支上追加新的提交呢?

关于git push命令的一些参数,我们后面会进行训练,我们这里先闯过这一关。

1
2
// 将本地提交上传到远端
git push

偏离的提交历史

game25

越写到后面越感觉得心应手了(这是一个双关),不仅仅是小伙伴们的git技术越来越熟练,我的博客写的也是越来越顺畅了。刚刚才说过push之前必须将节点更新至最新,这一题就来了。之前是使用git pull命令将代码拉到最新,但是可能会发生冲突,因为git pull会首先将代码拉取下来,然后进行合并,这时候我们尽量不要一步到位,所以一般不直接使用git pull。而是使用git fetch,然后可以根据需要使用merge合并操作或者git rebase换基操作。

这里我们还要对命令进行扩展,git pull = git fetch + git merge。那么git pull –rebase = git fetch + git rebase。

这很简单,git fetch之前就说过,是git pull的第一步,而git merge和git rebase在之前也详细介绍过,所以这部分知识点对于小伙伴们肯定是不算难了,下面我们直接到看这个题目吧。

1
2
3
4
5
6
7
8
9
10
// 克隆一条远端分支
git clone
// 模拟一次远程main分支的提交
git fakeTeamwork
// 在本地main分支进行一次提交
git commit -m "C3"
// 拉取远程分支的提交,换基到该分支上
git pull --rebase
// 将本地提交上传到远端
git push

锁定的Main(Locked Main)

game26
这个练习的知识点并不常用,我也不是很熟悉,我们就当扩展练习吧,看一看它出了哪些幺蛾子?

在工作中最常用的命令顺序是git clone、git add、git commit、git push。而有时候远程的main被锁定了,如果git push会被拒绝,此时应该创建一个分支,push该分支即可。

本题可以看出,main节点被锁定在C1,因此我们要先将本地main分支切换到C1,然后在C2点创建一个feature分支提交上去。

1
2
3
4
5
6
// 将本地main回退到上一个节点
git reset --hard HEAD^
// 在C2节点上创建feature分支,并切换到该分支上
git checkout -b feature C2
// 推本地feature到远端分支
git push origin feature

这里使用了git pull origin branchName,留了一个origin参数的伏笔,会在后面进行详细介绍。

关于origin和它的周边——Git远程仓库高级操作

推送主分支

game27
本关是一个练习关卡,我们运用上面学过的知识来解题吧~这里先让小伙伴们做一下,然后再给出思考过程。

1
2
3
4
5
6
7
8
// 将分支切换到main
git checkout main
// 拉取远程分支
git pull
// 在main分支后面追加C2 C3 C4 C5 C6 C7节点
git cherry-pick C2 C3 C4 C5 C6 C7
// 将本地提交上传到远端
git push

思考过程:我们可以发现远端有C8节点,本地没有,因此需要先更新本地代码。因为当前分支不在main分支上,所以前两步操作是git checkout main、git pull。然后我们发现最终的远程分支是在C8节点后追加C2-C7节点,因此可以cherry-pick本地的C2-C7,然后push上去,因此后两步操作是git cherry-pick C2 C3 C4 C5 C6 C7、git push。

然而这样做是可以通过本题的,也是最好的一种方式,但是和本题的结果不完全一样,因为本地结果side1分支应该指向C2‘节点,side2分支应该指向C4’节点,side3分支应该指向C7’节点,也就是指向新的C2、C4和C7节点,而上面的做法没有改变其指向。

如果小伙伴们追求和答案一样,也可以git branch -f side1 C2’、git branch -f side2 C4’、git branch -f side3 C7’,但是步骤就是7步,还有更好的方式,小伙伴们能否想出?

1
2
3
4
5
6
7
8
9
10
11
12
// 将远程分支更新到本地
git fetch
// 以o/main为基,追加side1上的节点
git rebase o/main side1
// 以side1为基,追加side2上的节点
git rebase side1 side2
// 以side2为基,追加side3上的节点
git rebase side2 side3
// 以side3为基,追加main上的节点(因为main是side3的父节点,因此直接将main移动至side3分支上)
git rebase side3 main
// 将本地提交上传到远端
git push

妙哇~第一步因为不使用cherry-pick操作,而是使用rebase操作,可以省略第一步的切换分支,直接使用git fetch即可。然后将side1、side2、side3都rebase到该分支上,最后将main分支也移动到该分支上提交即可。小伙伴们如果能将这两种方法都掌握,那么cherry-pick和rebase操作基本上都难不倒大家了。

合并远程仓库

game28
在本节中,我们探讨一个争论最大的话题,Merge OR Rebase,相信这个问题小伙伴们也听过很多次了,网上有很多人发表了极端的看法,能用Rebase绝不用merge,当然这也没问题,在本地想如何操作都是我们的自由,但是也不能认为merge毫无作用,rebase可以完全取代merge。要不然为什么会有merge的存在呢?

rebase的优点当然很突出,这里我们不讨论合并提交的话题,指对merge和rebase对两条提交树的变化。rebase可以让提交树变得简单,所有的提交都在一条直线上,查询历史记录也非常清晰。而merge就会导致一个节点有多个父节点,当大量使用merge的时候,会导致提交树的结构非常混乱。

那说回来,merge的优点是什么呢?merge不会改变提交树的历史,而rebase做不到这一点,比如A可以rebase到B的后面,在观察历史的时候,我们会有困惑,感觉为什么A会在B后面,但实际上A是在B前面提交的。但是merge就能保留这种关系。

我们对merge和rebase的对比就说到这里,我们来把本题解决了吧,本题和上一题几乎一样,而是要使用merge操作解答。

1
2
3
4
5
6
7
8
9
10
11
12
// 将分支切换到main
git checkout main
// 拉取远程分支
git pull
// 合并分支side1
git merge side1
// 合并分支side2
git merge side2
// 合并分支side3
git merge side3
// 将本地提交上传到远端
git push

远程追踪

game29
小伙伴们是否会感觉到困惑,为什么我们使用git pull和git push的时候,不用指定分支,而是自动将main和o/main进行了绑定呢?这也是git的机制,当你不使用任何额外命令的情况下,git默认会创建一个和远程分支名一样的本地分支名,为了区别标识在前面加了origin

当然这个是我们可以指定的,我们在创建新分支的时候,可以使用git checkout -b localBranchName remoteBranchName 这样可以将本地分支和远程分支进行绑定。

如果已经创建出来了本地分支,那么可以使用git branch -u remoteBranchName localBranchName来进行绑定,如果省略localBranchName,则默认为当前分支。

1
2
3
4
5
6
7
8
// 创建side分支并绑定o/main远程分支
git checkout -b side o/main
// 在side分支上进行一次提交
git commit -m "C3"
// 拉去远程分支,并换基到该分支上
git pull --rebase
// 将本地提交上传到远端
git push

Git Push的参数

game30
OK~,学到这里我们可以将最后一个遗留问题分享给大家了,在前面说过,git push时,可以加origin和branchName参数,具体什么意思呢?

这个意思是在本地找到branchName,然后在远端也找到绑定的分支名,然后将其提交上去。有的小伙伴就会有疑问,不加不也是这样吗?

其实是这样的,如果不加就会自动选择HEAD当前所在的分支。因为我们平时提交的时候,HEAD和要提交的分支在同一个节点,所以感觉不出来,如果将HEAD进行调整,则会看到不一样的情况,我们通过本题来加深理解。

1
2
3
4
// 将main分支提交到远端
git push origin main
// 将main分支提交到远端
git push origin foo

Git Push参数2

game31
git更灵活的地方在于,不仅可以将本地A分支推到远程A分支,也可以将本地A分支推到远程B分支。我们来看一看它是咋玩的?

git push origin localNode:remoteBranchName,这个命令厉害了,可以将本地的某个节点,推到远程的某个分支上,如果远程没有该分支,则会自动创建一个分支。我们直接做题练习。

1
2
3
4
// 将C4节点提交到远端main分支
git pull origin C4:main
// 将C5节点提交到远端foo分支
git pull origin C5:foo

Git Fetch的参数

game32
我们讲完了git pull的参数,git fetch的参数也就不用过多赘述了,只要记得git fetch origin remoteBranchName是将远端某个分支拉到本地,git fetch origin remoteNodeName localBranchName是将远端某个节点拉到本地。这里要注意,远端写在前面,这也是容易理解的,因为对于远端来说,remote是本地,local是远端。如果本地没有该分支,也会自动创建一个分支。

1
2
3
4
5
6
7
8
// 在本地foo分支上拉取远端main分支的前一个节点
git fetch origin main^:foo
// 在本地main分支上拉取远端foo分支
git fetch origin foo:main
// 将分支切换到foo分支上
git checkout foo
// 合并main分支
git merge main

没有source的source

game33
本节的标题是没有source的source,什么意思呢?在前面几节讲过的git push/fetch origin A:B表示从A推到B,或者从拉取A到B。则说明A是源(source),B是目的地(destination)。

这里表达的没有source是指source可以省略,写成git push/fetch origin :B。这个是什么意思呢?是不是源就是默认的HEAD呢?答案并不是这样,如果git push origin :B则表示删除远程的B分支,git fetch origin :B则表示在本地新建一个B分支。这两个操作并不常用,了解即可。

知道了这两个命令的作用,本题答案呼之欲出。

1
2
3
4
// 删除远程foo分支
git push origin :foo
// 在本地创建bar分支
git fetch origin :bar

Git Pull的参数

game34
我们终于来到了最后一节,这里并没有新奇的操作,git pull = git fetch + git merge已经快要讲吐了,然而对于这一节,还是要讲一次。因为git fetch的参数已经讲的非常详细了,因此这里直接作为一个练习即可。

1
2
3
4
// 在本地foo分支上拉取远端bar分支,并在当前分支合并foo分支
git pull origin bar:foo
// 在本地side分支上拉取远端main分支,并在当前分支合并side分支
git pull origin main:side

Git小游戏小结

  终于将git游戏讲解完毕了,洋洋洒洒一万多字的wiki,写作了半个多月,希望小伙伴们能够跟我一起练习,一起进步。

在这里我给大家分享一个平时开发的操作,一般是git clone拉代码,然后git checkout -b创建一个开发分支,在这个分支上面进行开发,后面是git add,git commit,git push,这三步操作让我们的代码从本地提交到远端。当代码合入以后,我们的链接已经可以在远端看到了,此时就可以git checkout main回到主分支,然后可以git branch -d删掉这个开发分支了,防止需求过多的时候,导致分支也过多。当下一次需要开发的时候,一定要记得git pull先拉取最新的分支,然后再git checkout -b创建一个新的开发分支,重复上述操作即可。这就是开发代码的主要流程。

对于git这个工具,我的很多朋友都认为,一步错步步错,有时候实在没办法,只能rm -rf了。如果我们完全熟悉git的常用操作,是可以完全避免这种情况的发生。上面说的这些都是git的常用操作,希望我们能够熟练运用它。

-------------本文结束感谢您的阅读-------------
0%