Git & GitHub 學習筆記

2023/11/17閱讀時間約 23 分鐘

俗話說的好,學習 Git 最好的時間點永遠是昨天。身為一個總是活在昨天、肖想未來的軟可頌,趁著近期開始學習 Git 與 GitHub 的機會,寫了一篇學習筆記。由於 Git 的世界浩瀚無垠,這篇學習紀錄會隨著更多雷爆與淚水更新內容。

如果正在閱讀的你/妳,同樣也是剛踏入 Git 未知領域的新手,希望這篇筆記對各位有幫助。當然,內容若有錯誤或不妥的地方,都歡迎和我說一聲,非常感謝!

註:學習筆記的內容大多參考以下文件以及線上課程:


Git 背景知識

什麼是 Git?

Git 是目前世界上最受歡迎的版本控制系統 (Version Control System)。事實上,Git 只是其中一種版本控制系統,但將近 90% 的開發團隊都使用 Git,所以它儼然成為了版本控制系統的代名詞。


什麼是 VCS?

版本控制系統,是用來追蹤、管理檔案變更的軟體。版本控制系統通常能讓使用者造訪更早以前的檔案版本、比對各個檔案版本之間的不同、還原版本等等。

我們可以想像成電玩遊戲中的儲存點,為了避免角色死亡後經驗值或寶物掉落,我們會希望在 boss 戰之前存檔,方便之後重新讀檔,保留角色死亡前的紀錄。

同理,我們只要電玩角色替換成程式碼,就能體會版本控制系統的價值。更棒的是,VCS 還能讓我們分支,確保多個開發流程不會影響到彼此,減少團隊合作的磨擦和溝通成本。


Git 能帶來什麼好處?

族繁不及備載,人們總說如果你要面試軟體工程師,Git 是面試官會尤其關注的技能。而在實際的開發生活中,Git 大致能帶來以下好處:

  • 追蹤不同檔案之間的改動歷史
  • 比對專案的前後版本
  • 坐上時光機,回到專案的某個舊版本
  • 方便開發人員彼此協作、共享版本更動資訊
  • 合併不同版本的更動內容


誰在使用 Git?

👨‍💻 工程師/開發人員:從新創團隊到上市企業,無論公司規模,Git 都是軟體工程師必備的技能。

👩‍🎨 設計師:由於設計師時常必須和開發團隊協作,所以現在越來越多設計師也學著使用 Git。

👩‍⚖️ 政府:越來越多國家透過 Git & GitHub 公布法令,人民若發現錯誤或希望勘誤,可以發 request

🧑‍🔬 科學家:科學家或研究員開始善用 Git & GitHub 分享實驗、研究成果,甚至進行論文 review。

✍️ 作家:尤其是多人的寫作專案,喜歡利用 Git & GitHub 進行校稿、合輯。


Git vs GitHub

雖說 Git 和 GitHub 不像 Java 與 JavaScript 或狗與熱狗那樣不相干,我們還是得搞清楚兩者之間的差別。簡單來說,Github 提供了一個讓大家上傳、下載 Git 資訊的共用空間。

Git 是當今最受歡迎的版本控制系統 (VCS)。使用者必須要把 Git 下載、安裝到自己的電腦,所以我們不用註冊帳號、連接網路也能使用 Git。不必使用 GitHub 也能夠使用 Git。
GitHub 是有別於 Git 的雲端服務,能讓我們將自己電腦中的 Git 資訊上傳到雲端倉庫 (repository) 與他人共享,同時也能下載其他人上傳的 Git 資訊。使用 GitHub 必須要註冊帳號。

Git 基礎知識

什麼是 Git Repo?

Git Rep 的全名為 Git Repository,是一個 Git 的工作空間單位,用來追蹤、管理一個資料夾的檔案。也就是說,每當我們希望在一個專案、應用程式等等使用 Git,都需要建立一個全新的 Git Repo。由於 Git 是安裝在電腦上的,因此我們可以在機器建立很多 Repo,每一個 Repo 都包含不同的歷史紀錄以及內容。


git status

git status 這組指令將回傳 repo 目前的狀態和內容給我們,當然前提是我們必須先進入到某個 repo 裡面才行,否則在 repo 外輸入這組指令,會得到以下訊息:

fatal: not a git repository (or any of the parent directories): .git


git init

我們會運用 git init 這組指令來建立全新的 git repo,換而言之,當我們想要在一個資料夾裡面做任何和 Git 相關的事情,都需要先初始化 repo 才行!通常一個專案只會初始化 repo 一次,並在專案頂層的資料夾進行初始化。

根據 Git 的官方文件 git init 建立出新 repo 之餘,還會建立一個名為 .git 的隱藏資料夾,裡面包含其他例如 objectsrefs/headsref/tags 等等子資料夾。.git 資料夾基本上存放了這個 repo 的所有資料,我們能透過 ls -a 呼叫或 rm -rf .git 刪除 .git 資料夾。

