前言
最近用 Git 用得很順手,但同事們都是用 SVN,不怎麼熟 Git,打這篇文章順便是幫自己複習,寫筆記作為紀錄以免自己忘記,也能順便讓他們當成教學。
簡介
Git 克隆下來的本機儲存庫,其結構通常是不僅僅有 .git 的隱藏資料夾,也包括工作目錄的(就是你的原始碼),如下圖左側的橘色區域。
而遠端儲存庫通常是沒有工作目錄的「裸」儲存庫,也就是 .git 資料夾內的內容。
Git 本機目錄,主要分為三個區域,工作目錄、暫存區、儲存庫,你的程式碼都在工作目錄,儲存庫保存的是你的完整歷史紀錄,你可以理解成「精簡過 (不重複檔案)」的所有版本的所有程式碼快照內容。
所以只要歷史紀錄還在,隨時都可以還原成各種版本,每次 commit 相當於一個快照,並且是去中心化的,即使離線也可以 commit。
常見的流程
- 修改工作目錄的程式碼。
- 儲存檔案到工作目錄。
- 使用
git add
將程式碼加入到暫存區。
- 使用
git commit
建立一個提交(類似快照)。
- 使用
git pull --rebase
將遠端儲存庫拉取最新進度合併。
- 最後用
git push
上傳到遠端儲存庫。
下面介紹各種指令。
常用指令
git clone
複製克隆一個倉庫。
git clone <repo_url> <folder_name>
git clone http://192.168.2.5/git/project.git project
git init
初始化一個倉庫,會自動建立 .git 資料夾。
git init
如果要建立裸倉庫當伺服端,需加上 --bare
。
git init --bare
git remote
查看遠端有哪些儲存庫,只下 git remote
通常只會看到遠端儲存庫的命名。
用下面的命令可以看到更完整的路徑。
git remote -v
添加新的遠端儲存庫。
git remote add <origin_name> <repo_url>
修改當前 origin 的遠端儲存庫的位置,如果伺服器 IP 有變,或網址有異動。
git remote set-url <orgin_name> <new_repo_url>
git log
查看目前的 commit 歷史紀錄。
git log
用下面的指令能看到比較完整的包含線圖的 log。
git log --oneline --graph
我在網路上有找到更加華麗的客製化命令。
git config --global alias.lg1 "log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold green)(%ar)%C(reset) %C(white)%s%C(reset) %C(dim white)- %an%C(reset)%C(auto)%d%C(reset)' --all"
git config --global alias.lg2 "log --graph --abbrev-commit --decorate --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%aD%C(reset) %C(bold green)(%ar)%C(reset)%C(auto)%d%C(reset)%n'' %C(white)%s%C(reset) %C(dim white)- %an%C(reset)'"
git config --global alias.lg lg1
顯示歷史紀錄中的簽章資訊,如果有用 PGP 簽名的話會顯示出來。
git log --show-signature
git fetch
抓取遠端儲存庫至本機儲存庫。
git fetch
如果要抓取所有遠端儲存庫的所有分支。
git fetch --all
如果也要抓取遠端儲存庫的所有標籤(Tag)。
git fetch --tags
git merge
合併分支
假如當前在 master 分支,要合併目標分支 branch_name。
git merge <branch_name>
要合併包括目標 commit 之前的所有提交。
git merge <commit_hash>
普通合併如下圖:
舉例:master 分支的紀錄是 A←B←C←D←E (HEAD),在 D 點開始分岔,產生新分支 feature,紀錄為 A←B←C←D←F←G (HEAD)。
在 master 上執行合併後,會產生 H 點,其父節點為 E、G,只有 master 包含 H 點提交,而 feature 分支不包含 H 點提交。
此時,master 上的分支紀錄變成 A←B←C←D←E←H (HEAD),而 feature 上的分支紀錄還是原本的紀錄:A←B←C←D←F←G (HEAD)。
如果不能快轉合併的話,這是 Git 的預設動作,下面介紹快轉合併。
快轉合併 (Fast-forward Merge)
快轉合併發生在目標分支的分岔點在來源分支中為 HEAD,來源分支分岔點之後正好沒有新的提交,那就可以進行快轉(快進)合併。
舉例:master 分支的紀錄是 A←B←C←D (HEAD),在 D 點開始分岔,產生新分支 feature,其紀錄為 A←B←C←D←E←F (HEAD)。
在 master 上執行後,通常是自動快轉合併,master 分支的 HEAD 會直接移動到 F,變成 A←B←C←D←E←F (HEAD)。兩條分支都會包含 E、F 點提交。
下面介紹 Rebase 合併。
git rebase
Rebase 合併能夠讓線圖呈現線性的,歷史線圖比較簡潔、清楚明瞭,在 commit 數量很大量、團隊人數很多,可能會導致線圖很亂的情況下,推薦採用這個方法。
git rebase <branch_name>
舉例:master 分支的紀錄是 A←B←C←D←E (HEAD),在 D 點開始分岔,產生新分支 feature,紀錄為 A←B←C←D←F←G (HEAD)。
在 feature 分支上執行 git rebase master
時,會將 feature 分支從 D 點開始的提交(F 和 G)重新應用於 master 分支的最新提交 E 之後。這樣,feature 分支的提交基底變為 E,並重新應用這些變更以形成新的提交 F' 和 G'。
合併後 feature 分支會變成 A←B←C←D←E←F'←G'。而 master 分支還是原來的 A←B←C←D←E,除非切換成 master 分支後,執行 git merge feature,才會使得 master 分支變成 A←B←C←D←E←F'←G'。
git pull
將本機的進度拉到最新。git pull
相當於 git fetch + git merge
。
git pull
在進行 git pull
時,Rebase 合併通常不是預設值,如果在拉取遠端儲存庫進行合併時,遇到建立新提交點的狀況,可以使用上面提到的 --rebase
參數,使得提交線圖呈現線性的,較為美觀以及清楚明瞭。
git pull --rebase
git add
將目標檔案加入到暫存區,如果在工作目錄建立一個新檔案,但沒有下 git add
,是無法 commit 的,Git 會認為沒有變更所以無法 commit。
git add <...files>
加入所有已經追蹤但修改過的檔案到暫存區,不存在的檔案會被視為不追蹤。
git add -u
下面的指令會把已經不存在的檔案視為不追蹤,任何新添加的檔案加入到追蹤,相當於將目錄的所有檔案整個提交到暫存區。
git add -A
git rm
git rm 用於從工作目錄中刪除文件並將此刪除操作記錄到暫存區中,基本上是 git add
的反向操作。當你執行 git rm <file>
時,該文件將從你的工作目錄和索引(暫存區)中被移除。
git rm <...files>
如果你想從 Git 的追蹤中移除文件但仍想保留它在工作目錄中的物理文件,可使用 --cached
。
git rm --cached <...files>
git mv
檔案重命名。
git mv old_filename new_filename
git status
查看當前的工作區與暫存區狀態。能通過這指令了解哪些檔案已經修改了,有沒有加入暫存區等。
git status
如果你的工作目錄太多沒有被追蹤的檔案,而你不想看到它,可以使用 -uno
參數。
git status -uno
git commit
將目前的暫存區的變更套用提交至儲存庫,相當於一次快照。通常情況下,不允許空提交(暫存區未有變更的狀態)。
git commit -m 'update message'
將有變更的已追蹤的檔案自動加入到暫存區,不包括全新創建的文件,會把已追蹤但不存在的檔案視為不追蹤。
下面這個指令我非常非常常用。
git commit -a -m 'update messsage'
如果不小心發現打錯 commit 訊息,想要修改的話,可以使用 --amend
,可以讓你修改最後一則 commit 的訊息。
git commit --amend
但如果你已經先 push 歷史紀錄上去遠端儲存庫的話,那麼已經來不及了,下這個指令的話只會導致你跟遠端分支不同步,產生分歧。
如果要進行簽章的話,可以加上 -S (大寫)。如果用小寫(-s)的話,commit的訊息會多出類似下面的訊息:
Signed-off-by: Weil Jimmer <me@weils.net>
加上 --allow-empty
可以允許沒有任何變更,卻能 commit。
git commit --allow-empty -m 'message'
git push
將本機的歷史紀錄推送到遠端儲存庫。
git push
如果要完整一點,比如指定遠端儲存庫,因為遠端可能有很多個,或是要指定分支。
git push <origin_name> <branch_name>
加上 -u
能夠修改預設的上游遠端儲存庫。
git push -u <origin_name> <branch_name>
如果被 reject 的話,建議可以下 git pull,更新一下歷史紀錄,然後再重新推送。
推送標籤。預設狀態下,標籤是不會跟著一起被推送出去。要推送標籤的話,加上 --tags
。
git push --tags
預設狀況下,只會推送當前的分支,如果要推送全部分支,加上 --all
。
git push --all
如果要刪除遠端儲存庫的分支或標籤,可以用 --delete
參數。
git push --delete origin <branch_name | tagname>
如果要鏡像儲存庫的話,可以使用 --mirror
。
git push <repo-url> --mirror
下面是危險指令,強制推送(Force Push)本機儲存庫到遠端儲存庫,會覆蓋掉遠端儲存庫的歷史紀錄,即便遠端有最新的進度,仍然會被蓋成跟你本機一樣的紀錄。
使用這指令,有可能導致組員的工作進度被你清洗掉,也可能導致接下來團隊的歷史紀錄出現混亂、不同步。
要使用前,先清楚知道你自己在做什麼,先確定沒有別人在推送,而且你的進度是最新的,沒有分岔,別人能夠正常銜接上去。
git push -f
通常現代化的 Git Server 會有分支保護,會阻止強制推送。
git reset
reset 通常用於退版本用,如果要查看特定 commit 的內容會用 git checkout
。
reset 有分三種狀態,soft、mixed、hard,預設值是 mixed。
--soft
這個指令,會把你的 HEAD 移到目標 commit,但是不會改變暫存區和工作目錄。如果較新的 commit 有新的檔案,仍然會在暫存區內。
git reset <commit_hash> --soft
--mixed
這是預設的模式,會把你的 HEAD 到目標 commit,但是不會改變工作目錄,會清空先前 commit 的暫存區,使得暫存區與目標 commit 一致。
git reset <commit_hash>
--hard
這是危險指令,它會把你的工作目錄(即便你有已經儲存到文件上的修改),全部重置到目標 commit,使得工作目錄與目標 commit 完全一致,會讓你已儲存但未 commit 的程式碼徹底丟失,並且無法通過 git reflog
恢復。
git reset <commit_hash> --hard
git reflog
Git 的復原紀錄的大絕招,如果某些 commit 已經因為被刪除標籤或因為 reset 看不到歷史紀錄,可以嘗試使用它。
能夠找回之前的 commit,只要有找到 commit hash,就基本上能還原。
git reflog
如果你想要找回的東西並沒有 commit hash,而且使用了 git reset --hard
造成程式碼的丟失,是無法用這個指令復原的。
git tag
替當前 commit 加上標籤。
git tag <tagname>
指定 commit 加上標籤。
git tag <tagname> <commit_hash>
刪除本機儲存庫上的標籤。
git tag -d <tagname>
如果要把 Tag 也加上簽章,可以使用參數 -s
。
Tag 名稱是不可以重複存在的,即使分支不同也不能重名。
git branch
Git 分支相比 SVN 很廉價的,不會複製很多個儲存庫,只佔幾個 bytes 而已,可以不用擔心空間問題。
下面的命令能查看本機有哪些分支,並且查看現在在哪個分支。
git branch
可以查看遠端儲存庫有哪些分支。
git branch -r
建立分支。
git branch <branch_name>
刪除本機儲存庫的分支。
git branch -d <branch_name>
git checkout
切換分支到 branch_name。
git checkout <branch_name>
查看特定 commit 或 tag 的內容。
git checkout <commit_hash | tagname>
使用上面這個指令的話,HEAD 會移動到目標 commit,會進入斷頭狀態(detached HEAD),如果在這裡 commit 的話,會從這邊長出一個新的 commit。(但是沒有分支指向它。)
下面這個指令是創建一個分支 branch_name,並切換過去。
git checkout -b <branch_name>
git stash
如果當前目錄已經正在修改,但是又還不能 commit,這時候無法使用 checkout,可以使用下面的指令先暫存起來。
git stash
還原先前的草稿。
git stash pop
查看當前暫存起來的列表。
git stash list
清除所有暫存內容。
git stash clear
git gc
清除無用多餘的紀錄。基本上一個分歧的紀錄,如果沒有分支指向它並長達一段時間,就會被清除掉。
一個無法到達的 commit 也會被清除,例如:通過 reset 退版本,然後重新 commit 的狀態,先前 commit 的歷史紀錄都會是無法到達。
git gc
解決衝突 (Conflict)
相同分支的情況
如果在 git pull 之後出現類似下面的訊息:
CONFLICT (content): Merge conflict in xxx.txt
Automatic merge failed; fix conflicts and then commit the result.
通常情況下,那代表,別人修改了跟你相同檔案中的同一行,要先手動解決衝突。
如果使用 VSCode 之類的編輯器,會很貼心的幫你找出衝突的地方並給出比較。
像是這樣:
<<<<<<< HEAD
我的修改
=======
別人的修改
>>>>>>> 75f66a4b22c06e2526b9ab01689b3becfdbf6c93
編輯器會讓你選擇要套用哪種變更,或是你自行修改,修改完之後,使用 git add
重新 commit 一個紀錄後,即可再次 git push
推送上去。
合併請求衝突 Pull Request Conflict
現在有一個情境,你建立了一條分支 feature 進行你的工作,然而你的同事上傳了新代碼到 master 分支,他修改了與你在 feature 分支中相同一行的程式碼,導致發生衝突,你無法自動合併 feature 到 master。
這種情況,可以採取下面的步驟解決問題:
切换到 master 分支並更新
(feature) $ git checkout master
(master) $ git pull origin master
切換回 feature 分支並合併 master 分支
(master) $ git checkout feature
(feature) $ git merge master
手動解決衝突並提交
(feature) $ git add some/conflict/file
(feature) $ git commit -m 'Resolve conflict'
推送代碼回遠端分支
(feature) $ git push origin feature
完成這些步驟後,就可以在 Pull Request 中繼續自動合併。
按下建立合併提交後,線圖大概長這樣。
合併後,master 分支已經包含你在 feature 修改過的代碼,此時,通常的作法是刪除 feature 分支,因為可能不再需要了。
總結:
Git 最常用的指令不外乎是 git clone/pull/push/add/commit
,其他的基本上不太會用到,只要把握好幾個基礎指令的用法,已經能應對大多數狀況了。
WeilsNetLogo