Git觀念
Git的特點
- 分散式開發
- 每個人都有完整的容器,各自獨立
- 不需要中央管理
- 非擠壓合併
- 合併後仍包含所有被合併分支的commit記錄
- 也可以用
git merge/pull --squash
強迫擠壓(不推薦)
Git安裝
- Debian/Ubuntu: 主要:
git-core
和git-doc
- 選擇性:
git-gui
,gitk
(圖形化),gitweb
...
- 選擇性:
- Windows:
Cygwin
,msysGit
,github on windows
Git常用名稱
-
origin: 遠端(remote) Repository 的別稱,預設即為origin/master分支
git clone
的時候會自動設定origin
-
master: 本地(local) Repository 的 master分支
- 名為master的分支通常是專案中的主要分支
-
HEAD
- 目前分支的最新一個提交
-
相對名稱
- HEAD/HEAD^/HEAD^^/HEAD~4
- 代表目前版本/前一版/前二版/前四版的提交
- HEAD^1/HEAD^2/ ...
- 代表第一個父提交,第二個父提交 ......
- A...B
- A和B的相對差異
- 列出A和B的祖先,直到A和B的分歧點
- HEAD/HEAD^/HEAD^^/HEAD~4
-
git rev-parse HEAD
: 取得代稱(HEAD^, HEAD~1, tag)的辨識碼 -
ORIG_HEAD:合併或重設前的HEAD
-
FETCH_HEAD:fetch所抓取的HEAD
-
MERGE_HEAD:合併時,另一個分支的HEAD
設定檔
- 容器的設定檔
.git/config
- 使用者的設定檔
~/.gitconfig
- 全系統的設定檔
/etc/gitconfig
優先級: 1 > 2 > 3
Git資料結構
Index 和 Data:[Blobs, Tree, Commits, Tags]
- Data
- Blob(binary large object): 檔案本身
- 新增檔案時,看的是檔案的sha1,而不是檔名
- 若有兩個檔案sha1相同,則git只會有一份blob
- 打包機制(pack file):找內容相似的檔案,只儲存一份+差異的部分
- 新增檔案時,看的是檔案的sha1,而不是檔名
- Tree: 目錄資訊,指向Blobs
- 記錄Blob的辨識碼,檔案的資訊(如檔名),子Tree(子資料夾)等
- Git可以用Tree快速產生兩個版本間的差異
- Commit: 更動資訊,新的Commit會指向前一個Commit
- 包含作者,時間,commit對應的目錄(Tree),commit message
- Tag: 記錄commit的別名
- lightweight tag
- 容器私有的
- annotated tag
- 正式的tag,以物件方式儲存
- lightweight tag
- Blob(binary large object): 檔案本身
- Index
- 即為Staging area(暫存區)
- 可視為一顆Tree,在
git add
之後就將新檔案加入Tree中 - commit時,commit的Tree就是目前的index
版本和Tag都是指標,指向某個commit
分支
- 可視為指向某個commit的指標
- 切換的時候工作目錄(容器所在的資料夾)會被改變
- 用途
- 分隔 測試、開發、穩定的版本
- debug分支
- feature分支
- 分隔 測試、開發、穩定的版本
- 命名時可以分層
bug/...
release/...
- 開發時用branch(pointer),完成後可用tag取代(const pointer)
- 分支是local的資訊
- 平常上傳只會將目前的HEAD和遠端分支的HEAD同步
分支種類
- 唯讀
- 遠端追蹤分支:蒐集遠端每個分支的變更
- 本地追蹤分支:蒐集本地分支和遠端追蹤分支的變更
- 本地分支:即平常使用的分支
- 遠端分支
合併
每次提交合併的時間間隔愈短,每次產生的衝突愈少,愈容易合併。
同一條線上的合併
- 不會有新提交,只會移動branch指標
- 目前版本比被合併版本新 → Already up to date
- 目前版本比被合併版本舊 → fast-forward(快轉)
- 將目前版本移動到被合併版本的HEAD
- git只允許fast-forward的push
正常的合併
- Resolve(直觀的方法)
- 只能用在兩個分支的合併
- 以共同的祖先為基礎,套用被合併版本的變更到目前版本
- Recursive(預設方法)
- 只能用在兩個分支的合併
- 兩個版本有多個共同祖先的時候,先將所有祖先合併成一個暫時性的版本,再以此版本為基礎用Resolve方法
- Octopus
- 可用在多分支的合併
- 多次呼叫Recursive,每次處理一個分支
特殊合併
- Ours
- 合併時只採用現在版本的變更
- 但留下歷史記錄(即其他版本的樹仍存在於父節點)
- 但留下歷史記錄(即其他版本的樹仍存在於父節點)
- 通常用於
- 已經有其他版本的變更時
- 只想要別人的歷史記錄,不想要其他人的變更
- 合併時只採用現在版本的變更
修改提交
理由
- 大小適當
- 將大範圍的提交拆解成小的,有主題的變更
- 結合相似的變更成為一個大的提交
- 步驟分明
- 排序使其更合理
- 移除不必要的提交
- 註:需要在其他開發者取得你的repository之前修改(git push之前),以避免歷史不同步
方法
git rebase
回復(git reset)
--soft
: 將HEAD還原至指定commit--mixed
: 將HEAD和Index(staging area)還原至指定commit,工作目錄不變 (預設選項)--hard
: 將HEAD和Index和工作目錄還原至指定commit- 被取代的HEAD會放在ORIG_HEAD
1 | git reset [--soft/mixed/hard] [commit] |
通常用於清除錯誤
- cherry-pick
- 拿取指定commit到目前分支
git cherry-pick [commit]
- 使用時機
- 若有多個branch都找到相同的bug,可以在每個branch都cherry-pick debug的commit
1 | git checkout release |
- 將某個分支的commit移植到另一個branch
1 | # in develop: W → X → Y → Z (newest) |
容器
- 裸容器(bare)
- 沒有工作目錄、目前分支
- 其他人可以用clone及fetch取得資料,push更新
- 如github
- 產生:
git init --bare
- 開發容器
建立備份
自行建立裸容器,並加入remote,即可push, pull了
1 | # assume mygitrepository is already a git repository |
1 | ssh myhost.com # build it on server |
Git tag
- 輕量級(lightweight)
- 像是沒有更動的分支
- 指到特定commit的指標
- 臨時加註標籤
git tag [tagname] [commit]
- 含附註(annotated)
- 實際存在Git資料庫上的完整物件
- 具備檢查碼、e-mail和日期,也包含標籤訊息
- 可以被GNU Privacy Guard (GPG)簽署和驗證
git tag -a [tagname] -m [tag message]
git tag -a [tagname] [commit]
- 顯示詳細資訊:
git show [tagname]
git push
指令並不會將標籤傳到遠端伺服器上。必須透過git push origin [tagname]
指令- push 所有標籤:
git push origin --tags
- push 所有標籤:
Git Submodule
- 新增 submodule
git submodule add [repository path] [local path]
- 產生
.gitmodules
文件。這是一個設定檔,保存了專案的remote URL和本地目錄
git clone
並不會下載submodule,需要自己更新git submodule init
- 根據
.gitmodules
的名稱和 URL,將這些資訊註冊到.git/config
內
- 根據
- 更新submodule
git submodule update
- 根據已註冊(也就是
.git/config
)的 submodule 進行更新,例如 clone 遺失的 submodule,所以執行這個指令前最好加上 --init - update並不一定是submodule的最新版,而是在主repository的目前版本中,submodule所在的版本
- 將submodule更新到最新版
git submodule foreach --recursive git pull origin master
git submodule sync
- 如果 submodule 的 remote URL 有變動,可以在
.gitmodules
修正 URL,然後執行這個指令,便會將 submodule 的 remote URL 更正。
- 如果 submodule 的 remote URL 有變動,可以在
Git Server Hosting
- gitlab
- 功能齊全
- 有開源版,可自架
- cgit
- cgit主網站
- 需開啟cgi功能
./cgit.cgi
可觀看產生的html
- 安裝成功後在
/etc/cgitrc
中修改設定,否則404 - 參考lantw44的設定檔
- 複製git裸容器
- 快速複製github中的repository
- cgitrc詳細設定
其他工具
- 產生容器的統計資料
- git quick stats
- gitstats
Git 常見錯誤
1 | Error: The following untracked working tree files would be overwritten by merge |
- 原因:本地端尚未進版本控制的檔案,在remote branch已經在版本控制中,所以報錯
- 解決方法:先將這些檔案放入stash中,等到pull完再還原
1 | git add * # 加入 stage |
參考資料
大檔案管理 - Git lfs
Large File Storage (LFS) 是 git管理非文字大檔案的擴充套件,可以將大檔案放在放在git repository之外,這樣可以減少repository的大小,讓repository的clone速度變快。
需要注意的是,LFS是一個擴充套件,所以需要額外安裝
lfs的檔案獨立於repository,只用.gitattributes
記錄檔案名,所以需要額外的指令來add/push/pull
lfs雖然支援刪除檔案,但在Github中不行,代表所有曾經上傳到Github lfs的檔案都會一直占用著Quota,目前還沒有解決方案,只能刪除原本的repository。
或是使用bfg-repo-cleaner將需要刪除的檔案完全從記錄中移除,然後將清理後的repository重新上傳至Github。
Commit Message 規範
參考Conventional Commit
如果遵守此規則,可以自動產生一個版本的commit message總結(CHANGELOG)和語意版本號;使CD/CI工具自動觸發。
1 | [分類] 描述 (備註) |
分類可參考下列[1]
- feat: 新增功能
- update: 修改功能
- fix: 修正bug
- chore: 其它工具的變動(通常是底層library或開發工具)
- refact: 重構、整理程式碼
- style: coding style調整 (format、空格或Tab、逗號等)
- docs: 檔案、註解撰寫
- build: 打包
描述:這個commit修改了什麼功能,可以用一行的短描述加上詳細描述
備註:可以是人名(commit作者、reviewer或負責人),編號(專案代號,issue流水號),參考資料(相關文件路徑,spec連結)等。
當然commit作者在commit資訊就有了,大多數的資訊寫在issue tracker即可,寫在commit中只是方便搜尋。
e.g.,
1 | [feat(search)] 用關鍵字搜尋網站頁面 (Reviewer: Alice) (#113) (Wiki: PageSearch/Keyword) |
將隱私檔案和git repository分開
隱私檔案:如設定檔、資料庫、內部檔案路徑、金鑰、個人資訊等等
最簡單的方法就是:不要放在repository內,缺點就是沒有版本控制、取得麻煩、可能會遺失。
以下提出一些將隱私檔案放在repository的方法
- 開兩個不同的repository,
public-repo
和private-repo
(不公開),將隱私檔案放在private-repo
- 需要使用隱私檔案時,clone
private-repo
,並合併到public-repo
- 缺點
- 合併兩個repository困難,這代表兩個repo需要有相同的資料夾結構,不然不易移動
- 需要使用隱私檔案時,clone
- 多一個branch
private
,此branch可以視為完整的repo,包含public的所有內容和隱私檔案- 只要保護
private
branch 即可- 將
private
branch 設立讀取權限
- 將
- 小缺點:需要額外維護一個branch
- 只要保護
private-repo
作為public-repo
的submodule- 缺點:代表隱私檔案需要放在同一個資料夾內
將多個repository合併成一個
我目前(2023)有六十多個repository(包含private repo),主因是一個專案就丟一個repository,像是大學的一門課就是一個repo,實在有點難管理。
可以把多個小專案合併成一個,如作業。
方法[2]
- 要移動的repo
- 開新branch
- 把所有檔案移動到新的子資料夾
- add 並 commit
- 要接受合併的repo
- 在remote中加入1. 的repo
- fetch branch
- merge (通常需要
--allow-unrelated-histories
)
此方法可以保留每一個被合併的repo的歷史
Reference
- Git-基礎-標籤
- 版本控制:使用Git
- git 設定集
- https://dotblogs.com.tw/hatelove/archive/2011/12/25/introducing-continuous-integration.aspx
- git: get ready to use it
- Git Submodule 用法筆記
- Git 工具 - 子模組 (Submodules)
- How to delete a file tracked by git-lfs and release the storage quota?
- Make some files private in a repository