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)