主要
基础篇
1.git commit
提交一次记录。
2.git branch
创建一个新分支,但是并没有改变HEAD的指向。
git checkout 分支名
将HEAD指向分支名。
git checkout -b 新分支名
创建一个新分支并指向该分支。
3.git merge
merge后面只跟一个参数A,指的是将参数A融合入当前工作指针HEAD指向的分支。
4.git rebase
Rebase 实际上就是取出一系列的提交记录,“复制”它们,然后在另外一个地方逐个的放下去。
Rebase 的优势就是可以创造更线性的提交历史。
git rebase
后面跟一个参数时,即git rebase A
,会将当前HEAD指向的分支更新到A的下面并移动指针到最新分支的最顶端,其原本的分支依然存在。
git rebase
后面跟两个参数时,即git rebase A B
,会将B所在的分支更新到A下面,并把HEAD指针指向B并移动到更新分支的最顶端,其原本的分支依然存在。
高级篇
1.分离HEAD
分离 HEAD 就是让其指向了某个具体的提交记录而不是分支名。在命令执行之前的状态如下所示:
HEAD -> main -> C1
HEAD 指向 main, main 指向 C1
在执行了git checkout C1
后
现在变成了
HEAD -> C1
2.相对引用1(~)
相对引用非常给力,这里我介绍两个简单的用法:
使用 ~<num>
向上移动多个提交记录,如 ~3
。
3.相对引用2(^)
使用 ^
向上移动 1 个提交记录。
main^
相当于“main
的父节点”。
main^^
是 main
的第二个父节点。
强制修改分支位置
你现在是相对引用的专家了,现在用它来做点实际事情。
我使用相对引用最多的就是移动分支。可以直接使用 -f
选项让分支指向另一个提交。例如:
git branch -f main HEAD~3
上面的命令会将 main 分支强制指向 HEAD 的第 3 级父提交。
4.撤销变更
主要有两种方法用来撤销变更 —— 一是 git reset
,还有就是 git revert
。
Git Reset(用于本地仓库)
git reset
通过把分支记录回退几个提交记录来实现撤销改动。你可以将这想象成“改写历史”。git reset
向上移动分支,原来指向的提交记录就跟从来没有提交过一样。例如:git reset HEAD~1
Git Revert(用于远程仓库)
虽然在你的本地分支中使用 git reset
很方便,但是这种“改写历史”的方法对大家一起使用的远程分支是无效的。
为了撤销更改并分享给别人,我们需要使用 git revert
。
执行git revert HEAD
之后
在我们要撤销的提交记录后面多了一个新提交,这是因为新提交记录 C2'
引入了更改 —— 这些更改刚好是用来撤销 C2
这个提交的。也就是说 C2'
的状态与 C1
是相同的。
revert 之后就可以把你的更改推送到远程仓库与别人分享了。
reset和revert指令的对象是head指针,因而想要撤销变更时要先让head指针指向要撤销的位置。
移动提交记录
1.git cherry-pick
将一些提交复制到当前所在的位置(HEAD
)下面的话, Cherry-pick 是最直接的方式。
例如执行git cherry-pick C2 C4
后 Git 就将被它们抓过来放到当前分支下。
2.交互式rebase
执行了git rebase -i HEAD~4
后,Git找到从HEAD至HEAD上面三个,并打开rebase UI界面
当 rebase UI界面打开时, 能做3件事:
- 调整提交记录的顺序(通过鼠标拖放来完成)
- 删除你不想要的提交(通过切换
pick
的状态来完成,关闭就意味着你不想要这个提交记录) - 合并提交。 它允许你把多个提交记录合并成一个。
杂项
1.只取一个提交记录
我们只要让 Git 复制解决问题的那一个提交记录就可以了。跟之前我们在“整理提交记录”中学到的一样,我们可以使用
git rebase -i
git cherry-pick
来达到目的。
2.提交的技巧#1
如果我们已有一个提交记录newImage
,然后又基于它创建了 caption
分支,然后又提交了一次。此时你想对newImage
进行一些小小的调整。
我们可以通过下面的方法来克服困难:
- 先用
git rebase -i
将提交重新排序,然后把我们想要修改的提交记录挪到最前 - 然后用
git commit --amend
修改它。 - 接着再用
git rebase -i
来将他们调回原来的顺序。 - 最后我们把 main 移到修改的最前端(用你自己喜欢的方法)大功告成。
3.提交的技巧#2
cherry-pick 可以将提交树上任何地方的提交记录取过来追加到 HEAD 上(只要不是 HEAD 上游的提交就没问题)。
抓过来改,改完之后在把原来的顺序抓过来拍好。
4.git tag
git tag v1 C1
(将C1添加一个标签命名为v1)
5.git describe
由于标签在代码库中起着“锚点”的作用,Git 还为此专门设计了一个命令用来描述离你最近的锚点(也就是标签),它就是 git describe
!
git describe
能帮你在提交历史中移动了多次以后找到方向。
git describe
的语法是:
git describe <ref>
<ref>
可以是任何能被 Git 识别成提交记录的引用,如果你没有指定的话,Git 会以你目前所检出的位置(HEAD
)。
它输出的结果是这样的:
<tag>_<numCommits>_g<hash>
tag
表示的是离 ref
最近的标签, numCommits
是表示这个 ref
与 tag
相差有多少个提交记录, hash
表示的是你所给定的 ref
所表示的提交记录哈希值的前几位。
当 ref
提记录上有某个标签时,则只输出标签名称。
高级话题
1.多次rebase
2.两个父节点
一个合并提交有两个父提交,遇到这样的节点时该选择哪条路径就不是很清晰了。
Git 默认选择合并提交的“第一个”父提交,在操作符 ^
后跟一个数字可以改变这一默认行为,跟几就是去第几个父提交。
使用 ^
和 ~
可以自由地在提交树中移动,非常给力。更厉害的是,这些操作符还支持链式操作!比如:
git checkout HEAD~^2~2
3.纠缠不清的分支
一关练习。
远程
Push & Pull ——Git 远程仓库!
1.git clone
2.远程分支
3.git fetch
git fetch 做了些什么
git fetch
完成了仅有的但是很重要的两步:
- 从远程仓库下载本地仓库中缺失的提交记录
- 更新远程分支指针(如
o/main
)
git fetch
实际上将本地仓库中的远程分支更新成了远程仓库相应分支最新的状态。远程分支反映了远程仓库在你最后一次与它通信时的状态。
git fetch 不会做的事
git fetch
并不会改变你本地仓库的状态。它不会更新你的 main
分支,也不会修改你磁盘上的文件。
理解这一点很重要,因为许多开发人员误以为执行了 git fetch
以后,他们本地仓库就与远程仓库同步了。它可能已经将进行这一操作所需的所有数据都下载了下来,但是并没有修改你本地的文件。
所以, 可以将 git fetch
的理解为单纯的下载操作。
4.git pull
git fetch
获取远程的数据, 现在我们学习如何将这些变化更新到我们的工作当中。
由于先抓取更新再合并到本地分支这个流程很常用,因此 Git 提供了一个专门的命令来完成这两个操作。它就是 git pull
。
git pull
就是 git fetch
和 git merge
的缩写!
pull同步远程仓库时,需要工作指针指向的是和远程指针相关联的指针;
fetch同步远程仓库时,则不需要。
5.模拟团队合作
6.git push
git push
负责将你的变更上传到指定的远程仓库,并在远程仓库上合并你的新提交记录。
可以将 git push
想象成发布你成果的命令。
注意 —— git push
不带任何参数时的行为与 Git 的一个名为 push.default
的配置有关。它的默认值取决于你正使用的 Git 的版本,但是在教程中我们使用的是 upstream
。 这没什么太大的影响,但是在你的项目中进行推送之前,最好检查一下这个配置。
关于 origin 和它的周边 —— Git 远程仓库高级操作
1.推送主分支
git pull --rebase
:拉取远程仓库的最新记录o/main并把自己追踪远程分支的指针所在分支例如main更新到o/main下面。
git pull --rebase
就是 git fetch
和 git rebase o/main [追踪o/main的分支名]
的缩写!("[]"可写可不写)
3.远程追踪
默认main远程追踪o/main,我们可以设置远程追踪分支。
设置远程追踪分支的两种方法:
1.git checkout -b totallyNotMain o/main
就可以创建一个名为 totallyNotMain
的分支,它跟踪远程分支 o/main
。
2.git branch -u o/main foo
(注意·:foo应当原本就存在)
这样 foo
就会跟踪 o/main
了。如果当前就在 foo 分支上(HEAD指向foo), 还可以省略 foo:
git branch -u o/main
4.git push的参数
在远程跟踪课程中,你已经学到了 Git 是通过当前检出分支的属性来确定远程仓库以及要 push 的目的地的。这是未指定参数时的行为,我们可以为 push 指定参数,语法是:
git push <remote> <place>
(remote是远程仓库名)
把这个命令翻译过来就是:
切到本地仓库中的“main”分支,获取所有的提交,再到远程仓库“origin”中找到“main”分支,将远程仓库中没有的提交记录都添加上去,搞定之后告诉我。
我们通过“place”参数来告诉 Git 提交记录来自于 main, 要推送到远程仓库中的 main。它实际就是要同步的两个仓库的位置。
需要注意的是,因为我们通过指定参数告诉了 Git 所有它需要的信息, 所以它就忽略了我们所检出的分支的属性!
5.git push的参数2
<place>参数详解
当为 git push 指定 place 参数为 main
时,我们同时指定了提交记录的来源和去向。
如果来源和去向分支的名称不同,比如你想把本地的 foo
分支推送到远程仓库中的 bar
分支呢?
要同时为源和目的地指定 <place>
的话,只需要用冒号 :
将二者连起来就可以了:
git push origin <source>:<destination>
这个参数实际的值是个 refspec,“refspec” 是一个自造的词,意思是 Git 能识别的位置(比如分支 foo
或者 HEAD~1
)
如果要推送到的目的分支不存在会怎么样呢?Git 会在远程仓库中根据你提供的名称帮你创建这个分支。
6.git fetch的参数
<place>
参数
1.如果你像如下命令这样为 git fetch 设置 的话:
git fetch origin foo
Git 会到远程仓库的 foo
分支上,然后获取所有本地不存在的提交,放到本地的 o/foo
上。
2.“如果我们指定 <source>:<destination>
会发生什么呢?”
不能在当前检出的分支上干这个事,但是其它分支是可以的。
这里有一点是需要注意的 —— source
现在指的是远程仓库中的位置,而 <destination>
才是要放置提交的本地仓库的位置。它与 git push 刚好相反,这是可以讲的通的,因为在往相反的方向传送数据。
如果执行命令前目标分支不存在,跟 git push 一样,Git 会在 fetch 前自己创建立本地分支, 就像是 Git 在 push 时,如果远程仓库中不存在目标分支,会自己在建立一样。
3.如果 git fetch
没有参数,它会下载所有的提交记录到各个远程分支。
7.没有 source 的 source
Git 有两种关于 <source>
的用法是比较诡异的,即你可以在 git push 或 git fetch 时不指定任何 source
,方法就是仅保留冒号和 destination 部分,source 部分留空。
git push origin :side
(删除远程仓库中名为side的分支)git fetch origin :bugFix
(创建一个名为bugFix的分支到本地仓库)
8.git pull的参数
git pull 到头来就是 fetch 后跟 merge 的缩写。你可以理解为用同样的参数执行 git fetch,然后再 merge 你所抓取到的提交记录。
以下命令在 Git 中是等效的:
git pull origin foo
相当于:
git fetch origin foo; git merge o/foo
还有…
git pull origin bar~1:bugFix
相当于:
git fetch origin bar~1:bugFix; git merge bugFix
看到了? git pull 实际上就是 fetch + merge 的缩写, git pull 唯一关注的是提交最终合并到哪里(也就是为 git fetch 所提供的 destination 参数)