[CLI] Git 指令
- CS Visualized: Useful Git Commands @ dev:用圖示的方式清楚說明 git cli 不同指令所做的事
- help @ Github
[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 的差別
假設現在有 main
和 feat-xyz
兩個 branch,如果我們在 main
這個 branch 去 merge feat-xyz
,無論是使用 git merge
或 git 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
即可。
Merge | Merge Squash |
---|---|
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: 標籤
- Git - Tagging
- 使用標籤 @ 為你自己學 Gits
- tag 包含
lightweight
和annotated
這兩種,一般來說,會建議使用 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 @ git
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
- How to Use Git Worktree @ Youtube
- Experiment on your code freely with Git worktree @ Youtube
適合用在「臨時需要切到其他 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
:::
參照說明(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,那些看起來好像不見的東西只是暫時看不到,但隨時都可以再撿回來。
模式 | mixed | soft | hard |
---|---|---|---|
工作目錄(unstaged) | 不變 | 不變 | 捨棄 |
暫存區(staged) | 丟回工作目錄 | 不變 | 捨棄 |
儲存庫(repository) 從 commit 拆出的檔案 | 丟回工作目錄 | 丟回暫存區 | 捨棄 |
合併分支:git rebase, git merge
rebase | merge |
---|---|
$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)。 | |
# 原本的 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)
- How to reference branch name in a git commit @ StackOverflow
- How to add Git's branch name to the commit message? @ StackOverflow
要取得當前 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_NAME
和 GIT_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,可以使用 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 clean @ git documentation
- git 解決 error: The following untracked working tree files would be overwritten by checkout @ iTread01
建立一個「全新的」分支
git checkout --orphan <NEW_BRANCH>
git rm -rf .
中文目錄顯示亂碼問題
打開 ~/.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
- git reset | 剛才的 Commit 後悔了,想要拆掉重做…
- git rebase | 把多個 Commit 合併成一個 Commit
- git rebase | 將一個 Commit 拆成多個 Commit
- git rebase | 在某些 Commit 中插入新的 Commit
- git rebase | 刪除某幾個 Commit 或是調整 Commit 的順序
- git fetch | 瞭解 git fetch 做的事
- 將已經加入 git 的檔案移除的乾乾淨淨
- 為每個專案設定不同的使用者
- 接受所有 local change - How can I discard remote changes and mark a file as “resolved”? @ StackOverflow
- Remove a folder from git tracking @ StackOverflow
- Ignoring an already checked-in directory's contents? @ StackOverflow
- Recover from git reset hard @ StackOverflow
- How to undo “git commit --amend” done instead of “git commit” @ StackOverflow
- 不小心把還沒合併的分支砍掉了,救得回來嗎? @ 為你自己學 git
- 取消 rebase @ 為你自己學 git
搜尋
# 搜尋 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。
No-fast-forward
但若「當前的分支」和「要被 merge 的分支」中間有一些 commit 是無法相互對應的(即,當前分支比「要被 merge 的分支」有多出一些 commits),這時候預設在 merge 時,就會產生一個新的 commit,並透過這個 commit 把有變更的部分合併進當前的分支。
改變檔名的大小寫
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 commit signature verification @ GitHub
- Telling Git about your SSH key @ GitHub
- Signing Commits @ GitHub
把 SSH Key 加到 Github 上時,要記得選這個 key type 是要 Signing Key:
使用 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
專有名詞
- Git 在計算、產生物件的時候,是根 據「檔案的內容」去做計算的,所以光是新增一個目錄,Git 是沒辦法處理它的(注意:空的目錄無法被提交!),因此慣例上可以放一個名為
.keep
或.gitkeep
的空檔案,讓 Git 能「感應」到這個目錄的存在。
Term | Description |
---|---|
HEAD | HEAD 是一個指標(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
參考
- Modern Git Commands and Features You Should Be Using
- Git Documentation
- 為你自己學 git @ 5xRuby
- git note @ Andyyou
- git-cheat sheet @ gist