[筆記] Git 快速入門教學
No Comments

更新於 2024-05-15 19:58:25

前言

最近用 Git 用得很順手,但同事們都是用 SVN,不怎麼熟 Git,打這篇文章順便是幫自己複習,寫筆記作為紀錄以免自己忘記,也能順便讓他們當成教學。

簡介

Git 克隆下來的本機儲存庫,其結構通常是不僅僅有 .git 的隱藏資料夾,也包括工作目錄的(就是你的原始碼),如下圖左側的橘色區域。

而遠端儲存庫通常是沒有工作目錄的「裸」儲存庫,也就是 .git 資料夾內的內容。

Git 本機目錄,主要分為三個區域,工作目錄、暫存區、儲存庫,你的程式碼都在工作目錄,儲存庫保存的是你的完整歷史紀錄,你可以理解成「精簡過 (不重複檔案)」的所有版本的所有程式碼快照內容。

所以只要歷史紀錄還在,隨時都可以還原成各種版本,每次 commit 相當於一個快照,並且是去中心化的,即使離線也可以 commit。

常見的流程

  1. 修改工作目錄的程式碼。
  2. 儲存檔案到工作目錄。
  3. 使用 git add 將程式碼加入到暫存區。
  4. 使用 git commit 建立一個提交(類似快照)。
  5. 使用 git pull --rebase 將遠端儲存庫拉取最新進度合併。
  6. 最後用 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,其他的基本上不太會用到,只要把握好幾個基礎指令的用法,已經能應對大多數狀況了。

By Weil Jimmer


This entry was posted in General, Git, Note By Weil Jimmer.

About Weil Jimmer

Hi! Everyone! My name is Weil Jimmer. This is my personal blog. I'm a webmaster of this site. I hope the site will be popular. Now, Let's go! Enjoy gaining more knowledge.
More Details About Me : https://weils.net/profile.php


Leave a message.

Only the first 10 comment will show.