跳至主要内容

[CLI] Git 指令

[TOC]

常用指令

git checkout .                            # 編輯檔案後,恢復目錄到最後一次的 commit 狀態
git checkout [FileName] # 把某支修改過的檔案還原到未修改狀態 commit 後修改的檔案內容移除

git reset HEAD [filename] # 取消已經被 add 加入索引的檔案(不會改變檔案內容)
git reset HEAD~1 # 取消最後一次提交的 commit ,檔案內容回到 unstaged 狀態(已編輯的內容會被保留),等同於 reset HEAD^
git reset HEAD~1 --hard # 取消最後一次提交的 commit ,檔案內容回到 unstaged 狀態(已編輯的內容不會被保留)
git reset --hard ORIG_HEAD # 將不小心透過上行指令刪除的 commit 復原

# 將 local 的 branch 對齊回 remote 的 branch 的 commits
git fetch && git reset --hard @{u}

##
# 合併分支
##
git rebase [some-hash]^ --onto [some-branch]

git merge --squash [some-branch]

##
# git commit
##
git commit --amend # 修改當前 commit 撰寫內容,ESC :wq 可離開 amend
git commit --amend -m "[message]" # 將新的 commit 合併到上一次的 commit 中(上一次的 commit 的內容會被覆蓋)
git commit --amend --no-edit # 修改當前 commit 但不修改 commit message

git commit --no-verify # 去掉 commit 前一些 lint 的檢查

##
# 刪除遠端分支
##
git push origin :[branch-name] # ":" 表示要刪除遠端 branch 的意思
git push origin --delete [branch-name] # 同上
git fetch -p origin # 刪除 br 內不存在的參考遠端分支

# 將和該次 push 有關的 tags 一起推上去
git push --follow-tags origin main

# 建立一個新分支並拉內容下來(不會衝突)
git fetch
git checkout [branch-name]

# 將 diff 的結果貼到剪貼簿(clipboard)
git diff | pbcopy
git diff --function-context # 顯示整個 code block 的程式碼
git diff -U10 # 顯示 10 行相關的 context code
git diff --no-index a.txt b.txt # 比較兩個檔案的差異

# Delete untracked files
$ git clean -n # 列出將被刪除的檔案
$ git clean -f # 刪除沒被 git 追蹤的檔案

安裝

brew install git

指令 CLI

git diff 檢視檔案內容差異

# 檢視檔案內容差異
git diff # 檢視未加入索引的檔案和原檔案有何差異
git diff --staged # 檢視加入索引的檔案和前次提交的檔案有何差異
git diff [first-branch] [second-branch] # 檢視兩個 branch 的檔案有何差異
git show [commit-hash] # 檢視某一次 commit 有何差異

git diff | pbcopy # 把內容複製到剪貼簿,可以存成 .diff 檔就可以看到差異

檢視記錄

keywords: git log, git reflog, git blame
# 檢視記錄
git log # 檢視提交記錄
git log -p [FileName] # 針對某一支檔案顯示內容的變更記錄
git log --follow [FileName] # 針對某一支檔案顯示 log
git log --graph # 以圖表方式顯示分支歷史
git log --oneline # 一次 commit 僅以一行顯示重點

git reflog # 每次 HEAD 移動時就會記錄(保留 30 天),因此可以檢視最完整的編輯記錄(包含 reset)

git blame [FileName] # 顯示某一支檔案內容的完整編輯記錄
git blame -L 5,10 [FileName] # 顯示某一支檔案內容的完整編輯記錄(只顯示特定行數)

git checkout 移動 HEAD

# 移動 HEAD
git checkout [FileName] # 編輯檔案後,恢復單一檔案到最後一次的 commit 狀態
git checkout . # 編輯檔案後,恢復工作目錄到最後一次的 commit 狀態
git checkout [commit-hash] # 將 HEAD 移動到特定 commit 位置(檔案內容會改變到該次 commit 的狀態)
git checkout master # 將 HEAD 回到 master 上
git checkout HEAD^
git checkout HEAD~2

git switch 切換分支

