本文是關於Git探索的一篇文章,闡述了Git的大部分命令和使用方式,並列舉了幾個典型的使用場景以供參考和體會。
那大約是在1年前,筆者剛接觸Git,迫於各種條件,上傳了一篇低質量的Git筆記,時隔一年,再翻看很是感慨~~~,
以上,是便是寫這篇文章的緣由。
對於Git這個分佈式的VCS,從鏈表的角度來看待是最容易理解的:
一次commit
相當於添加一個節點,節點由hash
標識,內容就是所做修改的索引;每個分支都是一條鏈,有一個指向頭結點的指針HEAD
。
以下是本文以命令方式列出的目錄,便於查詢:
多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
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 郵箱
至此,Git配置結束。
Git操作
Git相關的命令如果記不太清楚可以使用提示:
git --help
git 具體某條命令 --help
倉庫(clone,remote,fetch,pull,push)
在當前目錄初始化一個倉庫:
git init
克隆一個遠程倉庫:
git clone <URL> [./dir]
添加子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
拉取本分支的更新併合並:pull = fetch + merge
git pull origin <remote_branch>:<local_branch>
推送本分支的修改到遠程
已經關聯遠程分支:
git push
遠程分支不存在:
git push origin <local_branch>:<remote_branch>
遠程分支已存在,但未關聯:
git push -u origin <local_branch>:<remote_branch>
手動關聯分支,比較少用:
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
# 查看respository
# 提交記錄
git log --online --graph --all
# all: 所有分支(默認當前)
# oneline: 簡易hash, --pretty=oneline
# graph: 畫出合併切出圖
# 操作記錄,含reset
git reflog
# 文件對比
# 對比 (staging area | repository) 和 working directory
git diff <file>
# 對比 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
# 查看分支,加a表示包含遠程分支
git branch [-a]
# 切出分支
git checkout [-b <new_branch>] origin/release|$hash$|tags/v1.0
# 合併develop到master,參數squash表示融合commit詳見rebase
(master)$ git merge [--squash] develop
# 對於無法自動合併的衝突需要手動處理
<<<<<< HEAD
# 當前更改
======
# 傳入的更改
>>>>>> dev
# 處理完成後需要 add & commit 提交到respository
# 刪除分支,-r表示刪除遠程分支
git branch -d [-r] <branch_name>
git push origin -d <remote_branch>
標籤(tag)
# 查看當前分支的tag標籤
git tag
git tag -l 'v1.0.*'
# 爲當前分支的HEAD打tag
git tag v1.0
# push tag
git push origin v1.0
git push --tags orgin master
cherry-pick
使用cherry-pick
可以把分支A的一部分commit提交到分支B
# 切到分支B
git checkout branch_b
# 查看所有分支的提交日誌
git log --all --online
# cherry-pick
git cherry-pick [-n] $hash$ $hash$
#-n,表示先不commit,把commits保留到staging area
撤銷(checkout,reset,revert,rm)
# 用staging area覆蓋work directory
git checkout <file>
# 用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
# 找到某次reset操作,reset記錄會以reset前的HEAD做hash標識
git reset --hard $reset_hash$
# 或者
git rebase --onto HEAD HEAD $reset_hash$
# reset讓HEAD回退,revert相當於提交逆修改,HEAD繼續前進
git revert HEAD~1|$hash$
# 刪除追蹤索引,不會影響work directory的文件
# 文件從staging area|repository 移到work directory並置爲untracked
# 若commit過,會在staging area生成一個deleted:<file>記錄
git rm --cached [-r] <file>
# 刪除repository中的文件,並在staging area添加一個deleted:<file>
git rm <file>
# 刪除untracked文件和文件夾, -r 表示遞歸,-q 無刪除回顯
git rm <file> -f [-r] [-q]
# 遞歸刪除所有untracked的folder和file
git clean -fd
好了,重頭戲來了。
衍合(rebase)
這個翻譯其實有些抽象,rebase
的作用是對一段線性提交做一些操作,操作後分化出一個新的分支。
這裏我對提交的理解是:若干個commit以hash標識成點,形成一個commit鏈;其中HEAD
是指針變量,分支名
是指針常量(鏈表頭結點,或者理解成一維數組名)。
下面的語法中,endpoint
是可選的,默認爲HEAD
。如果發現endpoint
是常量指針
(即分支名),則分化出的分支自動應用到該分支並checkout
過去。
# 語法,其中線性提交段是 (startpoint, endpoint],前開後閉
git rebase [-i] [--onto] [base_branch] startpoint [endpoint]
# 加入分支最新修改
# merge按時間排序commit,rebase根據分支排序
# reabase把衝突處理交給提MR的人處理,提交記錄比merge更乾淨(少一條merge記錄)
git rebase origin/master [HEAD]
# solve conflicts
git add <conflict_files>
git rebase --continue
# rebase:reword,squash,reorder commits
git rebase -i startpoint [endpoint]
# p, pick = 使用提交
# r, reword = 使用提交,但修改提交說明
# e, edit = 使用提交,但停止以便進行提交修補
# s, squash = 使用提交,但和前一個版本融合
# f, fixup = 類似於 "squash",但丟棄提交說明日誌
# x, exec = 使用 shell 運行命令(此行剩餘部分)
# d, drop = 刪除提交
# ========================================================
# ------- 下面分析4個案例,假設當前分支狀態如下圖所示 --------
# ========================================================
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# 案例1-並集:next基於master,此時得知master有新修改,next需要同步master
# $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
# topic = master + topic
git rebase master topic
# 無論當前在哪個分支完成後自動切換到topic
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# 案例2-差集:topic基於next,next基於master,此時得知next有重大bug,topic只能基於master修改
# $$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
# 差集計算:topic = (topic - next) + master
git rebase --onto master next topic
# 多段差集:topic = (topic - next - other) + master
git checkout topic
# 這裏假設(master,HEAD~4]都是要排除出去的
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
git rebase -i next topic
# B同學合topic到next
(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"
以上。
此外,如題,筆者也在不停探索,如果有錯誤歡迎指出斧正~
最後感謝這個遊戲:Githug
以及這個通關攻略:「Githug」Git 遊戲通關流程