【CVS】Git之無盡探索

本文是關於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倉庫中文件大概有如下幾種狀態,並可通過相應的操作切換:

git-file-status

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"

案例分支狀態:
rebase_onto

其他命令(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 遊戲通關流程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章