Git 指令 10%提交记录

rebase

轨迹会变为线性提交

1
2
3
4
5
6
7
8
9
# 新建并且切换到bugFix
git checkout -b bugFix
# 做些提交
git commit
# bugFix新的提交放在master新的后面
git rebase master
git checkout master
# 把master指向到bugFix最新节点
git rebase bugFix

merge

相比 rebase 的线性提交,merge 的提交记录为两线,最后指向同样的结尾

1
2
3
4
5
6
7
# 新建并且切换到bugFix
git checkout -b bugFix
git commit
git merge master
git checkout master
git commit
git merge bugFix

分离 HEAD, 指向节点 hash 值

1
git checkout C4

^

指向上一个提交记录

1
git checkout HEAD^

移动分支

可以直接使用 -f 选项让分支指向另一个提交

上面的命令会将 master 分支强制指向 HEAD 的第 3 级父提交。

1
git branch -f master HEAD~3

撤销

reset

git reset 向上移动分支,原来指向的提交记录就跟从来没有提交过一样。

把上个操作重新回到暂存区,就是 commit 之前的状态

1
git reset HEAD~1

revert

为了撤销更改并分享给别人,我们需要使用 git revert

把当前的操作返回到暂存区,但刚提交的记录会显示为删除状态,用于告诉别人此删除操作

1
git revert HEAD

剩余的 10% 提交技巧

cherry-pick

当知道提交记录的 hash 值,复制这些提交记录
复制提交记录 C3 C4 C7 到当前分支

1
git cherry-pick C3 C4 C7

交互式 rebase

使用窗口 ui 复制提交节点,可以选择顺序以及选择包含节点是否复制过去

1
git rebase -i HEAD~4

只取一个提交记录

只取 C4 到 master

original

1
2
3
4
5
当前分支到master取提交记录
git rebase -i master

把master合并到取到的提交记录
git rebase bugFix master

complete

修改一个提交记录

使用 rebase -i

先用 git rebase -i 将提交重新排序,然后把我们想要修改的提交记录挪到最前
然后用 git commit –amend 来进行一些小修改
接着再用 git rebase -i 来将他们调回原来的顺序
最后我们把 master 移到修改的最前端(用你自己喜欢的方法),就大功告成啦!

original

要修改的是 C2 提交记录,使用 git rebase -i master 把 C2 提交记录挪到最前

rebase -i

使用 git commit --amend 进行一些小修改

commit --amend

再用 git rebase -i 来将他们调回原来的顺序

rebase -i 调回原来的顺序

把 master 合并到取到的提交记录 git rebase caption master

rebase caption master

我们可以使用 rebase -i 对提交记录进行重新排序。只要把我们想要的提交记录挪到最前端,我们就可以很轻松的用 –amend 修改它,然后把它们重新排成我们想要的顺序。

使用 cherry-pick

1
2
git checkout master
git cherry-pick C2

cherry-pick C2

1
git commit --amend

commit --amend

1
git cherry-pick C3

cherry-pick C3

tag 锚点

标签在代码库中起着“锚点”的作用

1
2
3
git tag v1 side~1
git tag v0 master~2
git checkout v1

git tag

Describe

由于标签在代码库中起着“锚点”的作用,Git 还为此专门设计了一个命令用来描述离你最近的锚点(也就是标签),它就是 git describe

git describe <ref>

<ref> 可以是任何能被 Git 识别成提交记录的引用,如果你没有指定的话,Git 会以你目前所检出的位置(HEAD)。

它输出的结果是这样的:

<tag>_<numCommits>_g<hash>

tag 表示的是离 ref 最近的标签, numCommits 是表示这个 reftag 相差有多少个提交记录, hash 表示的是你所给定的 ref 所表示的提交记录哈希值的前几位。

ref 提交记录上有某个标签时,则只输出标签名称

例 1

git describe master

git describe master 会输出:

v1_2_gC2

git describe side 会输出:

v2_1_gC4

Advance 综合用法

有序排序

