查看本地分支与远程分支的对应关系
1  | 查看远程分支与本地分支的对应关系  | 
新建远程分支 | push
1  | git checkout <local-branch>  | 
Tip: push 的时候只会上传当前的 branch 的指向,并不会把本地的 HEAD 的指向也一起上传到远程仓库。事实上,远程仓库的 HEAD 是永远指向它的默认分支(即 master,如果不修改它的名称的话),并会随着默认分支的移动而移动的。
--set-upstream 设置远程分支与本地分支关联
1  | 关联远程仓库的 master 分支与本地的 master 分支,该方法在 push 中可设置,如果要直接绑定分支但不 push 呢,见下面命令行  | 

拉取远程分支 | pull
1  | 说明:关联后 (--set-upstream) 可直接 push  | 
根据远程分支上创建本地分支
1  | 只有远程分支的时候,根据远程分支创建本地分支即可  | 
删除远程分支
1  | 说明:git push origin <local-branch>/<remote-branch>  | 
区分 git reset & git checkout
⭐Reference: https://blog.csdn.net/longintchar/article/details/82314102

git reset会带着当前指向的 branch 一起向前推进git checkout只会修改当前 HEAD 的指向,可先用 checkout 跳转到某次提交处,然后创建某个分支 feature(此时的 HEAD 并未指向 feature1,而是仅仅处于同一处 commit),最后再使用 checkout 切换到该 feature 分支上(即 HEAD 指向 feature 分支)
checkout 签出动作会将 HEAD 与 branch 分离开来,它有一个专门的参数用于让 HEAD 与 branch 脱离且不移动 HEAD 的用法:
1  | git checkout --detach  | 
git 各式后悔药
git 管理仓库时,往往需要撤销某些操作/提交/暂存内容。
1. 撤销工作区的文件修改
如果工作区的某个文件被改乱了,但还没有执行 git add,可以用 git checkout 命令找回本次修改之前的文件。
1  | $ git checkout -- [filename]  | 
⭐它的原理是先找暂存区,如果该文件有暂存的版本,则恢复该版本,否则恢复上一次提交的版本。
注意:工作区的文件变化一旦被撤销,就无法找回了。
2. 从暂存区撤销文件
如果不小心把一个文件添加到暂存区,可以用下面的命令撤销。
1  | $ git rm --cached [filename]  | 
上面的命令不影响已经提交的内容。
3. 替换上一次提交信息
💔情况一:提交以后,发现提交信息写错了,这时可以使用 git commit 命令的 --amend 参数,可以修改上一次的提交信息。
1  | $ git commit --amend -m "Fixes bug #42"  | 
它的原理是产生一个新的提交对象,替换掉上一次提交产生的提交对象。
💔情况二:提交之后,发现提交的文件需要修改!这时先修改好工作区,然后再执行 add 后执行 git commit --amend,因为这时暂存区有发生变化的文件,会一起提交到仓库。
1  | git commit --amend -m "append info"  | 
所以,--amend 不仅可以修改提交信息,还可以整个把上一次提交替换掉。
4. 撤销某次提交 | 但需新增来覆盖提交
一种常见的场景是,提交代码以后,你突然意识到这个提交有问题,应该撤销掉,这时执行下面的命令就可以了。
1  | $ git revert HEAD  | 