git switch other-branch
git switch - # Switch back to previous branch, similar to "cd -"
git switch remote-branch # Directly switch to remote branch and start tracking it

git restore 還原特定檔案

# Unstage changes made to a file, same as "git reset some-file.py"
git restore --staged some-file.py

# Unstage and discard changes made to a file, same as "git checkout some-file.py"
git restore --staged --worktree some-file.py

# Revert a file to some previous commit, same as "git reset commit -- some-file.py"
git restore --source HEAD~2 some-file.py

git reset 取消或刪除某些 commit

# git reset 取消或刪除某些 commit

# 沒給參數預設是使用 --mixed
git reset # 預設 mixed,把暫存區的檔案變成 unstaged,Commit 拆出來的檔案會留在工作目錄,但不會留在暫存區。
git reset [commit-hash] # 取消前一次的 commit 但不還原改變回檔案內容

git reset HEAD # 取消所有已進入 staging/index (add) 的檔案
git reset HEAD [FileName] # 取消單一檔案的索引(staged to unstaged)

git reset --hard HEAD~1 # 刪除前一次的 commit,檔案和索引會還原到前一次 commit 時的內容
git reset --hard ORIG_HEAD # 將不小心透過上行指令刪除的 commit 復原

git config 使用者資料設定

# 使用者資料設定
git --version # 查詢 git 版本
git config --global --add user.name “pjchender” # 設定使用者名稱
git config --global --add user.email “pjchender@gmail.com” # 設定使用者信箱
git config --global —list # 列出使用者設定
git config --global color.ui auto # 讓 git 好看
git config --global core.editor sublime # 變更預設編輯器

git clone 建立本機端的資料庫

# 建立本機端的資料庫
git init # 建立新的本地端數據庫(資料夾中會多一個名為 .git 的資料夾)
git clone [remote_url] [directory_name] # 複製遠端數據庫

git 基本指令

# git 基本指令
git add . # 將工作區的所有檔案加入索引/暫存區
git add [FileName]
git add -u # mark all conflicted files as merged
git commit -m ["commit-message"] # 將暫存區中的檔案加入儲存庫,提交並寫上註記
git status # 確認工作目錄與索引狀態
git commit --amend # 修改 commit 的訊息內容,會產生新的 SHA-1

git 雲端使用

keywords: git push, git fetch, git pull, git remote
# git 雲端使用
git push [repo] [branch] # 把本地端的 branch_name 推到遠端 [repo] 上
git push -u [repo] [branch] # 將該 [repo] 和 [branch] 設為預設值,以後不需要再次輸入
git push [repo] [local_branch]:[remote_branch] # 將本地端的 [local_branch] 分支推到遠端的 [remote_branch] 上
git push [repo] :[remote_branch] # 把遠端的 [remote_branch] 移除

git fetch # 將遠端的檔案拉下來,但尚未建立 merge

git pull [repo] [branch] # 將遠端的檔案拉下來,同時 merge(等同於 fetch + merge)
git pull [repo] [branch] --allow-unrelated-histories # 允許不同的歷史記錄
git pull --rebase [repo] [branch] # 將遠端的檔案拉下來,同時 rebase(不會多產生一個 merge 的 commit)

# git remote 遠端操作
git remote # 顯示所有遠端儲存庫
git remote -v # 在名稱後方顯示 URL
git remote add [short-name] [url] # 新增遠端儲存庫
git remote show [remote-name] # 監看遠端儲存庫
git remote rename [old-name] [new-name] # 更名遠端儲存庫(同時會改變分支名稱)
git remote rm [remote-name] # 移除遠端儲存庫

git branch: 分支操作

keywords: git branch, git checkout, git merge, git rebase
# 分支操作
# git branch 分支指令
git branch # 顯示所有分支紀錄,-a 可以顯示詳細
git branch [branch_name] [commit-key] # 在 commit-key 上面貼上 branch_name 這個貼紙
git branch -f [branch_name] [commit-key] # 強制移動分支
git branch [branch_name] # 新增分支
git branch -d [branch_name] # 刪除分支
git branch -m [old_name] [new_name] # 重新命名分支,-m 指 mv 的意思
git branch -m [new_name] # 重新命名當前分支
git branch --merged # 檢視已經合併過的分支

