Git觀念

Git的特點

  • 分散式開發
    • 每個人都有完整的容器,各自獨立
    • 不需要中央管理
  • 非擠壓合併
    • 合併後仍包含所有被合併分支的記錄
    • git merge/pull --squash 強迫擠壓

Git安裝

  • Debian/Ubuntu: 主要: git-coregit-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的分歧點
  • git rev-parse HEAD: 取得代稱(HEAD^, HEAD~1, tag)的辨識碼

  • ORIG_HEAD:合併或重設前的HEAD

  • FETCH_HEAD:fetch所抓取的HEAD

  • MERGE_HEAD:合併時,另一個分支的HEAD

設定檔

  1. 容器的設定檔 .git/config
  2. 使用者的設定檔 ~/.gitconfig
  3. 全系統的設定檔 /etc/gitconfig

優先級: 1 > 2 > 3

Git資料結構

Index 和 Data:[Blobs, Tree, Commits, Tags]

  • Data
    • Blob(binary large object): 檔案本身
      • 新增檔案時,看的是檔案的sha1,而不是檔名
        • 若有兩個檔案sha1相同,則git只會有一份blob
      • 打包機制(pack file):找內容相似的檔案,只儲存一份+差異的部分
    • Tree: 目錄資訊,指向Blobs
      • 記錄Blob的辨識碼,檔案的資訊(如檔名),子Tree(子資料夾)等
      • Git可以用Tree快速產生兩個版本間的差異
    • Commit: 更動資訊,新的Commit會指向前一個Commit
      • 包含作者,時間,commit對應的目錄(Tree),commit message
    • Tag: 記錄commit的別名
      • lightweight tag
        • 容器私有的
      • annotated tag
        • 正式的tag,以物件方向儲存
  • 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

正常的合併

  1. Resolve(直觀的方法)
    • 只能用在兩個分支的合併
    • 以共同的祖先為基礎,套用被合併版本的變更到目前版本
  2. Recursive(預設方法)
    • 只能用在兩個分支的合併
    • 兩個版本有多個共同祖先的時候,先將所有祖先合併成一個暫時性的版本,再以此版本為基礎用Resolve方法
      • 在多個祖先合併時可能也有相同的問題,此時用同方法遞迴
  3. 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
2
3
4
5
6
git reset [--soft/mixed/hard] [commit] # default is mixed

# 變更上一次的提交
git reset HEAD^ # 此時上一次的提交已經不存在,但是工作目錄中的資料相同
# do some modification
git commit # 會覆蓋上一次的commit

通常用於清除錯誤

  • cherry-pick
    • 拿取指定commit到目前分支
    • git cherry-pick [commit]
    • 使用時機
      • 若有多個branch都找到相同的bug,可以在每個branch都cherry-pick debug的commit
        1
        2
        git 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
    • 如果要合併多個,可以加上 -n 指令就不會先幫你 commit,這樣可以多選幾個要合併的commit,最後再 git commit即可

容器

  • 裸容器(bare)
    • 沒有工作目錄、目前分支
    • 其他人可以用clone及fetch取得資料,push更新
    • 如github
    • 產生: git init --bare
  • 開發容器

建立備份

自行建立裸容器,並加入remote,即可push, pull了

1
2
3
4
5
6
7
8
# assume mygitrepository is already a git repository
cd /tmp/Backup # for example, you may want to put on server
git clone --bare mygitrepository mygitrepository.git # create bare repository
# backup repository often use .git suffix
cd mygitrepository
git remote add origin /tmp/Backup/mygitrepository
# can use other name to replace "origin"
git remote update # 建立遠端追蹤分支(update remote information)
1
2
3
4
5
ssh myhost.com # build it on server
cd /git
mkdir newrepo.git
cd newrepo.git
git init --shared --bare

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

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 更正。

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
2
3
4
5
git add * # 加入 stage
git stash # 執行 stash(stage會將stage的檔案也放入stash)
git pull ...
git stash pop # 還原 stash
git reset # 回復 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?