Git常用指令
主要參考此書:版本控制:使用Git
建議初學者先看git觀念及此篇的第一段
Git基本觀念
檔案階段 | 檔案所在位置 | 說明 |
---|---|---|
已修改 | Working Directory(工作目錄) | 經過修改的檔案 |
已暫存 | Staging area(暫存區) | 要提交的變動清單 |
已提交 | Repository(容器) | 已提交的檔案及變動記錄 |
其他檔案階段
- 被忽略:在名為
.gitignore
的檔案中修改想忽略的檔案,如暫存檔、log、或編譯後的object file、筆記- 每個目錄都可以有自己的
.gitignore
檔,以較接近檔案的目錄為準 - 可以用
git add -f
強制將被忽略的檔案加入暫存區
- 每個目錄都可以有自己的
觀看檔案的狀態: git status
- 已修改:
Changes not staged for commit
- 已暫存:
Changes to be committed
- 已提交: 不顯示
基本流程: 修改檔案 → 將修改的檔案加入 Staging area (git add
) → 提交變更 (git commit
) → 繼續修改檔案(循環)
若要和他人共用,要在線上容器和本機容器間進行同步(git push
, git pull
)
提交(commit,也稱送交)
- 版本和容器變動的最小單位
- 一個commit = 一個版本
- git 中的
HEAD
等於目前所在的版本
- 不一定所有檔案都要提交
- 只有加入Staging area的檔案會被提交
- 提交時機
- 程式處於穩定狀態
- 可以正確執行
- 測試完成
- 以時間為單位
- 結束一天的工作前
- 程式處於穩定狀態
辨識碼
- 所有物件都有一個獨特的辨識碼
- 包含commit, tag, 文件…都有辨識碼
- 其值為物件的SHA1 hash
- 下述指令的
[commit]
即為識別碼- 在沒有重覆的情況下,可取前幾碼代表該物件
設定
先設定自己的名字與信箱,因為git用名字與信箱來分辨作者
1 | # 若不加 --global 則設定只會在該容器生效 |
設定預設提交格式
1 | # 方法1. 直接設定 |
實作範例
初始化一個專案
1 | git init # 初始化,使當前目錄成為git的容器 |
將本地的容器上傳到遠端(如github)的容器
1 | git remote add origin <URL> # 將遠端容器命名為origin |
本篇:Git常用指令
Git檔案操作
加入暫存區: add
git add
:將檔案加入暫存區(staging area)
1 | git add -i # 進入互動模式(推薦) |
參考資料
直接刪除/移動不會影響容器中的檔案,因為這項變動沒有告知git
若要在下一個版本刪除或移動檔案,需要用git rm
或git mv
1 | git rm [filename] # 在下一個版本移除,並實際刪除檔案 |
checkout
使檔案還原到指定提交時的版本
- 發現檔案改錯,用
git checkout filename
把檔案還原到最近的版本 git checkout [commit] filename
檔案會被還原到指定的commit時的版本
Git更新版本
提交新版本(commit)
1 | git commit # 會跳出編輯器,此時可編輯提交訊息 |
存取遠端容器: push, pull
遠端的repository名稱通常取名為origin
push
: 上傳, pull
: 下載
1 | git remote show # 顯示設定的遠端容器及其名稱、網址 |
- push時,如果和遠端容器有衝突,需要先pull,在本地merge(處理衝突)後再push
- pull的機制:先 fetch 遠端的 branch,然後與本地端的 branch 做 merge
- 若有衝突,會多產生一個 merge commit來取代遠端有衝突的所有commit
- 用
git pull --rebase origin master
可以保留雙方的commit
Git分支(branch)
分支(branch): 將特定版本(commit)命名
git branch
git checkout
1 | git branch # 列出本地分支(前方有星號的為目前所在分支) |
git checkout -m [branch]
# 將工作目錄的變動和分支合併,並切換到該分支- git checkout 時,若已修改的檔案和checkout的版本產生衝突,則checkout不會執行
- 衝突時先
git stash
(暫時清空已修改的檔案)
- 衝突時先
Git 標籤(tag)
1 | git tag [tagname] [commit] # 在指定commit新增tag |
Git合併(merge)
1 | git merge [branch/commit] # 將指定的版本合併到目前所在的分支 |
產生衝突時:先用git diff
git status
查看
衝突的地方為如下格式
1 | <<<<<<<< |
分別是兩個版本的差異。
將其修改後即可提交
git diff --ours/--theirs
可以查看原版本和衝突版本的差異(ours→合併的, theirs→被合併的)git diff
用在衝突檔案時只會顯示衝突的部分,而不會顯示只有一邊修改的部分
Git 修改版本
修改版本: rebase
rebase
可以重新排序、編輯、移除、合併、拆分提交
- 優點
- 可以不產生分支線和額外的merge commit
- 缺點
- 等於改變提交記錄,僅適合還沒上傳的commit
- commit被移動,所以特徵碼會改變(也就是原本的commit會被取代)
- commit的形狀會被改變(變成一條直線)
- 可用
--preserve-merge
參數保留形狀
- 可用
- 麻煩
- 移動的commit若屬於多個分支,則每個分支都要重新指定位置
使用時機:想保留額外commit資訊時,可替代merge
1 | // debug rebase on master |
1 | git rebase -i [commit] # rebase互動模式(推薦) |
參考資料:修改一個分支的歷史--使用rebase
merge還是rebase?
需要保留樹狀記錄就用merge,反之用rebase
個人較愛用rebase,理由是保留樹狀記錄只會造成日後版本維護的麻煩
修正版本: amend
- 修改最近一次的commit
- staging area的修改會合併到最近的提交
- 使用時機
- 提交訊息的修改
- 發現一些小錯誤
- 忘記add/多add檔案/找到小bug 的時候
1 | git add [filename] # 修改錯誤後加入暫存區 |
回到指定版本: reset
1 | git reset [commit] [--soft/--mixed/--hard] # 還原到指定提交 |
--mixed 暫存區也被改變,但工作目錄不變 (預設值,暫存區清空)
--hard 暫存區和工作目錄都被改變(目前的目錄中,所有檔案的修改會消失)
--soft
- 只有版本改變,檔案不改變
- 版本改變造成的所有變更都會放在staging area
- 若你
reset --soft
到五天前的版本,那這五天內的commit變更都會在staging area - 若
reset --soft
後直接git commit
,則最新的版本會和reset前的版本一模一樣
- 若你
--mixed
- 只有版本改變,檔案不改變
- 變更不會放在staging area, 要自己手動add
--hard
- 檔案會回到指定commit的狀態
- 通常是出了嚴重問題後的解決方法
- 若有檔案不在版本中(沒有被
add
,commit
過),reset --hard
後檔案依舊存在
Whats the difference between git reset --mixed, --soft, and --hard?
提交: revert
git revert [commit]
- 新增一筆commit,此commit會抵消之前的變更,使檔案像是回到指定版本
- 若最近的變更是寫一頁作文,revert會加上一個塗一頁立可白的變更
使用時機: 你的commit已經上傳,別人已下載,所以不適合用git rebase
,git reset
,git commit --amend
等方法隨便修改版本記錄
- 若最近的變更是寫一頁作文,revert會加上一個塗一頁立可白的變更
Git比較
1 | git diff # 比較實際檔案與目前版本的不同 |
Git記錄: log, show
git log
用來查詢提交記錄,有很多參數可以用
建議可以直接用GUI(gitk
, git gui
, git instaweb
)來看
1 | git log [commit] # 列出指定版本的記錄(也就是找其之前的記錄) |
可以將版本記錄視為一顆樹,前一個版本為其父節點
- 列出指定版本的記錄 = 找其祖先
git diff foo..bar
=git diff foo bar
git diff foo...bar
: foo和bar的"merge base"(最近的共同祖先)和bar的差異git log foo bar
: 顯示所有foo和bar的提交(其中一方有即可)git log foo..bar
: 不屬於foo,屬於bar的提交git log foo...bar
: 只有其中一方才有的提交
參考資料:What are the differences between double-dot 「..」 and triple-dot 「…」 in Git diff commit ranges?
調整記錄的格式
1 | git log --pretty=oneline # 一個commit一行 |
Git尋找提交: bisect, blame
git bisect
用二分搜尋法找尋有bug的提交
1 | git bisect start # git會依照你提供的資訊改變目前的工作目錄,以方便測試 |
用檔案尋找提交
1 | git blame [filename] # 顯示檔案的每一行最後是被哪個commit修改的 |
1 | git reflog [Reference] # 參照(Reference, 預設為HEAD)改變的歷史 |
checkout
,commit
,reset
,rebase
... 都會修改HEAD- 可做為
reset
的參考
Git藏匿變更: stash
git stash
: 把目前工作目錄(working directory)的變更丟到一個stack中,之後再回來拿
保持工作目錄的乾淨
使用時機
- 你不得不修改一個緊急bug,你可以先把目前工作目錄的變更丟到stash,這時候你的工作目錄和上次剛提交內容的狀況一樣,等到修完bug後再把stash中剛剛做到一半的東西還原以繼續
- 今天的工作時間結束後,還有未完成的部分,可以先stash後執行測試,確認最新版本是正確的
1 | git stash # 把目前工作區的修改丟到stash裡 |
不常用的指令
Git遠端容器管理: remote
1 | git remote # 列出預設遠端容器的名稱 |
Git clean
清除不包含在版本控制中的檔案(被.gitignore
忽略的檔案不會被清除)
1 | git clean -n # 列出會被清掉的檔案 |
垃圾回收: gc
git儲存的記錄很多: stash, reflog, log ...
gc(garbage collection):刪除所有沒有被指到(reference)且已存在一段時間的物件
1 | # 整理前和整理後的差異, 可由: git count-objects 看出 |
大檔案: lfs
處理單一大檔案的上傳
在git後加上lfs,其他邏輯大多和一般的git相同
1 | git lfs install # 初始化 |
參考資料
- 版本控制:使用Git
- Git scm
- Git情境劇系列
- Git初學筆記
- ihower: 還沒push前可以做的(壞)事
- git: get ready to use it