git branch -u [upstream] # set up stream

# git checkout 切換 HEAD
git checkout [branch_name] # 移動到某一分支
git checkout --ours . # checkout our local version of all files
git checkout -b [branch_name] # 建立該 branch 並切過去
git checkout -b remote_branch origin/remote_branch # 建立分支並拉下來

# git merge 合併分支
git merge [branch_name] # 合併某一分支到目前的分支,會產生新的 commit 記錄
git merge --squash [branch-name]

# git rebase 合併分支 # 將目前分支合併到某一分支後,不會產生新的 commit
git rebase [branch_name]

# 刪除遠端上的分支
git push origin :[branch-name] # ":" 表示要刪除遠端 branch 的意思
git push origin --delete [branch-name] # 同上
git fetch -p origin # 刪除 br 內不存在的參考遠端分支
merge 和 squash merge 的差別

假設現在有 mainfeat-xyz 兩個 branch,如果我們在 main 這個 branch 去 merge feat-xyz,無論是使用 git mergegit merge --squash 都會在 main 多加一個 commits,但最大的差別在於:

  • 使用 merge 的話,原本 feat-xyz 的頭會接到 main 上,且這個多出來的 commit 同時有兩個 parent。後續如果我們切回 feat-xyz 在新增 commit 的話,其實回讓線圖變的滿亂的。
  • 使用 merge squash 的話,原本的 feat-xyz 是保持不動的,但會在 main 多一個 commit,把所有 feat-xyz 所做的變更放在這個 commit 中。後續如果切回 feat-xyz 在新增 commit 的話,因為 feat-xyz 並沒有因為 merge 而改變,所以可以在把多新增出來的部分 merge 回 main 即可。
MergeMerge Squash
image-20230505114537474image-20230505114654842

git rebase: 修改、整理 commit 記錄

# 修改、整理 commit 記錄
# git rebase 分支指令
git rebase -i [commit-hash] # 從現在退到 commit-hash 分支進行修改,會出現介面可選擇要執行的項目。
git rebase -i --root # 退到第一個 commit 來進行修改

git cherry-pick

資料來源:git cherry-pick @ 阮一峰的網絡日誌

# 接下來所有動作都是把 commit 加進到 master 分支
$ git co master

# 只把某一其他分支的 commit 套用到進 master 分支
$ git cherry-pick <commit-hash>

# 一次轉移其他分支中的多個 commit 進 master 分支
$ git cherry-pick <hash-a> <hash-b>

# 一次轉移一連串的 commit 進 master 分支
$ git cherry-pick <hash-a>..<hash-b>

# 把該分支的最後一次 commit 套的到 master 分支
$ git cherry-pick <branch-name>

# 過程中若發生衝突,解決方式和 rebase 相同,修該完後先 add,然後 continue
$ git cherry-pick --continue
$ git cherry-pick --abort

git tag: 標籤

  • tag 包含 lightweightannotated 這兩種,一般來說,會建議使用 annotated tags,它才帶有完整的資訊(打 tag 的時間、人、訊息);但如果你只是想要有一個暫時用的 tag,這時候才來使用 lightweight tags。
  • [tag_name] 不能有空格,可以用 _ 連接。
# git tag 標籤
git tag # 檢視所有標籤
git tag [tag_name] # 新增 lightweight 標籤在目前的 commit 上
git tag -a [tag_name] -m [tag_message] # -a:annotated tag;-m:tagging message
git show [tag_name] # 檢視標籤
git tag [tag_name] [commit_hash] # 在某個 commit-hash 加上 tag(輕量標籤/lightweight tag)
git tag [tag_name] [commit_hash] -a -m [commit_message] # 在某個 commit-hash 加上帶有附註(-a)訊息(-m)的 tag(有附註標籤/annotated tag)
git tag -d [tag_name] # 刪除標籤

