本文是關於Git探索的一篇文章,闡述了Git的大部分命令和使用方式,並列舉了幾個典型的使用場景以供參考和體會。
多host環境
clone,remote,fetch,pull,push
gitignore
add
stash
commit
status,log,diff,blame,grep
branch,checkout,merge
tag
cherry-pick
checkout,reset,revert,rm
rebase
mv,repack,bisect
Other Case
對於Git這個分佈式的VCS,從鏈表的角度來看待是最容易理解的:
一次commit
相當於添加一個節點,節點由hash
標識,內容就是所做修改的索引;每個分支都是一條鏈,有一個指向頭結點的指針HEAD
。
Git配置
下載地址:點我跳轉下載
添加環境變量:
以Windows爲例:
%GIT_HOME%\bin;%GIT_HOME%\usr\bin;
生成SSH密鑰:
ssh-keygen -t rsa -C "[email protected]"
會提示輸入路徑,建議:
/c/User/用戶名/.ssh/id_rsa_abc
添加多Host環境
添加多host環境,對於在公司使用自己的機器是很有必要的,公私分明嘛~
文件位置/C/User/用戶名/.ssh/config
,沒有就新建一個:內容如下
# 配置git.oschina.net
Host git.oschina.net
HostName git.oschina.net
IdentityFile C:\\Users\\用戶名\\.ssh\\id_rsa_oschina
PreferredAuthentications publickey
User oschinaUserName
# 配置github.com
Host github.com
HostName github.com
IdentityFile C:\\Users\\用戶名\\.ssh\\id_rsa_github
PreferredAuthentications publickey
User githubUserName
測試連接:
連通後會生成 /C/User/用戶名/.ssh/known_hosts
文件:
ssh -T [email protected]
ssh -T git.oschina.net
這樣就可以把公鑰 id_rsa_xxx
添加到git服務器上的 SSH Keys
中去了。
最後可按需要,順帶配置用戶信息:
全局配置:
git config --global user.name 姓名
git config --global user.email 郵箱
爲當前倉庫設置用戶信息:
git config --local user.name 姓名
git config --local user.email 郵箱
一般倉庫可以採用SSH方式關聯,但必須以HTTP/HTTPS方式關聯遠程倉庫時,可以考慮記住密碼:在用戶目錄下生成文件.git-credential
記錄用戶名密碼的信息。
# 默認會話時長(據說是15min):
git config --local credential.helper cache # linux/mac
git config --local credential.helper wincred # windows
# 自己定義會話時間:
git config credential.helper 'cache --timeout=3600' # linux/mac
git config credential.helper 'wincred --timeout=3600' # windows
# 不過期會話時間:
git config --local credential.helper store
至此,Git配置結束。
Git操作
Git相關的命令如果記不太清楚可以使用提示:
git --help
git 具體某條命令 --help
倉庫(clone,remote,fetch,pull,push)
在當前目錄初始化一個倉庫:
git init
克隆一個遠程倉庫:
git clone <URL> [--branch <branch> --depth=1] [./dir]
# 準備後續免密push的操作(字符需要Escape方式編碼,git remote -v可看到密碼)
git clone http://賬號:密碼@git.ops.test.com.cn/root/puppet.git
添加子module,這樣子文件夾會做文件處理:
git submodule add <URL> <./submodule>
遠程倉庫關聯:
git remote [-v]
git remote add <name> <URL>
git remote set-url <name> <URL>
git remote remove <name>
拉取本地倉庫沒有的內容(新增分支,遠程分支修改等):
git fetch [origin <branch>]
拉取本分支的更新併合並:pull = fetch + merge
,默認拉去所有合併到對應分支,當指定遠程分支時默認合併到當前分支:
git pull origin <remote_branch>[:<local_branch>]
準備推送本分支的修改到遠程倉庫前可以先查看本地分支和遠程分支的關聯情況:
git branch -vv
已經關聯遠程分支可以直接push:
git push
遠程分支不存在:
git push [-u] origin <local_branch>:<remote_branch>
# -u 表示添加關聯
只關聯不push:
git branch --set-upstream-to <origin/remote_branch> <local_branch>
忽略追蹤(.gitignore)
編輯倉庫中的 .gitignore 文件,內容格式如下
vim .gitignore
# 忽略目錄
build/
# 忽略yml後綴文件
*.yml
# 不忽略a.yml
!a.yml
gitignore
只能忽略untracked
文件,對於stracked
文件需要刪除索引:
git rm --cached [-r] <file>
Git中的文件狀態
Git倉庫中文件大概有如下幾種狀態,並可通過相應的操作切換:
staging area(add)
-
保存(new-untracked)和(modified),不包括(remove-untracked):
git add .
-
保存(remove-untracked)和(modified),不包括(new-untracked):
git add -u
-
保存所有變化:
git add -A
-
部分提交,後續會有選擇,捨棄的修改將留在working directory:
git add -p filename # y - 存儲這個hunk # n - 不存儲這個hunk # q - 離開,不存儲這個hunk和其他hunk # a - 存儲這個hunk和這個文件後面的hunk # d - 不存儲這個hunk和這個文件後面的hunk # g - 選擇一個hunk # / - 通過正則查找hunk # j - 不確定是否存儲這個hunk,看下一個不確定的hunk # J - 不確定是否存儲這個hunk,看下一個hunk # k - 不確定是否存儲這個hunk,看上一個不確定的hunk # K -不確定是否存儲這個hunk,看上一個hunk # s - 把當前的hunk分成更小的hunks # e - 手動編輯當前的hunk # ? - 輸出幫助信息
暫存(stash)
這樣可以保持分支的工作現場乾淨,操作對象是整個staging area
:
# push
git stash
# pick
git stash apply
# pop
git stash drop
# pop & pick
git stash pop
repository(commit)
提交:
git commit [filename] -m "annotation"
提交working directory
,相當於:git add -u + git commit -m "boom~"
:
git commit -am "boom~"
撤銷HEAD並提交staging area
補充到HEAD提交:
git commit -amend -m "make up"
查看(status,log,diff,blame,grep)
查看staging area
和 working directory
的狀態:
git status
查看提交記錄:
git log --online --graph --all
# all: 所有分支(默認當前)
# oneline: 簡易hash, --pretty=oneline
# graph: 畫出合併切出圖
操作記錄,含reset
git reflog --oneline
文件對比:
-
對比 (
staging area
|repository
) 和working directory
:git diff [branch1 branch2] <file> # 加branch會對比兩個分支中的文件
-
對比
repository
和working directory
:git diff HEAD <file>
-
對比
repository
和staging area
:git diff --cached <file>
查看修改人:
git blame <file>
查找內容:
git grep '[123]\{1,\}'
分支(branch,checkout,merge)
分支可以理解爲鏈表,鏈表中的元素是commit
。
查看分支:
git branch [-a]
# -a 包含遠程分支
切出分支:
git checkout [-b <new_branch>] origin/release|$hash$|tags/v1.0
合併develop
到master
:
-
合併前檢查是否存在衝突:
git diff master develop --stat
-
開始合併,並解決衝突:
(master)$ git merge [--squash] develop # --squash 融合提交記錄,參見rebase # 對於無法自動合併的衝突需要手動處理 <<<<<< HEAD # 當前更改 ====== # 傳入的更改 >>>>>> dev
-
提交前檢查衝突是否處理完畢:
git diff --check # 如果沒問題就可以 commit -am 了
-
如果衝突太多太麻煩,想放棄解決了,可以終止:
git merge --abort
刪除分支:
# -r表示一併刪除origin裏面分支,這只是刪除了一個本地追蹤,下次不會再fetch它:
git branch -d [-r] <branch_name>
# 刪除遠程的分支還需要push過去刪除
git push origin -d <remote_branch>
標籤(tag)
Fetch倉庫TAG:
git fetch origin --tags
查看TAG:
git tag
git tag -l 'v1.0.*'
git show v1.0.0
在當前分支HEAD打TAG:
git tag v0.1
# 在某個commit上打tag,並加附註
git tag -a v0.2 -m "version 0.1 released" $hash$
push tag:
git push origin v1.0
# push 本地所有tag
git push origin --tags
刪除tag
# 刪除本地tag
git tag -d v1.0
# 刪除遠程tag
git push origin --delete tag v1.0
cherry-pick
使用cherry-pick
可以把其他分支的一部分commit提交到當前分支:
git log --all --online
# cherry-pick
git cherry-pick [-n] $hash$ $hash$
#-n,表示先不commit,把commits保留到staging area
撤銷(checkout,reset,revert,rm)
checkout:用staging area
覆蓋work directory
:
git checkout -- <file>
reset:用repository
覆蓋staging area
,若working directory
有修改,則丟棄staging area
:
git reset HEAD <file>
- soft, 把
repository
的($hash$,HEAD]切出到staging area
:git reset --soft HEAD~1|$hash$
- mixed, 把
repository
的($hash$,HEAD]切出到working directory
:git reset --mixed HEAD~1|$hash$
- hard, 把
repository
的($hash$,HEAD]拋棄:git reset --hard HEAD~1|$hash$
案例:找回reset掉的內容:
- 查看操作記錄:
git reflog --oneline
- 找到某次reset操作(reset記錄會以reset前的HEAD做hash標識),把
HEAD
指向它:git reset --hard $reset_hash$ # 或者 git rebase --onto HEAD HEAD $reset_hash$
revert:提交逆修改,HEAD
繼續前進,而reset
讓HEAD
回退:
git revert HEAD~1|$hash$
rm --cached:刪除追蹤索引,不能操作work directory
的文件;該操作會把文件從staging area|repository
移到work directory
並置爲untracked
;此外如果該文件曾commit
過,會在staging area
生成一個deleted:<file>
記錄:
git rm --cached [-r] <file>
rm:刪除repository
中的文件,並在staging area
添加一個deleted:<file>
:
git rm <file>
rm -f:刪除untracked文件和文件夾:
git rm <file> -f [-r] [-q]
# -r 表示遞歸
# -q 無刪除回顯
clean -fd:清理工作區,遞歸刪除所有untracked
的文件和文件夾:
git clean -fd
好了,重頭戲來了。
衍合(rebase)
這個翻譯其實有些抽象,rebase
的作用是對一段線性提交做一些操作,操作後分化出一個新的分支。
這裏我對提交的理解是:若干個commit以hash標識成點,形成一個commit鏈;其中HEAD
是指針變量,分支名
是指針常量(鏈表頭結點,或者理解成一維數組名)。
下面的語法中,endpoint
是可選的,默認爲HEAD
。如果發現endpoint
是常量指針
(即分支名),則分化出的分支自動應用到該分支並checkout
過去。
語法:
git rebase [-i] [--onto] [base_branch] startpoint [endpoint]
# 其中線性提交段是 (startpoint, endpoint],前開後閉
rebase合入分支最新修改(merge按時間排序commit,rebase根據分支排序),reabase把衝突處理交給提MR的人處理,提交記錄比merge更乾淨:
git rebase origin/master [HEAD]
# solve conflicts
git add <conflict_files>
git rebase --continue
編輯分支上的一段線性提交(reword,squash,reorder,drop):
git rebase -i startpoint [endpoint]
# p, pick = 使用提交
# r, reword = 使用提交,但修改提交說明
# e, edit = 使用提交,但停止以便進行提交修補
# s, squash = 使用提交,但和前一個版本融合
# f, fixup = 類似於 "squash",但丟棄提交說明日誌
# x, exec = 使用 shell 運行命令(此行剩餘部分)
# d, drop = 刪除提交
下面分析4個rebase
典型案例,假設當前分支狀態如下圖所示:
[外鏈圖片轉存失敗(img-odPtFdEq-1562834637827)(https://i.loli.net/2019/03/14/5c8a3fbc6f7d9.png)]
案例1-並集:next基於master;此時得知master有新修改,next需要同步master的最新修改。
git rebase master next
# 命令完成後自動切到next分支
# 此時next = [master + next#HEAD]
案例2-差集:topic基於next,next基於master;此時得知next有重大bug,要求topic中屬於next部分的提交都得去掉。
# 差集計算:topic = master + (topic - next)
git rebase --onto master next topic
# 多段差集:topic = master + (topic#HEAD~4, topic]
git rebase --onto master HEAD~4 topic
案例3-修改commit:編輯(reword,reorder,edit,drop)一段Commit:
git rebase -i HEAD~4 HEAD
案例4-融合commit:融合(squash)操作也屬於修改,但這裏單獨拿出來示例,因爲它可以多人合作完成,也可以個人獨立完成:
- 方式一:由A同學解衝突。 A同學利用squash合併commit,B同學直接合並。
# A同學 git rebase -i next topic # B同學 (next)$ git merge topic
- 方式二:由B同學解衝突。 B同學自己融合A同學提交的MR。
(next)$ git merge --squash topic
git commit -m "merged topic to next"
其他命令(mv,repack,bisect)
# 重命名/移動staged的文件後自動處於staged
git mv oldfile newfile
git mv file folder
# repack 把鬆散對象打包以提高git運行效率
git repack -d
# 二分查找有問題的提交
git bisect start master f608824
git bisect run make test
# test腳本爲判斷是否有問題的依據
line = gets
exit 1 if line != "15\n"
一些技巧
-
淺克隆:對於比較大的倉庫,本地不想要那麼多無關的分支,無關的歷史記錄,可以考慮淺克隆。
# 以淺克隆的方式獲取庫 git clone <URL> --branch <master> --depth=1 # 添加origin中的分支 git remote set-branches origin <branch_new1> <branch_new2> # 以淺克隆的方式fetch origin中的分支 git fetch --depth 1 origin [<branch_new1>] # 深化origin 中的克隆深度爲 N git fetch origin --deepen N # 刪除分支 git branch -rd <branch_name> git push origin -d <branch_name>
-
拓撲序:對於提交日誌,默認是按commit時間排序,拓撲序可以把相關聯的多個提交放在一起顯示。
# 默認按時間排序 git log --date-order --oneline # 指定拓撲排序 git log --topo-order --oneline
-
二分查找:有時候無法從final版本代碼定位問題,可能需要回滾代碼定位。可以考慮log(N)複雜度的二分查找。
# 開始二分查找(reset的時候會回到這個HEAD) git bisect start # 告訴git,當前的HEAD提交是有問題的 git bisect bad # 告訴git,某個提交是OK的 git bisect good $hash$ # 這時git會reset到中間的commit,此時編譯驗證,並告知結果 git bisect <bad/good> # 這樣git會不斷劃分區間查找,最終會告訴你哪個commit有問題。找到後退出查找模式 git bisect reset
以上。
此外,如題,筆者也在不停探索,如果有錯誤歡迎指出斧正~
最後感謝這個遊戲:Githug
以及這個通關攻略:「Githug」Git 遊戲通關流程
還有這個在線練習平臺:Git在線練習