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
    • 提交: 不顯示

提交(commit,送交)

  • 版本和容器變動的最小單位
  • 不一定所有檔案都要提交
    • 只要不將檔案加入staging area,就不會被提交
  • 提交時機
    • 處於穩定狀態
      • 測試完成
      • 移動函式至另一個檔案
    • 結束工作前

辨識碼

  • 所有物件都有一個獨特的辨識碼
    • SHA1 hash
    • 包含commit, tag, 檔案等……
  • 在沒有重覆的情況下,可取前幾碼代表

基本流程: 修改檔案 → 將修改的檔案加入 staging area (git add -i) → 提交變更 (git commit) → 繼續修改檔案(循環)

若要和他人共用,在線上容器和本機容器間進行同步(git push, git pull)

工作目錄 = 容器所在的資料夾

設定

第一次安裝git之後,先設定自己的名字與信箱,因為Git用名字與信箱來分辨貢獻者

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 若不加 --global 則設定只會在該容器生效

git config --global user.name "First Last"
# 設定使用者名稱(加上引號)
git config --global user.email "user@example.com"
# 設定使用者電子郵件(加上引號)
git config --global color.ui true
# 開啟顏色,使git內容不會只有單一白色
git config --global core.editor "emacs"
# 設定預設編輯器(提交時會用到)
git config --global alias.st status
# 設定指令的縮寫(將git st設為git status的簡寫)
git config -l
# 列出所有設定值

設定commit的template(預設訊息格式)

1
2
3
4
5
# 1. 直接設定
git config --global commit.template $HOME/.git-template
# 2. 在 .gitconfig 中設定
[commit]
template = /home/frank/.git-template

實作範例

初始化一個專案

1
2
3
4
git init # 初始化,使當前目錄成為git目錄
touch README.md # 加上專案的說明文件(非必要)
git add README.md # 加入暫存區
git commit -m "initial commit" # 提交

將本地的repository上傳到遠端(如github)的repository

1
2
3
# at git repository directory
git remote add origin <URL> # 將遠端容器命名為origin
git push -u origin master # 從本地端的master branch上傳到origin的master branch

Git常用指令

Git檔案操作

1
2
3
4
5
6
7
8
9
git add [filename] # 將檔案加入staging area
git add -u # 只把「修改/刪除」的檔案放入staging area,「新增」的檔案不加入
git add -i # 進入互動模式(推薦)
git add -p # 部分暫存:Git會詢問哪些檔,然後逐個顯示被選中檔案的每一個差異部分,
# 並詢問你是否希望暫存他們。(= git add --patch)

git add -A # stages All
git add . # stages new and modified, without deleted
git add -u # stages modified and deleted, without new

http://stackoverflow.com/questions/572549/difference-between-git-add-a-and-git-add

直接刪除/移動不會影響容器中的檔案,因為這項變動沒有告知Git
要在下一個版本刪除或移動檔案,需要將變動加入staging area(暫存區)

1
2
3
git rm [filename] # 在下一個版本移除,並刪除檔案
git rm --cache [filename] # 在下一個版本移除,但不刪除檔案
git mv [filename] [new-filename] # 改名、搬移檔案,並將變動加入staging area

checkout

使檔案還原到指定提交時的狀態

使用時機:
發現檔案改錯,用git checkout filename把檔案還原到最新的版本

git checkout [commit] filename 檔案會被還原到指定的commit時的狀態

Git更新版本

1
2
3
git commit
-m 'commit message' # 直接提交訊息
-a 將所有修改過的檔案直接 commit, 不包含新增的檔案

存取遠端容器

遠端的repository名稱通常取為origin
push: 上傳, pull: 下載

1
2
3
4
5
6
7
git remote show # 顯示設定的遠端容器及其名稱、網址
git remote add origin [url] # 設定遠端容器連結至origin
git push origin master # 從本地目前的branch上傳至遠端master
git pull origin master # 從遠端master更新到本地目前的branch
git pull origin/[branchB] [branchA] # 從遠端branchB更新到本地branchA
git clone [url] [newRepositoryPath] # 複製指定容器到和容器同名的新資料夾
git fetch origin # 抓取origin的branch到本地(不更新)
  • push時,如果和遠端容器有衝突,需要先pull,在本地merge(處理衝突)後再push
  • pull的機制:先 fetch 遠端的 branch,然後與本地端的 branch 做 merge
    • 若有衝突,會多產生一個 merge commit來取代遠端有衝突的所有commit
    • git pull --rebase origin master可以保留雙方的commit