# 預設的情況下,git push 並不會把 git 同步到遠端,需要手動 push tag
git push origin [tag_name] # 把 tag 推到遠端
git push origin --tags # 一次把所有遠端沒有的 tags(包含 lightweight 和 annotated)都推上去
git push origin --follow-tags # 只把 annotated tags 推上遠端

git push origin --delete [tag_name] # 刪除遠端上的標籤
git push origin :refs/tags/[tag_name] # 另一種寫法

git stash 暫存

# git stash 暫存
git stash (save) # 把目前狀況存下來
git stash save [description] # 把目前的狀況存下來,並加上描述

git stash apply # 把最後一次 stash 拿出來用
git stash pop [stash_key] # 把 stash 拿出來用並且將該暫存從 stash 移除(等同於 apply + drop)
git stash drop # 刪除最後一的 stash
git stash drop stash@{0} # 刪除特定一次的 stash
git stash list # 查看所有暫存的項目
git stash -u # Untracked 狀態的檔案預設沒辦法被 Stash,需要額外使用 -u 參數。

git cherry-pick 把其他分支的 commit 複製一份進來

# git cherry-pick 把其他分支的 commit 複製一份進來
git cherry-pick [commit_hash] [commit_hash] ... # 複製其他分支的 commit 進來當前的 branch
git cherry-pick [commit_hash] --no-commit # 複製其他分支的 commit 進來當前的 branch 但不直接 commit

git rm 刪除檔案

# git rm 刪除檔案
git rm [file_name] # 將該檔案從資料夾中刪除
git rm [file_name] --cached # 清除追蹤某個已經被加入 git 版控的檔案(不會從資料夾中刪除)
git rm -r --cached path_to_your_folder/ # 清除追蹤某個已經被加入 git 版控的資料夾(不會刪除檔案)
# git 目錄檔
rm -rf .git # 刪除這支檔案等於把整個 git 對這個專案的版控刪除

git describe

參考資料

git describe 會從當前 commit 找到最靠近的 tag 來幫助你了解該 commit。

# 直接使用 git describe 會回傳最近的一個 tag name、距離這個 tag 有幾個 commit
git describe # [tag-name]-[count]-[hash],例如 0.1.8-1-gb4038c03a

# 預設情況下,git describe 只會顯示 annotated tags,如果希望也包含 lightweight tags,則需加上 --tags
git describe --tags

# 如果只希望拿到 tag name,不要其他的資訊
git describe --abbrev=0 # 0.1.8

git worktree 允許同時在同一個 repo checkout 到多個 branch

適合用在「臨時需要切到其他 branch 修復 bug」

# 假設原本有 feature-abc 和 main 這兩個 branch

git worktree list

# 建立一個新的資料夾,並讓 git 能夠 tract 這裏的變更
# 範例:git worktree add ../wt-eslint-config-pjchender/main
git worktree add ../[folder_name]/[branch_name]

# (或者)從 brach 開一個新的 hotfix branch,並放在 ./hotfix 資料夾內
# git worktree add -b hotfix ./hotfix main

# 移除掉這個 worktree 和相關的資料夾
# 範例:git worktree remove ../wt-eslint-config-pjchender/main
git worktree remove ../[folder_name]/[branch_name]

git worktree prune

觀念

How Git Commands Work

:::資料來源

How Git Commands Work @ ByteByteGo

:::

Imgur

參照說明(Reference)

# 絕對(absolute)
git reset 830428
git reset 830428^ # 退回到 830428 的前一次

# 相對路徑(relative)
git reset HEAD^ # 退回一步
git reset @^ # @ 是 HEAD 的縮寫
git reset HEAD^^ # 退回兩步
git reset HEAD~1 # 退回一步
git reset HEAD~2 # 退回兩步

git reset

Reset 這個英文單字的翻譯是「重新設定」,但事實上 Git 的 Reset 指令用中文來說比較像是「前往」或「變成」,也就是「go to」或「become」的概念。因為實際上 git reset 並不是真的刪除或是重新設定 Commit,只是「前往」到指定的 Commit,那些看起來好像不見的東西只是暫時看不到,但隨時都可以再撿回來。

