Git說明:https://www.runoob.com/manual/git-guide/
騰訊Github:https://github.com/Tencent
阿里巴巴Github:https://github.com/alibaba
Git鏡像:https://www.gitclone.com/、https://ghproxy.com/
Git入門
資料來源:https://www.runoob.com/git/git-tutorial.html、http://git-scm.com/docs
查看Git命令的幫助信息,git <command> --help
1.Git 工作區(qū)、暫存區(qū)和版本庫(以本地舉例)、遠程倉庫
- 工作區(qū):就是你在電腦里能看到的目錄。
- 暫存區(qū):英文叫
stage
或index
。一般存放在 .git 目錄下的 index 文件(.git/index)中,所以我們把暫存區(qū)有時也叫作索引(index)。 - 版本庫:工作區(qū)有一個隱藏目錄 .git,這個不算工作區(qū),而是 Git 的版本庫。
- Git 工作區(qū)、暫存區(qū)和版本庫
- 圖中左側(cè)為工作區(qū),右側(cè)為版本庫。在版本庫中標記為 "index" 的區(qū)域是暫存區(qū)(stage/index),標記為 "master" 的是 master 分支所代表的目錄樹。
- 圖中我們可以看出此時 "HEAD" 實際是指向 master 分支的一個"游標"。所以圖示的命令中出現(xiàn) HEAD 的地方可以用 master 來替換。
- 圖中的 objects 標識的區(qū)域為 Git 的對象庫,實際位于 ".git/objects" 目錄下,里面包含了創(chuàng)建的各種對象及內(nèi)容。
- 當(dāng)對工作區(qū)修改(或新增)的文件執(zhí)行 git add 命令時,暫存區(qū)的目錄樹被更新,同時工作區(qū)修改(或新增)的文件內(nèi)容被寫入到對象庫中的一個新的對象中,而該對象的ID被記錄在暫存區(qū)的文件索引中。
- 當(dāng)執(zhí)行提交操作(git commit)時,暫存區(qū)的目錄樹寫到版本庫(對象庫)中,master 分支會做相應(yīng)的更新。即 master 指向的目錄樹就是提交時暫存區(qū)的目錄樹。
- 當(dāng)執(zhí)行 git reset HEAD 命令時,暫存區(qū)的目錄樹會被重寫,被 master 分支指向的目錄樹所替換,但是工作區(qū)不受影響。
- 當(dāng)執(zhí)行 git rm --cached 命令時,會直接從暫存區(qū)刪除文件,工作區(qū)則不做出改變。
- 當(dāng)執(zhí)行 git checkout . 或者 git checkout -- 命令時,會用暫存區(qū)全部或指定的文件替換工作區(qū)的文件。這個操作很危險,會清除工作區(qū)中未添加到暫存區(qū)中的改動。
- 當(dāng)執(zhí)行 git checkout HEAD . 或者 git checkout HEAD 命令時,會用 HEAD 指向的 master 分支中的全部或者部分文件替換暫存區(qū)和以及工作區(qū)中的文件。這個命令也是極具危險性的,因為不但會清除工作區(qū)中未提交的改動,也會清除暫存區(qū)中未提交的改動。
2.Git文件狀態(tài)
在Git中文件大概分為四種狀態(tài):已修改(modified)、已暫存(staged)、已提交(committed)、未追蹤(Untrack)
- .gitignore內(nèi)的文件,不會擁有任何一種狀態(tài),被git徹底無視。
- 處于ignore列表的文件,無法被add添加;但是可以強制添加
- 空目錄、以及子目錄全部是空目錄的目錄不會有Untrack狀態(tài),也無法通過add改變狀態(tài)(無效)
- 工作目錄新增文件時,只要不處于ignore目錄,都會變成Untrack狀態(tài);
- 沒有add過的文件或者被restore(不帶--staged)的文件,處于Untrack狀態(tài);
- 初次add和被add后產(chǎn)生修改的文件,會處于modifed狀態(tài)。
- 處于modified狀態(tài)的文件,最開始可以進行add和restore兩種操作,此時的add操作叫做
更新要提交的內(nèi)容
,add后變?yōu)閟taged狀態(tài),restore(不加staged標記)后變?yōu)閁ntrack; - add后變?yōu)閟taged狀態(tài)的文件,可用restore --staged 變回modified狀態(tài);這個staged狀態(tài)的內(nèi)容可以用來恢復(fù)內(nèi)容。沒有被add的modified狀態(tài)文件內(nèi)容沒有被記錄(雖然有撤回,但是本質(zhì)不一樣);
- 處于staged狀態(tài)的文件,在沒有commit之前再次產(chǎn)生修改時,會同時具有staged和modified兩個狀態(tài)(可以把statged狀態(tài)的內(nèi)容拉回來,覆蓋。);但是commit時會使用內(nèi)容最新的那個狀態(tài);
- commit會提交所有staged狀態(tài)的文件,所以commit可以理解有一個modified到staged狀態(tài)的過程(實際可能不存在,因為暫存區(qū)本來就有變動的記錄);所以暫存狀態(tài)不能理解為處于暫存區(qū),應(yīng)當(dāng)指的是被納入下一次提交的文件;任何被追蹤的產(chǎn)生修改的文件都會在暫存區(qū)被記錄;成為下一次提交的一部分;
- 未被追蹤的文件被刪除時,不會產(chǎn)生git狀態(tài)。處于modofy未add時,會變成deleted狀態(tài);處于staged狀態(tài)會保持暫存狀態(tài);
- 已經(jīng)被刪除的(deleted狀態(tài))被追蹤的文件,恢復(fù)后會變成modified狀態(tài);
- 提示
- add的作用是將文件添加到暫存區(qū),只有被add的文件才會被追蹤
- 暫存區(qū)
- (1)所謂的暫存區(qū)只是一個簡單的索引文件而已。
(2)暫存區(qū)這個索引文件里面包含的是文件的目錄樹,像一個虛擬的工作區(qū),在這個虛擬工作區(qū)的目錄樹中,記錄了文件名、文件的時間戳、文件長度、文件類型以及最重要的SHA-1值,文件的內(nèi)容并沒有存儲在其中,所以說 它像一個虛擬的工作區(qū)。
(3)索引指向的是.Git/objects下的文件。
(4)暫存區(qū)的作用:除非是繞過暫存區(qū)直接提交,否則Git想把修改提交上去,就必須將修改存入暫存區(qū)最后才能commit。每次提交的是暫存區(qū)所對應(yīng)的文件快照。
拓展:status提示信息
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
- 既然是Changes not staged for commit,就說明出現(xiàn)這個提示下的所有文件改動,都是存在于工作區(qū)的。stage是暫存區(qū)的意思,not stage說明都不在暫存區(qū),那么說明在工作區(qū)。
- (use “git add …” to update what will be committed)。執(zhí)行這條命令就可以工作區(qū)里面的改變加入到暫存區(qū)。可以執(zhí)行g(shù)it add .把當(dāng)前目錄下所有改動加入暫存區(qū)。
- (use “git checkout – …” to discard changes in working directory)。執(zhí)行這條命令將丟棄在工作區(qū)的改動??梢詧?zhí)行g(shù)it checkout *把當(dāng)前目錄下所有工作區(qū)的改動丟棄掉
Untracked files:
(use "git add <file>..." to include in what will be committed)
- Untracked files,就說明出現(xiàn)這個提示下的所有文件都是當(dāng)前HEAD沒有被加入過的文件。這種改動也屬于工作區(qū)。
- (use “git add …” to include in what will be committed)。把Untracked files加入暫存區(qū)。
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
- 當(dāng)前分支比遠程分支多了一次commit
Your branch and 'origin/master' have diverged, and have 1 and 1 different commits each, respectively
pull報錯了,查看狀態(tài)顯示這個,先留著待解決吧
3.HEAD是什么
HEAD是Git中非常重要的一個概念,你可以稱它為指針或者引用,它可以指向任意一個節(jié)點,并且指向的節(jié)點始終為當(dāng)前工作目錄,換句話說就是當(dāng)前工作目錄(也就是你所看到的代碼)就是HEAD指向的節(jié)點。
4.git重命名檢測
Git 采用了不同的方法:它沒有選擇去存儲與文件移動操作相關(guān)的信息,而是采用了重命名檢測算法。在該算法中,如果一個文件在某一次提交中消失了,它依然會存在于其前次提交,而如果某個擁有相同名字或相似內(nèi)容的文件出現(xiàn)在了另一個位置,Git 就會自動檢測到。如果是這種情況,Git 就會假定該文件被移動過了。
Git項目文件說明
Git init后主要有兩個重要的文件和目錄:.git目錄和.gitignore
1. .gitignore
.gitignore文件存在于根目錄(與.git同級的目錄)用于在將文件提交到git暫存區(qū)時,指定將哪些文件排除;
有時候你想添加(git add)一個文件到Git,但發(fā)現(xiàn)添加不了,多半原因是這個文件被.gitignore
忽略了
git add .不會添加被.gitignore忽視的文件,而git add -f . 強制添加所有文件,即使是.gitignore忽視的文件也添加。
當(dāng).gitignore文件不是你編寫的,但是它編寫的不符合實際需求,你可以使用git check-ignore命令進行檢查,看是哪一個規(guī)則有問題了
#檢測
git check-ignore -v App.class
#結(jié)果
.gitignore:3:*.class App.class
.gitignore只能忽略那些原來沒有被track的文件,如果某些文件已經(jīng)被納入了版本管理中,則修改.gitignore是無效的。解決方法就是先把本地緩存刪除(改變成未track狀態(tài)),然后再提交。
git rm -r --cached .
git add .
git commit -m ‘update .gitignore’
也可以手動指定一個文件作為git忽略文件
git config core.excludesfile ***
對于全局Git配置,可以使用如下命令對全部倉庫進行配置。
git config --global core.excludesfile **/.gitignore(文件相對或絕對位置)
忽略規(guī)則如下:
- 空格不匹配任意文件,可作為分隔符,可用反斜杠轉(zhuǎn)義
- #開頭的文件標識注釋,可以使用反斜杠進行轉(zhuǎn)義
- ! 開頭的模式標識否定,該文件將會再次被包含,如果排除了該文件的父級目錄,則使用 ! 也不會再次被包含。可以使用反斜杠進行轉(zhuǎn)義
- / 結(jié)束的模式只匹配文件夾以及在該文件夾路徑下的內(nèi)容,但是不匹配該文件
- / 開始的模式匹配項目跟目錄
- 如果一個模式不包含斜杠,則它匹配相對于當(dāng)前 .gitignore 文件路徑的內(nèi)容,如果該模式不在 .gitignore 文件中,則相對于項目根目錄
- ** 匹配多級目錄,可在開始,中間,結(jié)束
- ? 通用匹配單個字符
- [] 通用匹配單個字符列表
- 各種項目的gitignore
- 參考地址:https://github.com/github/gitignore
2. .git目錄
任意文件夾中,用 git init 命令初始化倉庫,即可在此文件夾下創(chuàng)建 .git 文件夾(.打頭為隱藏文件夾,所以平時可能看不到)。這個文件夾之外的部分叫做工作區(qū)(Working Directory),.git 文件夾我們稱做 Git倉庫 (Git Repository)。 通常會有7個文件5個目錄,常見目錄如下:
COMMIT_EDITMSG
HEAD
ORIG_HEAD
FETCH_HEAD
config
description
index
hooks/
info/
logs/
objects/
refs/
1. 文件 COMMIT_EDITMSG
此文件是一個臨時文件,存儲最后一次提交的信息內(nèi)容,git commit 命令之后打開的編輯器就是在編輯此文件,而你退出編輯器后,git 會把此文件內(nèi)容寫入 commit 記錄。
實際應(yīng)用: git pull 遠程倉庫后,新增了很多提交,淹沒了本地提交記錄,直接 cat .git/COMMIT_EDITMSG 就可以弄清楚最后工作的位置了。
2. HEAD
此文件永遠存儲當(dāng)前位置指針,就像 linux 中的 $PWD 變量和命令提示符的箭頭一樣,永遠指向當(dāng)前位置,表明當(dāng)前的工作位置。在 git 中 HEAD 永遠指向當(dāng)前正在工作的那個 commit。(孤立HEAD?????)
HEAD 存儲一個分支的 ref,Linux中運行:cat .git/HEAD 通常會顯示:
ref: refs/heads/master
這說明你目前正在 master 分支工作。此時你的任何 commit,默認自動附加到 master 分支之上
git cat-file -p HEAD
, 顯示詳細的提交信息:
tree 4cbb261560348e1727b5137f3ab6eceae8e1f34d
parent 22c457fe24f737505356edfb8696c7e50fd9d971
author Evan You <[email protected]> 1654857613 +0800
committer Evan You <[email protected]> 1654857613 +0800
chore: test pass
孤立head,不指向任何commit
3. ORIG_HEAD
正因為 HEAD 比較重要,此文件會在你進行危險操作時備份 HEAD,如以下操作時會觸發(fā)備份
git reset
git merge
git rebase
git pull
此文件應(yīng)用示例
# 回滾到上一次的狀態(tài)(慎用!!!)
git reset --hard ORIG_HEAD
4. FETCH_HEAD
這個文件作用在于追蹤遠程分支的拉取與合并,與其相關(guān)的命令有 git pull/fetch/merge
,而git pull 命令相當(dāng)于執(zhí)行以下兩條命令:
$ git fetch
$ git merge FETCH_HEAD
# 顯示如下>>>
From https://github.com/xxx/xxxx
* branch master -> FETCH_HEAD
Updating f785638..59db1b2
此時會默默備份 HEAD 到 ORIG_HEAD
5. config
此文件存儲項目本地的 git 設(shè)置,典型內(nèi)容如下:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
[remote "origin"]
url = [email protected]/xxx.git
fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
remote = origin
merge = refs/heads/master
[branch "v2.6.0"]
remote = origin
merge = refs/heads/v2.6.0
[branch "v2.8.0"]
remote = origin
merge = refs/heads/v2.8.0
[core]
段的內(nèi)容跟 git config 命令對應(yīng)
執(zhí)行以下命令:
git config user.name abc
git config user.email [email protected]
會在 config 文件中追加以下內(nèi)容:
... ...
[user]
name = abc
email = [email protected]
git config --global 影響的則是全局配置文件 ~/.gitconfig。
[remote] 段表示遠程倉庫配置
[branch] 段表示分支同步設(shè)置
假設(shè)當(dāng)前在 master 分支,執(zhí)行 git pull 若出現(xiàn)以下提示:
There is no tracking information for the current branch.
Please specify which branch you want to merge with.
See git-pull(1) for details.
git pull 就說明 .git/config 文件缺少對應(yīng)的 [branch "master"] 字段。
解決方案為:
git branch -u origin/master master
# 或者執(zhí)行一次 push
git push -u origin master
會出現(xiàn)提示:
Branch 'master' set up to track remote branch 'master' from 'origin'.
其實就是生成以下內(nèi)容在 .git/config中:
[branch "master"]
remote = origin
merge = refs/heads/master
手動編輯 .git/config,效果一樣。這就是 upstream 的真正含義,即生成 config 中的這段配置。
6. description
說明這個文件主要用于 GitWeb 的描述,如果要啟動 GitWeb 可用如下命令:
# 確保lighttpd已安裝: brew install lighttpd
$ git instaweb --start
默認會啟動 lighttpd 服務(wù)并打開瀏覽器 http://127.0.0.1:1234 (試著改成對外IP并分享給別人?)
以下顯示當(dāng)前的 git 倉庫名稱以及描述,默認的描述如下:
默認描述
上面這段話就是默認的 description
文件的內(nèi)容,編輯這個文件來讓你 GitWeb
描述更友好。
7. hooks/目錄
存放 git hooks,用于在 git 命令前后做檢查或做些自定義動作。運行 ls -F1 .git/hooks
prepare-commit-msg.sample # git commit 之前,編輯器啟動之前觸發(fā),傳入 COMMIT_FILE,COMMIT_SOURCE,SHA1
commit-msg.sample # git commit 之前,編輯器退出后觸發(fā),傳入 COMMIT_EDITMSG 文件名
pre-commit.sample # git commit 之前,commit-msg 通過后觸發(fā),譬如校驗文件名是否含中文
pre-push.sample # git push 之前觸發(fā)
pre-receive.sample # git push 之后,服務(wù)端更新 ref 前觸發(fā)
update.sample # git push 之后,服務(wù)端更新每一個 ref 時觸發(fā),用于針對每個 ref 作校驗等
post-update.sample # git push 之后,服務(wù)端更新 ref 后觸發(fā)
pre-rebase.sample # git rebase 之前觸發(fā),傳入 rebase 分支作參數(shù)
applypatch-msg.sample # 用于 git am 命令提交信息校驗
pre-applypatch.sample # 用于 git am 命令執(zhí)行前動作
fsmonitor-watchman.sample # 配合 core.fsmonitor 設(shè)置來更好監(jiān)測文件變化
參考
https://git-scm.com/docs/githooks
如果要啟用某個 hook,只需把 .sample 刪除即可,然后編輯其內(nèi)容來實現(xiàn)相應(yīng)的邏輯。
比如要校驗每個 commit message 至少要包含兩個單詞,否則就提示并拒絕提交,將 commit-msg.sample 改為 commit-msg 后,編輯如下:
#!/bin/sh
grep -q 'Ss+S' $1 || { echo '提交信息至少為兩個單詞' && exit 1; }
這樣當(dāng)提交一個 commit 時,會執(zhí)行 bash 命令: .git/hooks/commit-msg .git/COMMIT_EDITMSG,退出值不為 0,就拒絕提交。
8. info/目錄
此文件夾基本就有兩個文件:
- 文件 info/exclude 用于排除規(guī)則,與 .gitignore 功能類似。
- 可能會包含文件 info/refs ,用于跟蹤各分支的信息。此文件一般通過命令 git update-server-info 生成,內(nèi)容通常如下:
9. logs/目錄
記錄了操作信息,git reflog 命令以及像 HEAD@{1} 形式的路徑會用到。如果刪除此文件夾(危險?。?,那么依賴于 reflog 的命令就會報錯。
文件夾 objects/
此文件夾簡單說,就是 git的數(shù)據(jù)庫,運行 tree .git/objects,可以看到目錄結(jié)構(gòu):
.git/objects/
|-- 0c
| `-- d370696b581c38ee01e62b148a759f80facc2d
|-- 59
| `-- 3d5b490556791212acd5a516a37bbfa05d44dd
|-- 61
| `-- be44eedde61d723e5761577a2b420ba0fc2794
|-- 64
| `-- c0aed8ddcbb546bdcec2848938fc82348db227
|-- d4
| `-- 9904676ce8ddde276bdbfa9bbec313e90e0f50
|-- info
`-- pack
|-- pack-75e3f2aa378752ec93a8e9f375f01204d498605b.idx
`-- pack-75e3f2aa378752ec93a8e9f375f01204d498605b.pack
這些文件分兩種形式:pack壓縮包 形式放在 pack/ 目錄下,除此之外都是 hash文件 形式,被叫做 loost objects。
這個文件夾以及相應(yīng)的算法,我沒找到獨立的名稱,就叫它 hash-object 體系吧。因為確實有個 git hash-object 命令存在,是一個底層的負責(zé)生成這些 loost objects 文件,如果要看到這些文件各自的含義,執(zhí)行以下命令:
git cat-file --batch-check --batch-all-objects
可以看到
04c87c65f142f33945f2f5951cf7801a32dfa240 commit 194
098217953a6ca169bed33d2be8a07d584fcdaf30 tree 31
0cd370696b581c38ee01e62b148a759f80facc2d commit 245
2a810017bfc85d7db2627f4aabdaa1583212bda3 blob 19
3920a07c1d5694df6b8658592b0939241d70e9e5 tree 93
593d5b490556791212acd5a516a37bbfa05d44dd tag 148
61be44eedde61d723e5761577a2b420ba0fc2794 tree 154
... ...
但你會發(fā)現(xiàn)這個列表里有些值在文件夾中并不存在,因為除了 loost objects 它還匯總了 pack 文件中的內(nèi)容。hash文件
又稱為 loose object
,文件名稱共由40字符的 SHA-1 hash 值組成,其中前兩個字符為文件夾分桶,后38個字符為文件名稱。
按文件內(nèi)容可分為四種類型:commit, tree, blob, tag,若執(zhí)行以下命令會生成所有四種類型:
echo -en 'xxn' > xx # 共 3 個字符
git add .
git commit -m 'update xx'
git tag -a 'v1.0' -m 'release: 1.0.0'
經(jīng)過以上操作后,對比一下文件樹,發(fā)現(xiàn)多了四個 hash文件:
|-- 0c
| `-- d370696b581c38ee01e62b148a759f80facc2d
|-- 18
| `-- 143661f96845f11e0b4ab7312bdc0f356834ce
|-- 30
| `-- 20feea86d222d83218eb3eb5aa9f58f73df04d
|-- 59
| `-- 3d5b490556791212acd5a516a37bbfa05d44dd
|-- 61
| `-- be44eedde61d723e5761577a2b420ba0fc2794
|-- 64
| `-- c0aed8ddcbb546bdcec2848938fc82348db227
|-- ad
| `-- f4c9afac7afae3ff3e95e6c4eefe009d547f00
|-- cc
| `-- c9bd67dc5c467859102d53d54c5ce851273bdd
|-- d4
| `-- 9904676ce8ddde276bdbfa9bbec313e90e0f50
|-- info
`-- pack
|-- pack-75e3f2aa378752ec93a8e9f375f01204d498605b.idx
`-- pack-75e3f2aa378752ec93a8e9f375f01204d498605b.pack
這四個 hash文件 分別是:
cc/c9bd67dc5c467859102d53d54c5ce851273bdd # blob
30/20feea86d222d83218eb3eb5aa9f58f73df04d # commit
ad/f4c9afac7afae3ff3e95e6c4eefe009d547f00 # tree
18/143661f96845f11e0b4ab7312bdc0f356834ce # tag
其實這些文件都經(jīng)過了壓縮,壓縮形式為 zlib。先安裝一下解壓工具 macOS 版 brew install pigz 或 windows 版 pigz,后執(zhí)行:
$ pigz -d < .git/objects/cc/c9bd67dc5c467859102d53d54c5ce851273bdd
# BLOB類型,顯示結(jié)果為>>>>(注意xx后有個n)
blob 3xx
$pigz -d < .git/objects/30/20feea86d222d83218eb3eb5aa9f58f73df04d
# COMMIT類型,顯示結(jié)果為>>>>
commit 248tree adf4c9afac7afae3ff3e95e6c4eefe009d547f00
parent 0cd370696b581c38ee01e62b148a759f80facc2d
author jamesyang.yjm <[email protected]> 1562044880 +0800
committer jamesyang.yjm <[email protected]> 1562044880 +0800
update xx
$ pigz -d < .git/objects/ad/f4c9afac7afae3ff3e95e6c4eefe009d547f00
# TREE類型,顯示結(jié)果為>>>>
tree 154100644 abc*???]}?bJ??X2??100644 asdf???CK?)?wZ???S?100644 iou???CK?)?wZ???S?100644 xx??g?FxY-S?L?Q';?100644 yy???CK?)?wZ???S?
$ pigz -d < .git/objects/18/143661f96845f11e0b4ab7312bdc0f356834ce
# TAG類型,顯示結(jié)果為>>>>
tag 155object 3020feea86d222d83218eb3eb5aa9f58f73df04d
type commit
tag v1.0
tagger jamesyang.yjm <[email protected]> 1562045942 +0800
release: 1.0.0
會發(fā)現(xiàn),顯示結(jié)果都是 type size+內(nèi)容 形式,這就是 object 文件的存儲格式:
[type] [size][NULL][content]
type
可選值:commit, tree, blob, tag,NULL 就是C語言里的字符結(jié)束符:?,size 就是 NULL后內(nèi)容的字節(jié)長度。
type
的幾種類型可以使用 git cat-file -t hash 看到,內(nèi)容可以用 git cat-file -p hash 看到。
git cat-file -t ccc9bd67dc5c467859102d53d54c5ce851273bdd
# 顯示結(jié)果為>>>>
blob
git cat-file -p ccc9bd67dc5c467859102d53d54c5ce851273bdd
# 顯示結(jié)果為>>>>
xx
所以 blob 文件就是對原文件內(nèi)容的全量拷貝,同時前面加了 blob size?,而文件名稱的 hash 值計算是計算整體字符的 SHA-1 值:
echo -en 'blob 3?xxn' | shasum
# 顯示結(jié)果為>>>>
ccc9bd67dc5c467859102d53d54c5ce851273bdd -
知道原理后,其它類型格式請自行參考 斯坦福 Ben Lynn 所著的 GitMagic。
所以,當(dāng)我們 git show 3020feea86d222d83218eb3eb5aa9f58f73df04d
時,會發(fā)生些什么?
找到 3020feea86d222d83218eb3eb5aa9f58f73df04d 這個 commit
,顯示出來
找到此 commit 關(guān)聯(lián)的 tree object: adf4c9afac7afae3ff3e95e6c4eefe009d547f00
,拉取相應(yīng)的 blob 文件,并與當(dāng)前工作區(qū)內(nèi)的文件做 diff,然后顯示出來
這就是 objects/ 文件夾作為 git數(shù)據(jù)庫 被使用的真實例子。pack文件
為什么會有 .pack 文件?
由于每次 commit 都會生成許多 hash文件,并且由于 blob 文件都是全量存儲的,導(dǎo)致 git 效率下降,于是有了 pack-format,優(yōu)勢:
對于大倉庫存儲效率高
利于網(wǎng)絡(luò)傳輸,便于備份
增量存儲,優(yōu)化磁盤空間
將 .git/objects 下的部分文件打包成 pack格式
$ tree .git/objects/ | wc -l
311
$ git gc
Enumerating objects: 288, done.
Counting objects: 100% (288/288), done.
Delta compression using up to 4 threads
Compressing objects: 100% (287/287), done.
Writing objects: 100% (288/288), done.
Total 288 (delta 131), reused 90 (delta 0)
$ tree .git/objects/ | wc -l
12
可以看到文件數(shù)量減小了不少,其中大部分文件被打到一個 .pack 包中,并且是增量存儲,有部分變更的文件只存儲 基礎(chǔ)hash + 變更內(nèi)容,磁盤空間優(yōu)化很明顯。
git gc 其實運行了兩條命令:git repack 用來打包 和 git prune-packed 用來移除已打包的 hash文件;
11.文件夾refs
refs 可以理解成文件系統(tǒng)中的 symbol link,看下結(jié)構(gòu):
$ tree .git/refs/
.git/refs
|-- heads
| `-- master
`-- tags
`-- v1.0
$ cat .git/refs/heads/master
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5
$ cat .git/refs/tags/v1.0
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5
$ git cat-file -t 5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5
commit
可以看到 master 和 v1.0 都指向 5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 這個 commit。
refs/heads/ 文件夾內(nèi)的 ref 一般通過 git branch
生成。git show-ref --heads
可以查看。
refs/tags/ 文件夾內(nèi)的 ref 一般通過 git tag 生成。git show-ref --tags
可以查看。
如下:
$ git branch abc
$ tree .git/refs/
.git/refs/
|-- heads
| |-- abc
| `-- master
`-- tags
`-- v1.0
$ cat .git/refs/heads/abc
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5
說明新建分支其實就是生成了一個指向某個 commit 的 symbol link,當(dāng)然在這里叫做 ref。
而 git tag
命令本質(zhì)與 git branch
相同,只生成一個 ref 放在 tags 目錄下,所以被稱為 lightweight tag。
而 git tag -a xx
命令會首先生成一個類型為 tag 的 hash文件 放到 objects/ 目錄,然后生成 ref 放到 tags 目錄下指向那個文件。這就叫做 annotated tag,好處是可包含一些元信息如 tagger 和 message,被 git 的 hash-object 算法管理,可被 GPG 簽名等,所以更穩(wěn)定,更安全。
使用以下命令來拿到 refs 文件夾存儲的信息:
$ git show-ref --head --dereference
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 HEAD
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/heads/abc
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/heads/master
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/tags/v1.0
5e84371048faa20412f5492e6af264a7e1edfec1 refs/tags/xx
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/tags/xx^{}
我們來看這些信息如何變化的:
$ touch new_file && git add . && git commit -m 'add new_file'
[master 44b0d05] add new_file
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 new_file
$ git show-ref --head --dereference
44b0d05ddadaaa8d2cc40d6647cc474b26f5d8d3 HEAD
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/heads/abc
44b0d05ddadaaa8d2cc40d6647cc474b26f5d8d3 refs/heads/master
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/tags/v1.0
5e84371048faa20412f5492e6af264a7e1edfec1 refs/tags/xx
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/tags/xx^{}
diff 一下可以看到:
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 HEAD
5978c2c79cd3a4711fb8edd3166c9f9f5c8c97f5 refs/heads/master
這兩行發(fā)生了變化。也就是每次 commit 時,HEAD 與 heads 都會自動更新。
12. index文件
文件保存成二進制對象以后,還需要通知 Git 哪些文件發(fā)生了變動。所有變動的文件,Git 都記錄在一個區(qū)域,叫做"暫存區(qū)"(英文叫做 index 或者 stage)。等到變動告一段落,再統(tǒng)一把暫存區(qū)里面的文件寫入正式的版本歷史。
git update-index命令用于在暫存區(qū)記錄一個發(fā)生變動的文件。
$ git update-index --add --cacheinfo 100644
3b18e512dba79e4c8300dd08aeb37f8e728b8dad test.txt
上面命令向暫存區(qū)寫入文件名test.txt、二進制對象名(哈希值)和文件權(quán)限。
git ls-files命令可以顯示暫存區(qū)當(dāng)前的內(nèi)容。
$ git ls-files --stage
100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0 test.txt
上面代碼表示,暫存區(qū)現(xiàn)在只有一個文件test.txt,以及它的二進制對象名和權(quán)限。知道了二進制對象名,就可以在.git/objects子目錄里面讀出這個文件的內(nèi)容。
git status命令會產(chǎn)生更可讀的結(jié)果。
$ git status
要提交的變更:
新文件: test.txt
上面代碼表示,暫存區(qū)里面只有一個新文件test.txt,等待寫入歷史。
資料來源
參考:https://developer.aliyun.com/article/716483
Git遠程倉庫
Git 并不像 SVN 那樣有個中心服務(wù)器。目前我們使用到的 Git 命令都是在本地執(zhí)行,如果你想通過 Git 分享你的代碼或者與其他開發(fā)人員合作。 你就需要將數(shù)據(jù)放到一臺其他開發(fā)人員能夠連接的服務(wù)器上。
1.添加遠程倉庫
git remote add [shortname] [url] #添加遠程倉庫
git remote rm name # 刪除遠程倉庫
git remote rename old_name new_name # 修改倉庫名
2.查看遠端倉庫
$ git remote
origin
$ git remote -v
origin [email protected]:tianqixin/runoob-git-test.git (fetch)
origin [email protected]:tianqixin/runoob-git-test.git (push)
3.獲取遠端倉庫代碼 git fetch
# 只能fetch到一個空白的分支,然后可以手動merge
$ git fetch <遠程主機名> <遠程分支名>:<本地分支名>
不填的話都是默認
4.拉取 git pull
git pull <遠程主機名> <遠程分支名>:<本地分支名>
# 允許合并不相關(guān)的分支
$ git pull --allow-unrelated-histories
git pull操作其實是git fetch 與 git merge 兩個命令的集合。 git fetch 和 git merge FETCH_HEAD 的簡寫。
相關(guān)文檔:https://www.runoob.com/git/git-remote-repo.html
5.推送 git push
# 基本
$ git push <遠程主機名> <本地分支名>:<遠程分支名>
# 強制推送
$ git push --force origin master
# 刪除遠程分支
$ git push origin --delete master
# 允許合并不相關(guān)的分支
$ git push --allow-unrelated-histories
提示
如果另一個開發(fā)者在我們之前已經(jīng)做過一次 push 操作,此次 push 命令就會被拒絕傳送提交。這時候,我們必須要先做一次 pull 操作,將其他人新上載的更新取回,并本地合并。
如果本地分支名與遠程分支名相同,則可以省略冒號,帶上-u 參數(shù)相當(dāng)于記錄了push到遠端分支的默認值,這樣當(dāng)下次我們還想要繼續(xù)push的這個遠端分支的時候推送命令就可以簡寫成git push即可。
Git 分支
1.創(chuàng)建分支命令 git branch
# 創(chuàng)建分支
$ git branch <branch>
# 創(chuàng)建分支并跟蹤遠程分支
$ git branch -u o/master foo
2.切換分支命令 git checkout
第一作用是切換分支,第二個是撤銷修改。
# 切換指定分支
$ git checkout <branch>|<hash>|<tag>
# 創(chuàng)建一個的分支,它跟蹤遠程分支
$ git checkout -b 本地分支名x origin/遠程分支名x
# 從暫存區(qū)恢復(fù)到工作區(qū)
$ git checkout .
提示
當(dāng)你切換分支的時候,Git 會用該分支的最后提交的快照替換你的工作目錄的內(nèi)容, 所以多個分支不需要多個目錄。
實際測試:
假設(shè)分支1 a文件未提交,分支2 a文件已提交。切換到到分支2時會替換 分支1的a文件。1切換到2時也會替換a文件;
兩個分支都已經(jīng)提交的 切換時會互相替換。一個提交一個沒提交時,從a到b,b會保持a的暫存區(qū)和工作區(qū)
3.合并分支命令 git merge
# 合并指定分支到當(dāng)前分支
$ git merge <branch>
4.刪除分支 git branch -d
# 刪除指定分支
$ git branch -d <branch>
5.分支列表 git branch
# 列出所有分支
$ git branch
# 查看遠程所有分支
$ git branch -r
# 查看本地和遠程所有分支
$ git branch -a
列出分支時,帶*號的分支為當(dāng)前活動分支
5.重命名分支 git branch -M
# 重命名指定分支
# 不填old默認重命名當(dāng)前分支
$ git branch -m old new
# 強制重命名指定分支
$ git branch -M old new
git rebase 變基
1.介紹
Git rebase,通常被稱作變基或衍合, 可以理解為另外一種合并的方式,與merge 會保留分支結(jié)構(gòu)和原始提交記錄不同,rebase 是在公共祖先的基礎(chǔ)上,把新的提交鏈截取下來,在目標分支上進行重放,逐個應(yīng)用選中的提交來完成合并。
不同公司,不同情況有不同使用場景,不過大部分情況推薦如下:
自己單機的時候,拉公共分支最新代碼的時候使用rebase,也就是git pull -r或git pull --rebase。這樣的好處很明顯,提交記錄會比較簡潔。但有個缺點就是rebase以后我就不知道我的當(dāng)前分支最早是從哪個分支拉出來的了,因為基底變了嘛,所以看個人需求了。
往公共分支上合代碼的時候,使用merge。如果使用rebase,那么其他開發(fā)人員想看主分支的歷史,就不是原來的歷史了,歷史已經(jīng)被你篡改了。舉個例子解釋下,比如張三和李四從共同的節(jié)點拉出來開發(fā),張三先開發(fā)完提交了兩次然后merge上去了,李四后來開發(fā)完如果rebase上去(注意李四需要切換到自己本地的主分支,假設(shè)先pull了張三的最新改動下來,然后執(zhí)行,然后再git push到遠端),則李四的新提交變成了張三的新提交的新基底,本來李四的提交是最新的,結(jié)果最新的提交顯示反而是張三的,就亂套了。
正因如此,大部分公司其實會禁用rebase,不管是拉代碼還是push代碼統(tǒng)一都使用merge,雖然會多出無意義的一條提交記錄“Merge … to …”,但至少能清楚地知道主線上誰合了的代碼以及他們合代碼的時間先后順序
2.原理
變基操作的工作原理很簡單:Git 會讓我們想要移動的提交序列在目標分支上按照相同的順序重新再現(xiàn)一遍。這就相當(dāng)于我們?yōu)楦鱾€原提交做了個副本,它們擁有相同的修改集、同一作者、日期以及注釋信息。
3.命令
# 可以是commit 版本號、分支名稱,合并多個提交到一個版本
$ git rebase -i [startpoint] [endpoint]
# 變基發(fā)生沖突時,解決后繼續(xù)變基
$ git rebase --continue
# 無視沖突,繼續(xù)變基操作
$ git rebase --skip
# 發(fā)生沖突時中斷變基
$ git rebase --abort"
-i的意思是--interactive,即彈出交互式的界面讓用戶編輯完成合并操作,[startpoint] [endpoint]則指定了一個編輯區(qū)間,如果不指定[endpoint],則該區(qū)間的終點默認是當(dāng)前分支HEAD所指向的commit(注:該區(qū)間指定的是一個前開后閉的區(qū)間)。
Git 標簽
Git 中的tag指向一次commit的id,通常用來給開發(fā)分支做一個標記,如標記一個版本號。
1.添加標簽
git tag -a version -m "note"
注解:git tag 是打標簽的命令,-a 是添加標簽,其后要跟新標簽號,-m 及后面的字符串是對該標簽的注釋。
2.提交標簽到遠程倉庫
git push origin -tags