Git分支(branch)

1
2
3
4
5
6
7
8
9
10
11
12
13
git branch # 列出本地分支(前方有星號的為目前所在分支)
git branch -r # 列出遠端分支
git branch -a # 列出所有分支(本地+遠端)
git show-branch # 列出本地分支所包含的(有差異的)commit
git branch [branchname] [commit] # 在指定commit上建立分支
git branch -d [branchname] # 刪除指定分支
git branch -m [old-name] [new-name] # 重新命名分支
git branch [new-branch] [old-branch] # 由old-branch複製出新分支

git checkout [branch] # 切換到指定分支
git checkout [commit] # 也可以切換到任何commit
# 若commit不在任何branch上,用 git branch 查看時,會顯示 (no branch)
git checkout -b [new-branch] # 建立並切換到該分支(從目前分支複製)
  • 此處的[commit]即為sha-1識別碼
  • git checkout -m [branch] # 將工作目錄的變動和分支合併,並切換到該分支
  • git checkout 時,若已修改的檔案和checkout的版本產生衝突,則checkout不會執行
    • 衝突時先git stash(暫時清空已修改的檔案)

Git 標籤(Tag)

1
2
git tag [tagname] [commit] # 新增tag至指定commit
git tag -d [tagname] # 刪除tag

Git合併(merge)

1
2
git merge [branch/commit] # 將指定的版本合併到目前所在的分支
# 也就是說,只有目前的分支會被影響!

產生衝突時:先用git diff/status查看
衝突的地方為如下格式

1
2
3
4
5
6
7
8
9
<<<<<<<<
...
//version1 code
...
========
...
//version2 code
...
>>>>>>>>

分別是兩個版本的差異。
將其修改後即可提交

  • git diff --ours/--theirs 可以查看原版本和衝突版本的差異(ours→合併的, theirs→被合併的)
  • git diff 用在衝突檔案時只會顯示衝突的部分,而不會顯示只有一邊修改的部分

Git 修改版本

Git rebase:移動提交

可進行重新排序、編輯、移除、擠壓、拆散提交

  • 優點
    • 可以不產生分支線和額外的merge commit
  • 缺點
    • 等於改變提交記錄,僅適合還沒上傳的local branch
    • commit被移動,所以特徵碼會改變(也就是原本的commit會被取代)
    • commit的形狀會被改變(一條直線)
      • 可用--preserve-merge參數保留形狀
  • 麻煩
    • 移動的commit若屬於多個分支,則每個分支都要重新指定位置

使用時機:想保留額外commit資訊時,可替代merge

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// debug rebase on master
git checkout debug
git rebase master
//alternative command
git rebase master debug

Original:
master A → B→C→D→E
| ↘
debug F→G→H

After rebase:
master A → B→C→D→E
| ↘
debug F→G→H
1
2
3
4
5
6
7
8
git rebase --onto master [commitA] [commitB] #  使用onto可以將從A到B的提交移植到完全不同的分支(master)上
# 在rebase時遇到衝突的話,會提示使用者手動解決衝突(merge)
git rebase --continue # (解決衝突後)繼續rebase
git rebase --skip # 忽略一個原本要rebase的commit
git rebase --abort # 取消rebase

git rebase -i [commit] # interactive mode
# pick: commit, squash: 擠壓到前一個commit, edit: 停在此commit提交前的情形以修改(可以拆成多次commit) …

修改一個分支的歷史–使用rebase
git rebase -i [previous commit in the same branch]

merge還是rebase?
需要保留樹狀記錄就用merge,反之用rebase

amend:修改上一次的commit

  • 這一次的提交會合併到最後一次的提交
  • 若在最後一次提交後沒有做過任何修改,那麼唯一會更動的是提交訊息
  • 使用時機
    • commit message的修改
    • 發現一些小錯誤
      - 忘記add/多add檔案 的時候
