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