2016
Jul
30

git checkout

checkout 有两种用途,第一种是切换 git branch ,另一种则是回复档案修改。

git checkout branch_name 这个指令会切换目前的工作路径到 branch ,如果你想要建立一个新的 branch ,那么你可以使用 git checkout -b new_branch_name。

git checkout file_name 这个指令会把指定的档案,将其内容回复到该 branch 中最后一个 commit ,所以当你修改了某一个档案,在你还没有 commit 之前,都可以使用 git checkout file_name 来放弃你的修改,将档案还原。

git reset vs revert

这两个指令很像,两个都可以将档案还原到指定的 commit ,不同的是 revert 会保留 git commit log ,而 reset 会连 commit log 也一起被清除。


A → B → C →

例如上图是我的 git commit log ,如果我想要还原到 B 的状态,那么我使用 git reset B ,而我得到的结果会是 :

A → B →

revert

若是我想要保留 git log 记录,那么我要用 git revert C ,最后我得到的结果是:

A → B → C → B →

git rebase

git rebase 的功能是与 git server 同步 git commit log,当 local 的 commit log 与 server 端不一致时,git 是不允许你将 local 的 commit push 到 server 端的,这时就要靠 git rebase 来与 server 端同步 commit log ,让我来示范如何使用 git rebase :

假设我本机有个 git repository ,我的 commit log 如下:

A → B →

而 Server 端刚好有其他人 push 了 C 这个 commit log。

A → B → C →

但是我不知道有别人已经先 push code 到 git server 端了,所以我继续修改我的程式,并在 local 端 commit 了一个 D。

A → B → D

当我用 git push 后,就会看到下面这个讯息,git server 拒绝我这次的 push ,因为两边的 commit log 不一致, server 那边的 commit 顺序是 B → C,而我 local 端的 commit 顺序是 B → D, git 不能接受这样的 commit 顺序,就算你把档案内容修改到跟 C 一模一样也是不行的,git 是认 commit log 而不是 档案内容。

Example
  1. Warning: Permanently added '[github.com]' (RSA) to the list of known hosts.
  2. To github.com:/git/test
  3. ! [rejected] master -> master (fetch first)
  4. error: failed to push some refs to 'github.com:/git/test'
  5. hint: Updates were rejected because the remote contains work that you do
  6. hint: not have locally. This is usually caused by another repository pushing
  7. hint: to the same ref. You may want to first integrate the remote changes
  8. hint: (e.g., 'git pull ...') before pushing again.
  9. hint: See the 'Note about fast-forwards' in 'git push --help' for details.

这种状态有好几种解法,第一种比较正规的是用 git rebase ,也就是等一下会说明的方式,第二种是用 git pull github.com:/xxx 来拉 server 端最新的 code ,但是这个动作会变成你在 local 端 去 merge server 端的程式,然后再一起 commit ,所以你最后送出的 Pull Request 会包含 C, D 的所有内容,从 commit 记录上来看,会被人误会成 C 的修改跟你这次的 Pull Request 有关,第三个方式最无脑,就是先备份 local repository ,然后砍掉原本的 local repository ,再重新 clone 一次。

如何使用 git rebase

在用 rebase 之前,我们要先把 D 这个 commit log 移除掉 ,所以我要先使用 reset 的功能,但是你可能不知道要 reset 到那一个 commit log ,可以先用 git reflog 来查询目前的 commit log ,再用 reset B 回到 commit D 之前的状态。

到这一步,你可以用 git status 确认,你新加的程式有没有回到了 git add 之前的步骤,确定无误后,再用 git stash , git stash 会把你目前的所有修改先暂存起来,然后将你的工作目录还原到最后的一次 commit (B)。

接著我们就可以用 git pull --rebase 来同步 git server 上的 commit log ,成功后你的 local repository 就跟 server 上的 commit 会完全一致,这时再用 git stash pop 将我们刚刚修改的内容弄回来,到这里就差不多完成罗,你只要再重新做一次 git add, commit , push 就行了。

如果你在做 git stash pop 时,有出现 code conflict 的讯息,那么不用害怕,只要打开有 conflict 的档案,将 conflict 的地方修复就好。

Rebase 全部的步骤如下:

  • git reflog
  • git reset B
  • git stash
  • git pull --rebase github.com:/xxx/xxxx
  • git stash pop
  • git add files
  • git commit -m "commit new"
  • git push

Branch Rebase

如果你的 code 已经 commit 并 push 到 personal forked repo , 那么就不能用上面那招 reset 跟 stash pop 的方式来更新 repo ,我的习惯是先在 forked repo 上开一个 branch 叫 "dev" ,平常都在 dev 上开发,完全不动 master ,保持 master 与 Main repo 同步。

当我遇到 Conflict 的时候,先开一个 branch "dev2" (from master) ,并且把 dev2 用 rebase 跟上的 Main repo commit "git branch dev2 ; git pull --rebase github.com:/xxx/xxxx".

接著再 pull dev 的 code "git pull github.com:/xxx/xxxx dev",这时 dev2 的程式已经跟 main repo 同步,并且保有我在 dev 上 commit 的 code,最后重新做一个 push ,开 PR 即可。


目前回應 Comments(1 comments)

  • Zoken 2016/08/31

    git reset, revert 我一直搞不懂這兩個名詞的用法,看到這篇文章終於懂了。

回應 (Leave a comment)