簡介
Git 作為分布式版本控制系統,基于去中心化的設計思想,在每個分布式節點上都儲存有完整的版本,降低了對中心倉庫的依賴,增加了版本安全性。
Git 的使用過程中,并不是必須設定中心倉庫,各個節點之間完全可以互相推送和拉取更新内容。不過考慮到互相的通信問題和團隊協作,是以一般會選擇一個 24 小時運作的主機作為中心倉庫,以此來擷取和交換更新内容,例如 GitHub 則提供了這樣的托管服務。
三種狀态
Git 對檔案的跟蹤管理存在三個階段,工作區、暫存區和分支:
- 工作區就是實際操作的檔案目錄;
- 暫存區是一個索引檔案,記錄已跟蹤檔案的目錄樹,儲存檔案的時間戳、大小等易比較的資訊;
- 分支與暫存區類似,也儲存有檔案的目錄樹,用于記錄檔案系統的快照。
暫存區和分支都依賴 .git/objects 目錄,該目錄稱為對象庫,記錄了真實檔案系統的快照。暫存區隻是一個 index 檔案,存放在 .git 目錄中。當工作區檔案發生修改時,将工作區中檔案的時間戳和大小,與 index 中記錄的檔案時間戳和大小進行比較,可以快速的判斷工作區檔案是否發生了更改,然後再具體的進行工作區檔案内容與 objects 中記錄檔案内容進行比較。
暫存區的作用更像是工作區和分支之間的一個緩沖區域,或者稱之為 “預送出檔案改動到分支” 的區域。暫存區的存在,允許我們在工作區和暫存區之間友善的進行檔案修改的添加與撤回,以及對修改内容的分部分送出。例如當工作中需要修改兩部分内容,目前隻完成了一部分的修改時,可以将完成的這部分添加到暫存區,接着在工作區繼續修改另一部分,需要送出到分支上時,則送出緩存區的内容,未修改完成的部分可以繼續修改。
因為分支更重要的作用是維護檔案系統的版本序列,與遠端倉庫的通信,是以如果沒有暫存區的存在,那麼我們的檔案修改則隻能頻繁的與分支打交道,屆時版本序列将變得複雜且不易于維護,且每個版本記錄儲存的是檔案系統快照,若頻繁的進行檔案修改和送出,則倉庫大小将快速膨脹。
Git 使用
Git 安裝
下載下傳安裝 Git:|
Mac OS X|
Windows Linux/Unix官網下載下傳速度較慢,這裡提供一個 Windows 版本的下載下傳連結: Git for Windows
Git 配置
Git 安裝之後,首先進行使用者名和郵箱的配置,配置資訊會記錄到每次的送出記錄中,并且當推送更新到 GitHub 上的項目時,會與 GitHub 賬号進行比對,在曆史送出記錄中會顯示出使用者頭像,并且點亮送出次數。
通過
git config
指令進行使用者名和郵箱配置時存在三種級别:
-
:目前機器上的配置,面向所有使用者;--system
-
:目前使用者的配置,面向目前使用者的所有倉庫;--global
-
:目前倉庫的配置,隻對目前倉庫生效。--local
配置使用的優先級為:
local > global > system
,即範圍小的配置會覆寫範圍大的配置。
配置檔案所在位置為:
system:etc/gitconfig
global:~/.gitconfig
windows 系統中的位置為:
local:.git/config
:git 工具安裝位置的
system
etc/gitconfig
:目前使用者主目錄下的
global
.gitconfig
local:.git/config
配置方式為:
git config --global user.name "abc"
git config --global user.email "[email protected]"
查詢配置方式為:
git config --global --list
一般隻需要對目前使用者進行配置即可,即使用
--global
級别,如果某個倉庫有特殊安排,則可以在具體的倉庫級别進行配置即可。不明确指定級别的話,預設設定的為
--local
級别。
對于版本号在 2.0.0 以上的
git
,提供了一個簡單的查詢配置檔案目錄的指令:
git config --list --show-origin
建立倉庫
建立倉庫有兩種方式,選擇本地工作目錄初始化為倉庫,或者從遠端倉庫克隆到本地。克隆倉庫的方式在下面的内容中再進行講述,這裡首先使用初始化本地工作目錄為倉庫的方式。
進入標明目錄,執行如下指令即可:
git init
指令執行之後,在目前目錄中會生成 .git 檔案夾,此時目前目錄即為一個“嶄新”的倉庫。
之是以用“嶄新”來描述倉庫,是因為在執行倉庫初始化指令後,無論目前目錄下是否存在檔案,.git 目錄生成後都不存在 index 檔案,objects 目錄下的檔案夾中也沒有具體的檔案生成。即此時暫存區和分支都為空,隻有向倉庫中添加檔案後,才會生成暫存區 index 檔案,objects 目錄下才會生成檔案。
記錄檔案/更新
首先要明确一點,工作目錄中的檔案隻有兩種狀态,已跟蹤和未跟蹤,也就是已經納入版本記錄,和未納入版本記錄。使用上面的
git init
指令生成倉庫時,工作目錄中的所有檔案都是未跟蹤狀态,從遠端倉庫克隆生成本地倉庫時,工作目錄中的所有檔案都是已跟蹤狀态。
對于未跟蹤檔案,則無所謂檔案是否發生了修改,因為不會跟蹤記錄該檔案的狀态。對于已跟蹤檔案,則會檢測記錄該檔案是否發生了修改。
git add <file>
git add
指令面向兩種對象,一個是将未跟蹤檔案納入暫存區,進行跟蹤記錄;另外一個是将已跟蹤檔案的修改,添加到暫存區,記錄檔案的更新。當指令後跟着一個目錄時,則遞歸添加目錄及目錄下所有檔案。
git status
git status
指令用于檢視檔案的狀态,未跟蹤檔案隻有一種狀态:檔案未跟蹤,或者稱為未納入暫存區,狀态顯示為
Untracked files
。已跟蹤檔案有兩種狀态:一是納入暫存區,等待送出到版本庫,狀态顯示為
Changes to be committed
;二是檔案發生了修改,且修改部分尚未添加到暫存區,狀态顯示為
Changes not staged for commit
。
删除檔案
git add
指令用于向暫存區添加檔案,或記錄檔案的更新内容。若執行此指令後,發現該檔案并不需要跟蹤記錄,或者已經添加到暫存區的檔案更新内容需要取消,
git
提供了相應的撤回操作指令。
git rm --cache <file>
git rm --cache <file>
指令用于從暫存區移除對檔案的跟蹤。
git rm <file>
git rm <file>
指令不僅從暫存區移除對檔案的跟蹤,并且從工作目錄中也删除了該檔案。
指令和
git rm --cache <file>
指令都存在一個
git rm <file>
選項,用于強制删除。當已跟蹤的檔案發生了修改,并且修改未添加到暫存區時,則需要
-f
指令才能從暫存區移除對檔案的跟蹤;當已跟蹤的檔案發生了修改,并且修改已經添加到暫存區時,則需要
git rm --cache -f <file>
指令才能同時從暫存區和工作目錄中删除檔案。
git rm -f <file>
删除更新
這裡的更新有兩種情況:
- 工作目錄下已跟蹤檔案進行了更新,且更新内容尚未送出到暫存區;
- 工作目錄下已跟蹤檔案進行了更新,且更新内容已經送出到暫存區。
git checkout -- <file>
git checkout -- <file>
指令用于撤銷第一種情況下的更新内容,可以了解為拿暫存區的檔案内容替換掉工作區的檔案内容。
git reset HEAD <file>
git reset HEAD <file>
指令用于撤銷第二種情況下的更新内容,可以了解為拿上個版本的系統快照替換掉暫存區的檔案内容。
git checkout HEAD <file>
git checkout HEAD <file>
指令能夠同時撤銷工作區和暫存區的更新内容,可以了解為拿上個版本的系統快照替換掉工作區和暫存區的檔案内容。
因為容易引起工作内容丢失,是以使用 git checkout HEAD <file>
指令時需要注意。
送出檔案
工作中對每一個檔案修改完成後,将修改内容依次添加到暫存區,當完成所有修改後,則送出暫存區檔案到目前分支上。
git commit
git commit
指令用于送出暫存區檔案到目前分支上,執行該指令後會打開文本編輯器提示輸入送出資訊。
可以直接執行 git commit -m 'commit message'
指令,将送出資訊寫入指令中。
git log
git log
指令用于檢視送出曆史,每個送出都會記錄時間、使用者資訊、輸入的
commit
資訊及
commit
值 ,這裡的
commit
值是一個
SHA1
校驗和,在後續的版本回退中會使用到 。
git reflog
git reflog
指令用于檢視
HEAD
變動曆史,當執行送出、分支切換以及版本回退這類改變
HEAD
指向的操作時,都可以通過該指令查詢出
HEAD
指向的送出值,即
SHA1
校驗和。該指令更多時候用于版本回退時,若想撤銷回退操作,恢複到回退之前的記錄時,通過該指令可以查詢到回退之前的校驗和。
分支切換
分支的使用很廣泛,修改
bug
,或者開發新功能,都可以拉出一個新分支,等功能開發完成并測試通過後,再合并分支内容到主幹分支上。
在
git
的分支使用中,不同的分支實際就是指向各個檔案系統快照的指針,是以在諸多
VCS
中
git
提供了輕量級且高效的分支建立、切換操作。
HEAD
可以了解為一個指針,指向目前通路的分支。
git branch
git branch
指令用于檢視目前的分支情況。
git branch <name>
git branch <name>
指令用于建立新分支。
git branch -d <name>
git branch -d <name>
指令用于删除指定分支。
git checkout <name>
git checkout <name>
指令用于切換到指定分支。
git checkout -b <name>
git checkout -b <name>
指令用于建立分支,并切換到新分支。相當于
git branch <name>
和
git checkout <name>
兩條指令合并到一起。
分支合并與沖突解決
當在功能分支上完成新需求的開發任務後,需要切換回主分支,并将修改内容回合到主分支上,删除該功能分支。
git merge <name>
git merge <name>
指令用于合并指定分支的修改内容到目前分支上。
merge-1
以合并
dev
分支修改内容到
master
分支為例,若
master
分支的指向處于
dev
分支的直接上遊時,如圖
merge-1
所示,此時合并分支速度較快,因為隻需要更改
master
分支的指向即可。
after_merge
此時的合并方式為 Fast-forward
方式,因為隻需要更改分支的指向,是以速度較快,且不會産生新的送出記錄。
merge-2
若
master
分支的指向不處于
dev
的直接上遊,如圖
merge-2
所示,則合并過程需要比較
C3
送出的修改内容與
C4
送出的修改内容。如果兩個送出中不存在對 同一處檔案内容 的修改,則此時可以順利合并修改内容,并産生一次新的合并送出,如下圖中的
C5
;如果兩個送出中存在對 同一處檔案内容 的修改,則此時合并存在沖突,需要手動解決沖突并完成合并送出。
圖所示的合并方式為
merge-2
方式,或者稱之為三路合并方式。因為合并
recursive
與
C3
的送出,需要與公共上遊
C4
相比較,以達到與 同一處檔案内容 相比較的目的,是以該合并方式主要觀察
C2
、
C2
C3
三個檔案系統快照,是以稱為三路合并方式。因為複雜情況下公共上遊并不想圖中所示這麼明顯,可能需要進行多次疊代合并處理方可産生虛拟的公共上遊,是以也稱此方式為
C4
方式。
recursive
git cherry-pick <commitId>
git cherry-pick <commitId>
指令用于合并其他分支上的某次送出到目前分支上。
git cherry-pick <start_commitId>^..<end_commitId>
git cherry-pick <start_commitId>^..<end_commitId>
指令用于将其他分支上從
<start_commitId>
起始到
<end_commitId>
結束的送出合并到目前分支上,包括起始和結束送出。
理想的使用方式是,克隆、修改、送出代碼,不存在任何的
VCS
修改或者沖突解決問題,但這是不可能的,是以實際工作中總會遇到各樣的情形。例如暫時不準備将某個特性分支的開發修改合入主幹,但是又要引入特性分支的某次送出(
bug
修改、配置管理或者安全處理),此時可以使用
bug
來将指定的送出合入主幹。
cherry-pick
關聯遠端倉庫
在團隊協作過程中,經常的場景就是團隊的每位成員都
fork
一份項目代碼到自己的個人庫中,然後在自己的庫裡面做修改,修改完成再合入到團隊的項目代碼庫中。是以我們的本地倉庫一般關聯兩個遠端倉庫,一個是團隊空間的項目代碼,用于拉取最新更新内容;一個是個人庫中的項目代碼,用于推送個人修改内容。
git remote
git remote
指令用于展示目前倉庫關聯的遠端倉庫。
git remote add <name> <address>
git remote add <name> <address>
指令用于為目前倉庫添加關聯的遠端倉庫。
git remote remove <name>
git remote remove <name>
指令用于删除目前倉庫關聯的遠端倉庫。
git fetch <name>
git fetch <name>
指令用于從遠端倉庫拉取最新分支資訊。
指令隻會拉取分支資訊,生成
git fetch <name>
遠端分支,并不會為本地倉庫生成分支。
<remote_name>/<branch_name>
git branch -u <remote_name>/<branch_name>
git branch -u <remote_name>/<branch_name>
指令用于将目前分支與遠端分支進行關聯,即建立關聯關系。
git checkout --track <remote_name>/<branch_name>
git checkout --track <remote_name>/<branch_name>
指令用于在本地倉庫上建立分支,并與遠端分支進行關聯。指令
git checkout -b <branch_name> <remote_name>/<branch_name>
提供同樣建立關聯分支的作用。
git push <remote_name> <branch_name>
git push <remote_name> <branch_name>
指令用于推送本地倉庫的分支到遠端倉庫上,相當于在遠端倉庫上建立新分支。指令
git push <remote_name> <branch_name>:<branch_name>
提供同樣推送分支的作用。
使用該指令隻會在遠端倉庫上建立新分支,并不會自動與目前倉庫上的分支進行關聯,指令可以在推送分支的同時進行關聯,是以一般在
git push -u <remote_name> <branch_name>
網站上建立倉庫時都會有類似的提示,表示用于從本地初始化倉庫,然後推送到遠端倉庫上。
github
git push <remote_name> --delete <branch_name>
git push <remote_name> --delete <branch_name>
指令用于删除遠端倉庫上的指定分支。指令
git push <remote_name> :<branch_name>
提供同樣删除遠端倉庫分支的作用。
當使用指令來構造本地倉庫時,會自動建立本地分支
git clone <remote_address>
,并與遠端倉庫分支
master
進行關聯。
origin/master
當本地分支已經關聯到遠端分支之後,拉取更新和推送更新都變得較為簡單。在分支上直接執行即可推送更新到關聯的遠端分支上,執行
git push
即可拉取關聯分支更新,然後執行
git fetch
即可合入更新到目前分支上。此外,
git merge
還提供有指令可以直接拉取更新并合入到目前分支上,
git
指令相當于合并了
git pull
git fetch
兩個指令的功能。
git merge
版本回退
雖然有了暫存區可以檢查待送出内容的正确性,但是仍不免有錯誤或不恰當的内容被送出,
git
提供了在分支上回退版本記錄的指令。
git reset <level> <commitId>
git reset <level> <commitId>
指令用于回退版本到指定送出記錄點。這裡
commitId
是
SHA1
校驗和,用于辨別待回退到的送出記錄點。回退的
level
有三種:
-
:修改--soft
指向指定的送出記錄點,并将指定記錄到最新送出記錄之間的修改回退至暫存區,工作區不受影響。HEAD
-
--mixed
指向指定的送出記錄點,并将指定記錄與最新送出記錄之間的修改回退至工作區,暫存區會被清除。HEAD
-
--hard
指向指定的送出記錄點,并将指定記錄到最新送出記錄之間的修改清除。HEAD
該指令不填寫具體時,預設級别為
level
。這裡注意一下
--mixed
的使用,該級别會清除工作區和暫存區的修改,即便撤銷回退操作回到最新送出,工作區和暫存區的修改也不會恢複,是以謹慎使用。同理,
--hard
級别也會清除暫存區的修改,是以版本回退過程中,需要注意選擇恰當的回退方式。
--mixed
執行版本回退指令時,并不一定每次都要提供指定版本記錄的校驗和,也可以通過來指定回退到相鄰的哪一個版本記錄。
HEAD
表示回退到上一個版本記錄,
HEAD^
表示回退到上兩個版本記錄,
HEAD^^
表示回退到上
HEAD~n
個版本記錄。
n
git revert <commitId>
git revert <commitId>
指令用于回退指定送出記錄。
git revert <commitId>
指令都可以用于回退版本,不同之處在于
git reset <level> <commitId>
用于回退到指定送出記錄,
reset
用于撤銷指定送出記錄,并且産生一個新的送出記錄。
revert
除了對送出曆史的變動不同之外,
git revert <commitId>
指令使用的側重場景也不同。
git reset <level> <commitId>
指令更多用于在本地分支進行回退,避免對團隊其他人的送出産生影響;
reset
指令則可以使用在公共分支上,當進行代碼檢視時,發現送出曆史中的某一次送出存在
revert
,則可以使用
bug
指令撤銷那一次送出。
revert
在本地倉庫的分支上執行回退操作後,有些情況下可能要同步回退遠端倉庫。
git push -f
git push -f
指令用于同步回退目前分支關聯的遠端分支,因為目前分支的版本落後于遠端分支,是以需要加
-f
選項,執行強制推送。
檔案異同
git status
隻能檢視出檔案的狀态以及是否發生了修改,并不能具體的展示出差異内容。
git diff <file>
git diff <file>
指令為檢視工作目錄的檔案與暫存區檔案的差異,也就是檢視從上次送出檔案修改到暫存區後,到目前為止,工作目錄的檔案又做了什麼修改。
git diff --cached <file>
git diff --cached <file>
指令為檢視暫存區的檔案與目前分支的檔案差異,也就是此次準備送出到分支上的有哪些修改内容。
git diff
指令還有其他形式:
git diff <branch> <file>
git diff <branch> <file>
指令為檢視目前工作目錄檔案與其他分支檔案差異。
git diff --cached <branch> <file>
git diff --cached <branch> <file>
指令為檢視目前暫存區檔案與其他分支檔案差異。
其實工作中這種指令的使用場景不多,這裡隻是舉個例子,說明的使用形式是靈活多樣的,例如也可以用于比較兩次
git diff
的差異等。
commit
儲藏修改
當工作過程中需要臨時解決某個問題,即需要在主分支上拉取
bugfix
分支修複
bug
時,若目前特性開發分支
dev
上的工作還沒有完成,無法立即送出。此時切換并拉取新分支會對工作目錄的修改内容造成幹擾,則此時需要把目前修改存儲起來,保持工作目錄的整潔,然後再建立
bugfix
bug
git stash
git stash
指令用于儲藏目前工作目錄的修改和暫存區内容到一個棧空間上,使得目前的工作目錄和暫存區不存在任何修改,即保持幹淨狀态。
git stash pop --index
git stash pop --index
指令用于恢複棧空間上的儲藏到工作目錄和暫存區,即恢複原狀。指令的使用中若不加
--index
參數,則儲藏會恢複到工作目錄,暫存區會清空。
該指令不僅适用于分支切換時,對于之前提到的版本回退指令,若可能造成工作目錄和暫存區内容丢失,則可以使用該指令來儲藏資訊。