Git 筆記
05 Apr 2018簡介
Git 是一種分散式的版本控制系統(Distributed Version Control),優點是免費、開源、速度快、檔案小。
備註
- 由於 Git 的版本控管方式是記錄檔案內容的快照(Snapshot),因此可快速切換版本。而所謂的快照,即是除了記錄版本間的差異外,也將相同的部份指向最初尚未修改的版本。其他的版控系統大多只記錄了前後版本的差異,而 Git 就像 「拍照」一樣,記錄了完整的變化過程。
- 分散式的版本控制系統不需要一台專門的伺服器或網路與之連線才能做版控,在本機即可完成。
設定
使用者相關
- 看目前設定:
git config --list
- 新增/修改全域設定:
git config --global <key> <value>
,也可直接修改全域設定檔vim ~/.gitconfig
例如
git config --global user.name "Summer Tang"
git config --global user.email summer_tang@sample1.com
- 新增/修改特定專案設定:
git config --local <key> <value>
,也可直接修改該專案底下的設定檔vim .git/config
例如
git config --local user.name "Summer Tang"
git config --local user.email summer_tang@sample2.com
SSH Key 相關
檢查 SSH Key 是否存在,若存在則會看到 id_rsa.pub
檔。
ls -al ~/.ssh
-rw-r--r-- 1 Cythilya staff 744 1 25 21:54 id_rsa.pub
填入 email 並產生 SSH Key
ssh-keygen -t rsa -b 4096 -C "your e-mail"
將 Key 存在 /Users/you/.ssh/id_rsa
並按 Enter 鍵
Enter a file in which to save the key (/Users/you/.ssh/id_rsa): [Press enter]
確認 Agent 是否存在,若存在則會出現 Agent pid XXXXX 的訊息
eval "$(ssh-agent -s)"
將 Key 加到 Agent
ssh-add ~/.ssh/id_rsa
將 SSH Key 貼到 GitHub 個人設定頁上
pbcopy < ~/.ssh/id_rsa.pub
:到.ssh
的資料夾底下打開id_rsa
,將內容複製下來- 到 GitHub 網頁的 Settings > SSH keys,貼上之前複製的內容
測試
ssh -T git@XXX.XX.X.XX
出現此訊息代表可使用 SSH Key 連上 GitHub
> Hi username! You've successfully authenticated, but GitHub does not provide shell access.
這邊補充一個小東西 - 當利用 SSH 連線目標主機時,若是第一次連線,會出現訊息「The authenticity of host ‘XXXX’ can’t be established」,詢問是否要繼續連線,相關可參考資訊這裡。
指令縮寫
將常用指令設定縮寫,例如:git status
可簡寫為 git st
。
$ git config --global alias.st status
$ git config --global alias.co checkout
$ git config --global alias.pl 'pull origin master'
$ git config --global alias.ps 'push origin master'
常用命令列指令
cd
:切換目錄。~
表示從 Home 目錄開始,..
表示往上一層目錄。pwd
:顯示目前所在目錄。ls [-al]
:列出目前所在目錄的所有目錄和檔案,加上-al
可列出更多資訊,a
表示顯示以.
開頭的隱藏檔,l
表示顯示檔案權限、擁有者、建立與修改時間等。mkdir
:建立新目錄。touch
:若檔案不存在,則建立新檔案;若檔案存在,則修改檔案的更新時間,檔案內容不變更。cp <file_name_1> <file_name_2>
:複製檔案,複製<file_name_1>
並將複製後的檔名取為<file_name_2>
。mv <file_name_1> <file_name_2>
:更名,將<file_name_1>
更名為<file_name_2>
。rm <file_name>
:刪除檔案<file_name>
。clear
:清除畫面。vim <file_name>
:使用 vim 打開這個檔案<file_name>
,按下 i(insert) 或 a(append) 或 o(add new line)後可做編輯,按下「:wq」表示儲存並離開,按下「:w」表示儲存,按下「:q」表示離開。可參考模式切換。cat <file_name>
:標準輸出。stat <file_name>
:列出檔案資訊。chmod <XXX>
:設定權限為 XXX。chmod -f 755 $(find -type d)
:設定目錄權限為 755。chmod -f 644 $(find -type f)
:設定檔案權限為 644。- 如何推算權限?可參考權限整理。
grep
grep -r "hello_world" ./*
:找尋所有包含「hello_world」字串的檔案。grep -r --include=*.html "hello_world" ./
:找尋所有包含「hello_world」字串且檔案類型為.html
的檔案。
find
:搜尋特定檔案,可參考這裡。find ./* -[i]name summer.txt
:找尋檔名為 summer.txt 的檔案。i
表示不分大小寫。find ./* -type f -name "*.html"
:找尋檔案類型為.html
的檔案。find . -type f -empty
:找尋空檔案。find . -type d -empty
:找尋空目錄。find . -type f -name ".*"
:找尋隱藏檔。
ln -s <來源> <目的>
:設定捷徑(Symbolic Link)。
Git 檔案的三種狀態與專案的三個區域
在 Git 中檔案分為三種狀態
- 已修改(Modified):檔案已修改,但尚未存到資料庫。
- 已暫存(Staged):檔案被標記為已修改,並將此次修改擷取快照,準備下一次的提交。
- 已提交(Committed):檔案已存到本地資料庫。
在 Git 中專案分為三個區域
- Working Directory(工作目錄):從專案取出的某個版本。這些檔案從 Git 目錄內被壓縮過的資料庫中拿出來並放在磁碟機供使用或修改。
- Staging Area(暫存區域):又稱為索引(Index),暫存區域是個單純的檔案,放在 Git 目錄,儲存關於下一次提交的資訊。
- Git Repository / Directory(Git 倉儲 / 目錄):Git 儲存專案 Metadata 及物件的資料庫。
圖片來源:本地操作
Git 指令
git –version
檢視目前所用 Git 的版本。
$ git --version
git version 2.10.1
git init
建立 .git
的隱藏目錄,它是一個 Git Repository 或稱 Git Directory。目的是初始化這個目錄,讓 Git 對這個目錄做版本控管,之後若不想再控管這個目錄,將 .git
移除即可。.git
裡面放了很多東西,像是版號記錄、目前指到哪個分支、遠端連線位置等。
$ git init
Initialized empty Git repository in /Users/Cythilya/Documents/git/git_test/.git/
訊息提示在 git_test 目錄底下建立了 .git
這個 Git Repository。
git status
檢查 Working Directory 的狀態。例如:未進入版本控管(Untracked)、新增檔案(New File)、刪除檔案(Deleted)、檔案已修改(Modified)等。
git diff
比較差異。
git diff
:工作目錄 vs 倉儲。git diff <sha-1>
:工作目錄 vs 提交記錄<sha-1>
。git diff HEAD
:工作目錄 vs 當前分支的最新狀態。git diff —-cached <sha-1>
或git diff -—stages <sha-1>
:倉儲 vs 提交記錄<sha-1>
。git diff --cached HEAD
或git diff -—stages HEAD
:倉儲 vs 當前分支的最新狀態。git diff <sha-1_1> <sha-1_2>
:比較兩個不同提交版本的差異。
git add
將檔案的變更從 Working Directory 移到 Staging Area。
git add <file>
:將特定檔案的變更從 Working Directory 移到 Staging Area。git add *.html
:將所有的.html
檔案的變更從 Working Directory 移到 Staging Area。git add -A
或git add --all
或git add .
:將全部檔案的變更從 Working Directory 移到 Staging Area。- 備註:Git 版本 1.X
git add .
不包含刪除的檔案,並範圍只限於這個目錄之下。
- 備註:Git 版本 1.X
git add -f <file>
:忽略.gitignore
的設定,強迫加入會被忽略的檔案到 Staging Area。git add -p <file_name>
:只提交檔案的一部份,選擇「e」,接著把不要加入暫存區的程式碼刪除。
Unstage Files
將檔案的變更從 Staging Area 移到 Working Directory。
git rm --cached <file>
git reset HEAD
git commit
提交本次修改,將檔案的變更從 Staging Area 移到 Git Repository。
git commit -m <message>
:-m
表示附加訊息。git commit --allow-empty -m <message>
:沒有任何檔案變更卻執行提交。git commit -a -m <message>
近似於git add -A; git commit -m <message>
,但-a
只對已存在倉儲的檔案有效,對新加入的檔案(Untracked File)無效。git commit --amend -m <message>
:修改最後一次提交的訊息。
git log
從新到舊列出提交記錄,資料有提交者、提交時間、做了什麼事。
git log --oneline
:緊密(也就是一行)顯示提交資訊。git log --author="Summer\|Jimmy"
:查詢提交者 Summer or Jimmy 的提交紀錄。git log --grep="hello"
:從提交訊息中找尋符合內容「hello」的提交記錄。git log -S "pusheen"
:從提交的檔案中,搜尋檔案內容提到「pusheen」字串的提交記錄。git log --since="9am" --until="12am"
:找出「今日早上 9 點到 12 點間」所有的提交記錄。git log --since="9am" --until="12am" --after="208-01"
:找出「2018 年 1 月後,每天早上 9 點到 12 點間」的提交記錄。git log <file_name>
:檢視特定檔案的提交記錄。git log -p <file_name>
:檢視特定檔案的提交記錄的修改範圍。
git rm
移除檔案並將變更移到暫存區(工作區域 -> 暫存區)。 另比較 Unstage Files(暫存區 -> 工作區域)。
$ git rm <filename>
等同於
$ rm <filename>
$ git add <filename>
git mv
變更檔案名稱並將變更移到 Staging Area。
$ git mv <file1> <file2>
等同於
$ mv <file1> <file2>
$ git add -A
例如,將檔案 a.md 更名為 b.md,會出現 renamed 的提示訊息。
$ git mv a.md b.md
$ git status
# 省略一些提示訊息...
renamed: a.md -> b.md
git blame
git blame <file_name>
:查詢特定檔案、特定行數的作者。git blame -L <line_num_from,line_num_to> <file_name>
:查詢特定檔案、指定行數範圍內的作者。
$ git blame cinderella.html
e7f48a31 (Cythilya Tang 2018-03-29 16:29:57 +0800 1) I am Cinderella!
f26011a2 (Cythilya Tang 2018-03-31 00:34:46 +0800 2) test 0002
c0521d14 (Cythilya Tang 2018-03-31 00:41:02 +0800 3) test 0004
$ git blame -L 1,2 cinderella.html
e7f48a31 (Cythilya Tang 2018-03-29 16:29:57 +0800 1) I am Cinderella!
f26011a2 (Cythilya Tang 2018-03-31 00:34:46 +0800 2) test 0002
git checkout
git checkout <file_name>
:還原工作區域上特定檔案的變更,也就是把檔案從倉儲裡拿一份(覆蓋)到目前的工作目錄。git checkout .
:還原工作區域上全部檔案的變更。git checkout HEAD~3 <file_name>
:還原工作區域上特定檔案到前 3 個版本的狀態。git checkout HEAD~3 .
:還原工作區域上全部檔案的變更到前 3 個版本的狀態。git checkout <branch_name>
:切換到指定分支。git checkout -b <branch_name>
:切換到指定分支,若分支不存在,則會建立這個分支並切換過去。git checkout --ours <file_name>
:解衝突時選擇目前分支的檔案。git checkout --theirs <file_name>
:解衝突時選擇來源分支的檔案。
git reset
這裡的 Reset 並不是字面上的「重新設定」,而是「前往」,主要用來拆掉提交的變更。
git reset <last_sha1>^
或git reset master^
或git reset HEAD^
:使用相對距離,拆掉最新的 Commit,將變更重新放回工作目錄。git reset <last_sha1>~2
:使用相對距離,拆掉 2 個提交,如下例回復到 86ea6c6。git reset <sha1>
:使用絕對距離,退回至特定 Commit。若想要拆掉最新的 Commit,如下例會是git reset 372fbf8
。git reset --mixed
:變更留在工作目錄,而不在暫存區。若沒有指定 Reset 模式就是預設為 Mixed 模式。git reset --soft
:變更留在暫存區。git reset --hard
:直接丟掉,工作目錄以及暫存區的檔案都會丟掉。
範例 1
拆掉最新的 Commit 回復到前一個 Commit(372fbf8),並將變更放回工作目錄。
查看目前的提交記錄。
$ git log --oneline
4b551f2 commit_1
372fbf8 commit_2
86ea6c6 commit_3
若要拆掉最新的 Commit 回復到前一個 Commit(372fbf8),並將變更放回工作目錄,可使用以下任一方法
- 方法一,使用相對距離:
git reset 4b551f2^
,回復到最新一次提交記錄的前一個 Commit。 - 方法二,使用絕對距離:
git reset 372fbf8
,直接設定回復到 372fbf8。
由於沒有設定 Reset Mode,所以是 Mixed,也因此變更會放在工作目錄之下。如下,在 Reset 後保留檔案的變更但尚未提交至暫存區。
$ git reset 4b551f2^
Unstaged changes after reset:
M hello.html
檢視工作目錄的狀態,保留變更並放在工作目錄。
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: hello.html
no changes added to commit (use "git add" and/or "git commit -a")
範例 2
拆掉最新的 Commit 回復到前一個 Commit(4b551f2),並將變更留至暫存區。
查看目前的提交記錄。
$ git log --oneline
24469d5 commit_1
4b551f2 commit_2
372fbf8 commit_3
拆掉最新的 Commit 回復到前一個 Commit(4b551f2)。
$ git reset 24469d5^ --soft
檢視工作目錄的狀態,保留變更並放在暫存區。
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: hello.html
範例 3
拆掉最新的 Commit 回復到前一個 Commit(372fbf8),並將變更完全丟棄。
查看目前的提交記錄。
$ git log --oneline
38c6460 commit_1
4b551f2 commit_2
372fbf8 commit_3
拆掉最新的 Commit 回復到前一個 Commit(372fbf8),並將變更完全丟棄。
$ git reset 38c6460^ --hard
HEAD is now at 4b551f2 commit_2
丟棄變更,沒有看到之前變更的部份出現在工作目錄或暫存區。
$ git status
On branch master
nothing to commit, working tree clean
檢視提交記錄,已拆掉 commit_1。
$ git log --oneline
4b551f2 commit_2
372fbf8 commit_3
範例 4
拆掉多個提交記錄。
回到前三個 Commit 的狀態,也就是回到 70c3f56。
$ git log --oneline
4b551f2 commit_2
372fbf8 commit_3
86ea6c6 commit_4
70c3f56 commit_5
可用以下任一方法
- 方法一,使用相對距離:
git reset 4b551f2~3
- 方法二,使用絕對距離:
git reset 70c3f56
注意!由於一次回復多個提交記錄但打開檔案後看到修改仍被保留可能會覺得怪怪的,要記得預設 Reset Mode 是 Mixed。若希望回復後不要保留修改,改為 Hard 即可,像是這樣 git reset 70c3f56 --hard
。
範例 5
取回拆掉的提交記錄。
- Step 1:使用
git reflog
或git log -g --oneline
查詢拆掉的提交記錄。 - Step 2:使用
git reset <sha-1>
移動到拆掉的提交記錄上。
git branch
分支是指向某個提交記錄的指標。
git branch
:列出目前可用分支和標記所在分支。git branch <branch_name>
:開新分支。git branch [-f] master HEAD~3
:Branch Forcing,(強制)移動 master 指向從 HEAD 往前數的第三個 Parent Commit。這是使用 -f 選項直接讓分支指向另一個 Commit。-f
用於分支已存在的狀況。git branch -m <old_branch_name> <new_branch_name>
或git branch -M <old_branch_name> <new_branch_name>
:更新分支名稱。git branch -d <branch_name>
:刪除分支。若分支尚未合併到 master,則會出現提示訊息。git branch -D <branch_name>
:強制刪除尚未合併的分支。git branch <branch_name> <sha-1>
:開一個新的分支,並回復到特定提交記錄的狀態。可當成- (1)找回已刪除的分支
- (2)從特定 Commit 開分支並切換過去
git merge
合併。
合併分支 <branch_name>
到 master
- Step 1: back to master
git checkout master
- Step 2: merge changes in branch
<branch_name>
to mastergit merge <branch_name>
orgit merge <sha-1>
合併時不要使用「快轉模式(Fast Forward)」git merge <branch_name> --no-ff
,可參考Git-fast-forward快轉模式。
git rebase
用於重新定義參考基準。
重新定義參考基準
git rebase
與 git merge
不同之處是不會產生一個額外合併的 Commmit。
$ git rebase <branch_name> # 以分支 <branch_name> 當成新的參考基準
範例:rebase 分支 bugFix 到 master
將 bugFix 的修改接到 master 之後。
- Step 1:
git checkout bugFix
(切換到 bugFix) - Step 2:
git rebase master
(將 bugFix 的修改接到 master 之後)
備註:Step 1 + Step 2 等同於 git rebase <new_base> <branch>
,意即 git rebase master bugFix
如下圖示,比較 git rebase
與 git merge
的分支圖。
若使用 git merge
會產生一個額外的合併 Commit。
圖片來源:Learn Git Branching!
但若使用 git rebase
則不會產生一個額外的合併 Commit。這是因為直接將指標指到指定的新位置的關係。
圖片來源:Learn Git Branching!
這兩種方法各有好處,git merge
保留了合併過程,而 git rebase
讓記錄簡潔。
取消 Rebase
方法一:Reset
$ git reflog
$ git reset <sha-1> --hard
方法二:ORIG_HEAD
ORIG_HEAD 會記錄最近一次「危險操作」之前 HEAD 的位置。
$ git reset ORIG_HEAD --hard
進入互動模式
git rebase -i <sha-1>
「-i」表示進入互動模式,可用來標記提交記錄。
標記種類如下
- r:重新修改歷史提交訊息。
- squash:合併歷史提交變更與重寫訊息。
- edit:在 Rebase 的過程中,遇到標記 edit 的 Commit 就停下來,讓 HEAD 停在這裡。
- drop:刪除 Commit。
將多個 Commit 合併為一個 Commit
輸入指令 git rebase -i <sha-1>
後使用 squash 標記提交記錄,第一個提交訊息即為合併後的提交訊息。
將一個 Commit 拆解為多個 Commit
- Step 1:輸入指令
git rebase -i <sha-1>
後使用 edit 標記這多個提交記錄。 - Step 2:
git reset HEAD^
拆掉當前 Commit。 - Step 3:分段使用
git add <file_name>
將檔案分別加入暫存區後提交進入儲存區。 - Step 4:
git rebase --continue
繼續跑完 Rebase。
在 Commit 之間再加入新的 Commit
- Step 1:輸入指令
git rebase -i <sha-1>
後使用 edit 標記這多個提交記錄。 - Step 2:加入新的 Commit。
- Step 3:
git rebase --continue
繼續跑完 Rebase。
調整 Commit 的順序
- Step 1:輸入指令
git rebase -i <sha-1>
後使用 edit 標記這多個提交記錄。 - Step 2:編輯順序,存檔並離開。
刪除 Commit
- 方法一:輸入指令
git rebase -i <sha-1>
後使用 drop 標記提交記錄,存檔並離開。 - 方法二:輸入指令
git rebase -i <sha-1>
後刪除提交記錄,存檔並離開。
git revert
再做一個新的 Commit,來取消不要的 Commit。作用等同於 git reset HEAD^ --hard
,只是 reset 不產生新的提交記錄。
git revert HEAD --no-edit # 取消最新的提交記錄,並且不編輯提交訊息
備註:git checkout <file>
是還原工作區域上特定檔案的變更,不要搞混了。
git tag
設定標籤
標籤是指向特定 Commit 的指標,可當成對某個 Commit 下錨點,在開發時完成特定的里程碑時就很適合使用標籤做標記。若無 <sha-1>
則指向目前的 Commit。
輕量標籤(Lightweight Tag),用來個人使用或暫時標記用。
$ git tag <tag_name> <sha-1>
有附註的標籤(Annotated Tag),用來標註軟體版號。
$ git tag <tag_name> <sha-1> -a -m "這是備註訊息"
刪除標籤
$ git tag -d <tag_name>
git stash
儲存做到一半的工作。
方法一:先提交,之後再拆掉提交
$ git add -A
$ git commit -m "hahaha"
$ git reset HEAD^
方法二:先儲存變更,之後再把變更拿出來
$ git stash [-u] # Untracked Files 無法被 Stash,需要額外使用 -u 參數
$ git stash list # 查看暫存的變更
$ git stash pop stash@{<num>} # 把變更拿出來
$ git stash drop stash@{<num>} # 丟棄暫存的變更
$ git stash apply stash@{<num>} # 把 stash@{<num>} 這個 Stash 拿來套用在現在的分支上,但 Stash 不會刪除,還是會留在 Stash 列表上
git cherry-pick <sha-1_1> <sha-1_2> <…>
在此分支之下,揀選並複製這個專案上其他的 Commit,然後放到目前位置(HEAD)之下。
git cherry-pick <sha-1_1> <sha-1_2> <…>
:複製一個以上的 Commit 並接在目前的位置(HEAD)之下。git cherry-pick <sha-1_1> <sha-1_2> <…> --no-commit
:撿過來但先不合併,仍在暫存區。
git remote add...
加入遠端連線位置,以備後續將本地端的 Repo 推向遠端 (GitHub) Server。
$ git remote add origin <repository_url> # origin 即指向遠端 Repo 的代表名稱,可更名
git clone
取得現有 Git Repository 的複本。
git fetch
同步遠端伺服器上的資料到本地,可以把 git fetch
想成是在抓取資料(並且是目前本地端所沒有的部份),但不做更新。下載完成後會更新 Remote Branch 所指向的方向,意即 origin/<branch_name>
所指向的方向。
git pull
到遠端抓取更新的資料(Fetch),並且更新本地的進度(Merge),意即 git pull = git fetch + git merge
。
git push
將本地端的 Repo 推向遠端 Server。
git push origin master
或git push origin <branch_name>
git push origin <local_branch_name>:<remote_branch_name>
:當本地端的<local_branch_name>
分支推上遠端後,在遠端建立(或更新進度)一個叫做<remote_branch_name>
的分支。git push origin <空白>:<remote_branch_name>
:刪除遠端分支。git push -f
:經過 rebase 整理完提交紀錄後,若先前已經推到遠端,那整理完的提交紀錄就只能強制推上去了。指令git push <repository> <branch> -f
;範例git push origin master -f
。
git describe <sha-1>
顯示距離最近的錨點。
<sha-1>
是任何一個可以被 Git 解讀成 Commit 的位置,若沒有指定會以目前所在的位置為準(HEAD)。輸出結果為 <tag>_<num_commits>_g<sha-1>
。
tag
:離<ref>
最近的標籤。num_commits
:這個標籤離<ref>
有多少個 Commit。sha-1
:給定的<sha-1>
所表示前 7 個十六進制的數字。
git grep
搜尋。
git grep "test" <file_name>
:檔案內是否有「test」的字串。git grep "test"
:現在版本的哪個檔案內有「test」的字串。
git show
顯示資料。
QnA
如何找尋 Git 存放的位置?
$ which git
/usr/bin/git
.git
是什麼?
可參考 git init
。
origin 是什麼?
In Git, “origin” is a shorthand name for the remote repository that a project was originally cloned from. More precisely, it is used instead of that original repository’s URL - and thereby makes referencing much easier.
origin 是 Git 複製遠端倉庫(Remote Repository)時預設名字,意即當使用 git clone
時,Git 會自動把 remote 命名為 origin,可更名。
HEAD 是什麼?
HEAD 是指向目前所在分支的指標,位於 .git
的 HEAD 檔案。打開來看會是以下這樣…
ref: refs/heads/master
意思是目前指向分支 master。HEAD 的移動記錄可用 git reflog
來查看。Reflog 會保留 30 天。Git 1.8.5 後 HEAD 可縮寫為「@」。
若 HEAD 並非指向目前所在分支,則是「detached HEAD」的狀態。
detached HEAD 是什麼?
沒有指到「本地」某個分支的情況,可能發生這個情況的原因有
- 使用
git checkout <sha-1>
指令直接跳到某個 Commit,而此 Commit 剛好目前沒有分支指著它。 - Rebase 的過程即是處於不斷 detached HEAD 的狀態。
- 切換到某個遠端分支的時候。
如何離開 detached HEAD 狀態?讓 HEAD 有任何分支可以指向它就可以了,例如,讓它回到 master 分支
$ git checkout master
Switched to branch 'master'
新增與刪除分支
- 新增本地分支 bar:
git fetch origin :bar
- 刪除遠端分支 foo:
git push origin :foo
git reset
vs git rebase
vs git revert
可參考 Reset、Revert 跟 Rebase 指令有什麼差別?
git rebase
vs git merge
兩者皆可作為合併多個 Commit 的解法,而不同的是 git rebase
使用複製 Commit 的方式,git merge
會額外產生一個合併的 Commit。
點此看詳細內容。
git rebase
vs git cherry-pick
git rebase
和 git cherry-pick
皆可將 Commit 複製後接在某個 Commit 之後,但差異是什麼?使用的時機點是什麼?
- 確定要複製哪些 Commit 與 Hash 值,就可以使用
git cherry-pick
。 - 不確定要哪些 Commit,就可以使用互動式 Rebase 來做選取和編輯。
git fetch
vs git pull
git fetch
不會更新本地資料,但 git pull
會更新本地資料。
git pull
會一次下載遠端的更新並且合併(git merge
)。其他方法還有git fetch; git merge origin/master
git rebase origin/master
git cherry-pick origin/master
如何還原變更
修改提交訊息
git commit --amend -m "重新修正的訊息"
處理最後一次的提交記錄。- 使用 Rebase 修改更早的紀錄,可參考這裡。
追加檔案到最近一次的提交
方法一
使用 git reset
把最後一次的 Commit 拆掉,加入新檔案後再重新 Commit。
方法二
使用 --amend
進行 Commit,--no-edit
表示不要編輯 Commit 訊息。
$ git add <new_file> # 將新加入的檔案放到暫存區
$ git commit --amend --no-edit # 將新加入的檔案併入這個提交裡面
忽略已存在的檔案
由於忽略清單(.gitignore)的生效,必須是在檔案尚未進入版控之前,因此若要忽略已存在的檔案,就必須先將檔案移出版本控管,意即 Unstage。接著在將移出這個變更和更新後的忽略清單加入暫存區和倉儲區,最後可移除忽略檔案。
$ vim .gitignore # 設定忽略已存在的檔案
$ git rm --cached <file_name> # 將特定檔案移出版本控管
$ git add <file_name> # 將移出版本控管檔案的變更加入暫存區
$ git add .gitignore # 將修改後的忽略清單加入暫存區
$ git commit -m "ignore existing file" # 將移出版本控管檔案的變更加入倉儲區
$ git clean -fx # 清除忽略的檔案,若要刪除忽略的檔案可做這一步
如何解決非文字的衝突?
可參考這裡。
如何處理 Diverged History (分岔)?
同步遠端進度,並將本地更新推上遠端。
方法一:Rebase
- 將資料從遠端取下來。
- 複製本次修改並接在目前 origin/master 指向的位置,於是 master 指向最新的位置。
- 同步遠端狀態,並重新設定 origin/master 參考的基準點與 master 相同。
$ git fetch
$ git rebase origin/master
$ git push
等同於
$ git pull —-rebase; git push
方法二:Merge
- 將資料從遠端取下來,合併本次修改與遠端狀態,並產生一個合併的 Commit 同時指向兩個 Parent Commit。
- 同步遠端狀態,並重新設定 origin/master 參考的基準點與 master 相同。
$ git fetch
$ git merge origin/master
$ git push
等同於
$ git pull
$ git push
如何 Fork 自己在 GitHub 上的 Repository?
答案是「無法」,但可以先複製一份舊的 Repository 的程式碼,然後推到另一個新的 Repository 上。
複製一份舊的 Repository 的程式碼,並放到新的資料夾中。
$ git clone git@github.com/<user_name>/<repo_name> <new_repo_name>
$ cd <new_repo_name>
接著來看遠端連線狀況。
$ git remote -v
origin git@github.com:<user_name>/<repo_name>.git (fetch)
origin git@github.com:<user_name>/<repo_name>.git (push)
目前都是指向舊的 Repository,現在要改成指向新的 Repository。
$ git remote set-url origin git@github.com/<user_name>/<new_repo_name>
確認是指向新的 Repository。
$ git remote -v
origin git@github.com:<user_name>/<new_repo_name>.git (fetch)
origin git@github.com:<user_name>/<new_repo_name>.git (push)
如果之後要不定時抓舊的 Repository 的進度做更新,或把新的 Repository 的進度推到舊的 Repository,可設定 fetch 和 push 到特定遠端節點 upstream 時,會抓取或推到舊的 Repository 上。
$ git remote add upstream git@github.com/<user_name>/<repo_name>
最後,將程式碼推到遠端新的 Repository 上。
$ git push origin master
注意!這個新的 Repository 要夠乾淨,上面不可以有預設的 README.md、Licence 等系統預先準備好的檔案,否則會推失敗,就算使用 git pull
也會因「fatal: refusing to merge unrelated histories」而無法合併。
順道一提,在 GitHib 開新的 Repository 時常會看到 Quick setup 有 git push -u origin master
這個指令 (如下圖),-u
是 --set-upstream
的縮寫,代表我們在 local 對一個已存在的 repo 設定 remote url 然後推到遠端的意思。
如何設定遠端追蹤(Remote Tracking)?
方法一:使用 git checkout -b
git checkout -b foo origin/master; git pull
建立新的分支 foo 追蹤遠端的分支 master ,並將資料下載下來。git checkout -b foo origin/master; git commit; git push
設定 foo 追蹤遠端的分支 master,並將提交推上遠端。
方法二:使用 git branch -u
git branch -u origin/master foo 等同於 git checkout foo; git branch -u origin/master
設定分支 foo 追蹤的遠端分支是 master。
參考資料 / 推薦閱讀
- 為你自己學 Git:手牽手跟著範例由淺入深地學習。
- Try Git:互動式學習 Git 基本指令。
- Learn Git Branching!:這也是互動式的學習教材,可練習較難的部份,例如 rebase 和 merge 等。
- Git 初學筆記 - 指令操作教學:可當簡易字典查詢。
- Git - Reference
- 30 天精通 Git 版本控管
- Git Cheat Sheet