1
2
3
4
git rebase master bugFix
git rebase bugFix side
git rebase side another
git rebase another master

git advance 1-1

git advance 1-2

选择父提交记录

操作符 ^~ 符一样,后面也可以跟一个数字。

但是该操作符后面的数字与 ~ 后面的不同,并不是用来指定向上返回几代,而是指定合并提交记录的某个父提交。还记得前面提到过的一个合并提交有两个父提交吧,所以遇到这样的节点时该选择哪条路径就不是很清晰了。

Git 默认选择合并提交的“第一个”父提交,在操作符 ^ 后跟一个数字可以改变这一默认行为。

git checkout master^
git ^ 1

git checkout master^2

git ^ 2

链式操作

1
2
3
4
5
git checkout HEAD~;#上一个
git checkout HEAD^2#第二个父节点
git checkout HEAD~2
# 等于
git checkout HEAD~^2~2

在另外一个父节点创建一条分支

1
git branch bugWork master^^2^

git ^ create branch

复杂

复杂1

1
2
3
4
5
git checkout one
git cherry-pick C4 C3 C2
git checkout two
git cherry-pick C5 C4 C3 C2
git branch -f three C2 # 强制把trhee指向C2

复杂2

远程命令

pull

git pull 就是 git fetch 加上 git merge 的缩写!

git pull --rebase 就是 fetchrebase 的简写!

锁定的 master

新建一个分支 feature, 推送到远程服务器. 然后 reset 你的 master 分支和远程服务器保持一致, 否则下次你 pull 并且他人的提交和你冲突的时候就会有问题.

1
2
3
git reset --hard o/master
git checkout -b feature C2
git push origin feature

git reset –-soft:回退到某个版本,只回退了 commit 的信息,不会恢复到 index file 一级。如果还要提交,直接 commit 即可;
git reset -–hard:彻底回退到某个版本,本地的源码也会变为上一个版本的内容,撤销的 commit 中所包含的更改被冲掉;

推送主分支

在开发社区里,有许多关于 merge 与 rebase 的讨论。以下是关于 rebase 的优缺点:

优点:

Rebase 使你的提交树变得很干净, 所有的提交都在一条线上
缺点:

Rebase 修改了提交树的历史
比如, 提交 C1 可以被 rebase 到 C3 之后。这看起来 C1 中的工作是在 C3 之后进行的,但实际上是在 C3 之前。

rebase 差异

1
2
3
4
5
git fetch
git rebase o/master side1
git rebase side1 side2
git rebase side2 side3
git rebase side3 master

git-push-rebase

merge 差异

1
2
3
4
5
6
git checkout master
git pull
git merge side1
git merge side2
git merge side3
git push

git-push-merge

远程跟踪分支

在前几节课程中有件事儿挺神奇的,Git 好像知道 mastero/master 是相关的。当然这些分支的名字是相似的,可能会让你觉得是依此将远程分支 master 和本地的 master 分支进行了关联。这种关联在以下两种情况下可以清楚地得到展示:

  • pull 操作时, 提交记录会被先下载到 o/master 上,之后再合并到本地的 master 分支。隐含的合并目标由这个关联确定的。
  • push 操作时, 我们把工作从 master 推到远程仓库中的 master 分支(同时会更新远程分支 o/master) 。这个推送的目的地也是由这种关联确定的!

直接了当地讲,mastero/master 的关联关系就是由分支的“remote tracking”属性决定的。 master 被设定为跟踪 o/master —— 这意味着为 master 分支指定了推送的目的地以及拉取后合并的目标。

当你克隆时, Git 会为远程仓库中的每个分支在本地仓库中创建一个远程分支(比如 o/master)。然后再创建一个跟踪远程仓库中活动分支的本地分支,默认情况下这个本地分支会被命名为 master

local branch "master" set to track remote branch "o/master"

当然可以啦!你可以让任意分支跟踪 o/master, 然后该分支会像 master 分支一样得到隐含的 push 目的地以及 merge 的目标。 这意味着你可以在分支 totallyNotMaster 上执行 git push,将工作推送到远程仓库的 master 分支上。

