git基本概念與原理
-----------------------------------------------------------------------------------------------------------------------------------------------
一、git的安裝與簡單配置
1、安裝
apt install -y git-core
2、配置git名稱與郵箱
git config --global user.name "chenux"
git config --global user.email "[email protected]"
git config --global --list
mkdir 目錄 && cd 目錄
git init git_repo #git_repo就是倉庫,其目錄下有個.git
3、git設定優先級
git config --system:使對應配置針對系統内所有的使用者有效
git config --global:使對應配置針對目前系統使用者的所有倉庫生效
git config --local:使對應配置隻針對目前倉庫有效
local選項設定的優先級最高。
4、倉庫中有檔案後,儲存庫狀态
在倉庫中執行指令git add . && git commit -m "status01"
給倉庫裡加了些檔案
5、gitk,圖形化git管理工具,apt install -y gitk
6、此圖狀态為在4中已經執行過的狀态,此時輸入指令更改檔案内容
echo “aa11a1” >1.txt \
echo “b2b2b2” >> 2.txt \
touch project/pro-2.txt
執行後儲存狀态git add. && git commit -m “status02”,此時再啟動gitk
再次稍作修改後儲存一次狀态
二、git對象
1、三種不同類型的git對象:
(1)blob:一個blob就是由一個檔案轉換而來,blob中隻會存儲檔案的資料,而不會存儲檔案的中繼資料
(2)tree:一個tree就是有一個目錄轉換而來,tree隻會存儲一層目錄資訊,它隻存儲它的直接檔案和直接子目錄的資訊,但子目錄中的内容它不會儲存
(3)commit:一個commit就是我們的git commit送出,它指向了一個tree,這個tree儲存了某一時刻項目根目錄中的直接檔案和直接目錄資訊
總而言之,commit指向了tree,tree又指向了自己根下直接檔案的blob或者子目錄的tree,子目錄的tree對象指向了子目錄的檔案blob和子子目錄的tree,以此類推
2、每個git對象都有一個唯一的哈希碼(sha1算法),它是一個40位的16進制數
3、在初始送出後再進行二次送出,若存在
檔案f1沒有修改,在此過程後,它的blob哈希沒有改變;
檔案f2修改内容,在此過程後檔案為f22,它的blob哈希發生改變;
存放f1和f2的目錄alpha,在此過程中它的tree哈希發生改變,但指向檔案f1的blob仍為a以前的blob
三、git過程
1、目錄結構
可以看到,倉庫内部除了自己建的檔案,還有一個.git目錄,該隐藏目錄在git init時候就已經生成了。
.git目錄:可以稱之為版本庫
2、在之前送出的指令中,輸入的是
(1)git add -a:選擇将哪些變化的内容加入到下一次送出中,這些變化内容會被索引或者暫存起來,此時生成檔案的blob
(2)git commit -m “statusname”:建立commit送出對象,執行指令後git會根據索引/暫存中的結構,建立出對應的tree對象,之後git會建立一個commit對象,将新建立的commit對象指向新建立的tree
(3)這個新的送出産生後,記錄了一個狀态,該送出也會指向前一次的送出,每個送出都會指向自己的父送出
(4)圖檔所示,當修改3.txt後,由于它是位于上一次狀态中,修改它後會變紅,等git add 3.txt後它的狀态變為綠色,表明已加入暫存區,做好随時被送出的準備。4.txt由于沒有送出,一直是紅色,表明還在工作區
四、git使用
1、git log
顯示所有的git送出,git log --oneline,git log的簡單模式
git cat-file -t 哈希值 檢視對象的類型
git cat-file -p 哈希值 檢視對象的内容
git rev-parse 13614 通過簡短的哈希值擷取到整個哈希值
2、有如下操作:
git init git_test
cd git_test
echo f1>f1 echo f2>f2
git add .
git commit -m "test01"
echo -e "haha \nheihei" >> f1
git commit -m "test02"
我們可以得知f1産生了變化,如果我們此時後悔了對f1的操作,執行指令
git log --oneline,先找出對應送出的哈希值,之後git reset --hard 82a23e7
不難發現,雖說檔案恢複到test01的狀态了,但是剛才test02沒了,現在如果想再次回到test02,該如何操作?
(1)git reflog,輸入後可以檢視之前的test02哈希碼,查詢到後 git reset --hard 哈希碼便可回複
(2)驗證下,恢複成功
五、git分支
1、在實際環境中,檔案的修改是縱容交錯的,是以存在一個問題:檔案回退時,是否會影響其它檔案?這裡就會引入一個概念:分支
git status,顯示位于主分支master
建立新分支時,預設以目前所在分支為基礎建立,建立後分支的最新送出與master一樣
(1)建立分支,使用指令git branch分支名
建立後工作區仍處在master分支上,未切換到建立的slave分支。分支的建立,并不是說明slave複制了master的分支,而是git隻是建立了slave分支指針,指向了master分支對應的最新送出。
邏輯圖如下:
(2)檢視分支情況
git branch、git branch -v、git branch -vv
(3)分支切換
git checkout 分支名
切換後工作區也随之切換,之後的git add與git commit指令影響切換後的分支
現有操作
切換到我的分支slave後,輸入指令後再送出,此時邏輯圖就變為了
master上有5個送出,而slave上有6個送出。這時在切回master分支,看下f2檔案,内容并沒有改變。
同理,若此時在master分支修改任何檔案,切換到slave分支是看不到master修改的檔案,是以在項目時可以利用分支,負責某個項目a子產品開發的修改檔案在分支1,負責b子產品開發的修改檔案在分支2,後期項目合并時分支合并即可。具體的合并分支在後面。
六、head
(1)head指向分支
我們從之前圖中可以看到,通常情況下,當處于哪個分支,head就指向了哪個分支,是以git是通過head找到自己所處的工作區,在git倉庫下的.git裡,可以檢視head的檔案内容,其内部顯示就是目前指向的分支。
(2)head指向送出
特殊情況下,head可以指向某個送出,這種情況稱作頭分離,detached head。
如上圖所示,head沒有指向任何一個分支,而是指向了commit2這個送出。怎樣才能出現這種效果呢?git checkout commit_hash,如圖示
此時輸入指令,git log --oneline --all --graph
再看下此時的git status
分離頭使用場景:
對該送出點的檔案可以随便看看,或者做一些實驗性的修改,并且可以将這些修改做成 送出,送出後會組成匿名分支,如果最後實驗結果不滿意可以丢棄之前實驗行的送出, 如果實驗結果滿意,就可以建立一個新的分支,來永久儲存這些送出。
示例:首先分離頭已經指向了51fe144,此時輸入了以下指令:
sed -i "s/\:/\-/g" 1.txt
git add 1.txt && git commit -m "head_modify_1.txt"
echo headtest2 > 1.txt
git add 1.txt && git commit -m "head_modify_1.txt-02"
修改了兩次,送出了兩次,我們此時通過圖形工具看一下
gitk --all
注:這裡分離頭選的送出剛好選到master分支的最新送出了,這不能說明分離頭是從master分支分出來的
修改完成後,随便切換一個分支,會出現提示,比如我在git checkout test後,有圖
如果修改項目後實驗結果滿意,就可以使用提示的指令
git branch 分支名 哈希值
輸入git branch headfile 65a96e4後
如果不想儲存實驗性修改,切換分支後不用去管提示即可
七、差異比較
1、為比較差異,我們此處建立一個新的用來比對倉庫
#git init git_test && cd git_test
#cat > test1 << eof
> 1
> 2
> 3
> eof
#cat > test2 << eof
> a
> b
> c
#git add . && git commit -m “new test1&2”
之後做出修改:
#sed -i “s/2//” test1 && echo haha >> test1
#sed -i "s/c/cdef/" test2
做出修改後,我們并沒有git add将變化添加到暫存區,是以目前工作區和暫存區的内容是不同的,檢視不同,使用指令git diff
檢視單個檔案變化的指令:git diff -- 檔案名,該指令可以跟多個檔案,git diff -- test1 test2 ...
2、檢視工作區與目前分支的差別,使用指令git diff head,檢視工作區與某個分支的差別,如果head未指向需要檢視的那個分支,使用指令git diff 分支hash,比如說我目前在test分支,我查找test分支與之前status02分支的差別,找到status02的哈希碼,之後便可以使用指令檢視差別了。具體如下圖:(使用另一個送出比較多的git倉庫作示例)
結論:git diff比較的是工作區與暫存區的差異,git head比較的是工作區與送出的差別
3、再次回到建立的git_diff庫,我們繼續做出兩次修改
#echo test1 >>test1
#echo test2 >test2
#git add -a
#git commit -m "first modify"
#echo test1-2 >> test1
#echo test2-2 >> test2
#git commit -m “second modify”
檢視git log --oneline後獲得兩次送出的哈希碼,之後可以通過指令
git diff 送出hash1 送出hash2,此指令檢視兩次送出的差異
如下圖所示:
當然,圖中7ac86fc也是我們head的指向處,此處使用指令git diff cd692e5 head也可以
如果比對這次送出與前一次的送出,使用指令git diff head head~,這是一種簡易寫法,其它簡易寫法如下:
head:等于head~0,表示最新的一次送出
head~:等于head~1,表示最新送出的前一次送出
head~~:等于head~2,表示最新送出的前前一次送出
head~~~:等于head~3,表示最新送出的前前前送出
另:根據送出名擷取對應的哈希碼,使用指令舉例:
git rev-parse head~
git rev-parse master
4、如果是分支之間的比對,比如說git diff master slave,此時對比的是兩個分支各自最新送出的比對。
如圖所示
總結:
#git diff==>比較工作區和暫存區
#git diff head==>比較工作區和目前分支最新的送出,head可以換成别的分支的名字,比如test分支,"#git diff test"表示比較目前工作區和test分支最新的送出之間的差異,也可以把head替換成任何一個commit的id,表示比較目前工作區
和對應送出之間的差異。
#git diff --cached==>比較暫存區和目前分支最新的送出
#git diff -- file1==>比較工作區和暫存中file1檔案的差異
#git diff -- ./file1==>隻比較工作區和暫存區中file1檔案的差異
#git diff -- file1 file2==>比較暫存區file1和file2檔案差異
#git diff -- dir1/ ==>隻比較工作區和暫存區中dir1目錄中所有檔案的差異
#git diff head -- ./file1 ==>隻比較工作區和目前分支最新的送出中file1檔案的差異,head可以替換成分支名或者commitid
#git diff branch01 -- ./file1==>隻比較工作區和branch01分支最新的送出中file1檔案的差異
#git diff --cached branch01==>比較暫存區和branch01分支最新的送出
#git diff --cached branch01 --./file1==>隻比較暫存區和branch01分支最新的送出中file1檔案的差異
#git diff head~ head==>比較目前分支中最新的兩個送出之間的差異
#git diff head~ head -- file1==>比較目前分支中最新的兩個送出中的file1檔案的差異
#git diff commit01 commit02==>比較兩個commit之間的差異
#git diff commit01..commit02==>同比較兩個commit之間的差異,與上個指令等效
#git diff branch01 branch02==>比較兩個分支上最新送出之間的差異
#git diff branch01..branch02==>比較兩個分支上最新送出之間的差異,與上個指令等效
三、撤銷暫存中的變更
1、平時進行版本管理中,修改某處代碼送出了,之後後悔了怎麼辦?
使用指令,git reset head,此操作為“檔案修改已記錄到暫存中,但後悔這麼做,恢複到git add之前”,也就是把暫存區恢複到與最新的commit狀态保持一緻。如圖所示
從圖中我們看出,git reset head撤銷了之前的git add操作,f1和f2的修改又回到了工作區。
我們這裡是撤銷了所有檔案的更改,如果隻想撤銷一個檔案的更改,使用指令
git reset head -- f1
2、git reset、git reset --hard和git reset --soft
(1)git reset = git reset --mixed
git reset head,将所有已經暫存的内容從暫存區撤銷了(即暫存區與最近送出中的狀态一緻)
git reset commit_hash,将head指針指向該次送出,并且将送出中的内容同步到暫存區,但工作區中内容不受影響。産生的結果是工作區記錄的是reset前的最新送出與reset後的送出的變化内容,在git add和git commit之後,檔案依然是reset前的檔案,但head指向了reset後的送出。此處示例git reset commit_hash:
首先我們建立了一個新git庫,并做了7次送出,之後git reset到第3次送出
此時我們git diff,可以看下工作區與第3次送出的差異
這也是第3次送出與我們git reset前的最新送出相比産生的差異,哪怕是git add之後繼續git commit,結果也是git reset前的結果,但是head此時在第3次的送出上。
git reset --hard head,将所有區域恢複成了最近的送出中的狀态(即工作區、暫存區都與最近送出中的狀态一緻)
(2)git reset --hard commit_hash,将head指向該次送出,并且将所有區域内容進行同步,也就是恢複到該次送出狀态。此處示例git reset --hard commit_hash:
#echo "1 2 3" > f1
#echo "a b c" > f2
#git add .
#git commit -m "first add f1&f2"
#echo "456" >>f1
#git add f1
#git commit -m "second modify f1"
#echo "def" > f2
#git add f2
#git commit -m "third update f2"
#echo "linux" >> f1
#echo "chen" >>f2
#git commit -m "4th update f1&f2"
git reset --hard commit_hash,回到first f1&f2這個送出點
(2)git reset --soft
此指令隻将head指針指向commitid對應的送出,但是不會操作暫存區和工作區,也就是說,目前分支中的最新送出會變成commitid對應的送出,工作區和暫存區中的内容或者變更不會受到任何影響。此處示例:git reset --soft
如圖中所示,我們将送出指向了最初建立的commit,檔案結果沒有改變
3、git checkout撤銷檔案
撤銷工作區已修改但未git add進入暫存區的檔案,使其回到上一次暫存後的狀态,如果不存在上一次的暫存,則回到最新的送出狀态。該指令的使用,對象檔案必須之前被git add追蹤過,若是建立的檔案沒有被追蹤過,此撤銷方法對其無效。
git checkout -- file,若是該目錄全部撤銷更改,git checkout -- .
四、git合并分支
1、合并方向
如圖示,有兩種情況如左右兩圖,分支b是基于分支a産生的分支,紅色部分是分支a的送出,橙色部分是分支b的送出,左圖為合并分支後合并分支的head在a上,分支b的head仍在原處;右圖同理亦然。
2、fast-foward
上圖所示即為分支a與分支b的合并,不過我們也可以使用一種快捷的合并方式:fast-forward,也譯為“快進”或者“快速重定向”,這種情況下的快速合并,最終結果會變為如圖:
此種情況為:基于分支a建立的分支b,分支a沒有任何新送出産生,如果此時将分支a合并到分支b,隻需要将分支A的指針指向到B分支的最新送出即可。這就是fast-forward。
如圖所示,右圖分支a産生分支B後又産生了新的送出,是以不能用fast-forward的合并方式
示例:首先輸入以下指令,slv1從master分出去之後,master再無新的送出,slv1産生了新的送出
#touch f1
#git add f1
#git commit -m "add f1"
#echo f1> f1 && git add . && git commit -m "insert f1 in f1"
#git branch slv1
#git status
#git checkout slv1
#touch f2 && git add f2 && git commit -m "touch f2 at slv1"
#git log --oneline
#echo "f2 in slv1" > f2 && git add f2 && git commit -m "insert f2 in f2 at slv1"
輸入指令之後,我們的git符合了可用fast-forward的模型
此時我們進行分支合并
git merge 分支名
先git cheout 分支a ,再git merge 分支b:将分支b合并到分支A,
圖中所示,這種方式即為fast-forward,git log --oneline可用看到head同時指向了兩個分支
3、正常分支合并
建立個如下圖的模型
首先有如下操作:
#echo 1 > 1.txt && git add . && git commit -m "touch 1"
#echo 2 >> 1.txt && git add . && git commit -m "insert 2 to 1"
#git branch slv01
#echo 3 >> 1.txt && git add 1.txt && git commit -m "insert 3 to 1"
#git checkout slv01
#echo a > a.txt && git add a.txt && git commit -m "touch a"
#echo ab >> a.txt && git add a.txt && git commit -m "insert ab to a"
gitk --all看到模型
此時再合并分支:git checkout master && git merge slv01
輸入後會彈出nano編輯器,編輯器是需要為合并後的新送出而準備。
不添加就用它的預設注釋即可。
gitk後看到結構已變為:
4、常用合并指令
#git merge 分支a:表示将分支a合并到目前分支。
#git merge --no-ff 分支a:表示将分支a合并到目前分支,但是明确指定不使用"fast-forward"的模式進行合并
#git merge --ff-only 分支a:表示将a分支合并到目前分支,但是隻有在符合"fast-forward"模式的前提下才能合并成功,在不符合"fast-forward"模式的前提下,合并操作會自動終止,換句話說就是,當能使用"fast-forward"模式合并時,合并正常執行,當不能使用"fast-forward"模式合并時,則不進行合并。
#git merge --no-edit 分支a:表示将a分支合并到目前分支,但是沒有編輯預設注釋的機會,也就是說,在建立合并送出之前不會調用編輯器(上文的示例中會預設調用vim編輯器,以便使用者能夠有機會編輯預設的注釋資訊),換句話說就是,讓合并送出直接使用預設生成的注釋,預設注釋為" merge branch 'branchname' "
#git merge 分支a --no-ff -m "merge a into master,test merge message":表示将a分支合并到目前分支,并且使用-m參數指定合并送出對應的注釋資訊。
注意,為了保險起見,需要同時使用"--no-ff"參數,否則在滿足"fast-forward"模式的情況下,會直接使用"fast-forward"模式進行合并,進而忽略了-m選項對應的注釋資訊(因為使用"fast-forward"模式合并後不會産生新送出,是以為送出準備的注釋資訊會被忽略)
5、合并沖突解決
如果兩個分支中同一個檔案中的同一行内容不一樣,合并分支時就會出現沖突,此時需要我們認為介入确認,這就是解決沖突的過程。
示例:首先輸入指令造成兩條分支同一檔案同一行内容不同
#git init git_test
#echo 1 > f1.txt && git add . && git commit -m "add 1 in f1"
#echo 2 >> f1.txt && git add f1.txt && git commit -m "add 2 in f1"
#sed -i "s/2/2master/" f1.txt
#git add f1.txt && git commit -m "modify 2 at master"
#git checkout -b slv01 --------------->建立并切換新分支
#sed -i "s/2/2slv01/" f1.txt
#git add f1.txt && git commit -m “modify 2 at slv01”
得到結果:
切到master分支,進行合并,有結果
(1)對于一樣的内容已經合并,對于沖突内容給出提示,修複成一樣的内容或者git merge --abort放棄合并。
此時再看f1.txt,會标出沖突結果:
目前分支中的沖突内容: "<<<<<<< head"與"=======" 之間。
另一條合并分支中的沖突内容: "======="與"<<<<<<< new" 之間。
我們取消沖突,git merge --abort後檢視git status,已經提示幹淨的工作區了
(2)更改沖突檔案使其不沖突
将兩個分支中的f1都修改後送出,變為2master 2slv01,之後git merge後就進入了nano編輯器編輯自己想要的注釋了。
(3)合并後對于之前的slv01分支,就可以不要了,處于非slv01分支下删除slv01分支,git branch -d slv01。
當存在某個分支沒有合并過并且需要删除時,git會提示,如果不予理會這種提示,可以使用指令:git branch -d 分支名
五、github
github:git的遠端倉庫
1、使用前準備
(1)https://github.com新增賬號
注冊好後登入賬号,右上方選new repository
繼續進行建立遠端倉庫
建立好以後螢幕左側會有自己的遠端倉庫名,點選進去可以看到連接配接自己遠端倉庫的兩種方式,https或者ssh
(2)ssh方式添加公鑰
右上角姓名處下箭頭點選settings,之後選擇ssh and gpg keys
建立公鑰私鑰:
ssh-keygen -t rsa,建立密鑰對
去檢視公鑰檔案,将檔案内容寫入github的ssh下
點選add ssh key
至此,github上的遠端倉庫可以使用ssh方式連接配接了。
2、git clone
git clone [email protected]:json-chenux/test.git
注意:如果這裡提示ssh拒絕連接配接的權限問題,需要将之前本機做過ssh免密登入時生成的id_rsa.pub檔案中公鑰内容粘貼至清單
git remote -v,可以看到本地倉庫與遠端倉庫的對應關系
圖中orgin為遠端倉庫名稱,可以更改
3、推送至遠端倉庫
git push,推送本地倉庫至遠端倉庫
剛在github上的倉庫建立好後,進入該倉庫目錄,輸入一些指令:
#touch t1
#git add . && git commit -m "build t1"
#echo 123 > t1 && git add . && git commit -m "add 123 in t1"
#git checkout -b slv
#echo abc > t2 && git add t2 && git commit -m "built add t2"
之後測試遠端連接配接的倉庫,使用指令:
git push origin master:master,将本地master分支推送到遠端master分支,
"master:master"中冒号左邊的master代表你要推送的本地master分支,冒号右邊的master代表推送到遠端倉庫中的master分支
指令結果:
如果本地倉庫master 分支推送至遠端倉庫并不存在的分支,即使用指令:
git push origin master:m1,遠端倉庫會自動建立新的分支m1。
#git push:如果git中隻有一條分支,推送時可以使用指令git push,當遠端倉庫沒有該分支,會自動建立這個分支。
當我們把建立的分支推向遠端倉庫時,需要輸入指令
#git push --set-upstream origin slv:slv ----------->git 1.x版本
#git push -u orgin slv:slv ------------->git 2.x版本
,将此分支slv推到遠端倉庫後,等下次在slv分支檔案做出改變時,位于slv分支時輸入git push,便可以自動推送slv分支到遠端slv分支了。
圖示:
#git branch -vv:顯示本地分支資訊
#git branch -vva:顯示本地與遠端分支資訊
圖中origin隻有master有,slv沒有,說明slv沒有推送至遠端倉庫
将slv分支推入遠端倉庫,之後再git branch -vva,發現本地slv分支已經至遠端倉庫的myremote-slv分支。
4、他人加入該遠端倉庫
使用指令:git remote add origin [email protected]:json-chenux/test.git
5、git pull
#git push --all:此指令表示當本地各分支與遠端倉庫各分支同名時,push所有分支的更新到對應的遠端分支。
#git fetch:此指令表示擷取遠端倉庫的更新到本地,但是不會更新本地分支中的代碼。
#git pull:此指令表示當本地分支與上遊分支同名時,對目前分支執行pull操作,對其他分支執行fetch操作,具體的差異主要取決于對應的遠端分支有沒有更新。
#git pull remote brancha:此指令表示将remote倉庫的a分支pull到本地目前所在分支,如果你想要pull到本地的a分支,需要先checkout到本地a分支中。
#git pull remote brancha:branchb:此指令表示将遠端倉庫的a分支pull到本地的b分支,成功之後(如果操作失敗,則後面的操作不會執行)再将遠端a分支pull到本地的目前所在的分支。