Git觀念
Git的特點
- 分散式開發
- 每個人都有完整的容器,各自獨立
- 不需要中央管理
- 非擠壓合併
- 合併後仍包含所有被合併分支的記錄
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] # default is mixed |
通常用於清除錯誤
- cherry-pick
- 拿取指定commit到目前分支
git cherry-pick [commit]
- 使用時機
- 若有多個branch都找到相同的bug,可以在每個branch都cherry-pick debug的commit
1
2git checkout release
git cherry-pick develop~2 # get debug commit from develop branch - 將某個分支的commit移植到另一個branch
1
2
3
4
5
6
7
8# in develop: W → X → Y → Z
git checkout master
git cherry-pick develop^ # Y
git cherry-pick develop~3 # W
git cherry-pick develop~2 # X
git cherry-pick develop # Z
# alternative
git cherry-pick develop^3..develop
- 若有多個branch都找到相同的bug,可以在每個branch都cherry-pick debug的commit
- 如果要合併多個,可以加上 -n 指令就不會先幫你 commit,這樣可以多選幾個要合併的commit,最後再 git commit即可
容器
- 裸容器(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。
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?