1
2
3
4
5
git add <filename> # 修改錯誤後加入暫存區
git commit --amend # 將此修改加入最後一次的提交

git commit --amend <file1> <file2> ... # 直接將指定檔案加入上一次commit
git commit --amend --reset-author # 將上次commit的作者資訊更新

reset:移動至指定版本

將HEAD視為指標,平常branch的HEAD是指向最新的一次提交。但git reset可以移動HEAD到指定的提交版本
(checkout, commit 等指令都會改變HEAD指標的位置)

  • HEAD = 目前所在的版本的指標,可用於[commit]
    • git reset HEAD = git reset 目前commit
1
2
3
4
5
git reset [commit] [--hard/--soft/--mixed] # 還原到指定提交的狀態
--soft 只移動HEAD指針,其他不變
--mixed 暫存區也被改變,但工作目錄不變 (預設值,暫存區清空)
--hard 暫存區和工作目錄都被改變(目前的目錄中,所有檔案的修改會消失)
git reset HEAD filename # 將檔案從staging area移除 (= 將暫存區的這個檔案還原到HEAD時的狀態)
  • soft
    • reset後工作目錄中的檔案不變,只有版本改變
    • 從[commit]到最新一次提交的所有變更都會放在staging area
      • git reset --soft後直接git commit,則最新的版本會和reset前的版本一模一樣(commit不一定一樣,因為我們是一次commit所有變更)
  • mixed
    • reset後工作目錄中的檔案不變,只有版本改變
    • staging area不變
      • 要自己手動add
  • hard
    • 工作目錄中的檔案會被改變成[commit]時的樣子
      • 即回到[commit],而且是乾淨的狀態
    • 通常是出了嚴重問題後的解決方法

Whats the difference between git reset –mixed, –soft, and –hard?

revert:新增「還原」的提交

新增一筆commit,做還原的變更。等同於還原到指定版本
使用時機: 你的送交已經上傳,別人有相同的一份記錄,所以不適合用git rebase, git reset, git commit --amend等方法修改版本記錄

1
git revert [commit]

Git比較

1
2
3
4
5
6
7
8
9
git diff [commit] # 比較指定版本和當前目錄的差別(不包含staging area)(預設為HEAD)
git diff # 比較目前版本與工作目錄的不同([commit]=HEAD)
git diff --cached/--staged [commit] # 比較暫存區(staging area)與指定版本(預設是HEAD)的不同
git diff [commit] [commit] # 比較兩個版本的差別
git diff [...] [filename] # 比較特定檔案差異
git diff -S[string] # 只列出變更中,有包含string字串的差異
git diff --stat # 差異資訊(只顯示檔名和行數)
git diff -w # 不將空白視為變更
git diff [version1]:[file1] [version2]:[file2] # 比較兩個版本的指定檔案

Git記錄

git log 可以用來查詢提交的歷史記錄及其細節,有很多參數可以用,建議用GUI(gitk, git gui, git instaweb)比較清楚

1
2
3
4
5
6
7
8
9
10
11
12
13
git log [version] # 列出指定版本的記錄(也就是找其祖先)
git log [commitA]..[commitB] # 列出從A到B的提交(不含A, 含B)
git log ^[commitA] # A之後的提交(不列出A之前的提交,不含A)
git log -[num] # 最近[num]筆的提交紀錄
git log --no-merges # 不顯示合併的提交
git log --graph # 顯示樹狀結構圖,GUI的替代品
git log --stat # 列出修改行數
git log --author [username] # 指定使用者的提交
git log --since="2 weeks ago" # 近2週內
git log --follow [filename] # 列出包含該檔案變動的提交(包含改名前)

git show [version] # 列出指定版本的最新提交的詳細修改內容
git show [version]:[filename] # 顯示該提交的指定檔案

可以將版本變動視為一顆樹

  • 列出指定版本的記錄 = 找其祖先
  • 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
2
3
4
5
git log --pretty [...] # 調整記錄的格式
git log --pretty=oneline # 一個commit一行
git log --pretty=short # 簡短
git log --pretty=format:'%h was %an, %ar, message: %s'
# %s: 提交訊息, %h: 辨識碼(hash), %an: 作者, %ar: 時間