上面命令的原理是,在当前提交后面,⭐新增一次提交(commit+1),抵消掉上一次提交导致的所有变化(workspace&stage change)。它不会改变过去的历史,所以是首选方式,没有任何丢失代码的风险。
git revert 命令只能抵消上一个提交,如果想抵消多个提交,必须在命令行依次指定这些提交。比如,抵消前两个提交,要像下面这样写。
1  | $ git revert [倒数第一个提交] [倒数第二个提交]  | 
git revert 命令还有两个参数。
--no-edit:执行时不打开默认编辑器,直接使用 Git 自动生成的提交信息。--no-commit:只抵消暂存区(stage)和工作区的文件变化,不产生新的提交。
5. 丢弃提交 | 回溯
如果希望以前的提交在历史中彻底消失,而不是被抵消掉,可以使用git reset命令,丢弃掉某个提交之后的所有提交。
1  | $ git reset [last good SHA]  | 
git reset的原理是,让最新提交的指针回到以前某个时点,该时点之后的提交都从历史中消失。
默认情况下,git reset不改变工作区的文件(但会改变暂存区),--hard 参数可以让工作区里面的文件也回到以前的状态。
1  | $ git reset --hard [last good SHA]  | 
执行 git reset 命令之后,如果想找回那些丢弃掉的提交,可以使用 git reflog 命令,具体做法参考这里。不过,这种做法有时效性,时间长了可能找不回来。
6. 撤销当前分支的变化
你在当前 error 分支上做了几次提交,突然发现放错了分支,这几个提交本应该放到 master 分支。
1  | # 将 error 分支上的<最新一次>提交转移到 master 分支  | 
目标:将 error 分支上的最新两次 commit 转移到 master 分支

先执行
git checkout master再执行
git cherry-pick [SHA1] [SHA2]有冲突解决冲突,没冲突即可完成

OK!error 分支想要 reset 就 reset~

合并分支 | merge
由于现在 Git 仓库处于冲突待解决的中间状态(已执行 merge 操作),所以如果你最终决定放弃这次 merge,也需要执行一次 merge --abort 来手动取消它。
1  | 回到 merge 前的状态  | 
主流工作流 Feature Branching
这种工作流的核心内容可以总结为两点:
- 任何新的功能(feature)或 bug 修复全都新建一个 
branch来写; branch写完后,合并到master,然后删掉这个branch。
这就是这种工作流最基本的模型。
从上面的动图来看,这种工作流似乎没什么特别之处。但实质上,Feature Branching 这种工作流,为团队开发时两个关键的问题提供了解决方案:
- 代码分享
 - 一人多任务
 
1. 代码分享
1  | 本地电脑  | 
借助 GitHub 的 Pull Request 简化 Feature Branching 工作流
事实上,上面所说的这个流程,还可以利用 Pull Request 来进一步简化。
Pull Request 并不是 Git 的内容,而是一些 Git 仓库服务提供方(例如 GitHub)所提供的一种便捷功能,它可以让团队的成员方便地讨论一个 branch ,并在讨论结束后一键合并这个 branch 到 master。
同样是把写好的 branch 给同事看,使用 Pull Request 的话你可以这样做:
- 把 
branchpush到中央仓库; - 在中央仓库处创建一个 Pull Request。以 GitHub 为例:
 

然后你的同事就可以在 GitHub 上看到你创建的 Pull Request 了。他们可以在 GitHub 的这个页面查看你的 commits,也可以给你评论表示赞同或提意见,你接下来也可以根据他们的意见把新的 commits push 上来,这也页面会随着你新的 push 而展示出最新的 commits。
在讨论结束以后,你们一致认为这个 branch 可以合并了,你只需要点一下页面中那个绿色的 “Merge pull request” 按钮,GitHub 就会自动地在中央仓库帮你把 branch 合并到 master 了:

然后你只要在本地 pull 一下,把最新的内容拉到你的电脑上,这件事情就算完成了。
另外,GitHub 还设计了一个贴心的 “Delete branch” 按钮,方便你在合并之后一键删除 branch。
完整的例子:




1  | 拉取 feature2 分支  | 

