簡介
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
local:.git/config
windows 系統中的位置爲:system
:git 工具安裝位置的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
;如果兩個提交中存在對 同一處文件內容 的修改,則此時合並存在衝突,需要手動解決衝突並完成合並提交。
after_merge
圖
merge-2
所示的合併方式爲recursive
方式,或者稱之爲三路合併方式。因爲合併C3
與C4
的提交,需要與公共上游C2
相比較,以達到與 同一處文件內容 相比較的目的,所以該合併方式主要觀察C2
、C3
與C4
三個文件系統快照,所以稱爲三路合併方式。因爲複雜情況下公共上游並不想圖中所示這麼明顯,可能需要進行多次迭代合併處理方可產生虛擬的公共上游,所以也稱此方式爲recursive
方式。
關聯遠程倉庫
在團隊協作過程中,經常的場景就是團隊的每位成員都 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 <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 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
的差異等。