有两种方法设置这个属性,

第一种

就是通过远程分支检出一个新的分支,执行:

git checkout -b totallyNotMaster o/master

就可以创建一个名为 totallyNotMaster 的分支,它跟踪远程分支 o/master

git checkout -b foo o/master; git pull

正如你所看到的, 我们使用了隐含的目标 o/master 来更新 foo 分支。需要注意的是 master 并未被更新!

git远程跟踪分支

第二种

另一种设置远程追踪分支的方法就是使用:git branch -u 命令,执行:

git branch -u o/master foo

这样 foo 就会跟踪 o/master 了。如果当前就在 foo 分支上, 还可以省略 foo:

git branch -u o/master

1
2
3
4
5
6
git checkout -b side
git branch -u o/master
git commit
git fetch
git rebase o/master side
git push

等于

1
2
3
4
git checkout -b side o/master
git commit
git pull --rebase
git push

Git Push 的参数

git push <remote> <place>

git push origin master

切到本地仓库中的“master”分支,获取所有的提交,再到远程仓库“origin”中找到“master”分支,将远程仓库中没有的提交记录都添加上去,搞定之后告诉我。

当参数都明确,可以不用切换分支就能提交目标分支

git push `<remote> <place>`
git push `<remote> <place>`

refspecs(<source>:<destination>) <place>参数详解

如果来源和去向分支的名称不同呢?比如你想把本地的 foo 分支推送到远程仓库中的 bar 分支。

要同时为源和目的地指定 <place> 的话,只需要用冒号 : 将二者连起来就可以了:

git push origin <source>:<destination>

这个参数实际的值是个 refspec,“refspec” 是一个自造的词,意思是 Git 能识别的位置(比如分支 foo 或者 HEAD~1

git push origin foo^:master

git push origin foo^:master
Git 将 foo^ 解析为一个位置,上传所有未被包含到远程仓库里 master 分支中的提交记录。

git push origin foo^:master

如果你要推送到的目的分支不存在会怎么样呢?没问题!Git 会在远程仓库中根据你提供的名称帮你创建这个分支!

git push origin master:newBranch

练习

git push place 1

1
2
git push origin master^:foo
git push origin foo:master

git push place 2

Git Fetch

git fetch 的参数和 git push 极其相似。他们的概念是相同的

git fetch origin foo

Git 会到远程仓库的 foo 分支上,然后获取所有本地不存在的提交,放到本地的 o/foo 上。

refspecs(<source>:<destination>)

这里有一点是需要注意的 —— source 现在指的是远程仓库中的位置,而 <destination> 才是要放置提交的本地仓库的位置。它与 git push 刚好相反,这是可以讲的通的,因为我们在往相反的方向传送数据。

git fetch origin foo~1:bar

git fetch origin foo~1:bar

Git 将 foo~1 解析成一个 origin 仓库的位置,然后将那些提交记录下载到了本地的 bar 分支(一个本地分支)上。注意由于我们指定了目标分支,fooo/foo 都没有被更新。

git fetch origin foo~1:bar

如果执行命令前目标分支不存在的话,Git 会在 fetch 前自己创建立本地分支, 就像是 Git 在 push 时,如果远程仓库中不存在目标分支,会自己在建立一样。

例子

git fetch refspecs

1
2
3
4
git fetch origin master~1:foo
git fetch origin foo:master
git checkout foo
git merge master

git fetch refspecs

古怪的 <source>

有两种关于 <source> 的用法是比较诡异的,即你可以在 git push 或 git fetch 时不指定任何 source,方法就是仅保留冒号和 destination 部分,source 部分留空。

  • git push origin :side
  • git fetch origin :bugFix

git push origin :foo

push 传空值 source,成功删除了远程仓库中的 foo 分支, 这真有意思…

git fetch origin :bar
fetch 空 到本地,会在本地创建一个新分支。

Git pull

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 参数)

git push refspecs 例子

git push refspecs

1
2
git pull origin bar:foo
git pull origin master:side

git push refspecs