資料來源:https://www.runoob.com/git/git-tutorial.html、http://git-scm.com/docs
查看Git命令的幫助信息,git
1.Git 工作區、暫存區和版本庫(以本地舉例)、遠程倉庫
- 工作區:就是你在電腦里能看到的目錄。
- 暫存區:英文叫
stage
或index
。一般存放在 .git 目錄下的 index 文件(.git/index)中,所以我們把暫存區有時也叫作索引(index)。 - 版本庫:工作區有一個隱藏目錄 .git,這個不算工作區,而是 Git 的版本庫。
- Git 工作區、暫存區和版本庫
- 圖中左側為工作區,右側為版本庫。在版本庫中標記為 "index" 的區域是暫存區(stage/index),標記為 "master" 的是 master 分支所代表的目錄樹。
- 圖中我們可以看出此時 "HEAD" 實際是指向 master 分支的一個"游標"。所以圖示的命令中出現 HEAD 的地方可以用 master 來替換。
- 圖中的 objects 標識的區域為 Git 的對象庫,實際位于 ".git/objects" 目錄下,里面包含了創建的各種對象及內容。
- 當對工作區修改(或新增)的文件執行 git add 命令時,暫存區的目錄樹被更新,同時工作區修改(或新增)的文件內容被寫入到對象庫中的一個新的對象中,而該對象的ID被記錄在暫存區的文件索引中。
- 當執行提交操作(git commit)時,暫存區的目錄樹寫到版本庫(對象庫)中,master 分支會做相應的更新。即 master 指向的目錄樹就是提交時暫存區的目錄樹。
- 當執行 git reset HEAD 命令時,暫存區的目錄樹會被重寫,被 master 分支指向的目錄樹所替換,但是工作區不受影響。
- 當執行 git rm --cached 命令時,會直接從暫存區刪除文件,工作區則不做出改變。
- 當執行 git checkout . 或者 git checkout -- 命令時,會用暫存區全部或指定的文件替換工作區的文件。這個操作很危險,會清除工作區中未添加到暫存區中的改動。
- 當執行 git checkout HEAD . 或者 git checkout HEAD 命令時,會用 HEAD 指向的 master 分支中的全部或者部分文件替換暫存區和以及工作區中的文件。這個命令也是極具危險性的,因為不但會清除工作區中未提交的改動,也會清除暫存區中未提交的改動。
2.Git文件狀態
在Git中文件大概分為四種狀態:已修改(modified)、已暫存(staged)、已提交(committed)、未追蹤(Untrack)
- .gitignore內的文件,不會擁有任何一種狀態,被git徹底無視。
- 處于ignore列表的文件,無法被add添加;但是可以強制添加
- 空目錄、以及子目錄全部是空目錄的目錄不會有Untrack狀態,也無法通過add改變狀態(無效)
- 工作目錄新增文件時,只要不處于ignore目錄,都會變成Untrack狀態;
- 沒有add過的文件或者被restore(不帶--staged)的文件,處于Untrack狀態;
- 初次add和被add后產生修改的文件,會處于modifed狀態。
- 處于modified狀態的文件,最開始可以進行add和restore兩種操作,此時的add操作叫做
更新要提交的內容
,add后變為staged狀態,restore(不加staged標記)后變為Untrack; - add后變為staged狀態的文件,可用restore --staged 變回modified狀態;這個staged狀態的內容可以用來恢復內容。沒有被add的modified狀態文件內容沒有被記錄(雖然有撤回,但是本質不一樣);
- 處于staged狀態的文件,在沒有commit之前再次產生修改時,會同時具有staged和modified兩個狀態(可以把statged狀態的內容拉回來,覆蓋。);但是commit時會使用內容最新的那個狀態;
- commit會提交所有staged狀態的文件,所以commit可以理解有一個modified到staged狀態的過程(實際可能不存在,因為暫存區本來就有變動的記錄);所以暫存狀態不能理解為處于暫存區,應當指的是被納入下一次提交的文件;任何被追蹤的產生修改的文件都會在暫存區被記錄;成為下一次提交的一部分;
- 未被追蹤的文件被刪除時,不會產生git狀態。處于modofy未add時,會變成deleted狀態;處于staged狀態會保持暫存狀態;
- 已經被刪除的(deleted狀態)被追蹤的文件,恢復后會變成modified狀態;
- 提示
- add的作用是將文件添加到暫存區,只有被add的文件才會被追蹤
- 暫存區
- (1)所謂的暫存區只是一個簡單的索引文件而已。
(2)暫存區這個索引文件里面包含的是文件的目錄樹,像一個虛擬的工作區,在這個虛擬工作區的目錄樹中,記錄了文件名、文件的時間戳、文件長度、文件類型以及最重要的SHA-1值,文件的內容并沒有存儲在其中,所以說 它像一個虛擬的工作區。
(3)索引指向的是.Git/objects下的文件。
(4)暫存區的作用:除非是繞過暫存區直接提交,否則Git想把修改提交上去,就必須將修改存入暫存區最后才能commit。每次提交的是暫存區所對應的文件快照。
拓展:status提示信息
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
- 既然是Changes not staged for commit,就說明出現這個提示下的所有文件改動,都是存在于工作區的。stage是暫存區的意思,not stage說明都不在暫存區,那么說明在工作區。
- (use “git add …” to update what will be committed)。執行這條命令就可以工作區里面的改變加入到暫存區。可以執行git add .把當前目錄下所有改動加入暫存區。
- (use “git checkout – …” to discard changes in working directory)。執行這條命令將丟棄在工作區的改動??梢詧绦術it checkout *把當前目錄下所有工作區的改動丟棄掉
Untracked files:
(use "git add ..." to include in what will be committed)
- Untracked files,就說明出現這個提示下的所有文件都是當前HEAD沒有被加入過的文件。這種改動也屬于工作區。
- (use “git add …” to include in what will be committed)。把Untracked files加入暫存區。
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
- 當前分支比遠程分支多了一次commit
Your branch and 'origin/master' have diverged, and have 1 and 1 different commits each, respectively
pull報錯了,查看狀態顯示這個,先留著待解決吧
3.HEAD是什么
HEAD是Git中非常重要的一個概念,你可以稱它為指針或者引用,它可以指向任意一個節點,并且指向的節點始終為當前工作目錄,換句話說就是當前工作目錄(也就是你所看到的代碼)就是HEAD指向的節點。
4.git重命名檢測
Git 采用了不同的方法:它沒有選擇去存儲與文件移動操作相關的信息,而是采用了重命名檢測算法。在該算法中,如果一個文件在某一次提交中消失了,它依然會存在于其前次提交,而如果某個擁有相同名字或相似內容的文件出現在了另一個位置,Git 就會自動檢測到。如果是這種情況,Git 就會假定該文件被移動過了。
Git項目文件說明
Git init后主要有兩個重要的文件和目錄:.git目錄和.gitignore
1. .gitignore
.gitignore文件存在于根目錄(與.git同級的目錄)用于在將文件提交到git暫存區時,指定將哪些文件排除;
有時候你想添加(git add)一個文件到Git,但發現添加不了,多半原因是這個文件被.gitignore
忽略了
git add .不會添加被.gitignore忽視的文件,而git add -f . 強制添加所有文件,即使是.gitignore忽視的文件也添加。
當.gitignore文件不是你編寫的,但是它編寫的不符合實際需求,你可以使用git check-ignore命令進行檢查,看是哪一個規則有問題了
#檢測
git check-ignore -v App.class
#結果
.gitignore:3:*.class App.class
.gitignore只能忽略那些原來沒有被track的文件,如果某些文件已經被納入了版本管理中,則修改.gitignore是無效的。解決方法就是先把本地緩存刪除(改變成未track狀態),然后再提交。
git rm -r --cached .
git add .
git commit -m ‘update .gitignore’
也可以手動指定一個文件作為git忽略文件
git config core.excludesfile ***
對于全局Git配置,可以使用如下命令對全部倉庫進行配置。
git config --global core.excludesfile **/.gitignore(文件相對或絕對位置)
忽略規則如下:
- 空格不匹配任意文件,可作為分隔符,可用反斜杠轉義
- #開頭的文件標識注釋,可以使用反斜杠進行轉義
- ! 開頭的模式標識否定,該文件將會再次被包含,如果排除了該文件的父級目錄,則使用 ! 也不會再次被包含??梢允褂梅葱备苓M行轉義
- / 結束的模式只匹配文件夾以及在該文件夾路徑下的內容,但是不匹配該文件
- / 開始的模式匹配項目跟目錄
- 如果一個模式不包含斜杠,則它匹配相對于當前 .gitignore 文件路徑的內容,如果該模式不在 .gitignore 文件中,則相對于項目根目錄
- ** 匹配多級目錄,可在開始,中間,結束
- ? 通用匹配單個字符
- [] 通用匹配單個字符列表
- 各種項目的gitignore
- 參考地址:https://github.com/github/gitignore
2. .git目錄
任意文件夾中,用 git init 命令初始化倉庫,即可在此文件夾下創建 .git 文件夾(.打頭為隱藏文件夾,所以平時可能看不到)。這個文件夾之外的部分叫做工作區(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
此文件是一個臨時文件,存儲最后一次提交的信息內容,git commit 命令之后打開的編輯器就是在編輯此文件,而你退出編輯器后,git 會把此文件內容寫入 commit 記錄。
實際應用: git pull 遠程倉庫后,新增了很多提交,淹沒了本地提交記錄,直接 cat .git/COMMIT_EDITMSG 就可以弄清楚最后工作的位置了。
2. HEAD
此文件永遠存儲當前位置指針,就像 linux 中的 $PWD 變量和命令提示符的箭頭一樣,永遠指向當前位置,表明當前的工作位置。在 git 中 HEAD 永遠指向當前正在工作的那個 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 1654857613 +0800
committer Evan You 1654857613 +0800
chore: test pass
孤立head,不指向任何commit
3. ORIG_HEAD
正因為 HEAD 比較重要,此文件會在你進行危險操作時備份 HEAD,如以下操作時會觸發備份
git reset
git merge
git rebase
git pull
此文件應用示例
# 回滾到上一次的狀態(慎用!!!)
git reset --hard ORIG_HEAD
4. FETCH_HEAD
這個文件作用在于追蹤遠程分支的拉取與合并,與其相關的命令有 git pull/fetch/merge
,而git pull 命令相當于執行以下兩條命令:
$ 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 設置,典型內容如下:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
[remote "origin"]
url = git@gitlab.xxxx.com/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]
段的內容跟 git config 命令對應
執行以下命令:
git config user.name abc
git config user.email abc@abc.com
會在 config 文件中追加以下內容:
... ...
[user]
name = abc
email = abc@abc.com
git config --global 影響的則是全局配置文件 ~/.gitconfig。
[remote] 段表示遠程倉庫配置
[branch] 段表示分支同步設置
假設當前在 master 分支,執行 git pull 若出現以下提示:
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 文件缺少對應的 [branch "master"] 字段。
解決方案為:
git branch -u origin/master master
# 或者執行一次 push
git push -u origin master
會出現提示:
Branch 'master' set up to track remote branch 'master' from 'origin'.
其實就是生成以下內容在 .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 服務并打開瀏覽器 http://127.0.0.1:1234 (試著改成對外IP并分享給別人?)
以下顯示當前的 git 倉庫名稱以及描述,默認的描述如下:
默認描述
上面這段話就是默認的 description
文件的內容,編輯這個文件來讓你 GitWeb
描述更友好。
7. hooks/目錄
存放 git hooks,用于在 git 命令前后做檢查或做些自定義動作。運行 ls -F1 .git/hooks
prepare-commit-msg.sample # git commit 之前,編輯器啟動之前觸發,傳入 COMMIT_FILE,COMMIT_SOURCE,SHA1
commit-msg.sample # git commit 之前,編輯器退出后觸發,傳入 COMMIT_EDITMSG 文件名
pre-commit.sample # git commit 之前,commit-msg 通過后觸發,譬如校驗文件名是否含中文
pre-push.sample # git push 之前觸發
pre-receive.sample # git push 之后,服務端更新 ref 前觸發
update.sample # git push 之后,服務端更新每一個 ref 時觸發,用于針對每個 ref 作校驗等
post-update.sample # git push 之后,服務端更新 ref 后觸發
pre-rebase.sample # git rebase 之前觸發,傳入 rebase 分支作參數
applypatch-msg.sample # 用于 git am 命令提交信息校驗
pre-applypatch.sample # 用于 git am 命令執行前動作
fsmonitor-watchman.sample # 配合 core.fsmonitor 設置來更好監測文件變化
參考
https://git-scm.com/docs/githooks
如果要啟用某個 hook,只需把 .sample 刪除即可,然后編輯其內容來實現相應的邏輯。
比如要校驗每個 commit message 至少要包含兩個單詞,否則就提示并拒絕提交,將 commit-msg.sample 改為 commit-msg 后,編輯如下:
#!/bin/sh
grep -q 'Ss+S' $1 || { echo '提交信息至少為兩個單詞' && exit 1; }
這樣當提交一個 commit 時,會執行 bash 命令: .git/hooks/commit-msg .git/COMMIT_EDITMSG,退出值不為 0,就拒絕提交。
8. info/目錄
此文件夾基本就有兩個文件:
- 文件 info/exclude 用于排除規則,與 .gitignore 功能類似。
- 可能會包含文件 info/refs ,用于跟蹤各分支的信息。此文件一般通過命令 git update-server-info 生成,內容通常如下:
9. logs/目錄
記錄了操作信息,git reflog 命令以及像 HEAD@{1} 形式的路徑會用到。如果刪除此文件夾(危險!),那么依賴于 reflog 的命令就會報錯。
文件夾 objects/
此文件夾簡單說,就是 git的數據庫,運行 tree .git/objects,可以看到目錄結構:
.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。
這個文件夾以及相應的算法,我沒找到獨立的名稱,就叫它 hash-object 體系吧。因為確實有個 git hash-object 命令存在,是一個底層的負責生成這些 loost objects 文件,如果要看到這些文件各自的含義,執行以下命令:
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
... ...
但你會發現這個列表里有些值在文件夾中并不存在,因為除了 loost objects 它還匯總了 pack 文件中的內容。hash文件
又稱為 loose object
,文件名稱共由40字符的 SHA-1 hash 值組成,其中前兩個字符為文件夾分桶,后38個字符為文件名稱。
按文件內容可分為四種類型:commit, tree, blob, tag,若執行以下命令會生成所有四種類型:
echo -en 'xxn' > xx # 共 3 個字符
git add .
git commit -m 'update xx'
git tag -a 'v1.0' -m 'release: 1.0.0'
經過以上操作后,對比一下文件樹,發現多了四個 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
其實這些文件都經過了壓縮,壓縮形式為 zlib。先安裝一下解壓工具 macOS 版 brew install pigz 或 windows 版 pigz,后執行:
$ pigz -d < .git/objects/cc/c9bd67dc5c467859102d53d54c5ce851273bdd
# BLOB類型,顯示結果為>>>>(注意xx后有個n)
blob 3xx
$pigz -d < .git/objects/30/20feea86d222d83218eb3eb5aa9f58f73df04d
# COMMIT類型,顯示結果為>>>>
commit 248tree adf4c9afac7afae3ff3e95e6c4eefe009d547f00
parent 0cd370696b581c38ee01e62b148a759f80facc2d
author jamesyang.yjm 1562044880 +0800
committer jamesyang.yjm 1562044880 +0800
update xx
$ pigz -d < .git/objects/ad/f4c9afac7afae3ff3e95e6c4eefe009d547f00
# TREE類型,顯示結果為>>>>
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類型,顯示結果為>>>>
tag 155object 3020feea86d222d83218eb3eb5aa9f58f73df04d
type commit
tag v1.0
tagger jamesyang.yjm 1562045942 +0800
release: 1.0.0
會發現,顯示結果都是 type size+內容 形式,這就是 object 文件的存儲格式:
[type] [size][NULL][content]
type
可選值:commit, tree, blob, tag,NULL 就是C語言里的字符結束符:?,size 就是 NULL后內容的字節長度。
type
的幾種類型可以使用 git cat-file -t hash 看到,內容可以用 git cat-file -p hash 看到。
git cat-file -t ccc9bd67dc5c467859102d53d54c5ce851273bdd
# 顯示結果為>>>>
blob
git cat-file -p ccc9bd67dc5c467859102d53d54c5ce851273bdd
# 顯示結果為>>>>
xx
所以 blob 文件就是對原文件內容的全量拷貝,同時前面加了 blob size?,而文件名稱的 hash 值計算是計算整體字符的 SHA-1 值:
echo -en 'blob 3?xxn' | shasum
# 顯示結果為>>>>
ccc9bd67dc5c467859102d53d54c5ce851273bdd -
知道原理后,其它類型格式請自行參考 斯坦福 Ben Lynn 所著的 GitMagic。
所以,當我們 git show 3020feea86d222d83218eb3eb5aa9f58f73df04d
時,會發生些什么?
找到 3020feea86d222d83218eb3eb5aa9f58f73df04d 這個 commit
,顯示出來
找到此 commit 關聯的 tree object: adf4c9afac7afae3ff3e95e6c4eefe009d547f00
,拉取相應的 blob 文件,并與當前工作區內的文件做 diff,然后顯示出來
這就是 objects/ 文件夾作為 git數據庫 被使用的真實例子。pack文件
為什么會有 .pack 文件?
由于每次 commit 都會生成許多 hash文件,并且由于 blob 文件都是全量存儲的,導致 git 效率下降,于是有了 pack-format,優勢:
對于大倉庫存儲效率高
利于網絡傳輸,便于備份
增量存儲,優化磁盤空間
將 .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
可以看到文件數量減小了不少,其中大部分文件被打到一個 .pack 包中,并且是增量存儲,有部分變更的文件只存儲 基礎hash + 變更內容,磁盤空間優化很明顯。
git gc 其實運行了兩條命令:git repack 用來打包 和 git prune-packed 用來移除已打包的 hash文件;
11.文件夾refs
refs 可以理解成文件系統中的 symbol link,看下結構:
$ 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/ 文件夾內的 ref 一般通過 git branch
生成。git show-ref --heads
可以查看。
refs/tags/ 文件夾內的 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,當然在這里叫做 ref。
而 git tag
命令本質與 git branch
相同,只生成一個 ref 放在 tags 目錄下,所以被稱為 lightweight tag。
而 git tag -a xx
命令會首先生成一個類型為 tag 的 hash文件 放到 objects/ 目錄,然后生成 ref 放到 tags 目錄下指向那個文件。這就叫做 annotated tag,好處是可包含一些元信息如 tagger 和 message,被 git 的 hash-object 算法管理,可被 GPG 簽名等,所以更穩定,更安全。
使用以下命令來拿到 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
這兩行發生了變化。也就是每次 commit 時,HEAD 與 heads 都會自動更新。
12. index文件
文件保存成二進制對象以后,還需要通知 Git 哪些文件發生了變動。所有變動的文件,Git 都記錄在一個區域,叫做"暫存區"(英文叫做 index 或者 stage)。等到變動告一段落,再統一把暫存區里面的文件寫入正式的版本歷史。
git update-index命令用于在暫存區記錄一個發生變動的文件。
$ git update-index --add --cacheinfo 100644
3b18e512dba79e4c8300dd08aeb37f8e728b8dad test.txt
上面命令向暫存區寫入文件名test.txt、二進制對象名(哈希值)和文件權限。
git ls-files命令可以顯示暫存區當前的內容。
$ git ls-files --stage
100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0 test.txt
上面代碼表示,暫存區現在只有一個文件test.txt,以及它的二進制對象名和權限。知道了二進制對象名,就可以在.git/objects子目錄里面讀出這個文件的內容。
git status命令會產生更可讀的結果。
$ git status
要提交的變更:
新文件: test.txt
上面代碼表示,暫存區里面只有一個新文件test.txt,等待寫入歷史。
資料來源
參考:https://developer.aliyun.com/article/716483
Git遠程倉庫
Git 并不像 SVN 那樣有個中心服務器。目前我們使用到的 Git 命令都是在本地執行,如果你想通過 Git 分享你的代碼或者與其他開發人員合作。 你就需要將數據放到一臺其他開發人員能夠連接的服務器上。
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 git@github.com:tianqixin/runoob-git-test.git (fetch)
origin git@github.com:tianqixin/runoob-git-test.git (push)
3.獲取遠端倉庫代碼 git fetch
# 只能fetch到一個空白的分支,然后可以手動merge
$ git fetch <遠程主機名> <遠程分支名>:<本地分支名>
不填的話都是默認
4.拉取 git pull
git pull <遠程主機名> <遠程分支名>:<本地分支名>
# 允許合并不相關的分支
$ git pull --allow-unrelated-histories
git pull操作其實是git fetch 與 git merge 兩個命令的集合。 git fetch 和 git merge FETCH_HEAD 的簡寫。
相關文檔:https://www.runoob.com/git/git-remote-repo.html
5.推送 git push
# 基本
$ git push <遠程主機名> <本地分支名>:<遠程分支名>
# 強制推送
$ git push --force origin master
# 刪除遠程分支
$ git push origin --delete master
# 允許合并不相關的分支
$ git push --allow-unrelated-histories
提示
如果另一個開發者在我們之前已經做過一次 push 操作,此次 push 命令就會被拒絕傳送提交。這時候,我們必須要先做一次 pull 操作,將其他人新上載的更新取回,并本地合并。
如果本地分支名與遠程分支名相同,則可以省略冒號,帶上-u 參數相當于記錄了push到遠端分支的默認值,這樣當下次我們還想要繼續push的這個遠端分支的時候推送命令就可以簡寫成git push即可。
Git 分支
1.創建分支命令 git branch
# 創建分支
$ git branch
# 創建分支并跟蹤遠程分支
$ git branch -u o/master foo
2.切換分支命令 git checkout
第一作用是切換分支,第二個是撤銷修改。
# 切換指定分支
$ git checkout ||
# 創建一個的分支,它跟蹤遠程分支
$ git checkout -b 本地分支名x origin/遠程分支名x
# 從暫存區恢復到工作區
$ git checkout .
提示
當你切換分支的時候,Git 會用該分支的最后提交的快照替換你的工作目錄的內容, 所以多個分支不需要多個目錄。
實際測試:
假設分支1 a文件未提交,分支2 a文件已提交。切換到到分支2時會替換 分支1的a文件。1切換到2時也會替換a文件;
兩個分支都已經提交的 切換時會互相替換。一個提交一個沒提交時,從a到b,b會保持a的暫存區和工作區
3.合并分支命令 git merge
# 合并指定分支到當前分支
$ git merge
4.刪除分支 git branch -d
# 刪除指定分支
$ git branch -d
5.分支列表 git branch
# 列出所有分支
$ git branch
# 查看遠程所有分支
$ git branch -r
# 查看本地和遠程所有分支
$ git branch -a
列出分支時,帶*號的分支為當前活動分支
5.重命名分支 git branch -M
# 重命名指定分支
# 不填old默認重命名當前分支
$ git branch -m old new
# 強制重命名指定分支
$ git branch -M old new
git rebase 變基
1.介紹
Git rebase,通常被稱作變基或衍合, 可以理解為另外一種合并的方式,與merge 會保留分支結構和原始提交記錄不同,rebase 是在公共祖先的基礎上,把新的提交鏈截取下來,在目標分支上進行重放,逐個應用選中的提交來完成合并。
不同公司,不同情況有不同使用場景,不過大部分情況推薦如下:
自己單機的時候,拉公共分支最新代碼的時候使用rebase,也就是git pull -r或git pull --rebase。這樣的好處很明顯,提交記錄會比較簡潔。但有個缺點就是rebase以后我就不知道我的當前分支最早是從哪個分支拉出來的了,因為基底變了嘛,所以看個人需求了。
往公共分支上合代碼的時候,使用merge。如果使用rebase,那么其他開發人員想看主分支的歷史,就不是原來的歷史了,歷史已經被你篡改了。舉個例子解釋下,比如張三和李四從共同的節點拉出來開發,張三先開發完提交了兩次然后merge上去了,李四后來開發完如果rebase上去(注意李四需要切換到自己本地的主分支,假設先pull了張三的最新改動下來,然后執行,然后再git push到遠端),則李四的新提交變成了張三的新提交的新基底,本來李四的提交是最新的,結果最新的提交顯示反而是張三的,就亂套了。
正因如此,大部分公司其實會禁用rebase,不管是拉代碼還是push代碼統一都使用merge,雖然會多出無意義的一條提交記錄“Merge … to …”,但至少能清楚地知道主線上誰合了的代碼以及他們合代碼的時間先后順序
2.原理
變基操作的工作原理很簡單:Git 會讓我們想要移動的提交序列在目標分支上按照相同的順序重新再現一遍。這就相當于我們為各個原提交做了個副本,它們擁有相同的修改集、同一作者、日期以及注釋信息。
3.命令
# 可以是commit 版本號、分支名稱,合并多個提交到一個版本
$ git rebase -i [startpoint] [endpoint]
# 變基發生沖突時,解決后繼續變基
$ git rebase --continue
# 無視沖突,繼續變基操作
$ git rebase --skip
# 發生沖突時中斷變基
$ git rebase --abort"
-i的意思是--interactive,即彈出交互式的界面讓用戶編輯完成合并操作,[startpoint] [endpoint]則指定了一個編輯區間,如果不指定[endpoint],則該區間的終點默認是當前分支HEAD所指向的commit(注:該區間指定的是一個前開后閉的區間)。
Git 標簽
Git 中的tag指向一次commit的id,通常用來給開發分支做一個標記,如標記一個版本號。
1.添加標簽
git tag -a version -m "note"
注解:git tag 是打標簽的命令,-a 是添加標簽,其后要跟新標簽號,-m 及后面的字符串是對該標簽的注釋。
2.提交標簽到遠程倉庫
git push origin -tags
注解:就像git push origin master 把本地修改提交到遠程倉庫一樣,-tags可以把本地的打的標簽全部提交到遠程倉庫。
3.刪除標簽
git tag -d version
注解:-d 表示刪除,后面跟要刪除的tag名字
4.刪除遠程標簽
git push origin :refs/tags/version
注解:就像git push origin :branch_1
可以刪除遠程倉庫的分支branch_1一樣, 冒號前為空表示刪除遠程倉庫的tag。
5.查看標簽
git tag或者git tag -l
Git補丁
1.git fromat-patch
git format-patch 命令用于將 Git 倉庫中的提交記錄轉換為補丁文件。補丁文件可以用于將更改應用到其他 Git 倉庫或版本控制系統中。
# 生成單個補丁文件
git format-patch
# 和 是要生成補丁文件的提交范圍
git format-patch ..
# -n 選項可以指定要生成的補丁文件數量。
git format-patch -n ..
# --stdout 選項可以將補丁文件輸出到標準輸出中,包括提交信息和更改內容。
git format-patch --stdout
# --no-signature 選項可以生成不包含提交信息的補丁文件,只包含更改內容
git format-patch --no-signature
2.git apply
# 應用單個補丁文件,將補丁文件中的更改應用到工作目錄中。
git apply
# 應用多個補丁文件
git apply ...
# --ignore-space-change 選項可以忽略補丁文件中的空格更改。
git apply --ignore-space-change
# 應用補丁文件并忽略空格和換行符:
git apply --ignore-whitespace --ignore-whitespace
# --index 選項可以將更改添加到暫存區中,而不是僅僅應用更改到工作目錄中。
git apply --index
3.git am
- git am 和 git apply 命令都用于將補丁文件應用到 Git 倉庫中,但它們之間有一些區別。
- git am 命令可以自動創建一個新的提交,而 git apply 命令只會將更改應用到工作目錄中,需要手動創建一個新的提交。
- git am 命令可以應用交互式補丁,以便手動選擇要應用的更改,而 git apply 命令只能應用非交互式補丁。
- git am 命令可以應用多個補丁文件,并自動創建多個新的提交,而 git apply 命令只能應用一個補丁文件。
- git am 命令通常用于應用由 git format-patch 命令生成的補丁文件,而 git apply 命令可以應用由其他方式生成的補丁文件。
Git存儲
git stash將本地未提交代碼作為一個本地緩存。作用范圍為本地工作區以及本地暫存區。
1.使用場景
- 當正在dev分支上開發某個項目,這時項目中出現一個bug,需要緊急修復,但是正在開發的內容只是完成一半,還不想提交,這時可以用git stash命令將修改的內容保存至堆棧區,然后順利切換到hotfix分支進行bug修復,修復完成后,再次切回到dev分支,從堆棧中恢復剛剛保存的內容。
- 由于疏忽,本應該在dev分支開發的內容,卻在master上進行了開發,需要重新切回到dev分支上進行開發,可以用git stash將內容保存至堆棧中,切回到dev分支后,再次恢復內容即可。
2.git stash
# 保存當前工作區、暫存區的所有未提交代碼
# 執行存儲時,添加備注,方便查找
# git stash save “save message”
只有git stash 也是可以的,但查找時不方便識別。
3.查看stash列表
# 查看stash了哪些存儲
$ git stash list
# 顯示緩存的詳細信息,n為第幾個,默認數第一個,也就是0
$ git stash show stash@{0}
4.恢復緩存
git stash pop
命令恢復之前緩存的工作目錄,將緩存堆棧中的對應stash刪除,并將對應修改應用到當前的工作目錄下,默認為第一個stash,即stash@{0},如果要應用并刪除其他stash,命令:
git stash pop stash@{$num}
比如應用并刪除第二個:
git stash pop stash@{1}
5.git stash apply
使用apply命令恢復,stash列表中的信息是會繼續保留的,而使用pop命令進行恢復,會將stash列表中的信息進行刪除。參數同pop
6.刪除緩存
git stash drop stash@{num}
刪除某個保存,num是可選項,通過git stash list可查看具體值
7.刪除所有緩存
# 刪除所有緩存的stash
$ git stash clear
8.導出到文件
使用 git stash list 命令查看當前的 stash 棧,并確定要導出的 stash 的索引號。
使用 git stash show -p > stash.diff 命令將 stash 中的修改導出到一個文件中。其中, 是要導出的 stash 的索引號,-p 參數表示顯示修改的差異,stash.diff 是導出的文件名。
# 導出stash到文件
git stash show 1 -p > stash.diff
Git常用命令,基于使用步驟
使用步驟
提示:正常步驟應該是:先commit 然后pull 再 push ;
1.git config,配置Git
用于查看和修改Git配置信息,當安裝Git后首先要做的事情是設置用戶名稱和email地址。這是非常重要的,因為每次Git提交都會使用該用戶信息
#設置用戶信息
git config --global user.name “未進化的程序猿”
git config --global user.email “486566947@qq.com”
#讀取配置信息
git config --list
git config user.name
- /etc/gitconfig 文件:系統中對所有用戶都普遍適用的配置。若使用 git config 時用 --system 選項,讀寫的就是這個文件。
- ~/.gitconfig 文件:用戶目錄下的配置文件只適用于該用戶。若使用 git config 時用 --global 選項,讀寫的就是這個文件。
- 當前項目的 Git 目錄中的配置文件(也就是工作目錄中的 .git/config 文件):這里的配置僅僅針對當前項目有效。每一個級別的配置都會覆蓋上層的相同配置,所以 .git/config 里的配置會覆蓋 /etc/gitconfig 中的同名變量。
- 提示
- PHPstorm中使用Git時的賬號密碼同樣是Git配置中使用的賬號密碼
2.初始化倉庫,git init
Git 使用 git init 命令來初始化一個 Git 倉庫,Git 的很多命令都需要在 Git 的倉庫中運行,所以 git init 是使用 Git 的第一個命令。
在執行完成 git init 命令后,Git 倉庫會生成一個 .git 目錄,該目錄包含了資源的所有元數據,其他的項目目錄保持不變。
使用當前目錄作為 Git 倉庫,我們只需使它初始化。
git init
該命令執行完后會在當前目錄生成一個 .git 目錄。
使用我們指定目錄作為Git倉庫。
git init newrepo
初始化后,會在 newrepo 目錄下會出現一個名為 .git 的目錄,所有 Git 需要的數據和資源都存放在這個目錄中。
3.將文件納入版本控制 ,git add
如果當前目錄下有幾個文件想要納入版本控制,需要先用 git add 命令告訴 Git 開始對這些文件進行跟蹤,然后提交:
# 當前目錄下.c結尾的所有文件(通配符)
$ git add *.c
# 指定README等多個文件文件
$ git add README README.md
# .git add all無論在哪個目錄執行都會提交相應文件。
$ git add --all
# .git add .只能夠提交當前目錄或者它后代目錄下相應文件。
$ git add .
4. 提交文件 ,git commit
注意
注: 在 Linux 系統中,commit 信息使用單引號 ',Windows 系統,commit 信息使用雙引號 "。
所以在 git bash 中 git commit -m '提交說明' 這樣是可以的,在 Windows 命令行中就要使用雙引號 git commit -m "提交說明"。
暫存區保留本次變動的文件信息,等到修改了差不多了,就要把這些信息寫入歷史,這就相當于生成了當前項目的一個快照(snapshot)。
項目的歷史就是由不同時點的快照構成。Git 可以將項目恢復到任意一個快照??煺赵?Git 里面有一個專門名詞,叫做 commit,生成快照又稱為完成一次提交。
提示
每一次commit都會產生一個新的版本。產生一個代表版本號的散列值
git commit -m '初始化項目版本,記錄提交信息'
git commit -am "新增提交說明"
設置了用戶名和 Email,保存快照的時候,會記錄是誰提交的。
Git文件生命周期
5. 克隆倉庫,git clone
我們使用 git clone 從現有 Git 倉庫中拷貝項目,Git倉庫一般是一個遠程連接
git clone
- repo:Git 倉庫。
- directory:本地目錄。
6. 當前提交狀態,git status
git status 命令用于查看在你上次提交之后是否有對文件進行再次修改。
$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached ..." to unstage)
new file: README
new file: hello.php
通常我們使用 -s
參數來獲得簡短的輸出結果:
$ git status -s
AM README
A hello.php
AM 狀態的意思是這個文件在我們將它添加到緩存之后又有改動。
7. git diff,比較差異
git diff 命令比較文件的不同,即比較文件在暫存區和工作區的差異。(-c 上下文格式的diff、-u 合并格式diff)
git diff 命令顯示已寫入暫存區和已經被修改但尚未寫入暫存區文件的區別。
git diff 有兩個主要的應用場景。
- 尚未緩存的改動:git diff
- 查看已緩存的改動: git diff --cached
- 查看已緩存的與未緩存的所有改動:git diff HEAD
- 顯示摘要而非整個 diff:git diff --stat
顯示暫存區和工作區的差異:
$ git diff [file]
顯示兩次提交之間的差異:
$ git diff --cached [file]
或
$ git diff --staged [file]
顯示暫存區和上一次提交(commit)的差異:
$ git diff [first-branch]...[second-branch]
分支比較
# 直接將兩個分支上最新的提交做diff
$ git diff topic master [file]
$ #或
$ git diff topic..master [file]
# 輸出自topic和master分別開發以來,master分支上的變更。
$ git diff topic...master
# 查看簡單的diff結果,可以加上--stat參數
# --name-only輸出時只顯示文件名
提示
git diff命令,在終端高度小于輸出的文字高度時,會自動生成一個滾動區域,通過上、下來滾動查看文件差異。當窗口高度不夠時,輸出的內容較少,容易誤認為diff差異只有這么點
8. 回退版本,git reset
git reset 命令用于回退版本,可以指定退回某一次提交的版本。只對本地分支有效,對遠程分支無效
git reset [--soft | --mixed | --hard] [HEAD]
--mixed 為默認,可以不用帶該參數,用于重置暫存區的文件與上一次的提交(commit)保持一致,工作區文件內容保持不變。
git reset [HEAD]
git reset HEAD
執行 git reset HEAD 以取消之前 git add 添加,但不希望包含在下一提交快照中的緩存。
實例:
$ git reset HEAD^ # 回退所有內容到上一個版本
$ git reset HEAD^ hello.php # 回退 hello.php 文件的版本到上一個版本
$ git reset 052e # 回退到指定版本
--soft
參數用于回退到某個版本:
$ git reset --soft HEAD~3 # 回退上上上一個版本
--hard
參數撤銷工作區中所有未提交的修改內容,將暫存區與工作區都回到上一次版本,并刪除之前的所有信息提交:
$ git reset –hard HEAD~3 # 回退上上上一個版本
$ git reset –hard bae128 # 回退到某個版本回退點之前的所有信息。
$ git reset --hard origin/master # 將本地的狀態回退到和遠程的一樣
- HEAD 表示當前版本
- HEAD^ 上一個版本
- HEAD^^ 上上一個版本
- HEAD^^^ 上上上一個版本
HEAD~0 表示當前版本 - HEAD~1 上一個版本
- HEAD^2 上上一個版本
- HEAD^3 上上上一個版本
9.刪除文件,git rm
將文件從暫存區和工作區中刪除:
git rm
如果想把文件從暫存區域移除,但仍然希望保留在當前工作目錄中,換句話說,僅是從跟蹤清單中刪除,使用 --cached 選項即可:
git rm --cached
可以遞歸刪除,即如果后面跟的是一個目錄做為參數,則會遞歸刪除整個目錄中的所有子目錄和文件:
git rm –r *
10. 移動目錄、文件,git mv
git mv 命令用于移動或重命名一個文件、目錄或軟連接。
git mv [file] [newfile]
如果新文件名已經存在,但還是要重命名它,可以使用 -f 參數:
git mv -f [file] [newfile]
11.查看提交歷史git log、git blame
# 所有歷史記錄
$ git log
# 簡潔版
$ git log --oneline
# 拓撲圖版
$ git log --graph
# 反向顯示
$ git log --reverse
# 指定提交者
$ git log --author=lius
# 時間范圍
$ git log --before={3.weeks.ago} --after={2010-04-18}
# 反向顯示
$ git log --reverse
# 指定文件
# git blame
以上多種參數可以組合使用
12. git show
# 查看分支最后一次提交或版本在代碼層面的改動
$ git show |
13. git reflog
reflog是reference log的縮寫,含義是引用日志,它會記錄下HEAD節點和分支引用所指向的歷史??梢允褂胓it reflog命令來查看引用日志
14. git gc
gc 命令(gc 指的是垃圾回收)可用于清理版本庫,移除所有不屬于當前分支的提交對象。
15. git ls-files
查看一下git跟蹤了哪些文件,此命令就可以列出所有git正在跟蹤的文件
12. 撤銷文件 git restore
# 將在 工作區 但是 不在暫存區 的文件撤銷更改
$ git restore
# 作用是將 暫存區的文件從暫存區撤出,但不會更改文件
$ git restore --staged
問題記錄
1.push和commit的區別
git commit操作的是本地庫,git push操作的是遠程庫。
git commit是將本地修改過的文件提交到本地庫中。
git push是將本地庫中的最新信息發送給遠程庫。
如果本地不commit的話,修改的紀錄可能會丟失,而有些修改當前是不需要同步至服務器的,所以什么時候同步過去由用戶自己選擇。什么時候需要同步再push到服務器
2. pull requests 和 merge requests
github可以對不同的用戶賦予不同的分支權限,例如Gitlab中的:
- Guest:可以創建issue、發表評論,不能讀寫版本庫
- Reporter:可以克隆代碼,不能提交,QA、PM可以賦予這個權限
- Developer:可以克隆代碼、開發、提交、push,RD可以賦予這個權限
- Master:可以創建項目、添加tag、保護分支、添加項目成員、編輯項目,核心RD負責人可以賦予這個權限
- Owner:可以設置項目訪問權限 - Visibility Level、刪除項目、遷移項目、管理組成員,開發組leader可以賦予這個權限
github基于fork的模式下,PR用于請求分支管理員 合并自己提交的代碼(理解為請求拉取自己的代碼),merge requests同理;
3. fetch和pull的區別
git在本地會保存兩個版本的倉庫,分為本地倉庫和遠程倉庫。
fetch 只能更新遠程倉庫的代碼為最新的,本地倉庫的代碼還未被更新,我們需要通過 git merge origin/master 來合并這兩個版本,你可以把它理解為合并分支一樣的。
pull 操作是將本地倉庫和遠程倉庫(本地的)更新到遠程的最新版本。fetch+merge,自動進行合并
4.checkout和reset時的變化
簽出切換分支、版本、標簽時文件的變化
- 當執行 git reset HEAD 命令時,暫存區的目錄樹會被重寫,被 master 分支指向的目錄樹所替換,但是工作區不受影響。加上--hard時會強制替換工作區、暫存區的內容;
- git restore會清除暫存區的修改內容,例如修改了test.vue ,會變為未修改時的內容;
- 當執行 git rm --cached 命令時,會直接從暫存區刪除文件,工作區則不做出改變。
5. 保存遠程時輸入的賬號密碼
在 git bash 里輸入命令:
git config --global credential.helper store
然后執行 git 操作,輸入一遍密碼后就會記錄密碼,以后就不用輸入了。
要更改記錄的用戶名和密碼,只需要更改用戶目錄下的 .git-credentials 文件即可。
6.移除文件的版本控制
- 還沒有git add,在 .gitignore中添加
- 已經git add,先 git rm -r --cached 文件,然后在 .gitignore中添加
- 已經加到版本控制中,先 git rm -r --cached 文件,然后在 .gitignore中添加,最后 gti commit -m '提交.gitignore'
7.Git問題解決記錄
- git config --global credential.helper store,解決SSl連接錯誤
- 克隆一個指定目錄:https://blog.csdn.net/constant_rain/article/details/124913407
8.Github常規目錄說明:
- dist 是指編譯后的文件,可以理解為壓縮發布版
- src 源碼文件,適合動手能力強的童鞋
- docs 文檔
- examples 示例文件
- test 測試腳本
- .gitignore 告訴git哪些文件不要上傳到 GitHub,比如數據庫配置文件等
- LICENSE.txt 授權協議
- README.md 自述文件,整個項目的簡介、使用方法等
- bower.json Bower 包管理器配置文件
- package.json npm 包管理器配置文件
- 設置某個分支的權限(保護分支)
- 1. 管理員身份登錄GitHub,找到項目
2. Settings-->Branches-->Protected branches--->Choose a branch... ,選擇需要保護的分支,然后點擊edit按鈕,
3. Branch protection for 所選的分支名 --> 勾選Restrict who can push to this branch People and teams with push access
若不選擇任何人,則任何人都沒有push代碼到該分支的權限。
9. git做不到一個文件一個分支有一個分支沒有
例如一個數據庫配置文件,本地和線索不一樣,把它從暫存區拉出來,取消追蹤變成deleted狀態,本地文件實際還存在,同步到分支,遠程分支當前版本已經沒有這個文件了。
10. Git拉取指定分支
git clone 倉庫地址 //默認master分支
git clone -b 分支名 倉庫地址 //指定分支
11. git 常見的輸出內容
# 已經提交的改變
$ Changes to be committed:
# 暫未提交的改變
$ Changes not staged for commit:
# 常見狀態
deleted、modified
12.報錯non-fast-forward
non-fast-forward
Dealing with “non-fast-forward” errors:(From time to time you may encounter this error while pushing)
To prevent you from losing history, non-fast-forward updates were rejected. Merge the remote changes before pushing again. See the 'non-fast forward' section of 'git push --help' for details.
This error can be a bit overwhelming at first, do not fear. Simply put, git cannot make the change on the remote without losing commits, so it refuses the push. Usually this is caused by another user pushing to the same branch. You can remedy this by fetching and merging the remote branch, or using pull to perform both at once.
In other cases this error is a result of destructive changes made locally by using commands like git commit --amend or git rebase. While you can override the remote by adding --force to the push command, you should only do so if you are absolutely certain this is what you want to do. Force-pushes can cause issues for other users that have fetched the remote branch, and is considered bad practice. When in doubt, don’t force-push.
以上時較為官方的解釋,簡單說就是push之前需要先同步遠程版本。pull會自動合并,所以要改為fetch手動合并;
問題分析
可以這樣理解這個問題就是:別人上傳到遠程倉庫后,你沒有及時的同步(、拉?。┑奖镜?,但是你同時又添加了一些內容(提交),以致于你在提交時,它會檢測到你之前從遠程倉庫拉取的時候的倉庫狀態和現在的不一樣。于是,它為了安全起見拒絕了你的提交(然后就報了這個錯誤)。
“不能快速前進”的原因是因為路不一樣了,變得不好走了;體現在git里面就是提交歷史出現分叉,主線不再是一條直線,而是在前端出現了分叉,git不知道該如何前進,所以報錯了,讓你來覺得走哪條路!說的簡單點,就是遠程倉庫和本地倉庫不同步了
解決問題
1.先合并之前的歷史,再進行提交——提倡使用
先把git的東西fetch到你本地然后merge后再push,(如果有沖突就要解決沖突后再合并,沖突問題比較復雜,這里就不詳細說了),這樣就可以使遠程倉庫和你本地倉庫一致了,然后就可以提交修改了。
$ git fetch origin master
$ git merge origin FETCH_HEAD
# 提示:這2句命令等價于,但是使用git fetch + git merge 更加安全。
$ git pull origin master
# 然后執行,重新定基,可以使得歷史更加統一,即提交歷史趨向于一條直線。
$ git pull --rebase origin master
2.丟棄之前的歷史,強推(謹慎使用)
利用強覆蓋方式用你本地的代碼替代git倉庫內的內容,遠程倉庫的東西會被本地倉庫覆蓋?。?!
$ git push -f 或者 $ git push --force
官方文檔提示:This flag disables these checks, and can cause the remote repository to lose commits; use it with care.
(即:此標志禁用這些檢查,并可能導致遠程存儲庫丟失提交;小心使用。)
不僅在此處,在平時使用時,也要非常注意,除非你真的是想覆蓋遠程倉庫,不然最好不要強制執行。
13.徹底清理歷史版本
先創建一個分支,添加所有文件,刪除其他所有分支。
14.回退遠程倉庫的版本
先把遠程倉庫指定分支拉下來,手動回退,然后再強制推送上去(拉回來的遠程版本庫同時帶了這個分支的所有歷史版本);
Gitee和Github
1.查看fork的子倉
- github點擊 Insights -> fork
- gitee點擊fork邊上的數字
2.保護分支
- github默認只有分支創建者和倉庫管理員有push的權限,其他人可以提交PR
- gitee需要自己設置分支為保護分支,并在保護分支的設置內設置相應的保護級別和權限。
- github可以邀請別人成為協作者,個人倉庫的協作者可以拉?。ㄗx取)倉庫的內容并向倉庫推送(寫入)更改。
Markdown語法
- 使用 = 和 - 標記一級和二級標題
- 使用 # 號可表示 1-6 級標題,一級標題對應一個 # 號,二級標題對應兩個 # 號,以此類推。
- 可以在一行中用三個以上的星號、減號、底線來建立一個分隔線,行內不能有其他東西。
- 如果段落上的文字要添加刪除線,只需要在文字的兩端加上兩個波浪線 ~~ 即可
- 帶下劃線文本,以下是列表
- 有序列表使用數字并加上 . 號來表示,如:
- Markdown 區塊引用是在段落開頭使用 > 符號 ,然后后面緊跟一個空格符號,如果要在列表項目內放進區塊,那么就需要在 > 前添加四個空格的縮進。:
- 代碼區塊使用 4 個空格或者一個制表符(Tab 鍵)。如果是段落上的一個函數或片段的代碼可以用反引號把它包起來(`)。
- 鏈接使用方法如下: [鏈接名稱](鏈接地址),我們可以通過變量來設置一個鏈接,變量賦值在文檔末尾進行:
- Markdown 圖片語法格式如下:,表格語法如下:
- ``,表示單行代碼,``` ```,用于表示多行代碼。
資料來源:https://www.runoob.com/git/git-tutorial.html、http://git-scm.com/docs
查看Git命令的幫助信息,git
1.Git 工作區、暫存區和版本庫(以本地舉例)、遠程倉庫
- 工作區:就是你在電腦里能看到的目錄。
- 暫存區:英文叫
stage
或index
。一般存放在 .git 目錄下的 index 文件(.git/index)中,所以我們把暫存區有時也叫作索引(index)。 - 版本庫:工作區有一個隱藏目錄 .git,這個不算工作區,而是 Git 的版本庫。
- Git 工作區、暫存區和版本庫
- 圖中左側為工作區,右側為版本庫。在版本庫中標記為 "index" 的區域是暫存區(stage/index),標記為 "master" 的是 master 分支所代表的目錄樹。
- 圖中我們可以看出此時 "HEAD" 實際是指向 master 分支的一個"游標"。所以圖示的命令中出現 HEAD 的地方可以用 master 來替換。
- 圖中的 objects 標識的區域為 Git 的對象庫,實際位于 ".git/objects" 目錄下,里面包含了創建的各種對象及內容。
- 當對工作區修改(或新增)的文件執行 git add 命令時,暫存區的目錄樹被更新,同時工作區修改(或新增)的文件內容被寫入到對象庫中的一個新的對象中,而該對象的ID被記錄在暫存區的文件索引中。
- 當執行提交操作(git commit)時,暫存區的目錄樹寫到版本庫(對象庫)中,master 分支會做相應的更新。即 master 指向的目錄樹就是提交時暫存區的目錄樹。
- 當執行 git reset HEAD 命令時,暫存區的目錄樹會被重寫,被 master 分支指向的目錄樹所替換,但是工作區不受影響。
- 當執行 git rm --cached 命令時,會直接從暫存區刪除文件,工作區則不做出改變。
- 當執行 git checkout . 或者 git checkout -- 命令時,會用暫存區全部或指定的文件替換工作區的文件。這個操作很危險,會清除工作區中未添加到暫存區中的改動。
- 當執行 git checkout HEAD . 或者 git checkout HEAD 命令時,會用 HEAD 指向的 master 分支中的全部或者部分文件替換暫存區和以及工作區中的文件。這個命令也是極具危險性的,因為不但會清除工作區中未提交的改動,也會清除暫存區中未提交的改動。
2.Git文件狀態
在Git中文件大概分為四種狀態:已修改(modified)、已暫存(staged)、已提交(committed)、未追蹤(Untrack)
- .gitignore內的文件,不會擁有任何一種狀態,被git徹底無視。
- 處于ignore列表的文件,無法被add添加;但是可以強制添加
- 空目錄、以及子目錄全部是空目錄的目錄不會有Untrack狀態,也無法通過add改變狀態(無效)
- 工作目錄新增文件時,只要不處于ignore目錄,都會變成Untrack狀態;
- 沒有add過的文件或者被restore(不帶--staged)的文件,處于Untrack狀態;
- 初次add和被add后產生修改的文件,會處于modifed狀態。
- 處于modified狀態的文件,最開始可以進行add和restore兩種操作,此時的add操作叫做
更新要提交的內容
,add后變為staged狀態,restore(不加staged標記)后變為Untrack; - add后變為staged狀態的文件,可用restore --staged 變回modified狀態;這個staged狀態的內容可以用來恢復內容。沒有被add的modified狀態文件內容沒有被記錄(雖然有撤回,但是本質不一樣);
- 處于staged狀態的文件,在沒有commit之前再次產生修改時,會同時具有staged和modified兩個狀態(可以把statged狀態的內容拉回來,覆蓋。);但是commit時會使用內容最新的那個狀態;
- commit會提交所有staged狀態的文件,所以commit可以理解有一個modified到staged狀態的過程(實際可能不存在,因為暫存區本來就有變動的記錄);所以暫存狀態不能理解為處于暫存區,應當指的是被納入下一次提交的文件;任何被追蹤的產生修改的文件都會在暫存區被記錄;成為下一次提交的一部分;
- 未被追蹤的文件被刪除時,不會產生git狀態。處于modofy未add時,會變成deleted狀態;處于staged狀態會保持暫存狀態;
- 已經被刪除的(deleted狀態)被追蹤的文件,恢復后會變成modified狀態;
- 提示
- add的作用是將文件添加到暫存區,只有被add的文件才會被追蹤
- 暫存區
- (1)所謂的暫存區只是一個簡單的索引文件而已。
(2)暫存區這個索引文件里面包含的是文件的目錄樹,像一個虛擬的工作區,在這個虛擬工作區的目錄樹中,記錄了文件名、文件的時間戳、文件長度、文件類型以及最重要的SHA-1值,文件的內容并沒有存儲在其中,所以說 它像一個虛擬的工作區。
(3)索引指向的是.Git/objects下的文件。
(4)暫存區的作用:除非是繞過暫存區直接提交,否則Git想把修改提交上去,就必須將修改存入暫存區最后才能commit。每次提交的是暫存區所對應的文件快照。
拓展:status提示信息
Changes not staged for commit:
(use "git add ..." to update what will be committed)
(use "git checkout -- ..." to discard changes in working directory)
- 既然是Changes not staged for commit,就說明出現這個提示下的所有文件改動,都是存在于工作區的。stage是暫存區的意思,not stage說明都不在暫存區,那么說明在工作區。
- (use “git add …” to update what will be committed)。執行這條命令就可以工作區里面的改變加入到暫存區??梢詧绦術it add .把當前目錄下所有改動加入暫存區。
- (use “git checkout – …” to discard changes in working directory)。執行這條命令將丟棄在工作區的改動??梢詧绦術it checkout *把當前目錄下所有工作區的改動丟棄掉
Untracked files:
(use "git add ..." to include in what will be committed)
- Untracked files,就說明出現這個提示下的所有文件都是當前HEAD沒有被加入過的文件。這種改動也屬于工作區。
- (use “git add …” to include in what will be committed)。把Untracked files加入暫存區。
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
(use "git push" to publish your local commits)
- 當前分支比遠程分支多了一次commit
Your branch and 'origin/master' have diverged, and have 1 and 1 different commits each, respectively
pull報錯了,查看狀態顯示這個,先留著待解決吧
3.HEAD是什么
HEAD是Git中非常重要的一個概念,你可以稱它為指針或者引用,它可以指向任意一個節點,并且指向的節點始終為當前工作目錄,換句話說就是當前工作目錄(也就是你所看到的代碼)就是HEAD指向的節點。
4.git重命名檢測
Git 采用了不同的方法:它沒有選擇去存儲與文件移動操作相關的信息,而是采用了重命名檢測算法。在該算法中,如果一個文件在某一次提交中消失了,它依然會存在于其前次提交,而如果某個擁有相同名字或相似內容的文件出現在了另一個位置,Git 就會自動檢測到。如果是這種情況,Git 就會假定該文件被移動過了。
Git項目文件說明
Git init后主要有兩個重要的文件和目錄:.git目錄和.gitignore
1. .gitignore
.gitignore文件存在于根目錄(與.git同級的目錄)用于在將文件提交到git暫存區時,指定將哪些文件排除;
有時候你想添加(git add)一個文件到Git,但發現添加不了,多半原因是這個文件被.gitignore
忽略了
git add .不會添加被.gitignore忽視的文件,而git add -f . 強制添加所有文件,即使是.gitignore忽視的文件也添加。
當.gitignore文件不是你編寫的,但是它編寫的不符合實際需求,你可以使用git check-ignore命令進行檢查,看是哪一個規則有問題了
#檢測
git check-ignore -v App.class
#結果
.gitignore:3:*.class App.class
.gitignore只能忽略那些原來沒有被track的文件,如果某些文件已經被納入了版本管理中,則修改.gitignore是無效的。解決方法就是先把本地緩存刪除(改變成未track狀態),然后再提交。
git rm -r --cached .
git add .
git commit -m ‘update .gitignore’
也可以手動指定一個文件作為git忽略文件
git config core.excludesfile ***
對于全局Git配置,可以使用如下命令對全部倉庫進行配置。
git config --global core.excludesfile **/.gitignore(文件相對或絕對位置)
忽略規則如下:
- 空格不匹配任意文件,可作為分隔符,可用反斜杠轉義
- #開頭的文件標識注釋,可以使用反斜杠進行轉義
- ! 開頭的模式標識否定,該文件將會再次被包含,如果排除了該文件的父級目錄,則使用 ! 也不會再次被包含??梢允褂梅葱备苓M行轉義
- / 結束的模式只匹配文件夾以及在該文件夾路徑下的內容,但是不匹配該文件
- / 開始的模式匹配項目跟目錄
- 如果一個模式不包含斜杠,則它匹配相對于當前 .gitignore 文件路徑的內容,如果該模式不在 .gitignore 文件中,則相對于項目根目錄
- ** 匹配多級目錄,可在開始,中間,結束
- ? 通用匹配單個字符
- [] 通用匹配單個字符列表
- 各種項目的gitignore
- 參考地址:https://github.com/github/gitignore
2. .git目錄
任意文件夾中,用 git init 命令初始化倉庫,即可在此文件夾下創建 .git 文件夾(.打頭為隱藏文件夾,所以平時可能看不到)。這個文件夾之外的部分叫做工作區(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
此文件是一個臨時文件,存儲最后一次提交的信息內容,git commit 命令之后打開的編輯器就是在編輯此文件,而你退出編輯器后,git 會把此文件內容寫入 commit 記錄。
實際應用: git pull 遠程倉庫后,新增了很多提交,淹沒了本地提交記錄,直接 cat .git/COMMIT_EDITMSG 就可以弄清楚最后工作的位置了。
2. HEAD
此文件永遠存儲當前位置指針,就像 linux 中的 $PWD 變量和命令提示符的箭頭一樣,永遠指向當前位置,表明當前的工作位置。在 git 中 HEAD 永遠指向當前正在工作的那個 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 1654857613 +0800
committer Evan You 1654857613 +0800
chore: test pass
孤立head,不指向任何commit
3. ORIG_HEAD
正因為 HEAD 比較重要,此文件會在你進行危險操作時備份 HEAD,如以下操作時會觸發備份
git reset
git merge
git rebase
git pull
此文件應用示例
# 回滾到上一次的狀態(慎用!!!)
git reset --hard ORIG_HEAD
4. FETCH_HEAD
這個文件作用在于追蹤遠程分支的拉取與合并,與其相關的命令有 git pull/fetch/merge
,而git pull 命令相當于執行以下兩條命令:
$ 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 設置,典型內容如下:
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
[remote "origin"]
url = git@gitlab.xxxx.com/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]
段的內容跟 git config 命令對應
執行以下命令:
git config user.name abc
git config user.email abc@abc.com
會在 config 文件中追加以下內容:
... ...
[user]
name = abc
email = abc@abc.com
git config --global 影響的則是全局配置文件 ~/.gitconfig。
[remote] 段表示遠程倉庫配置
[branch] 段表示分支同步設置
假設當前在 master 分支,執行 git pull 若出現以下提示:
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 文件缺少對應的 [branch "master"] 字段。
解決方案為:
git branch -u origin/master master
# 或者執行一次 push
git push -u origin master
會出現提示:
Branch 'master' set up to track remote branch 'master' from 'origin'.
其實就是生成以下內容在 .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 服務并打開瀏覽器 http://127.0.0.1:1234 (試著改成對外IP并分享給別人?)
以下顯示當前的 git 倉庫名稱以及描述,默認的描述如下:
默認描述
上面這段話就是默認的 description
文件的內容,編輯這個文件來讓你 GitWeb
描述更友好。
7. hooks/目錄
存放 git hooks,用于在 git 命令前后做檢查或做些自定義動作。運行 ls -F1 .git/hooks
prepare-commit-msg.sample # git commit 之前,編輯器啟動之前觸發,傳入 COMMIT_FILE,COMMIT_SOURCE,SHA1
commit-msg.sample # git commit 之前,編輯器退出后觸發,傳入 COMMIT_EDITMSG 文件名
pre-commit.sample # git commit 之前,commit-msg 通過后觸發,譬如校驗文件名是否含中文
pre-push.sample # git push 之前觸發
pre-receive.sample # git push 之后,服務端更新 ref 前觸發
update.sample # git push 之后,服務端更新每一個 ref 時觸發,用于針對每個 ref 作校驗等
post-update.sample # git push 之后,服務端更新 ref 后觸發
pre-rebase.sample # git rebase 之前觸發,傳入 rebase 分支作參數
applypatch-msg.sample # 用于 git am 命令提交信息校驗
pre-applypatch.sample # 用于 git am 命令執行前動作
fsmonitor-watchman.sample # 配合 core.fsmonitor 設置來更好監測文件變化
參考
https://git-scm.com/docs/githooks
如果要啟用某個 hook,只需把 .sample 刪除即可,然后編輯其內容來實現相應的邏輯。
比如要校驗每個 commit message 至少要包含兩個單詞,否則就提示并拒絕提交,將 commit-msg.sample 改為 commit-msg 后,編輯如下:
#!/bin/sh
grep -q 'Ss+S' $1 || { echo '提交信息至少為兩個單詞' && exit 1; }
這樣當提交一個 commit 時,會執行 bash 命令: .git/hooks/commit-msg .git/COMMIT_EDITMSG,退出值不為 0,就拒絕提交。
8. info/目錄
此文件夾基本就有兩個文件:
- 文件 info/exclude 用于排除規則,與 .gitignore 功能類似。
- 可能會包含文件 info/refs ,用于跟蹤各分支的信息。此文件一般通過命令 git update-server-info 生成,內容通常如下:
9. logs/目錄
記錄了操作信息,git reflog 命令以及像 HEAD@{1} 形式的路徑會用到。如果刪除此文件夾(危險?。?,那么依賴于 reflog 的命令就會報錯。
文件夾 objects/
此文件夾簡單說,就是 git的數據庫,運行 tree .git/objects,可以看到目錄結構:
.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。
這個文件夾以及相應的算法,我沒找到獨立的名稱,就叫它 hash-object 體系吧。因為確實有個 git hash-object 命令存在,是一個底層的負責生成這些 loost objects 文件,如果要看到這些文件各自的含義,執行以下命令:
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
... ...
但你會發現這個列表里有些值在文件夾中并不存在,因為除了 loost objects 它還匯總了 pack 文件中的內容。hash文件
又稱為 loose object
,文件名稱共由40字符的 SHA-1 hash 值組成,其中前兩個字符為文件夾分桶,后38個字符為文件名稱。
按文件內容可分為四種類型:commit, tree, blob, tag,若執行以下命令會生成所有四種類型:
echo -en 'xxn' > xx # 共 3 個字符
git add .
git commit -m 'update xx'
git tag -a 'v1.0' -m 'release: 1.0.0'
經過以上操作后,對比一下文件樹,發現多了四個 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
其實這些文件都經過了壓縮,壓縮形式為 zlib。先安裝一下解壓工具 macOS 版 brew install pigz 或 windows 版 pigz,后執行:
$ pigz -d < .git/objects/cc/c9bd67dc5c467859102d53d54c5ce851273bdd
# BLOB類型,顯示結果為>>>>(注意xx后有個n)
blob 3xx
$pigz -d < .git/objects/30/20feea86d222d83218eb3eb5aa9f58f73df04d
# COMMIT類型,顯示結果為>>>>
commit 248tree adf4c9afac7afae3ff3e95e6c4eefe009d547f00
parent 0cd370696b581c38ee01e62b148a759f80facc2d
author jamesyang.yjm 1562044880 +0800
committer jamesyang.yjm 1562044880 +0800
update xx
$ pigz -d < .git/objects/ad/f4c9afac7afae3ff3e95e6c4eefe009d547f00
# TREE類型,顯示結果為>>>>
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類型,顯示結果為>>>>
tag 155object 3020feea86d222d83218eb3eb5aa9f58f73df04d
type commit
tag v1.0
tagger jamesyang.yjm 1562045942 +0800
release: 1.0.0
會發現,顯示結果都是 type size+內容 形式,這就是 object 文件的存儲格式:
[type] [size][NULL][content]
type
可選值:commit, tree, blob, tag,NULL 就是C語言里的字符結束符:?,size 就是 NULL后內容的字節長度。
type
的幾種類型可以使用 git cat-file -t hash 看到,內容可以用 git cat-file -p hash 看到。
git cat-file -t ccc9bd67dc5c467859102d53d54c5ce851273bdd
# 顯示結果為>>>>
blob
git cat-file -p ccc9bd67dc5c467859102d53d54c5ce851273bdd
# 顯示結果為>>>>
xx
所以 blob 文件就是對原文件內容的全量拷貝,同時前面加了 blob size?,而文件名稱的 hash 值計算是計算整體字符的 SHA-1 值:
echo -en 'blob 3?xxn' | shasum
# 顯示結果為>>>>
ccc9bd67dc5c467859102d53d54c5ce851273bdd -
知道原理后,其它類型格式請自行參考 斯坦福 Ben Lynn 所著的 GitMagic。
所以,當我們 git show 3020feea86d222d83218eb3eb5aa9f58f73df04d
時,會發生些什么?
找到 3020feea86d222d83218eb3eb5aa9f58f73df04d 這個 commit
,顯示出來
找到此 commit 關聯的 tree object: adf4c9afac7afae3ff3e95e6c4eefe009d547f00
,拉取相應的 blob 文件,并與當前工作區內的文件做 diff,然后顯示出來
這就是 objects/ 文件夾作為 git數據庫 被使用的真實例子。pack文件
為什么會有 .pack 文件?
由于每次 commit 都會生成許多 hash文件,并且由于 blob 文件都是全量存儲的,導致 git 效率下降,于是有了 pack-format,優勢:
對于大倉庫存儲效率高
利于網絡傳輸,便于備份
增量存儲,優化磁盤空間
將 .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
可以看到文件數量減小了不少,其中大部分文件被打到一個 .pack 包中,并且是增量存儲,有部分變更的文件只存儲 基礎hash + 變更內容,磁盤空間優化很明顯。
git gc 其實運行了兩條命令:git repack 用來打包 和 git prune-packed 用來移除已打包的 hash文件;
11.文件夾refs
refs 可以理解成文件系統中的 symbol link,看下結構:
$ 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/ 文件夾內的 ref 一般通過 git branch
生成。git show-ref --heads
可以查看。
refs/tags/ 文件夾內的 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,當然在這里叫做 ref。
而 git tag
命令本質與 git branch
相同,只生成一個 ref 放在 tags 目錄下,所以被稱為 lightweight tag。
而 git tag -a xx
命令會首先生成一個類型為 tag 的 hash文件 放到 objects/ 目錄,然后生成 ref 放到 tags 目錄下指向那個文件。這就叫做 annotated tag,好處是可包含一些元信息如 tagger 和 message,被 git 的 hash-object 算法管理,可被 GPG 簽名等,所以更穩定,更安全。
使用以下命令來拿到 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
這兩行發生了變化。也就是每次 commit 時,HEAD 與 heads 都會自動更新。
12. index文件
文件保存成二進制對象以后,還需要通知 Git 哪些文件發生了變動。所有變動的文件,Git 都記錄在一個區域,叫做"暫存區"(英文叫做 index 或者 stage)。等到變動告一段落,再統一把暫存區里面的文件寫入正式的版本歷史。
git update-index命令用于在暫存區記錄一個發生變動的文件。
$ git update-index --add --cacheinfo 100644
3b18e512dba79e4c8300dd08aeb37f8e728b8dad test.txt
上面命令向暫存區寫入文件名test.txt、二進制對象名(哈希值)和文件權限。
git ls-files命令可以顯示暫存區當前的內容。
$ git ls-files --stage
100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0 test.txt
上面代碼表示,暫存區現在只有一個文件test.txt,以及它的二進制對象名和權限。知道了二進制對象名,就可以在.git/objects子目錄里面讀出這個文件的內容。
git status命令會產生更可讀的結果。
$ git status
要提交的變更:
新文件: test.txt
上面代碼表示,暫存區里面只有一個新文件test.txt,等待寫入歷史。
資料來源
參考:https://developer.aliyun.com/article/716483
Git遠程倉庫
Git 并不像 SVN 那樣有個中心服務器。目前我們使用到的 Git 命令都是在本地執行,如果你想通過 Git 分享你的代碼或者與其他開發人員合作。 你就需要將數據放到一臺其他開發人員能夠連接的服務器上。
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 git@github.com:tianqixin/runoob-git-test.git (fetch)
origin git@github.com:tianqixin/runoob-git-test.git (push)
3.獲取遠端倉庫代碼 git fetch
# 只能fetch到一個空白的分支,然后可以手動merge
$ git fetch <遠程主機名> <遠程分支名>:<本地分支名>
不填的話都是默認
4.拉取 git pull
git pull <遠程主機名> <遠程分支名>:<本地分支名>
# 允許合并不相關的分支
$ git pull --allow-unrelated-histories
git pull操作其實是git fetch 與 git merge 兩個命令的集合。 git fetch 和 git merge FETCH_HEAD 的簡寫。
相關文檔:https://www.runoob.com/git/git-remote-repo.html
5.推送 git push
# 基本
$ git push <遠程主機名> <本地分支名>:<遠程分支名>
# 強制推送
$ git push --force origin master
# 刪除遠程分支
$ git push origin --delete master
# 允許合并不相關的分支
$ git push --allow-unrelated-histories
提示
如果另一個開發者在我們之前已經做過一次 push 操作,此次 push 命令就會被拒絕傳送提交。這時候,我們必須要先做一次 pull 操作,將其他人新上載的更新取回,并本地合并。
如果本地分支名與遠程分支名相同,則可以省略冒號,帶上-u 參數相當于記錄了push到遠端分支的默認值,這樣當下次我們還想要繼續push的這個遠端分支的時候推送命令就可以簡寫成git push即可。
Git 分支
1.創建分支命令 git branch
# 創建分支
$ git branch
# 創建分支并跟蹤遠程分支
$ git branch -u o/master foo
2.切換分支命令 git checkout
第一作用是切換分支,第二個是撤銷修改。
# 切換指定分支
$ git checkout ||
# 創建一個的分支,它跟蹤遠程分支
$ git checkout -b 本地分支名x origin/遠程分支名x
# 從暫存區恢復到工作區
$ git checkout .
提示
當你切換分支的時候,Git 會用該分支的最后提交的快照替換你的工作目錄的內容, 所以多個分支不需要多個目錄。
實際測試:
假設分支1 a文件未提交,分支2 a文件已提交。切換到到分支2時會替換 分支1的a文件。1切換到2時也會替換a文件;
兩個分支都已經提交的 切換時會互相替換。一個提交一個沒提交時,從a到b,b會保持a的暫存區和工作區
3.合并分支命令 git merge
# 合并指定分支到當前分支
$ git merge
4.刪除分支 git branch -d
# 刪除指定分支
$ git branch -d
5.分支列表 git branch
# 列出所有分支
$ git branch
# 查看遠程所有分支
$ git branch -r
# 查看本地和遠程所有分支
$ git branch -a
列出分支時,帶*號的分支為當前活動分支
5.重命名分支 git branch -M
# 重命名指定分支
# 不填old默認重命名當前分支
$ git branch -m old new
# 強制重命名指定分支
$ git branch -M old new
git rebase 變基
1.介紹
Git rebase,通常被稱作變基或衍合, 可以理解為另外一種合并的方式,與merge 會保留分支結構和原始提交記錄不同,rebase 是在公共祖先的基礎上,把新的提交鏈截取下來,在目標分支上進行重放,逐個應用選中的提交來完成合并。
不同公司,不同情況有不同使用場景,不過大部分情況推薦如下:
自己單機的時候,拉公共分支最新代碼的時候使用rebase,也就是git pull -r或git pull --rebase。這樣的好處很明顯,提交記錄會比較簡潔。但有個缺點就是rebase以后我就不知道我的當前分支最早是從哪個分支拉出來的了,因為基底變了嘛,所以看個人需求了。
往公共分支上合代碼的時候,使用merge。如果使用rebase,那么其他開發人員想看主分支的歷史,就不是原來的歷史了,歷史已經被你篡改了。舉個例子解釋下,比如張三和李四從共同的節點拉出來開發,張三先開發完提交了兩次然后merge上去了,李四后來開發完如果rebase上去(注意李四需要切換到自己本地的主分支,假設先pull了張三的最新改動下來,然后執行,然后再git push到遠端),則李四的新提交變成了張三的新提交的新基底,本來李四的提交是最新的,結果最新的提交顯示反而是張三的,就亂套了。
正因如此,大部分公司其實會禁用rebase,不管是拉代碼還是push代碼統一都使用merge,雖然會多出無意義的一條提交記錄“Merge … to …”,但至少能清楚地知道主線上誰合了的代碼以及他們合代碼的時間先后順序
2.原理
變基操作的工作原理很簡單:Git 會讓我們想要移動的提交序列在目標分支上按照相同的順序重新再現一遍。這就相當于我們為各個原提交做了個副本,它們擁有相同的修改集、同一作者、日期以及注釋信息。
3.命令
# 可以是commit 版本號、分支名稱,合并多個提交到一個版本
$ git rebase -i [startpoint] [endpoint]
# 變基發生沖突時,解決后繼續變基
$ git rebase --continue
# 無視沖突,繼續變基操作
$ git rebase --skip
# 發生沖突時中斷變基
$ git rebase --abort"
-i的意思是--interactive,即彈出交互式的界面讓用戶編輯完成合并操作,[startpoint] [endpoint]則指定了一個編輯區間,如果不指定[endpoint],則該區間的終點默認是當前分支HEAD所指向的commit(注:該區間指定的是一個前開后閉的區間)。
Git 標簽
Git 中的tag指向一次commit的id,通常用來給開發分支做一個標記,如標記一個版本號。
1.添加標簽
git tag -a version -m "note"
注解:git tag 是打標簽的命令,-a 是添加標簽,其后要跟新標簽號,-m 及后面的字符串是對該標簽的注釋。
2.提交標簽到遠程倉庫
git push origin -tags
注解:就像git push origin master 把本地修改提交到遠程倉庫一樣,-tags可以把本地的打的標簽全部提交到遠程倉庫。
3.刪除標簽
git tag -d version
注解:-d 表示刪除,后面跟要刪除的tag名字
4.刪除遠程標簽
git push origin :refs/tags/version
注解:就像git push origin :branch_1
可以刪除遠程倉庫的分支branch_1一樣, 冒號前為空表示刪除遠程倉庫的tag。
5.查看標簽
git tag或者git tag -l
Git補丁
1.git fromat-patch
git format-patch 命令用于將 Git 倉庫中的提交記錄轉換為補丁文件。補丁文件可以用于將更改應用到其他 Git 倉庫或版本控制系統中。
# 生成單個補丁文件
git format-patch
# 和 是要生成補丁文件的提交范圍
git format-patch ..
# -n 選項可以指定要生成的補丁文件數量。
git format-patch -n ..
# --stdout 選項可以將補丁文件輸出到標準輸出中,包括提交信息和更改內容。
git format-patch --stdout
# --no-signature 選項可以生成不包含提交信息的補丁文件,只包含更改內容
git format-patch --no-signature
2.git apply
# 應用單個補丁文件,將補丁文件中的更改應用到工作目錄中。
git apply
# 應用多個補丁文件
git apply ...
# --ignore-space-change 選項可以忽略補丁文件中的空格更改。
git apply --ignore-space-change
# 應用補丁文件并忽略空格和換行符:
git apply --ignore-whitespace --ignore-whitespace
# --index 選項可以將更改添加到暫存區中,而不是僅僅應用更改到工作目錄中。
git apply --index
3.git am
- git am 和 git apply 命令都用于將補丁文件應用到 Git 倉庫中,但它們之間有一些區別。
- git am 命令可以自動創建一個新的提交,而 git apply 命令只會將更改應用到工作目錄中,需要手動創建一個新的提交。
- git am 命令可以應用交互式補丁,以便手動選擇要應用的更改,而 git apply 命令只能應用非交互式補丁。
- git am 命令可以應用多個補丁文件,并自動創建多個新的提交,而 git apply 命令只能應用一個補丁文件。
- git am 命令通常用于應用由 git format-patch 命令生成的補丁文件,而 git apply 命令可以應用由其他方式生成的補丁文件。
Git存儲
git stash將本地未提交代碼作為一個本地緩存。作用范圍為本地工作區以及本地暫存區。
1.使用場景
- 當正在dev分支上開發某個項目,這時項目中出現一個bug,需要緊急修復,但是正在開發的內容只是完成一半,還不想提交,這時可以用git stash命令將修改的內容保存至堆棧區,然后順利切換到hotfix分支進行bug修復,修復完成后,再次切回到dev分支,從堆棧中恢復剛剛保存的內容。
- 由于疏忽,本應該在dev分支開發的內容,卻在master上進行了開發,需要重新切回到dev分支上進行開發,可以用git stash將內容保存至堆棧中,切回到dev分支后,再次恢復內容即可。
2.git stash
# 保存當前工作區、暫存區的所有未提交代碼
# 執行存儲時,添加備注,方便查找
# git stash save “save message”
只有git stash 也是可以的,但查找時不方便識別。
3.查看stash列表
# 查看stash了哪些存儲
$ git stash list
# 顯示緩存的詳細信息,n為第幾個,默認數第一個,也就是0
$ git stash show stash@{0}
4.恢復緩存
git stash pop
命令恢復之前緩存的工作目錄,將緩存堆棧中的對應stash刪除,并將對應修改應用到當前的工作目錄下,默認為第一個stash,即stash@{0},如果要應用并刪除其他stash,命令:
git stash pop stash@{$num}
比如應用并刪除第二個:
git stash pop stash@{1}
5.git stash apply
使用apply命令恢復,stash列表中的信息是會繼續保留的,而使用pop命令進行恢復,會將stash列表中的信息進行刪除。參數同pop
6.刪除緩存
git stash drop stash@{num}
刪除某個保存,num是可選項,通過git stash list可查看具體值
7.刪除所有緩存
# 刪除所有緩存的stash
$ git stash clear
8.導出到文件
使用 git stash list 命令查看當前的 stash 棧,并確定要導出的 stash 的索引號。
使用 git stash show -p > stash.diff 命令將 stash 中的修改導出到一個文件中。其中, 是要導出的 stash 的索引號,-p 參數表示顯示修改的差異,stash.diff 是導出的文件名。
# 導出stash到文件
git stash show 1 -p > stash.diff
Git常用命令,基于使用步驟
使用步驟
提示:正常步驟應該是:先commit 然后pull 再 push ;
1.git config,配置Git
用于查看和修改Git配置信息,當安裝Git后首先要做的事情是設置用戶名稱和email地址。這是非常重要的,因為每次Git提交都會使用該用戶信息
#設置用戶信息
git config --global user.name “未進化的程序猿”
git config --global user.email “486566947@qq.com”
#讀取配置信息
git config --list
git config user.name
- /etc/gitconfig 文件:系統中對所有用戶都普遍適用的配置。若使用 git config 時用 --system 選項,讀寫的就是這個文件。
- ~/.gitconfig 文件:用戶目錄下的配置文件只適用于該用戶。若使用 git config 時用 --global 選項,讀寫的就是這個文件。
- 當前項目的 Git 目錄中的配置文件(也就是工作目錄中的 .git/config 文件):這里的配置僅僅針對當前項目有效。每一個級別的配置都會覆蓋上層的相同配置,所以 .git/config 里的配置會覆蓋 /etc/gitconfig 中的同名變量。
- 提示
- PHPstorm中使用Git時的賬號密碼同樣是Git配置中使用的賬號密碼
2.初始化倉庫,git init
Git 使用 git init 命令來初始化一個 Git 倉庫,Git 的很多命令都需要在 Git 的倉庫中運行,所以 git init 是使用 Git 的第一個命令。
在執行完成 git init 命令后,Git 倉庫會生成一個 .git 目錄,該目錄包含了資源的所有元數據,其他的項目目錄保持不變。
使用當前目錄作為 Git 倉庫,我們只需使它初始化。
git init
該命令執行完后會在當前目錄生成一個 .git 目錄。
使用我們指定目錄作為Git倉庫。
git init newrepo
初始化后,會在 newrepo 目錄下會出現一個名為 .git 的目錄,所有 Git 需要的數據和資源都存放在這個目錄中。
3.將文件納入版本控制 ,git add
如果當前目錄下有幾個文件想要納入版本控制,需要先用 git add 命令告訴 Git 開始對這些文件進行跟蹤,然后提交:
# 當前目錄下.c結尾的所有文件(通配符)
$ git add *.c
# 指定README等多個文件文件
$ git add README README.md
# .git add all無論在哪個目錄執行都會提交相應文件。
$ git add --all
# .git add .只能夠提交當前目錄或者它后代目錄下相應文件。
$ git add .
4. 提交文件 ,git commit
注意
注: 在 Linux 系統中,commit 信息使用單引號 ',Windows 系統,commit 信息使用雙引號 "。
所以在 git bash 中 git commit -m '提交說明' 這樣是可以的,在 Windows 命令行中就要使用雙引號 git commit -m "提交說明"。
暫存區保留本次變動的文件信息,等到修改了差不多了,就要把這些信息寫入歷史,這就相當于生成了當前項目的一個快照(snapshot)。
項目的歷史就是由不同時點的快照構成。Git 可以將項目恢復到任意一個快照??煺赵?Git 里面有一個專門名詞,叫做 commit,生成快照又稱為完成一次提交。
提示
每一次commit都會產生一個新的版本。產生一個代表版本號的散列值
git commit -m '初始化項目版本,記錄提交信息'
git commit -am "新增提交說明"
設置了用戶名和 Email,保存快照的時候,會記錄是誰提交的。
Git文件生命周期
5. 克隆倉庫,git clone
我們使用 git clone 從現有 Git 倉庫中拷貝項目,Git倉庫一般是一個遠程連接
git clone
- repo:Git 倉庫。
- directory:本地目錄。
6. 當前提交狀態,git status
git status 命令用于查看在你上次提交之后是否有對文件進行再次修改。
$ git status
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached ..." to unstage)
new file: README
new file: hello.php
通常我們使用 -s
參數來獲得簡短的輸出結果:
$ git status -s
AM README
A hello.php
AM 狀態的意思是這個文件在我們將它添加到緩存之后又有改動。
7. git diff,比較差異
git diff 命令比較文件的不同,即比較文件在暫存區和工作區的差異。(-c 上下文格式的diff、-u 合并格式diff)
git diff 命令顯示已寫入暫存區和已經被修改但尚未寫入暫存區文件的區別。
git diff 有兩個主要的應用場景。
- 尚未緩存的改動:git diff
- 查看已緩存的改動: git diff --cached
- 查看已緩存的與未緩存的所有改動:git diff HEAD
- 顯示摘要而非整個 diff:git diff --stat
顯示暫存區和工作區的差異:
$ git diff [file]
顯示兩次提交之間的差異:
$ git diff --cached [file]
或
$ git diff --staged [file]
顯示暫存區和上一次提交(commit)的差異:
$ git diff [first-branch]...[second-branch]
分支比較
# 直接將兩個分支上最新的提交做diff
$ git diff topic master [file]
$ #或
$ git diff topic..master [file]
# 輸出自topic和master分別開發以來,master分支上的變更。
$ git diff topic...master
# 查看簡單的diff結果,可以加上--stat參數
# --name-only輸出時只顯示文件名
提示
git diff命令,在終端高度小于輸出的文字高度時,會自動生成一個滾動區域,通過上、下來滾動查看文件差異。當窗口高度不夠時,輸出的內容較少,容易誤認為diff差異只有這么點
8. 回退版本,git reset
git reset 命令用于回退版本,可以指定退回某一次提交的版本。只對本地分支有效,對遠程分支無效
git reset [--soft | --mixed | --hard] [HEAD]
--mixed 為默認,可以不用帶該參數,用于重置暫存區的文件與上一次的提交(commit)保持一致,工作區文件內容保持不變。
git reset [HEAD]
git reset HEAD
執行 git reset HEAD 以取消之前 git add 添加,但不希望包含在下一提交快照中的緩存。
實例:
$ git reset HEAD^ # 回退所有內容到上一個版本
$ git reset HEAD^ hello.php # 回退 hello.php 文件的版本到上一個版本
$ git reset 052e # 回退到指定版本
--soft
參數用于回退到某個版本:
$ git reset --soft HEAD~3 # 回退上上上一個版本
--hard
參數撤銷工作區中所有未提交的修改內容,將暫存區與工作區都回到上一次版本,并刪除之前的所有信息提交:
$ git reset –hard HEAD~3 # 回退上上上一個版本
$ git reset –hard bae128 # 回退到某個版本回退點之前的所有信息。
$ git reset --hard origin/master # 將本地的狀態回退到和遠程的一樣
- HEAD 表示當前版本
- HEAD^ 上一個版本
- HEAD^^ 上上一個版本
- HEAD^^^ 上上上一個版本
HEAD~0 表示當前版本 - HEAD~1 上一個版本
- HEAD^2 上上一個版本
- HEAD^3 上上上一個版本
9.刪除文件,git rm
將文件從暫存區和工作區中刪除:
git rm
如果想把文件從暫存區域移除,但仍然希望保留在當前工作目錄中,換句話說,僅是從跟蹤清單中刪除,使用 --cached 選項即可:
git rm --cached
可以遞歸刪除,即如果后面跟的是一個目錄做為參數,則會遞歸刪除整個目錄中的所有子目錄和文件:
git rm –r *
10. 移動目錄、文件,git mv
git mv 命令用于移動或重命名一個文件、目錄或軟連接。
git mv [file] [newfile]
如果新文件名已經存在,但還是要重命名它,可以使用 -f 參數:
git mv -f [file] [newfile]
11.查看提交歷史git log、git blame
# 所有歷史記錄
$ git log
# 簡潔版
$ git log --oneline
# 拓撲圖版
$ git log --graph
# 反向顯示
$ git log --reverse
# 指定提交者
$ git log --author=lius
# 時間范圍
$ git log --before={3.weeks.ago} --after={2010-04-18}
# 反向顯示
$ git log --reverse
# 指定文件
# git blame
以上多種參數可以組合使用
12. git show
# 查看分支最后一次提交或版本在代碼層面的改動
$ git show |
13. git reflog
reflog是reference log的縮寫,含義是引用日志,它會記錄下HEAD節點和分支引用所指向的歷史??梢允褂胓it reflog命令來查看引用日志
14. git gc
gc 命令(gc 指的是垃圾回收)可用于清理版本庫,移除所有不屬于當前分支的提交對象。
15. git ls-files
查看一下git跟蹤了哪些文件,此命令就可以列出所有git正在跟蹤的文件
12. 撤銷文件 git restore
# 將在 工作區 但是 不在暫存區 的文件撤銷更改
$ git restore
# 作用是將 暫存區的文件從暫存區撤出,但不會更改文件
$ git restore --staged
問題記錄
1.push和commit的區別
git commit操作的是本地庫,git push操作的是遠程庫。
git commit是將本地修改過的文件提交到本地庫中。
git push是將本地庫中的最新信息發送給遠程庫。
如果本地不commit的話,修改的紀錄可能會丟失,而有些修改當前是不需要同步至服務器的,所以什么時候同步過去由用戶自己選擇。什么時候需要同步再push到服務器
2. pull requests 和 merge requests
github可以對不同的用戶賦予不同的分支權限,例如Gitlab中的:
- Guest:可以創建issue、發表評論,不能讀寫版本庫
- Reporter:可以克隆代碼,不能提交,QA、PM可以賦予這個權限
- Developer:可以克隆代碼、開發、提交、push,RD可以賦予這個權限
- Master:可以創建項目、添加tag、保護分支、添加項目成員、編輯項目,核心RD負責人可以賦予這個權限
- Owner:可以設置項目訪問權限 - Visibility Level、刪除項目、遷移項目、管理組成員,開發組leader可以賦予這個權限
github基于fork的模式下,PR用于請求分支管理員 合并自己提交的代碼(理解為請求拉取自己的代碼),merge requests同理;
3. fetch和pull的區別
git在本地會保存兩個版本的倉庫,分為本地倉庫和遠程倉庫。
fetch 只能更新遠程倉庫的代碼為最新的,本地倉庫的代碼還未被更新,我們需要通過 git merge origin/master 來合并這兩個版本,你可以把它理解為合并分支一樣的。
pull 操作是將本地倉庫和遠程倉庫(本地的)更新到遠程的最新版本。fetch+merge,自動進行合并
4.checkout和reset時的變化
簽出切換分支、版本、標簽時文件的變化
- 當執行 git reset HEAD 命令時,暫存區的目錄樹會被重寫,被 master 分支指向的目錄樹所替換,但是工作區不受影響。加上--hard時會強制替換工作區、暫存區的內容;
- git restore會清除暫存區的修改內容,例如修改了test.vue ,會變為未修改時的內容;
- 當執行 git rm --cached 命令時,會直接從暫存區刪除文件,工作區則不做出改變。
5. 保存遠程時輸入的賬號密碼
在 git bash 里輸入命令:
git config --global credential.helper store
然后執行 git 操作,輸入一遍密碼后就會記錄密碼,以后就不用輸入了。
要更改記錄的用戶名和密碼,只需要更改用戶目錄下的 .git-credentials 文件即可。
6.移除文件的版本控制
- 還沒有git add,在 .gitignore中添加
- 已經git add,先 git rm -r --cached 文件,然后在 .gitignore中添加
- 已經加到版本控制中,先 git rm -r --cached 文件,然后在 .gitignore中添加,最后 gti commit -m '提交.gitignore'
7.Git問題解決記錄
- git config --global credential.helper store,解決SSl連接錯誤
- 克隆一個指定目錄:https://blog.csdn.net/constant_rain/article/details/124913407
8.Github常規目錄說明:
- dist 是指編譯后的文件,可以理解為壓縮發布版
- src 源碼文件,適合動手能力強的童鞋
- docs 文檔
- examples 示例文件
- test 測試腳本
- .gitignore 告訴git哪些文件不要上傳到 GitHub,比如數據庫配置文件等
- LICENSE.txt 授權協議
- README.md 自述文件,整個項目的簡介、使用方法等
- bower.json Bower 包管理器配置文件
- package.json npm 包管理器配置文件
- 設置某個分支的權限(保護分支)
- 1. 管理員身份登錄GitHub,找到項目
2. Settings-->Branches-->Protected branches--->Choose a branch... ,選擇需要保護的分支,然后點擊edit按鈕,
3. Branch protection for 所選的分支名 --> 勾選Restrict who can push to this branch People and teams with push access
若不選擇任何人,則任何人都沒有push代碼到該分支的權限。
9. git做不到一個文件一個分支有一個分支沒有
例如一個數據庫配置文件,本地和線索不一樣,把它從暫存區拉出來,取消追蹤變成deleted狀態,本地文件實際還存在,同步到分支,遠程分支當前版本已經沒有這個文件了。
10. Git拉取指定分支
git clone 倉庫地址 //默認master分支
git clone -b 分支名 倉庫地址 //指定分支
11. git 常見的輸出內容
# 已經提交的改變
$ Changes to be committed:
# 暫未提交的改變
$ Changes not staged for commit:
# 常見狀態
deleted、modified
12.報錯non-fast-forward
non-fast-forward
Dealing with “non-fast-forward” errors:(From time to time you may encounter this error while pushing)
To prevent you from losing history, non-fast-forward updates were rejected. Merge the remote changes before pushing again. See the 'non-fast forward' section of 'git push --help' for details.
This error can be a bit overwhelming at first, do not fear. Simply put, git cannot make the change on the remote without losing commits, so it refuses the push. Usually this is caused by another user pushing to the same branch. You can remedy this by fetching and merging the remote branch, or using pull to perform both at once.
In other cases this error is a result of destructive changes made locally by using commands like git commit --amend or git rebase. While you can override the remote by adding --force to the push command, you should only do so if you are absolutely certain this is what you want to do. Force-pushes can cause issues for other users that have fetched the remote branch, and is considered bad practice. When in doubt, don’t force-push.
以上時較為官方的解釋,簡單說就是push之前需要先同步遠程版本。pull會自動合并,所以要改為fetch手動合并;
問題分析
可以這樣理解這個問題就是:別人上傳到遠程倉庫后,你沒有及時的同步(、拉?。┑奖镜?,但是你同時又添加了一些內容(提交),以致于你在提交時,它會檢測到你之前從遠程倉庫拉取的時候的倉庫狀態和現在的不一樣。于是,它為了安全起見拒絕了你的提交(然后就報了這個錯誤)。
“不能快速前進”的原因是因為路不一樣了,變得不好走了;體現在git里面就是提交歷史出現分叉,主線不再是一條直線,而是在前端出現了分叉,git不知道該如何前進,所以報錯了,讓你來覺得走哪條路!說的簡單點,就是遠程倉庫和本地倉庫不同步了
解決問題
1.先合并之前的歷史,再進行提交——提倡使用
先把git的東西fetch到你本地然后merge后再push,(如果有沖突就要解決沖突后再合并,沖突問題比較復雜,這里就不詳細說了),這樣就可以使遠程倉庫和你本地倉庫一致了,然后就可以提交修改了。
$ git fetch origin master
$ git merge origin FETCH_HEAD
# 提示:這2句命令等價于,但是使用git fetch + git merge 更加安全。
$ git pull origin master
# 然后執行,重新定基,可以使得歷史更加統一,即提交歷史趨向于一條直線。
$ git pull --rebase origin master
2.丟棄之前的歷史,強推(謹慎使用)
利用強覆蓋方式用你本地的代碼替代git倉庫內的內容,遠程倉庫的東西會被本地倉庫覆蓋?。?!
$ git push -f 或者 $ git push --force
官方文檔提示:This flag disables these checks, and can cause the remote repository to lose commits; use it with care.
(即:此標志禁用這些檢查,并可能導致遠程存儲庫丟失提交;小心使用。)
不僅在此處,在平時使用時,也要非常注意,除非你真的是想覆蓋遠程倉庫,不然最好不要強制執行。
13.徹底清理歷史版本
先創建一個分支,添加所有文件,刪除其他所有分支。
14.回退遠程倉庫的版本
先把遠程倉庫指定分支拉下來,手動回退,然后再強制推送上去(拉回來的遠程版本庫同時帶了這個分支的所有歷史版本);
Gitee和Github
1.查看fork的子倉
- github點擊 Insights -> fork
- gitee點擊fork邊上的數字
2.保護分支
- github默認只有分支創建者和倉庫管理員有push的權限,其他人可以提交PR
- gitee需要自己設置分支為保護分支,并在保護分支的設置內設置相應的保護級別和權限。
- github可以邀請別人成為協作者,個人倉庫的協作者可以拉?。ㄗx?。﹤}庫的內容并向倉庫推送(寫入)更改。
Markdown語法
- 使用 = 和 - 標記一級和二級標題
- 使用 # 號可表示 1-6 級標題,一級標題對應一個 # 號,二級標題對應兩個 # 號,以此類推。
- 可以在一行中用三個以上的星號、減號、底線來建立一個分隔線,行內不能有其他東西。
- 如果段落上的文字要添加刪除線,只需要在文字的兩端加上兩個波浪線 ~~ 即可
- 帶下劃線文本,以下是列表
- 有序列表使用數字并加上 . 號來表示,如:
- Markdown 區塊引用是在段落開頭使用 > 符號 ,然后后面緊跟一個空格符號,如果要在列表項目內放進區塊,那么就需要在 > 前添加四個空格的縮進。:
- 代碼區塊使用 4 個空格或者一個制表符(Tab 鍵)。如果是段落上的一個函數或片段的代碼可以用反引號把它包起來(`)。
- 鏈接使用方法如下: [鏈接名稱](鏈接地址),我們可以通過變量來設置一個鏈接,變量賦值在文檔末尾進行:
- Markdown 圖片語法格式如下:,表格語法如下:
- ``,表示單行代碼,``` ```,用于表示多行代碼。