⚠️ 不要在一個 repo 裡面初始化另一個 repo!凡是在跑 git init 之前,請先用 git status 確認我們目前是否已經處在一個 repo 裡面了。如果不幸犯錯,直接刪除掉子層的 .git 資料夾即可。


Git 的 committing 工作流程


什麼是 commit?

在 Git 進行 commit 就好比在電玩遊戲中儲存進度,我們能在某個時間點即時給 git repo 拍下快照 (snapshot)。值得注意的是,commit 和儲存檔案有所差異。一般在儲存檔案時,我們會將某個檔案的當下狀態全部儲存下來;相較之下,commit 則可以一次將多個檔案或資料夾的更動狀態同時儲存下來,比起檔案儲存更具彈性。

另外,committing 其實是一整套工作流程,裡面不只包含 commit 一個指令。

來源:https://www.udemy.com/course/git-and-github-bootcamp/

來源:https://www.udemy.com/course/git-and-github-bootcamp/

git add

在正式 commit 之前,我們需要先透過 git add 指令進行 staging。也就是把要從 working directory 上傳 commit 至 repo 的檔案,先記錄到一個暫時性的 staging area。我們可以想像成表演的後台,在正式登台演出之前,我們會先在後台那一幕戲要出場的演員先集合在後台 (stagiing),確定大家到位之後再登台演出 (commit)。

我們可以分開將檔案加入到 staging area git add file1,也可以用空格隔開不同的檔案名稱 git add file1 file2 一次將多個檔案加入到 staging area。

除此之外,若不加檔名直接輸入 git add .,Git 就會把當下專案裡面的變更全部加入到 staging area。


💡 善用 git status 來查看哪些變更已經被加入到 staging area,哪些還沒有。


git commit

我們利用 git commit 來將 staging area 裡面的變更紀錄上傳至 repo,也就是 .git 資料夾。在進行 commit 時,我們需要提供 commit message 描述變更內容以及這次 commit 的目的等等。如何撰寫有效的 commit message 是一門深奧的學問,保持開放的心胸多看多學習吧!

如果只是單純輸入 git commit,那 Git 會開啟電腦預設的文字編輯器,要求我們提供 commit message。所以比較簡潔的作法,是在指令後面加上 -m flag 以及 inline message。完整的指令會長這樣:git commit -m “my message”


git commit — amend

若要更改上一則上傳到 repo 的 commit,可以先 git add 新的 commit 之後,輸入 git commit —amend。Git 會在綁定的文字編輯器打開前一則 commit 供我們修改。但是請注意,這只能修改上一則 commit 而已。

git commit -m 'some commit'
git add forgotten_file
git commit --amend



git log

如果我們希望看到 repo 裡面的 commit 紀錄,可以利用 git log 這組指令。畫面會呈現出每筆 commit 的作者、上傳時間、訊息內容等等,除此之外,每一筆 commit 的開頭都會附上一組 commit hash,可以把它想成 commit 的身分證號碼。


.gitignore

💡 gittignore.io 可以用程式語言搜尋常用的 .gitignore 檔案內容,非常實用!

我們可以透過 .gitignore 資料夾,要求 Git 刻意忽略 repo 當中的特定檔案或資料夾。總結來說,只要是你不希望 commit 出去的檔案或資料夾,都能善用這個功能。通常這些會是我們不想要傳到 GitHub 上面的檔案或資料夾。

  • API keys、credentials…
  • 作業系統檔案
  • log 檔案、報告
  • dependencies & packages (Python 和 Node.js 很常用到)

我們在 repo 的根目錄建立名為 .gitignore 的檔案,然後在裡面寫入希望 Git 刻意忽略的資料模式:

  • .filename 將忽略名為 filename 的檔案
  • folderName/ 會忽略這整份資料夾
  • *.log 會忽略所有 .log 結尾的檔案

如何寫出好的 commit

❤️ 要養成版本控制和書寫 commit 的良好習慣,需要把與人協作放在心上。


撰寫 commit 的心態

使用版本控制的習慣反映出開發者的素養。我們應該去理解同事、合作夥伴需要花費心力管理龐大的程式碼,因此程式碼的縮排、註解以及 commit message 的品質展現出了我們的同理心與尊重。以下列出一些撰寫 commit message 的目的:

  • 幫助你的前輩加速審查你的程式碼
  • 幫助撰寫 release note 的同事掌握這一波更新的內容
  • 幫助未來的維護者 (可能就是你自己) 能從龐大的程式碼中找出一個特定的 bug 發生的原因


Atomic Commits

