Git 只關心檔案的內容,所以只要是檔案都可以使用 git 來管理。 [git](https://gitbook.tw/#git--1) [git book](https://git-scm.com/book/zh-tw/v2) [為你自己學 Git](https://gitbook.tw/) :::spoiler Git GUI - [Sourcetree](https://www.sourcetreeapp.com/) (Win / MacOS) - gitk / gitg (Linux) - [gitkraken](https://www.gitkraken.com/) (Win / MaxOS / Linux) ::: # 知識點 * Git 並不是做差異備份,而是建構式備份,讀取檔案的時候不需要從一個個歷史紀錄拼湊,而是直接讀取當前的 snapshot,相對於差異備份會多耗費一點空間。 * HEAD 有個縮寫 @,所以 `HEAD^` 等同於 `@^` * ORIG_HEAD 是最近一次 `git merge/rebase/reset` 的備份 ```bash git ls-files [-s] ls-files # 列出 tracking 的檔案 (包含 SHA-1) ``` :::spoiler *Branch 只是一個 commit 的指標而已* Branch 就只是存放在 *.git/refs/heads/* 目錄底下紀錄 SHA-1 的檔案而已,刪掉其中一個檔案相當於將這個 branch 刪掉。 ::: :::spoiler *HEAD 是紀錄當前 commit 狀態的指標* 其實就是一個檔案 ```bash cat .git/HEAD ref: refs/heads/master cat .git/refs/heads/[master/another branch]` dd5c8314... # 這是 160-bit (40 個十六進位數) 的 SHA-1 echo "content" | git hash-object --stdin ``` ::: :::spoiler Git 有四種物件 * Blob 負責檔案, * 以 SHA-1 命名和辨識,壓縮後儲存在 *.git/objects/*,目錄是 SHA-1 hashed 前 2 個字元,檔名是後 38 字元。要解壓縮內容可以用 ```bash git cat-file -t ... # 察看他是哪一種 object git cat-file -p ... # 察看內容 ``` * Tree 負責目錄結構; * 以相同命名方式放在 *.git/objects/* 當中, * 會指向 blob 檔案和 subtree 子目錄; * Commit * 以相同命名方式放在 *.git/objects/* 當中, * 會指向一個 tree 目錄和 parent commit(s), * 只要目錄內容有更動,就會有新的 tree object; * Tag * 以相同命名方式放在 *.git/objects/* 當中, * 指向一個 commit。 ```graphviz digraph { HEAD [style="filled,rounded" shape=rect penwidth=0] branch [style="filled,rounded" shape=rect penwidth=0] remote [style="filled,rounded" shape=rect penwidth=0] tag [style="filled,rounded" shape=rect color=yellow] commit [style="filled" shape=rect color=red] tree [style="filled,rounded" shape=rect color=brown] blob1 [label="blob" style="filled,rounded,dotted" shape=circle color=green] blob2 [label="blob" style="filled,rounded,dotted" shape=circle color=green] blob3 [label="blob" style="filled,rounded,dotted" shape=circle color=green] HEAD -> branch -> commit tag -> commit remote -> commit -> tree -> blob1 commit -> commit tree -> blob2 tree -> blob3 tree -> tree } ``` ::: # 指令操作 ## git 設定 / 初始化 ```bash git config --global user.name babysuse user.email babysuse2018@gmail.com core.editor vim alias.co/state/br checkout/status/branch alias.ls "log --oneline --graph" alias.ll 'log --graph --pretty=format:"%h <%an> %ar %s"' # 參見 git help log git init ... git branch -m main # 更改 branch 名稱 ``` * 要有了第一次的 commit 才會有 branch,預設是 *master* ## git 基本操作 *untracked => tracked => staging => committed* ```bash git add -p|--patch ... # 可以只加入部份修改內容 git diff [--staged] [...] # 查看更改 (staged) 內容 git mv ... git rm ... git rm --cached ... # untracked git commit -a|--all [...] # 相當於先 git add 再 git commit # 跟 git checkout -b ... 一樣合併了兩個指令 git commit --amend --no-edit # 將更改合併到前一次 commit # 另外一種方法是先 git reset HEAD~ git commit --allow-empty -m "..." # 可以再 staging area 空的情況下 commit git remote add REMOTE REMOTE_URL git push -u REMOTE LOCAL_BRANCH[:REMOTE_BRANCH] --delete REMOTE_BRANCH # 移除 REMOTE_BRANCH :REMOTE_BRANCH # 移除 REMOTE_BRANCH ``` * `origin` 只是 `REMOTE` 的慣用名而已 * `REMOTE_BRANCH` 預設和 `LOCAL_BRANCH` 同名 * 空的 commit 不能幹嘛,就是教學上會很方便 * 空的目錄不能提交,習慣上會再空的目錄下新增一個 *.keep* 或是 *.gitkeep* *.gitignore* 只對建立之後的檔案有效,不溯及既往,對於早就存在的檔案需要用 ```bash git rm --cached ... git clean -X ``` 來追加。另外 `git add -f|--force ...` 可以無視 *.gitignore* 規則添加進 staging area。 ```bash # .gitignore *.tmp somedir/ ``` ### git reset/checkout/clean ```bash git reset dd5c8314...|HEAD[^^|~5] # 回到指定 commit (前前筆 / 前五筆) # commit id 只要前綴就行 git checkout BRANCH # 切換 branch git checkout [HEAD~2] ... # 回復檔案/目錄到指定狀態 (預設是上一次的 commit) git clean -n # dry-run (for checking) -f # 只刪除 untracked 的檔案,不會所有移除 unstaged 的更動 ``` | | 工作目錄 | 暫存區 | |:------------------- |:--------:|:-------:| | `--soft` | - | - | | `--mixed` (default) | - | discard | | `--hard` | discard | discard | 用 `git log` 察看會看起來好像 commit 刪掉,但其實只是從 commit tree 的一處移到令外移處而已,用 `git reflog` 可以查到那些看起來不見了的 ID。 - 每次更動到 HEAD 時,reflog 都會紀錄,包括 `git rebase` ### git branch / tag / reflog ```bash git branch # 列出所有 branch git branch [-d/D] ... # ((強制)刪除/) 創建 branch git checkout [-b] BRANCH # (創建並) 切換 branch # 跟 git commit -a|--all [...] 一樣合併了兩個指令 git merge ... [--no-ff] git rebase ... git tag -a TAG -m "..." # 新增 tag git tag -d/l # 移除/列出所有 tag ``` * 還沒 merge 的 branch 需要強制才能刪除,而當前的 branch 無法刪除 * **刪除 branch 只是移除了那個 commit 的指標,紀錄還在,可以用 `git reflog` 查詢;** * **同理,rebase 會創建新的 commit,舊的也還在,可以用 `git reflog` 查到。** * `git merge` 是以當前的 branch 為主,所以以當前的 branch 繼續下去 * 只會多一個 commit * `git rebase` 是以當前的 branch 為底,所以以另外一個 branch 繼續下去 * 另外一個 branch 有幾個 commit 就會多幾個 commits * `git tag` 會在 `git log` 當中秀出來 要復元 `git rebase` 最快有兩個方法 * 用 **`git reflog`** 查到先前的 commit,`git reset --hard` 回去 * 用 **`ORIG_HEAD`** ```bash git reset ORIG_HEAD --hard ``` #### Conflict `git merge` 遇到了 conflict 要修正完了才會完成 commit; ```bash git merge ... # git add ... # git checkout --ours cute_animal.jpg git commit ... ``` `git rebase` 遇到了 conflict 會停在造成 conflict 的那個 commit ```bash git rebase ... # git add ... # git checkout --theirs cute_animal.jpg git rebase --continue ``` 如果是文字檔用 `git add` 修正;反之,用 `git checkout`。 ### git log ```bash git log --oneline --graph --author="...|..." --grep="Title" -S"content" --since="9am" --until="12am" --after="2017-01" git log [-p] ... # 察看檔案的 commit 歷史 (和修改內容) git blame [-L 5,10] ... # 察看修改者 ``` ### git stash 用來暫存不想 commit 的更改 ```bash git stash [push] --patch # 暫存部分更改 --all # 暫存並移除,相當於 git stash && git clean git stash list git stash apply [stash@{2}] # 不指定就採用最新的一筆 --index # 保留 staging 狀態 (預設復原後都是 unstaged) git stash drop [stash@{2}] git stash pop [stash@{2}] # 相當於 git stash apply && git stash pop ``` 還可以將暫存記錄在 index 當中,也就是可以用 `git status -s` 察看 ```bash git stash --keep-index git status -s ``` # 情境 ## 復原 | | Untracked | Modified | | ------------------ |:---------:|:--------:| | `git checkout .` | - | discard | | `git clean -f` | discard | - | | `git reset --hard` | discard | discard | * 只復原單一檔案的話就 ```bash git checkout [HEAD^] -- file ``` ## 修改 commit 紀錄 * `git commit --amend`:只能修改最後一次 * `git rebase` * `git reset` ## 查看兩次 commits 間更動的檔案 ```bash git diff --name-only HEAD^ HEAD~3 ``` # [git hooks](https://git-scm.com/book/zh-tw/v2/Customizing-Git-Git-Hooks) 就是會針對特定行為執行特定 script 的機制。這些 scripts 就是存放在 .git/hooks 中副檔名為 .sample 且可執行的 shell scripts (當以 `git init` 初始化 git repo 的時候就會有) ## server-side - 由 receiving push commits 觸發,push 階段所執行的 hooks,回傳非零即拒絕 push - #### pre-receive - 當接收到 push 的時候所執行 - #### update - 每個 branch 要更新時都會執行,當 client 同時 push 到 n 個 branch,pre-receive 只會執行一次,update 會執行 3 次 - #### post-receive - push 完了之後執行的 ## client-side - 透過 git 一般操作觸發,像是 commit ### commit 階段所執行的 hooks - #### pre-commit hooks - 在輸入 commit message 之前做檢查,回傳非零即終止 commit - code style - trailing whitespace - 用 `git commit --no-verify` 來略過檢查 - #### prepare-commit-msg hooks - 用來自動生成 templated commit message (就是輸入 commit message 前就已經出現在編輯器中的那些內容) - 需要以下參數 - 儲存當前 commit message 的檔案路徑 - commit type (normal, amended, merged, squashed) - commit SHA-1 如果是 amended - #### commit-msg - commit message 提交前對 project state 和 commit message 做檢查,回傳非零即終止 commit - 需要以下參數 - 儲存當前 commit message 的檔案路徑 - #### post-commit - 完成 commit 之後所執行的 hooks
{"metaMigratedAt":"2023-06-15T05:09:06.935Z","metaMigratedFrom":"YAML","title":"Git & 測試工具","breaks":true,"description":"Git 只關心檔案的內容,所以只要是檔案都可以使用 git 來管理。","contributors":"[{\"id\":\"bd34bb29-6393-4de1-a879-0655a63bb8b4\",\"add\":16150,\"del\":8184}]"}
Expand menu