模式mixedsofthard
工作目錄(unstaged)不變不變捨棄
暫存區(staged)丟回工作目錄不變捨棄
儲存庫(repository) 從 commit 拆出的檔案丟回工作目錄丟回暫存區捨棄

合併分支:git rebase, git merge

rebasemerge
$cat: rebase dog$cat: merge dog
從 cat 分支 rebase dog 分支時,會把原本的 cat 分支接到 dog 後面(很像嫁接的感覺)。意思是 "cat" 分支要重新定義參考基準,以 "dog" 分支當做新的參考基準,所以 dog 在前 cat 在後。Git 會產生一個額外的 Commit 來處理這件事(Merge branch 'dog' into 'cat')。
進行 Rebase 的時候,Commit 物件並不是剪下、貼上而已,而因為要接的前一個 Commit 不同(其實時間也不同),所以會重新計算並做出一顆新的 Commit(有新的 SHA-1)。
rebasemerge
# 原本的 hist
# Master Branch
* 18dab6b 2017-09-21 [pjchender] Add HTML Template (HEAD -> master)
* b0b6efc 2017-09-21 [pjchender] Add index.html

# DOG Branch
* 7554922 2017-09-21 [pjchender] dog2 (HEAD -> dog) - 4
* 121d3c8 2017-09-21 [pjchender] dog1 - 2
* 18dab6b 2017-09-21 [pjchender] Add HTML Template (master)
* b0b6efc 2017-09-21 [pjchender] Add index.html

# Cat Branch
* b0dfffe 2017-09-21 [pjchender] cat2 (HEAD -> cat) - 3
* 4e13674 2017-09-21 [pjchender] cat1 - 1
* 18dab6b 2017-09-21 [pjchender] Add HTML Template (master)
* b0b6efc 2017-09-21 [pjchender] Add index.html

# 使用 git rebase (rebase cat into dog),cat 會接在 dog 後
$cat: git rebase dog
* 589dc6f 2017-09-21 [pjchender] cat2 (HEAD -> cat)
* 8170e43 2017-09-21 [pjchender] cat1
* 7554922 2017-09-21 [pjchender] dog2 (dog)
* 121d3c8 2017-09-21 [pjchender] dog1
* 18dab6b 2017-09-21 [pjchender] Add HTML Template (master)
* b0b6efc 2017-09-21 [pjchender] Add index.html

# 使用 git merge (merge dog into cat),會產生新的 commit
$cat: git merge dog:
* 0bbc9c7 2017-09-21 [pjchender] Fix Conflict (New Commit in Merge) (HEAD -> cat)
|\
| * 7554922 2017-09-21 [pjchender] dog2 (dog)
| * 121d3c8 2017-09-21 [pjchender] dog1
* | b0dfffe 2017-09-21 [pjchender] cat2
* | 4e13674 2017-09-21 [pjchender] cat1
|/
* 18dab6b 2017-09-21 [pjchender] Add HTML Template (master)
* b0b6efc 2017-09-21 [pjchender] Add index.html

使用 rebase 合併分支 @ 為你自己學 git 使用 merge 合併分支 @ 為你自己學 git

修改提交:reset, rebase, revert

指令改變歷史記錄說明
reset把目前的狀態設定成某個指定的 Commit 的狀態,通常適用於尚未推出去的 Commit。
rebase不管是新增、修改、刪除 Commit 都相當方便,用來整理、編輯還沒有推出去的 Commit 相當方便,但通常也只適用於尚未推出去的 Commit。
revert新增一個 Commit 來反轉(或說取消)另一個 Commit 的內容,原本的 Commit 依舊還是會保留在歷史紀錄中。雖然會因此而增加 Commit 數,但通常比較適用於已經推出去的 Commit,或是不允許使用 Reset 或 Rebase 之修改歷史紀錄的指令的場合。
# rebase 是 merge branch 的第二種方法。rebase 就是取出一連串的 commit,"複製"它們,然後把它們接在別的地方。
git rebase branchName

# reset 把分支的參考點退回到上一個 commit 來取消修改。你可以認為這是在"重寫歷史"。
# git reset 往回移動 branch,原來的 branch 所指向的 commit 好像從來沒有存在過一樣。
git reset HEAD^