Atomic commits 的概念是每一則 commit 只專注於一件事情上面。這個原則有助於倒退成先前的版本時更加清楚、容易,同時也能讓我們的程式碼、專案更容易閱讀、檢查。但是切記,這不代表 commit message 越細越好,我們也要注意程式碼的相關性,比方說單元測試就適合和相關的程式碼放在同一則 commit。


Commit message 撰寫原則

關於 commit message 的整體架構,可以參考 Conventional Commits 的規範。

<type>[optional scope]: <description>

[optional body]

[optional footer]


Subject 和 Body 中間要空行

有些 commit 簡單到根本不用 body 描述,那使用 git commit -m “my message” 即可。

$ git commit -m"Fix typo in introduction to user guide"

反觀若是較為複雜的 commit,我們就需要撰寫 body了。

Derezz the master control program

MCP turned out to be evil and had become intent on world domination.
This commit throws Tron's disc into MCP (causing its deresolution)
and turns it back into a chess game.

由於 -m flag 並不適合空行寫詳細內容,建議改使用文字編輯器。如果是 Windows 作業系統,Git 應該會自動綁定 VS Code。落實 subject 與 body 分離,在使用 git log 時便能發現其價值所在。

$ git log
commit 42e769bdf4894310333942ffc5a15151222a87be
Author: Kevin Flynn <kevin@flynnsarcade.com>
Date: Fri Jan 01 00:00:00 1982 -0200

Derezz the master control program

MCP turned out to be evil and had become intent on world domination.
This commit throws Tron's disc into MCP (causing its deresolution)
and turns it back into a chess game.

此外,使用 git log —oneline 或是 git shortlog 則只會顯示出 subject,也非常容易閱讀!

$ git log --oneline
42e769 Derezz the master control program


Subject 不要超過 50 個字

除了維持易讀性之外,這樣做還能強迫撰寫者言簡意賅。若發現無論怎麼修改,都很難把 subject 壓縮在 50 字之內,可能代表這則 commit 夾雜太多內容了,請參考 Automic Commits 原則,拆分成多則 commit,一則 commit 只關注一件事情。


Subject 使用動詞祈使句

簡單來說就是用動詞開頭啦。這個議題業界爭執滿久的,有人主張用祈使句,有人則認為過去式動詞比較合理。Git 本身的 commit 就是使用祈使句,所以遵循官方的用法不失為解決之道。總而言之,最重要的是維持一致性,至於要用哪一種書寫方式,可以依照加入的開發團隊準則為主


善用 body 解釋 what、why、how

儘管程式碼本身和註解已經解釋了不少版本更動的原因與結果,但為了節省溝通成本,何不用 body 在 80 字之內行文說明,順便釐清事發經過呢?請把重點放在版本更動的原因、更動前的情況、出了什麼問題、更動之後的新狀況、為什麼選擇這樣的解決方式等等。


Branching

什麼是 branch?

Branch 是 Git 的核心功能!我們可以把它想像成時間軸上的不同分支 (漫威……),每個分支彼此互不影響,確保開發團隊成員平行工作的同時,codebase 不會陷入混亂。但是和一般科幻小說不同,Git 的分支是可以合併的,舉例來說,當某人在 branch A 測試好了新功能,需要將 branch A 與主軸 (codebase) 合併。

什麼是 Master Branch?

💡 礙於一些用詞 (政治正確) 的爭議,GitHub 已經把 Master 改成 Main 了,Git 目前仍舊維持 Master,但極度可能於不久後的未來一起改成 Main。

從 Git 的角度來看,master 和其他 branch 完全沒有任何差別,只是預設的 branch 而已。但有許多開發團隊習慣把 master branch 當作 source of truth,但這就端看每個團隊的決議。

什麼是 HEAD?

HEAD 是個指向 (pointer),標示出我們目前於 repo 的所在位置,HEAD 必定會指向一個特定的 branch reference,比方說很常見的 HEAD → MASTER。所謂的 branch reference 則是我們在某個 branch 的最終 commit 位置。


git branch

我們可以使用 git branch 指令來查看 repo 裡面的所有支線。若某支線前面帶有 * 符號,代表它是我們目前的所在支線。

$ git branch
* master
branch2
branch
...


git branch <branch-name>

在 git branch 後面空一格,然後加上支線名稱,就可以建立一條新支線囉。但請注意,這組指令只會建立分支,不會直接轉移過去 (HEAD 留在原地)。

$ git branch newBranch


git switch <branch name>

git switch 讓我們切換所在的 branch。如果希望在建立新支線的同時切換過去,可以在支線名稱前面加上 -c flag。

$ git switch other-branch
$ git switch -c new-branch


git branch -d <name>

git branch -d 後面接上支線名稱,可以刪除該支線。請注意我們無法在瀏覽支線時刪除它,需要先切換到其他 branch。另外,Git 可能會跳出通知,提醒這條 branch 尚未完整合併,是否真的要刪除它。如果非常確定的話,將指令改為 git branch -D 即可強制完成刪除。

