标题有点儿大,其实只是最近审文章的时候学了两小招儿罢了,但也足以让我感受到git的博大精深。用git一年整了,我的常用命令无非就以下几个:

  • git remote add
  • git remote -v
  • git config
  • git clone
  • git pull
  • git push
  • git fetch
  • git add
  • git rm
  • git commit
  • git merge
  • git checkout
  • git checkout -b
  • git branch –list
  • git branch -d
  • git status
  • git log

咦,写了一遍发现自己用的命令还挺多,然而这些都是最基本的。最近入了review pr的坑,遇到了一些看似必须要愚蠢地手动解决但实际上可以用命令解决的小问题。于是乎,再次感叹git的博大精深。

问题一

我fork了一个库,准备提pr,但是期间这个远程库会发生变化,我要怎么跟主干保持一致呢,是不是它变一次我就要删了我fork的,然后重新fork?

嗯…答案当然是否定的,git能有这么傻么?我不信。

  • 解决方案

比如,最近fork了cosx.org这个库,然后克隆到本地

git clone https://github.com/tomatoiscoding/cosx.org.git

看下远程库

git remote -v

2017-03-25 8 37 16

我需要在本地做修改,然后提交到自己的库里,然后去提pr。但我有个需求,实时追踪主干库也就是cosname/cosx.org的改动,这样能很好的避免冲突。这个时候,我可以把主干库作为另一个远程库添加到本地,很形象的叫它上游upstream。然后把它拉下来。

git remote add upstream https://github.com/cosname/cosx.org.git
git fetch upstream

再看下远程库

2017-03-25 9 30 17

这个时候继续在你自己的master分支上做rebase操作,这个好处就是,让你的提交重新回到top的位置,之后可以很干净利落地去完成你的pr。

git rebase upstream/master

当然了,这些操作目前还在本地是吧,所以你还要推到远程库才可以。于是乎,强推一发。这里-f--force的简写,表明不管远程库现在怎么样,我都要强制把目前的本地库推上去。

git push -f origin master

好了,去提pr吧,祝你一切顺利!

  • 一些新知识

git rebase是我从来不用的命令,因为它一直出现在git的高级用法里,也懒得去折腾。但因为这个需求,我看了看,似乎并不是特别特别晦涩。简单来说,两个用途:1. 用于合并 2. git rebase -i origin/master用于交互式地修改你的提交,让commit更干净。

问题二

终于可以开心地提pr了,我一开心连着commit好几次,但是只有里面一个或者几个commit我想拿去提pr,这个时候我该怎么办呢,哎呀,好纠结

额…这个嘛,接招吧~

  • 解决方案

基于主干库upstreammaster分支上目前的内容来开一个新的分支并切换到这个分支

git checkout -b cherry upstream/master

运行git log查看commit长hash,选出来你要提pr的那个commit,比如:

2017-03-25 10 08 35

这个时候你biu地一下,把这个commit从你的诸多commits里面揪出来,高级魔法在此:

git cherry-pick e0519a1bcc8d47dc391b7c51e9b06ee9df002991

然后满心欢喜

git push origin cherry

大功告成,拿着这一个commit去提pr吧,保证主干库的主人不打你!如果是多个,请用空格分割哈希值。

被merge之后,可以安心地把这个樱桃分支删除。

git branch -d cherry
git push origin :cherry
  • 一些新知识

git cherry-pick这个命令,别说不用了,连听都没听过。中文名:摘樱桃。用来干啥呢?就是当你想merge的时候呀,你只想merge掉一部分commit,这个时候你就要摘樱桃了,说白了就是对已经存在的commit做再次提交。建个新分支,揪出原分支的提交的哈希值,重演。

问题三

我的一个提交已经从本地推向远程了,但是我发现了不能忍的错误,有啥解决方案不?

我觉得吧,这要看你到了哪种不能忍的地步,如果只是简单的小错误,你可以重新commit一次,纠错;如果是那种你不小心把password提交上去了,你就得让它永远消失在提交历史中,那得考虑考虑别的招数了。比如本小姐最近觉得有一个提交让我觉得很衰很蠢,想从commit history里面拿掉,所以必须用时光机退回到从前,那我该怎么办呢?

  • 解决方案

    1. 如果你是发现其中有个无大碍的小错误,你可以改完继续git commit一次推上去,就不用赘述了。所有修改历史将被保存。
    2. 如果你在上一次commit里做了很多修改,然而这整个commit你都不想要了,你肯定不能手动地一个一个改回去,这个时候git revert可以帮你把整个commit撤回,但此次撤回作为一个新的commit提交。具体就是git revert加上SHA(去git log里查哈),把那个commit丢掉,但提交历史依然被保存。所以它是个安全的命令。
    3. 如果不小心跳票了,提交了敏感信息,比如账号密码一类,这样的话,不管你是手动删除再提交还是把整个commit逆转,都不管用,因为它们都保留了提交历史,其他人还是可以从提交历史里面看到这些敏感信息,这个时候你就要重写历史了。

      重写历史是个有点麻烦的事情,又得用一大堆git高级知识才能填坑,虽然这里我只用了一句命令就给它一锅端了(主要是我嫌麻烦),这句命令还是挺危险的,git reset --hard,它会把你尚未提交的修改全都一锅端了,比较适合我这种本地库比较干净的情况,于是我就坚决地回到从前仿佛什么都没发生一样。而且如果你的库是被很多人fork过的,那你修改提交历史也得小心,目测会给别人带来些麻烦。但这些又都不是不可挽救的,你得学学学。特别详细的文档,我看这个不错:https://www.atlassian.com/git/tutorials/undoing-changes

这些都是一些有用的回滚技能,还好有git,还好我能用技术解决这个bug,不然感觉自己蠢到家又没人救的了。用理性的技术手段解决感性的问题大概也是我一直都推崇的吧~