# revert ,雖然在你的 local branch 中使用 git reset 很方便,但是這種「改寫歷史」的方法對別人的 remote branch 是無效的哦!
# 為了取消修改並且把這個狀態分享給別人,我們需要使用 git revert,它會再做一個新的 Commit,來取消你不要的 Commit
git revert HEAD

標籤(tag)和分支(branch)的差別

其實標籤跟分支真正的差別,是「分支會隨著 Commit 而移動,但標籤不會」。在「對分支的誤解」章節曾介紹過,當 Git 往前推進一個 Commit 的時候,它所在的分支會跟著往前移動。但標籤一旦貼上去之後,不管 Commit 怎麼前進,標籤還是留在原來貼的那個位置上。

犯錯處理

git reflog                             # 檢視過去的操作紀錄(保留 30天),之後可以在透過 reset 回到該操作點
git reset --hard ORIG_HEAD # 將不小心透過 reset 刪除的 commit 復原
git reset --soft HEAD@{1} # 將不小心 --amend 的內容重新回到 staged 狀態
git push --force-with-lease origin 'HEAD^:master' # 刪除已經 push 到遠端的 commit,但本地端的 commit 還會在,要自己 reset [參考資料]

使用 git-lfs 紀錄大檔

Git Large File Storage @ git-lfs

安裝 git-lfs

$ brew install git-lfs # 安裝 git-lfs
$ git lfs install # setup Git LFS 在該 git account(只需執行一次)

# 進到 Git 專案
$ git lfs track "*.pdf" # 將大檔案透過 lfs 追蹤
$ git add .gitattributes # 將 .gitattributes 加入追蹤

# 原先的 git 流程
$ git add file.pdf
$ git commit -m "Add PDF file"
$ git push origin master

各種情境

在 commit message 中加入 branch name(commit with branch name as prefix)

要取得當前 branch name 的方式很多:

$ git branch --show-current    # release/230321
$ git rev-parse --abbrev-ref HEAD # release/230321
$ git symbolic-ref -q HEAD # refs/heads/release/230321

inline 使用

如果要在 commit 的時候透過 -m 直接帶入當前 branch name 可以這樣:

$ git commit -m "$(git branch --show-curren): [commit message]"

使用 git alias

# ~/.gitconfig
[alias]
co = checkout
ci = "!f() { \
git commit -m \"$(git branch --show-current): $1\"; \
}; f"

使用 prepare-commit-msg

如果希望在每次 commit 時的 commit message 都自動加上這個,可以在 /project/.git/hooks/prepare-commit-msg 中加上(但這麼做不能使用 -m,需要直接下 git commit):

注意

可能需要給執行的權限:sudo chmod 755 .git/hooks/prepare-commit-msg

# PROJECT/.git/hooks/prepare-commit-msg
#!/bin/bash

branchName=$(git branch --show-current)
firstLine=$(head -n1 $1)

#Check that this is not an amend by checking that the first line is empty
if [ -z "$firstLine" ] ;then
#Insert branch name at the end of the commit message file
sed -i "" "1s@^@$branchName: @" $1
fi

修改 commiter

How can I rebase a commit made by another author without adding myself as the committer? @ StackOverflow

我們可以透過 GIT_COMMITTER_NAMEGIT_COMMITTER_EMAIL 這兩個參數來修改某個 commit 的 commiter。另外,因為我們只希望在 rebase 的時候修改 commiter name 和 commiter email,而不是修改全域的 commiter,所以我們可以這樣做:

$ GIT_COMMITER_NAME=foobar GIT_COMMITER_EMAIL=foobar@example git pull --rebase origin <branch-name>
檢視 commiter 的 name 和 email

如果希望檢視 commiter 的 name 和 email,可以使用 git log 指令。

fetch merge request 到 local 執行

How to checkout merge request locally, and create new local branch? @ StackOverflow

# git fetch origin merge-requests/REQUEST_ID/head:BRANCH_NAME
$ git fetch origin merge-requests/10/head:branch_name_you_want

不重新 commit 更改檔案內容