$ git branch -d deleteMe
error: The branch 'deleteMe' is not fully merged.
If you are sure you want to delete it, run 'git branch -D deleteMe'.
$ git branch -D deleteMe


git branch -m <new name>

要替某條 branch 改名,使用 git branch -m 後面接上新名稱即可。


Merging

什麼是 Merging?

Branching 讓團隊能夠在不相互影響的情況下平行開發,但有時候我們還是希望希望將某條 branch 合併到另一條 branch 上面,通常會整併到 master branch。這時便可以善用 git merge 這組指令。


merge 指令和概念對於初學者來說似乎挺複雜的,我們先記住以下兩大原則:

  • 我們合併的是 branch,而不是 commit -
  • 使用 git merge 時,一定是將某條 branch 合併到我們目前所在的 branch (HEAD)


有鑑於以上兩大原則,在進行 merging 時請遵循以下基本步驟:

  1. 使用 git switchgit checkout 切換到你希望整併進去的 branch
  2. 使用 git merge 將某條 branch 的更動紀錄合併至當前所在的 branch (HEAD)
$ git switch master
$ git merge bugfix


我們時常看到的分支圖,其實是為了人類視覺化方便所描繪的。事實上,Git 在進行 merging 時,只不過是把 HEAD 所指向的 branch,往前拉到我們希望被合併的 branch 支線。以上面的例子來說,合併完成之後,Master 和 Bugfix 兩條 branch 會指向同一個 commit,直到兩者再度支開。

上述的 merging 場景被稱為 Fast-Forward,只有一條 branch 的 commit 往前增加,而要整併進去的 branch 沒有任何改動,所以向前追上去即可。可惜並非所有 merging 狀況都如此單純 😭


Merge commits

Fast-Forward merging 非常容易理解,但在現實生活中,當我們在某條 branch (bugfix) 作業時,另一邊 (master) 往往也會增加其他 commit,這就會造成 bugfix 本身並不知道 master 新增的那些 commit 存在。為了解決這個問題,Git 會在這種狀況合併下,自動新增一個 merge commit,然後要求你在文字編輯器裡面寫上 commit message。

Merge conflicts!

⚠️ 有時候根據版本更動的狀況,Git 無法自動將兩條 branch 合併起來,產生 merge conflict。Git 會顯示通知,但需要我們手動處理衝突。通常會是同份檔案在不同 branch 所做的程式碼更動有重疊到。

CONFLICT (content): Merge conflict in example.txt
Automatic merge failed; flix conflicts and then commit the result.


Git 會在 merge conflict 發生時警告你,同時 Git 也會改動相關的檔案的內容,透過 conflict marker 告訴你衝突發生在哪幾行程式碼。

  • 附圖中 HEAD 到 ===== 之間的程式碼,代表原先的版本更動紀錄
  • ====== 到 develop 之間的程式碼,是 HEAD 另一條 branch (develop) 起衝突的程式碼
  • 我們可以選擇刪除起衝突的程式碼,或甚至保留兩者,直接把 conflict marker 刪掉即可
此照片來自於 CSS Trick

此照片來自於 CSS Trick

衝突檔案修改完畢之後,重新 git addgit commit 就好囉~


Comparing

git diff

我們使用 git diff 指令來查看 commit、branch、檔案之間的變化。若後面沒有添加其他 flag,git diff列出目前所在資料夾中,所有尚未加入 staging area 的變更紀錄git diff HEAD 則會列出截至上一次 commit 之前,working tree 裡面的所有變更紀錄。


解讀難懂的 diff 結果

每一則 git diff 比較結果的最上方,都會列出正在比較的兩份檔案。一般來說是同一份檔案的兩種版本。Git 會自動把兩種版本宣告為 A 與 B

diff --git a/cats.txt b/cats.txt

下一行為檔案的 metadata,頭兩組數列為兩份檔案的 hash,而最後一組數列為檔案內部的模式辨識碼 (不是那麼重要)。

index 72d1d5a..f2c8117 100644

下一行稱作 markers,分別說明了 A 檔案與 B 檔案接下來會用什麼符號表示,通常一邊用 + 符號,另一邊則用 - 符號。

--- a/cats.txt
+++ n/cats.txt

下一行為 chunk header,夾於各兩個 @ 標記裡面。- 和 + 符號代表 a 或 b 檔案,後面的數字則代表從第幾行開始截取多少行程式碼顯示於 chunk。

16會員
34內容數
Bonjour à tous,我本身是法文系畢業,這邊會刊登純文組學習網頁開發的筆記。如果能鼓勵更多文組夥伴一起學習,那就太開心了~
留言0
查看全部
發表第一個留言支持創作者!