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 而不是 檔案內容。
- Warning: Permanently added '[github.com]' (RSA) to the list of known hosts.
- To github.com:/git/test
- ! [rejected] master -> master (fetch first)
- error: failed to push some refs to 'github.com:/git/test'
- hint: Updates were rejected because the remote contains work that you do
- hint: not have locally. This is usually caused by another repository pushing
- hint: to the same ref. You may want to first integrate the remote changes
- hint: (e.g., 'git pull ...') before pushing again.
- 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 即可。