# 不重新 commit 更改檔案內容

git add .
git commit --amend
git push origin master --force-with-lease

修改過去的 commit message

git rebase -i HEAD~3     # 要修改到過去第 3 個 commit

將 rebase interactive 中的 pick 改成 reword 即可修改 commit message 但不修改 commit 內容

Changing commit messages @ Github Help

修改遠端分支名稱(rename git remote branch name)

# 修改 local 的 branch name
git branch -m [old-name] new-name

# 將新的 branch 推上去,並重設 upstream
git push origin -u new-name

# 刪除 remote 上的舊分支
git push origin --delete old-name

刪除遠端分支(delete a git remote branch)

git push <remote_name> --delete <branch_name>

git push origin :[branch-name]            # ":" 表示要刪除遠端 branch 的意思
git fetch -p origin # 刪除 br 內不存在的參考遠端分支

刪除沒被追蹤的檔案(delete untracked files)

keywords: git clean
###
# git clean [-d] [-f] [-i] [-n] [-q] [-e <pattern>] [-x | -X] [--] <path>
# -n:--dry-run,顯示將要刪除的檔案和目錄
# -x:不使用標準的 ignore 規則,而是使用 ignore -e 的方式
# -f:--force 強制執行
# -d:以 recursive 的方式檢查 untracked 資料夾
##

$ git clean -d -fx

# 針對檔案
$ git clean -n # 列出將被刪除的檔案
$ git clean -f # 刪除沒被 git 追蹤的檔案

# 針對資料夾
$ git clean -n -d # 列出將被刪除的資料夾
$ git clean -f -d # 刪除沒被 git 追蹤的資料夾

建立一個「全新的」分支

git checkout --orphan <NEW_BRANCH>
git rm -rf .

new empty git branch

中文目錄顯示亂碼問題

打開 ~/.gitconfig,加入下面的設定:

[core]
quotepath = false

設定專案的 .gitignore

$ npx gitignore node

設定全域的 .gitignore

cd ~
touch .gitignore
git config --global core.excludesfile '~/.gitignore'

global-git-ignore @ Stack Overflow

只在本地 ignore 特定的檔案

keywords: git ignore certain file in local

忽略已追蹤的檔案 @ Clouding City 克勞丁城市

把下述指令加到 ~/.gitconfig 檔案的 alias 區塊中:

# .gitconfig

[alias]
ignore = update-index --skip-worktree
unignore = update-index --no-skip-worktree
ignored = !git ls-files -v | grep ^S

接著即可使用這些指令:

# 希望只在本機不要讓 `redux/NotificationProvider/foo.ts` 被 git 追蹤
git ignore redux/NotificationProvider/foo.ts

# 讓某支檔案可以再次被 git 追蹤
git unignore redux/NotificationProvider/foo.ts

# 檢視所有沒被追蹤的檔案
git ignored
資訊

除了 --skip-worktree 外,還有一個類似但不同的是 assume-unchanged,想了解的話可以進一步參考 Git - Difference Between 'assume-unchanged' and 'skip-worktree' @ Stack Overflow

在 githook 不要做 lint 的檢查

git commit --no-verify    # 等同於 -n
git push --no-verify

git amend the commit user and email

How to change the commit author for one specific commit? @ Stackoverflow

git commit --amend --author="Author Name <email@address.com>" --no-edit

其他 Q & A

搜尋

# 搜尋 commit 的訊息內容
git log --oneline --grep="<word_to_search>"

# 搜尋 commit 的檔案內容
git log -S "<word_to_search>"

常見問題

git merge 中 Fast-forward 和 No-fast-forward 的差別

CS Visualized: Useful Git Commands @ dev.io

Fast-forward

預設的情況下,若「當前的分支」和「要被 merge 的分支」之間的 commit hash 可以直接完全對應時(即,當前分支比「要被 merge 的分支」沒有更多的 commits),git 會使用 fast-forward 的方式直接把另一個分支的 commit 添加到「當前分支」的後面,這種方式將不會產生多的 commit。

可以參考 CS Visualized: Useful Git Commands此圖

No-fast-forward