2. 一人多任务
你正在认真写着代码,忽然同事过来跟你说:「内个……你这个功能先放一放吧,我们最新讨论出要做另一个更重要的功能,你来做一下吧。」
如果你是在独立的 branch 上做事,切换任务是很简单的。你只要稍微把目前未提交的代码简单收尾一下,然后做一个带有「未完成」标记的提交(例如,在提交信息里标上「TODO」),然后回到 master 去创建一个新的 branch 就好了。
1  | 切换回 master 主分支!!!因为需要从主分支上创建分支  | 
查改历史改动记录
git log
1  | 在 .bashrc 中设置 git-log 别名,可图形化输出 git log 的信息  | 
git show
1  | 查看某个具体的 commit: $ git show 查看当前 commit  | 
git diff
比对本地工作目录与暂存区的内容(即显示未使用 git add 加入暂存区的内容与暂存区的内容的不同之处)
1  | git diff  | 
比对暂存区与上一条提交的内容(即显示 git add 后的内容与上次 commit 之间内容的不同之处)
1  | 二者命令完全等价  | 
比对工作目录和上一条提交的内容:
使用 git diff HEAD 可以显示工作目录和上一条提交之间的不同,它是上面这二者的内容相加。换句话说,这条指令可以让你看到「如果你现在把所有文件都 add 然后 git commit,你将会提交什么」(不过需要注意,没有被 Git 记录在案的文件(即从来没有被 add 过 的文件,untracked files 并不会显示出来。为什么?因为对 Git 来说它并不存在啊)。

1  | git diff HEAD  | 
实质上,如果你把 HEAD 换成别的 commit,也可以显示当前工作目录和这条 commit 的区别。
不喜欢 merge 的分叉,那就用 rebase 吧
rebase —— 变基?!
其实这个翻译还是比较准确的。rebase 的意思是,给你的 commit 序列重新设置基础点(也就是父 commit)。展开来说就是,把你指定的 commit 以及它所在的 commit 串,以指定的目标 commit 为基础,依次重新提交一次。例如下面这个 merge:
1  | merge 过来;rebase 过去  | 
master: 1-2-3-4-7
branch1: 1-2-5-6-7
如果把 merge 换成 rebase,可以这样操作:
1  | git checkout branch1  | 
master: 1-2-3-4-7-8
branch1: 1-2-5-6
master 上的 7、8 是 branch1 上的 5、6 rebase 过去的~
可以看出,通过 rebase,5 和 6 两条 commits 把基础点从 2 换成了 4 。通过这样的方式,就让本来分叉了的提交历史重新回到了一条线。这种「重新设置基础点」的操作,就是 rebase 的含义。
另外,在 rebase 之后,记得切回 master 再 merge 一下,把 master 移到最新的 commit:
1  | git checkout master  | 
master/branch1: 1-2-3-4-7-8
为什么要从
branch1来rebase,然后再切回master再merge一下这么麻烦,而不是直接在master上执行rebase?从图中可以看出,
rebase后的commit虽然内容和rebase之前相同,但它们已经是不同的commits了。如果直接从master执行rebase的话,就会是下面这样:
这就导致
master上之前的两个最新commit被剔除了。如果这两个commit之前已经在中央仓库存在,这就会导致没法push了:
所以,为了避免和远端仓库发生冲突,一般不要从
master向其他branch执行rebase操作。而如果是master以外的branch之间的rebase(比如branch1和branch2之间),就不必这么多费一步,直接rebase就好。
⭐以上情况是不发生冲突的 rebase,如果发生冲突了,那么就需要先解决冲突再执行如下命令:
以下展示另外一个例子!
1  | 1.修改冲突文件  | 
初始状态

git checkout rebase-branch
git rebase master:发生 conflict 在 “step 3”!


解决冲突后,再执行
add与commit(⭐甚至这一步都不需要commit,在add之后即可)

git rebase --continue

git checkout master
git merge rebase-branch:相当于 fast-forward!

⭐总结:需要说明的是,rebase 是站在需要被 rebase 的 commit 上进行操作,这点和 merge 是不同的(相反)。
如何修复倒数第 2 个 commit | 交互式 rebase
commit --amend可以修复最新commit的错误,但如果是倒数第二个commit写错了,怎么办?
如果不是最新的 commit 写错,就不能用 commit --amend 来修复了,而是要用 rebase。不过需要给 rebase 也加一个参数:-i。
rebase -i 是 rebase --interactive 的缩写形式,意为「交互式 rebase」。
所谓「交互式 rebase」,就是在 rebase 的操作执行之前,你可以指定要 rebase 的 commit 链中的每一个 commit 是否需要进一步修改。
那么你就可以利用这个特点,进行一次「原地 rebase」。
例如你是在写错了 commit 之后,又提交了一次才发现之前写错了:
1  | git log  | 
现在再用 commit --amend 已经晚了,但可以用 rebase -i:
1  | git rebase -i HEAD^^  | 
说明:在 Git 中,有两个「偏移符号」:
^和~。
^的用法:在commit的后面加一个或多个^号,可以把commit往回偏移,偏移的数量是^的数量。例如:master^表示master指向的commit之前的那个commit;HEAD^^表示HEAD所指向的commit往前数两个commit。
~的用法:在commit的后面加上~号和一个数,可以把commit往回偏移,偏移的数量是~号后面的数。例如:HEAD~5表示HEAD指向的commit往前数 5 个commit。
上面这行代码表示,把当前 commit ( HEAD 所指向的 commit) rebase 到 HEAD 之前 2 个的 commit 上:
如果没有 -i 参数的话,这种「原地 rebase」相当于空操作,会直接结束。而在加了 -i 后,就会跳到一个新的界面:
把 pick 修改成 edit 后,就可以退出编辑界面了:
上图的提示信息说明,rebase 过程已经停在了第二个 commit 的位置,那么现在你就可以去修改你想修改的内容了。
修改完成之后,和上节里的方法一样,用 commit --amend 来把修正应用到当前最新的 commit:
1  | git add 笑声  | 
在修复完成之后,就可以用 rebase --continue 来继续 rebase 过程,把后面的 commit 直接应用上去。
1  | git rebase --continue  | 
然后,这次交互式 rebase 的过程就完美结束了,你的那个倒数第二个写错的 commit 就也被修正了:
实质上,交互式 rebase 并不是必须应用在「原地 rebase」上来修改写错的 commit ,这只不过是它最常见的用法。你同样也可以把它用在分叉的 commit 上,不过这个你就可以自己去研究一下了。
丢弃倒数第二个提交 | 强大的 rebase
如果要丢弃刚提交的 commit,那只需要执行 git reset --hard HEAD^ 即可,那如果是倒二个呢?
git rebase -i
1  | git rebase -i HEAD^^  | 
git rebase --onto
除了用交互式 rebase ,你还可以用 rebase --onto 来更简便地撤销提交。
rebase 加上 --onto 选项之后,可以指定 rebase 的「起点」。一般的 rebase,告诉 Git 的是「我要把当前 commit 以及它之前的 commits 重新提交到目标 commit 上去,这其中,rebase 的「起点」是自动判定的:选取当前 commit 和目标 commit 在历史上的交叉点作为起点。
例如下面这种情况:
如果在这里执行:
1  | git rebase <第3个commit>  | 
那么 Git 会自动选取 3 和 5 的历史交叉点 2 作为 rebase 的起点,依次将 4 和 5 重新提交到 3 的路径上去。
而 --onto 参数,就可以额外给 rebase 指定它的起点。例如同样以上图为例,如果我只想把 5 提交到 3 上,不想附带上 4,那么我可以执行:
1  | git rebase --onto <第3个commit> <第4个commit> branch1  | 
--onto 参数后面有三个附加参数:目标 commit、起点 commit(注意:rebase 的时候会把起点排除在外)、终点 commit。所以上面这行指令就会从 4 往下数,拿到 branch1 所指向的 5,然后把 5 重新提交到 3 上去。
1  | git rebase --onto HEAD^^ HEAD^ branch1  | 
上面这行代码的意思是:以倒数第二个 commit 为起点(起点不包含在 rebase 序列里哟),branch1 为终点,rebase 到倒数第三个 commit 上。
也就是这样:
reset 的本质 | 参数解析
reset 的三种参数:
--hard:重置位置的同时,清空工作目录的所有改动;--soft:重置位置的同时,保留工作目录和暂存区的内容,并把重置HEAD的位置所导致的新的文件差异放进暂存区。--mixed(默认git reset):重置位置的同时,保留工作目录的内容,并清空暂存区。
checkout 的本质 | 除了切换分支还可签出某个提交
不过实质上,checkout 并不止可以切换 branch。checkout 本质上的功能其实是:签出( checkout )指定的 commit。
直接上案例:
1  | git checkout HEAD^^  | 
另外,如果你留心的话可能会发现,在 git status 的提示语中,Git 会告诉你可以用 checkout -- 文件名 的格式,通过「签出」的方式来撤销指定文件的修改:
即撤销工作目录下的修改(此时未添加到暂存区)
Emergency!放下你手上的工作 | stash 临时存放工作目录变动
“stash” 这个词,和它意思比较接近的中文翻译是「藏匿」,是「把东西放在一个秘密的地方以备未来使用」的意思。在 Git 中,stash 指令可以帮你把工作目录的内容全部放在你本地的一个独立的地方,它不会被提交,也不会被删除,你把东西放起来之后就可以去做你的临时工作了,做完以后再来取走,就可以继续之前手头的事了。
具体说来,stash 的用法很简单。当你手头有一件临时工作要做,需要把工作目录暂时清理干净,那么你可以:
1  | git stash  | 
就这么简单,你的工作目录的改动就被清空了,所有改动都被存了起来。
然后你就可以从你当前的工作分支切到 master 去给你的同事打包了……
打完包,切回你的分支,然后:
1  | git stash pop  | 
你之前存储的东西就都回来了。很方便吧!
注意:没有被 track 的文件(即从来没有被 add 过的文件不会被 stash 起来,因为 Git 会忽略它们。如果想把这些文件也一起 stash,可以加上
-u参数,它是--include-untracked的简写。就像这样:
 1 git stash -u
从暂存区撤回工作目录 | restore
use “git restore –staged
“ to unstage 
如果已经将文件添加到暂存区,然后想要撤销暂存区中该文件的内容(打回工作目录),则使用以下命令:
1  | git restore --staged <file>  | 
tip:如果文件在工作目录下修改过但未添加到暂存区,则通过前文提到的
git checkout -- <file>来撤销该修改。
找回丢失的 branch | reflog
reflog 是 “reference log” 的缩写,使用它可以查看 Git 仓库中的引用的移动记录。如果不指定引用,它会显示 HEAD 的移动记录。假如你误删了 branch1 这个 branch,那么你可以查看一下 HEAD 的移动历史:
1  | git reflog  | 

从图中可以看出,HEAD 的最后一次移动行为是「从 branch1 移动到 master」。而在这之后,branch1 就被删除了。所以它之前的那个 commit 就是 branch1 被删除之前的位置了,也就是第二行的 c08de9a。
所以现在就可以切换回 c08de9a,然后重新创建 branch1 :
1  | git checkout -b branch1  | 
这样,你刚删除的 branch1 就找回来了。
注意:不再被引用直接或间接指向的
commits 会在一定时间后被 Git 回收,所以使用reflog来找回删除的branch的操作一定要及时,不然有可能会由于commit被回收而再也找不回来。
reflog 默认查看 HEAD 的移动历史,除此之外,也可以手动加上分支名称查看其他分支的引用移动历史,例如 master 分支:
1  | git reflog master  | 

不可移动的 branch | tag
tag 是一个和 branch 非常相似的概念,它和 branch 最大的区别是:tag 不能移动。所以在很多团队中,tag 被用来在关键版本处打标记用。
更多关于 tag:git-scm.com/docs/git-ta…
Git Flow:复杂又高效的工作流
除了前面讲到的 “Feature Branching”,还有一个也很流行的工作流:Git Flow。Git Flow 的机制非常完善,很适合大型团队的代码管理。不过由于它的概念比较复杂(虽然难度并不高),所以并不适合新手直接学习,而更适合在不断的自我研究中逐渐熟悉,或者在团队合作中慢慢掌握。基于这个原因,我最终也没有在这本小册里讲 Git Flow,但我推荐你自己在有空的时候了解一下它。