Git Tips

分享几个常用的 git 操作。

git stash

假设你现在在 feature 开发一个新的功能,然后发现线上有 bug,这个时候你可能需要切换到 hotfix 分支去紧急修复这个 bug,可是当前 feature 的开发到一半,按照严格的 git-flow 流程,你不应该把 feature 分支修改的代码直接 add、commit 提交,我们要确保每次的 commit 都是有意义的,这个时候就可以使用 git stash 来暂存当前的修改。

执行 git stash 会把当前分支修改的代码记录起来,之后你的整个工作目录都是干净的,接下来你就可以切换到 hotfix 分支修复 bug,并且提交测试,最后合并到 master。bug 修复完毕之后,切回 feature 分支,执行 git stash pop 可以恢复原来修改尚未提交的代码,就可以继续进行开发。

执行 git stash 会把当前代码的修改记录保存起来,多次执行 git 会把记录以栈的形式保存,需要恢复之前的代码执行 git stash pop 就会从栈中取出。执行 git stash list 可以查看当前栈中的记录。不过,不建议很经常使用 git stash,因为太多次的 git stash 会让你忘记之前 stash 保存了些什么。

git cherry-pick

假设你现在在 feature 分支开发 A、B 两个功能,经过几天奋战,终于 A 功能开发完了,一顿 git add && git commit -m "add A feature" 之后继续 B 功能开发。B 功能开发到一半,决定 A 功能先上,这时候又不能直接把 feature 合并到 master,因为 B 功能还没完成。在这种情况下,可以使用 git cherry-pick 把某个分支的 commit 合并到另一个分支上。

commit 2ec269a2d4adebc0d7a360ed1d09cb98ec2fcc37 (HEAD -> feature)
Author: jaychen 
Date:   Thu Dec 13 16:17:28 2018 +0800

    feature B-1

commit 73901e266e8bf4ec691d27d59b9642f85e7b38c0
Author: jaychen 
Date:   Thu Dec 13 16:17:13 2018 +0800

    feature A-2

commit 2137a55f94ef5d7c22d6bb669920323d37480866
Author: jaychen 
Date:   Thu Dec 13 16:17:01 2018 +0800

    feature A-1

commit b6845c1d3572ab501732873384194f37061c7037 (master)
Author: jaychen 
Date:   Thu Dec 13 16:16:31 2018 +0800

    add test

如上 log 日志,featureA 功能有两个 commit,由于需要提前上线,需要把这两个 commit 修改的代码并到 master 中。

使用 cherry-pick 实现上面的 git 操作:

执行完之后发现 master 分支中已经有这两个 commit 了,操作成功了。

cherry-pick 是有可能尝试冲突的,冲突产生需要手动解决,如果要撤销 cherry-pick 操作,可以执行 git cherry-pick --abort 吃后悔药。

git revert

revert 操作的作用的 reset 都是撤销某次 commit,但是 revert 的方式更加温和,更加符合团队合作的要求。

revert 撤销的方式是执行一个『反向』的操作。假设某一个 commitA 提交了一个新的函数,后面你需要撤销掉这个函数,只需要找到 commitA 的 commit id,然后执行 git revert commit-id 就可以撤销 commitA 的提交。该操作在 git log 上产生一个新的 commit id,操作内容就是『撤销 commitA 提交的 commit』。

因为 revert 可以撤销某一次 commit 的内容,所以 revert 也可以用于撤销 merge。不过执行的命令会有所不同。

        commit-idA
        |\
        | \
        |  | 
 master |  | dev

上面是一个 merge 产生的 git flow,commit-idA 是 merge 的 commit-id,如果我们要撤销这个 merge,可以尝试执行 git revert commit-idA 会看到一个报错

error: commit 438c19fb89e20dbdc41bdf34ad400ec78d861010 is a merge but no -m option was given.
fatal: revert failed

大意就是说 commit0idA 是的修改是一个 merge,无法直接撤销。为什么一个 merge 就无法撤销?

从 git flow 中我们可以看出是 dev 合并到 master 中,但是对于 revert 而言它只认为是两个分支的内容合并到了一起,dev 和 master 是平等的,不存在一个分支合并到另一个分支中。

        commit-idA
            /\
           /  \
          |    |
  master  |    |  dev
  

所以在执行 git revert commit-idA 的时候下面它不知道应该撤销掉 dev 的内容,还是撤销掉 master 的内容。 这种情况下,revert 需要携带一个参数 -m 告诉 revert 保留哪个内容。

执行 revert 撤销之前需要执行 git show commit-idA 查看 commit 信息:

test git:(master) git show e419a9fa70eeef02c0a16611654e9aa61e1b7429
commit e419a9fa70eeef02c0a16611654e9aa61e1b7429 (HEAD -> master)
Merge: 867df3a c11c5a8   //这行
Author: jaychen <chenjiayaooo@gmail.com>
Date:   Sat Dec 15 17:13:50 2018 +0800

其中可以看到是把 867df3ac11c5a8 这两个 commit 的内容合并了,在 revert 撤销合并的时候,如果我们要保留第一个 commit-id 的内容就执行 git revert -m 1 commit-idA,如果要保留第二个 commit-id 的内容就执行 git revert -m 2 commit-idA

总结来说,合并 dev 分支到 master 之后,如果要撤销合并并且保留 master 分支的内容,执行 git revert -m 1 commit-id 就可以了。

同样,在 revert 的时候也会产生冲突图,这个时候执行 git revert --abort 就可以撤销 revert。

Fast-forward

fast-forwoard 不是一个操作,只是一个概念。 通常我们在进行 merge 的时候 git flow 会变成下面这样子:


master
a--->b--->c---->d
               / 
    e--->f--->g
    dev

在 master 分支会产生一个 commit-d 表示 merge,但是 git 有时候会以另外一种方式合并,使得 git flow 看起来跟进清爽,合并之后整个 git flow 看起来像下面这样

a--->b--->c--->d--->e--->f--->g

可以看出 merge 之后,直接把 dev 的 commit 接在了 master 的 commit 之后,从 git flow 中看不出有过 merge 的痕迹。这种合并方式就是 Fast-forward。如果不希望以这种方式合并,那么在 merge 的时候添加参数 --no-ff 强制不使用 Fast-forward 方式合并,整个命令像这样子 git merge --no-ff dev

*****
Written by JayChen on 14 December 2018