但若「當前的分支」和「要被 merge 的分支」中間有一些 commit 是無法相互對應的(即,當前分支比「要被 merge 的分支」有多出一些 commits),這時候預設在 merge 時,就會產生一個新的 commit,並透過這個 commit 把有變更的部分合併進當前的分支。

可以參考 CS Visualized: Useful Git Commands此圖

改變檔名的大小寫

How do I commit case-sensitive only filename changes in Git? @ StackOverflow

git mv -f yOuRfIlEnAmE yourfilename

不小心執行了 git push -f

How can I recover from an erroneous git push -f origin master? @ stack overflow

# work on local master
git checkout master

# reset to the previous state of origin/master, as recorded by reflog
git reset --hard origin/master@{1}

# at this point verify that this is indeed the desired commit.
# (if necessary, use git reflog to find the right one, and
# git reset --hard to that one)

# finally, push the master branch (and only the master branch) to the server
git push --force-with-lease origin master

Signing Commits

把 SSH Key 加到 Github 上時,要記得選這個 key type 是要 Signing Key:

image-20240425152417341

使用 SSH Key 來 sign commits:

git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub

預設就會 sign commits:

# 只針對某個 local repository
git config commit.gpgsign true

# 對所有的 local repository
git config --global commit.gpgsign true

專有名詞

Imgur

  • Git 在計算、產生物件的時候,是根據「檔案的內容」去做計算的,所以光是新增一個目錄,Git 是沒辦法處理它的(注意:空的目錄無法被提交!),因此慣例上可以放一個名為 .keep.gitkeep 的空檔案,讓 Git 能「感應」到這個目錄的存在。
TermDescription
HEADHEAD 是一個指標(reference),指向某一個分支,通常你可以把 HEAD 當做「目前所在分支」看待,但有些時候會有斷頭分支的情況(detached HEAD)。
detached HEAD正常情况下,HEAD 會指向某一個分支,而分支會指向某一個 Commit。但 HEAD 偶爾會發生「沒有指到某個分支」的情況,這個狀態的 HEAD 便稱之「detached HEAD」。
暫存區(Staging Area)透過 git add 可以將檔案加入暫存區(Staging Area),而暫存區又稱作索引(index)
儲存庫(Repository)透過 git commit 將暫存區中的檔案加到儲存庫
分支(branch)分支只是一個指向某個 Commit 的指標,刪除這個指標並不會造成那些 Commit 消失。所謂的「合併分支」,其實是合併「分支指向的那個 Commit」。分支只是一張貼紙,它是沒辦法被合併的,只是我們會用「合併分支」這個說法,畢竟它較「合併 Commit」來得容易想像。
ORIG_HEAD這個 ORIG_HEAD 會記錄「危險操作」之前 HEAD 的位置。例如分支合併或是 Reset 之類的都算是所謂的「危險操作」。

Git Config

# ~/.gitconfig
# This is Git's per-user configuration file.

[user]
# Please adapt and comment out the following lines:
# name = pjchender
# email = pjchender@pjchender-MacBook-Pro.local
[user]
email = pjchender@gmail.com
name = pjchender

[filter "lfs"]
process = git-lfs filter-process
required = true
clean = git-lfs clean -- %f
smudge = git-lfs smudge -- %f

[alias]
co = checkout
ci = commit
st = status
br = branch
hist = log --pretty=format:\"%h %ad [%an] %s%d \" --graph --date=short
type = cat-file -t
dump = cat-file -p
lg = log --oneline --graph
ignore = update-index --skip-worktree
unignore = update-index --no-skip-worktree
ignored = !git ls-files -v | grep ^S

[core]
editor = /Applications/Sublime\\ Text.app/Contents/SharedSupport/bin/subl -n -w
quotepath = false

[color]
ui = auto

設定避免 local config 避免推錯 user

多設定一個名為 pushurl 的欄位,並且隨便設一個值:

# .git/config
[remote "wavinfo"]
url = git@192.168.1.7:Wavinfo/ioh-only-views.git
fetch = +refs/heads/*:refs/remotes/wavinfo/*
pushurl = oops

參考