尋找提交: bisect, blame

找尋發生問題的提交

1
2
3
4
5
6
7
git bisect start # 用二分搜尋法,git會依照你提供的資訊改變目前的工作目錄,以方便測試
# 註:原本工作目錄的修改會遺失,所以可先commit
git bisect good [commit] # 告知到此commit之前是好的
git bisect bad [commit] # 告知此commit仍有錯誤(通常用HEAD)
git bisect log # 顯示之前提供的good, bad記錄
git bisect visualize # 顯示在搜尋範圍的commit
git bisect reset # 找到錯誤的提交後,還原至原本的分支

用檔案尋找提交

1
2
git blame [filename] # 顯示檔案的每一行最後是被哪個commit修改的
git log -S [string] [filename] # 顯示變動包含string字串的commit
1
git reflog [Reference] # 參照(預設為HEAD)改變的歷史
  • checkout, commit, reset, rebase … 都會修改HEAD
  • 命名: HEAD@{N}代表第前N個提交, HEAD = HEAD@{0}
  • 出問題的時候可以快速reset

Git stash

把目前工作區(working directory)的東西丟到一個stack(stash)中,等之後在回來拿。

使用時機

  1. 你不得不修改一個緊急Bug,你可以先把目前工作目錄的變更丟到stash,這時候你的工作目錄和上次剛提交內容的狀況一樣,等到修完Bug後再把stash中剛剛做到一半的東西還原以繼續
  2. 今天的工作時間結束後,還有未完成的部分
  3. pull到一個不乾淨的樹而不用merge
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
git stash                # 將目前把目前工作區的修改丟到stash裡
git stash save [message] # 將目前把目前工作區的修改丟到stash裡
git stash pop # 取出最新放入的一筆修改,並從stash中移除(若有衝突時會保留)
git stash apply # 將最新投入的一筆修改套用在工作目錄上
git stash drop # 從stash中移除最新的一筆修改
git stash show [stashName] # 列出某一筆stash的修改內容
# 命名:最新的為 stash@{0}, 以此類推
git stash show -p [stashName] # 列出某一筆stash的詳細變更
# 命名:最新的為 stash@{0}, 以此類推
git stash list # 列出所有在stash的修改
git stash [file] # 將檔案丟進stash
git stash clear # 清空stash
git stash branch [branchName] # 用最新一筆stash新增branch
git stash -p # 挑選要暫存的修改
git stash --includev # 同時暫存尚未在版本控制中的檔案(也就是從來沒被add過的檔案)

不常用的指令

git remote 遠端容器管理

1
2
3
4
5
6
7
8
9
10
11
12
git remote # 列出預設遠端容器的名稱
git remote show # 顯示預設遠端容器的詳細資料,如遠端連結
git remote add [remote-name] [remote-url] # 加入一個遠端容器
git remote update # 更新Repository的所有branch
git remote rm [branch] # 刪除遠端分支

git push --tags # 上傳標籤資訊(預設不會)
git push [remote] [branch] # 上傳分支
git push [remote] [branchname]:[uploadname] # 上傳分支並在遠端容器中重新命名
git push [remote] --delete [branch] # 刪除遠端分支

git branch --set-upstream [branch] [remote] # 連結一個本地分支和遠端分支

Git clean

清除不包含在版本控制中的檔案(被gitignore忽略的檔案不會被清除)

1
2
3
4
git clean -n #  列出會被清掉的檔案
git clean -f # 執行清除
git clean -d # 清除資料夾
git clean -x # 連 gitignore 裡列的檔案也清掉

Git垃圾回收

git儲存的記錄很多: stash, reflog, log …

gc(garbage collection):收集所有沒有被指到(reference)且已存在一段時間的物件刪除

1
2
3
# 整理前和整理後的差異, 可由: git count-objects 看出
git gc
git gc --auto # 執行 auto gc 命令

參考資料

  • 版本控制:使用Git
  • Git scm
  • Git情境劇系列
  • Git初學筆記
  • ihower: 還沒push前可以做的(壞)事
  • git: get ready to use it