1. Git系列之底層原理篇
本章節是Git的核心知識點,主要是介紹Git底層原理與在使用Git過程中的幾個重要區域,弄懂Git的整個使用流程,以及數據的存儲過程。
工作區(Working Directory):
工作區就是我們平時編寫文本文件的地方
暫存區(Stage/Index):
暫存區是我們提交文本文件到本地倉庫的來源地,只有把工作區的文件添加至暫存區,才可以被提交至本地倉庫。
本地倉庫(Repository):
本地倉庫是保存每次文件更新的記錄,包括提交人,提交時間,提交的內容等詳細信息,方便追溯歷史版本。
遠程倉庫(Remote Repository):
遠程倉庫算是本地倉庫的一個副本,主要是方便合作夥伴之間的倉庫文件同步。
因此它的使用流程可以簡單的概括為:
1、在本地搭建一個目錄,用來創建git倉庫
$ git init gitDirectory
2、在倉庫目錄下創建文本文件(工作區)
$ cd gitDirectory
$ echo "first txt" > first.txt
3、把工作區的first.txt文件添加至git暫存區
$ git add first.txt
4、將暫存區中的文件first.txt提交至本地倉庫
$ git commit -m "first commit"
5、將文件保存至本地倉庫就已經可以記錄我們每次提交的歷史信息了,但是為了方便其他夥伴一起協作,還需要搭建一個遠程服務。(本次以GitHub為例)
在GitHub創建一個和本地一樣名稱的倉庫,創建成功後會生成一個倉庫地址:
https://github.com/mr-kings/gitDirectory.git
6、將本地倉庫和遠程倉庫關聯起來
$ git remote add origin https://github.com/mr-kings/gitDirectory.git
7、第一次將本地倉庫提交至遠程倉庫
$ git push -u origin master
第一次需要添加 -u 參數,即把本地的master分支和遠程倉庫的master分支對應上
8、此時本地倉庫和遠程倉庫就已經實現了同步,其他協作夥伴只需到遠程倉庫把倉庫克隆到自己的電腦即可進行協作編輯
$ git clone https://github.com/mr-kings/gitDirectory.git
9、克隆下來以後會在本地生成本地倉庫以及工作區,後續的操作和2步驟及以後步驟一致
需要注意的是:遠程倉庫有兩種連接方式https/ssh,上面的例子使用的https,其實ssh方式會比https快的多,它還可以通過添加密鑰的方式省去每次提交時都要輸入用戶名和密碼的問題,這里不做詳細介紹。https也是可以通過配置省去每次推送都需要輸入用戶名和密碼的問題。
Git安裝成功後,在本地新建一個Git倉庫,$ git init Gitstudy會生成一個.git文件夾,如果你創建的時候沒有發現.git目錄那應該是你的電腦默認隱藏了.git文件夾,有兩種方式可以查看它:
第一種方式:
命令行工具,在當前目錄下,在命令行里輸入 $ ll -a 即可查看
第二種方式:
在當前目錄下,點擊查看菜單,然後勾選上隱藏的項目即可
.git目錄就是暫存區和本地倉庫的位置,所以它的核心就在這里,下面看看它有哪些內容:
由上圖可知,初始化的時候.git目錄下有以下文件及文件夾:
config(文件):存放當前倉庫的一些配置信息,比如記住用戶名和密碼,別名等
下面是它的常用選項:
[core] ignorecase 是否忽略文件大小寫
[remote "origin"] url 配置遠程倉庫地址
[remote "origin"] fetch 遠程分支映射關系
[user] name 用戶名
[user] email 郵箱
[alias] 命令別名配置 : cmt = commit
description(文件):創建倉庫的描述文件
HEAD(文件):指示當前被檢出(所在)的分支,如當前在test分支,文件內容則為ref: refs/heads/test。
hooks/(文件夾):包含客戶端或服務端的鉤子腳本(hook scripts),如pre-commit,post-receive等
info/ (文件夾):用以存儲一些有關git倉庫的信息,如exclude
objects/ (文件夾):用以存儲git倉庫中的所有數據內容
refs/(文件夾):包含 heads 文件夾,remote文件夾。heads 記錄本地相關的各 git分支操作記錄,remote 記錄遠程倉庫相關的各git分支 操作記錄
當第一次提交的時候還會生成以下文件及文件夾:
index (文件) -- (在git add file的時候生成):是當前版本的文件索引,包含生成當前樹(唯一確定的)對象的所虛信息,可用於快速比對工作樹和其他提交樹對象的差異(各commit和HEAD之間的diff),可用於存儲單文件的多個版本以有效的解決合並沖突。可使用git ls-files 查看index文件內容
COMMIT_EDITMSG(文件) -- (在git commit -m "first commit"的時候生成):最近一次的 commit edit message
logs/ (文件夾) -- (在git commit -m "first commit"的時候生成):放置git倉庫操作記錄的文件夾,包含HEAD文件 和 refs文件夾
以上簡單介紹了.git目錄下的文件及文件夾,重點則是objects文件夾:
經過第一次提交後objects文件夾下多出了3個文件夾:44/、d0/、f6/。通過提交的日誌我們發現,commit後會生成一個40位的16進制字元串(前兩位作為文件夾名稱,後38位為內容哈希值)
官方描述:這是一個 SHA-1 哈希值——一個將待存儲的數據外加一個頭部信息(header)一起做 SHA-1 校驗運算而得的校驗和(前2位作為文件夾名稱 -- 後面38位作為內容的哈希值)
通過cat-file -t sha-1 命令查看當前哈希值所屬的類型:
由圖可知它是一個commit對象
再通過命令cat-file -p sha-1查看該commit對象包含有哪些信息
由圖知一個commit對象包含了tree對象,本地倉庫信息,提交人信息及提交時的備注信息。
再通過上述命令查看tree對象又包含又哪些信息:
由圖知tree對象又包含了blob對象,在多目錄的情況下tree對象還會包含其他tree對象。
由此得出結論:剛才提交的時候生成的那個3個文件夾分別對應的是3個對象即:44/文件夾對應的是commit對象,f6/文件夾對應的是tree對象,d0/文件夾對應的是blob對象。層級關系是:commit對象對應一個tree對象,tree對象可以包含一個或多個其他tree對象和blob對象。
下面就簡單介紹git中的幾個對象:
blob:
blob對象只跟文本文件的內容有關,和文本文件的名稱及目錄無關,只要是相同的文本文件,會指向同一個blob。
tree:
tree對象記錄文本文件內容和名稱、目錄等信息,每次提交都會生成一個頂層tree對象,它可以指向其引用的tree或blob。
commit:
commit對象記錄本次提交的所有信息,包括提交人、提交時間,本次提交包含的tree及blob。
tag:
標簽引用,它指向某一個commit。
用下面的圖可以把今天的內容概括起來:上半部分描述了git的操作流程圖,下半部分描述git底層數據存儲結構圖
Git流程及底層結構圖
下面就對圖的下半部分做個詳細說明:
1、在與.git同級目錄下新建文件夾directory,再在directory目錄下新建一個文本文件first.txt,裡面的內容為1。當執行$ git add first.txt 的時候就會在.git/objects/生成一個blob對象的文件夾(前兩位為文件夾名稱 -- 後38位為文本內容的哈希值)對應上圖的第一個d00491 -- blob對象。
2、當執行$ git commit -m "first commit" 的時候就會在.git/objects/下新生成了2個tree對象和一個commit對象的文件夾(前兩位為文件夾名稱 -- 後38位為文本內容的哈希值)對應上圖的f6589b -- tree對象,4a2e3e -- tree對象,6b18a7 -- commit對象。之所以生成兩個tree對象是因為directory目錄為一個tree對象還有與commit對象一一對應的頂層tree對象。這個時候HEAD游標指向的是當前master分支的first commit。並且在這次提交的時候打個v1.0版本的標簽。
3、在與.git同級目錄下新建一個新的文本文件second.txt,內容為2。當執行$ git add second.txt 的時候就會在.git/objects/生成一個新的blob對象的文件夾(前兩位為文件夾名稱 -- 後38位為文本內容的哈希值)對應上圖的0cfbf0 -- blob對象。
4、當執行$ git commit -m "second commit" 的時候就會在.git/objects/下新生成了1個tree對象和一個commit對象的文件夾(前兩位為文件夾名稱 -- 後38位為文本內容的哈希值)對應上圖的35e40c -- tree對象,d6dca9 -- commit對象。只生成一個與commit對象一一對應的頂層tree對象。由於本次提交directory目錄下的first.txt內容沒有變化,所以上圖的35e40c -- tree對象還會指向f6589b -- tree對象。這個時候HEAD游標指向的是當前master分支的second commit(HEAD索引向前移動),second commit 會指向上一次的提交即 parent指向first commit。
後續的操作以此類推,但需要注意的點是:
1、blob對象只對文件的內容有關,和文件名稱無關,如果不同的文件名稱,內容相同只會有一個blob對象,生成的新tree對象會指向該blob對象。例如上圖的third.txt和four.txt裡面的內容都為3。所以不會生成新的blob對象,新的tree對象只會指向同一個blob。
2、如果每次提交的時候包含的某些文件並沒有改動(更新),那麼就會直接指向它原來的索引,不會重新生成。例如上圖的directory/first.txt,second.txt
3、每次commit對象都會和頂層的tree對象一一對應。
2. git清除歷史紀錄
若我想刪除歷史記錄里比較考前的提交,而後面還有很多需要保留的提交,則:
1.2 如果要刪除的歷史記錄是分散的,則可以考慮 Interactive Rebase,自行挑揀/合並等。如git rebase -i <ref>
1.1 如果要刪除的歷史記錄是連續的,比如說從最開始到某一刻全部都刪除或者是中間一截可以刪除,則可以考慮 Onto Rebase,如 git rebase --onto <ONTO_BASE_ref> <START_ref> <END_ref>,其中 START 到 END 之間的是需要保留的部分,而 ONTO_BASE 則是最新的基點;換言之,從 ONTO_BASE 到 START 之間的歷史記錄會被幹掉。
若我要刪除的歷史記錄很多,要保留的則很少(比如說就保留最近的一個,以前都不想要了),那索性可以直接創建 Orphan Branch 來重建歷史記錄。如 git checkout --orphan new_start,這條命令會創建一個叫做 new_start 的分支,該分支沒有任何歷史記錄,但是所有的文件都會原封不動的存在,你可以據此開始重新提交。完成之後甚至可以把舊的分支直接廢棄。另外,也可以指定新分支的起點,默認當然是從 HEAD 開始了。
你還可以把歷史記錄分成兩份(或更多份),其中有的完整,有的則簡化等等,具體參見這篇關於 git replace 的文檔:http://git-scm.com/2010/03/17/replace.html
其實還有很多種場景可以說道,Git 的用法非常靈活,即使暫時用不到也值得細細過一遍知道它能做什麼樣的事情,然後遇到各種復雜的場景就可以自己推導出解決方案了。
3. 一個Git倉庫管理多個Git項目
平時我會把所有需要儲存的資料都用git進行管理.
我需要使用一個命令, 把工作中所有git倉庫都提交到自己的阿里雲或Dropbox上, 在不同的地方使用它.
使用git配合Dropbox的另外一個好處是, 由於 .gitignore 忽略了許多公共資源, Dropbox只需要儲存很少的內容:
如圖, 我的所有項目文件有8.75GB, 但是Dropbox上只保存著430MB的倉庫, 並且還擁有Git的版本管理功能.
如果你有和我一樣的需求, 這篇文章會幫到你.
首先得確保當前有 Nodejs 環境, 安裝 merge-sub-gits
思路很簡單:
其中添加 -l 參數會列印重命名日誌
每次都需要使用 merge-sub-gits 命令包括 git 提交操作很是繁瑣, 我們可以在 ~/.bash_profile 文件中添加以下內容:
我們平時會有需要把工作文件放入各類網盤中, 方便在公司和家裡進行同步, 但是Dropbox\iCloud等網盤都沒有給予文件夾忽略和更細膩的Git的文件歷史.
例如一個React前端項目大概有幾百MB, 如果忽略 node_moles 文件夾就只剩下十幾MB.
我們可以把所有工作和電腦環境相關的資料都放入一個work文件, 使用 merge-sub-gits 把改文件夾的內容同步到網盤中:
我們已經在Dropbox中創建了一個倉庫, 並且clone到了本地, 接下來我們拷貝所有需要備份的文件都放入 ~/backup-all 文件夾中, 然後繼續下面的操作:
如前文所述, 通過.gitignore文件和git的壓縮, 把8.75GB的內容, 變為430MB進行網盤管理, 並且還有Git的版本管理功能.
由於Git倉庫中保存了許多歷史信息, 隨著長時間的使用, Git 倉庫會緩慢的逐步增大, 由於我們所有子項目都保留著自己的Git歷史, 所以如果有一天根Git倉庫太冗餘了, 我們只需要刪除Dropbox的Git,重新提交即可.
如果曾經在根git項目中使用過 git commit , 會把子git項目標記為忽略提交
這種情況需要清空git記錄:
歡迎 Star: github.com/ymzuiku/merge-sub-gits
4. Git比SVN相比有什麼區別
區別1、GIT是分布式的,SVN不是
這是GIT和其它非分布式的版本控制系統,最核心的區別;GIT跟SVN一樣有自己的集中式版本庫或伺服器。但,GIT更傾向於被使用於分布式模式,也就是每個開發人員從中心版本庫/伺服器上chectout代碼後會在自己的機器上克隆一個自己的版本庫。
區別2、Git直接記錄快照,而非差異比較
Git和其他版本控制系統的主要差別在於,Git 只關心文件數據的整體是否發生變化,而大多數其他系統則只關心文件內容的具體差異。Git 並不保存這些前後變化的差異數據。實際上,Git 更像是把變化的文件作快照後,記錄在一個微型的文件系統中。每次提交更新時,它會縱覽一遍所有文件的指紋信息並對文件作一快照,然後保存一個指向這次快照 的索引。為提高性能,若文件沒有變化,Git不會再次保存,而只對上次保存的快照作一鏈接。
區別3、近乎所有操作都是本地執行
在 Git 中的絕大多數操作都只需要訪問本地文件和資源,不用連網。但如果用 CVCS 的話,差不多所有操作都需要連接網路。因為 Git 在本地磁碟上就保存著所有當前項目的歷史更新,所以處理起來速度飛快。
5. Git 清除所有歷史記錄
有些時候,git 倉庫累積了太多無用的歷史更改,導致 clone 文件過大。如果確定歷史更改沒有意義,可以採用下述方法清空歷史,
『完』
6. git回滾歷史版本後面版本的數據還在嗎
git回滾歷史版本後面版本的數據還在
下面詳細介紹這些函數。
1. csvread、csvwrite
csvread函數的調用格式如下:
● M = csvread('filename'),將文件filename中的數據讀入,並且保存為M,filename中只能包含數字,並且數字之間以逗號分隔。M是一個數組,行數與filename的行數相同,列數為filename列的最大值,對於元素不足的行,以0補充。
● M = csvread('filename', row, col),讀取文件filename中的數據,起始行為row,起始列為col,需要注意的是,此時的行列從0開始。
● M = csvread('filename', row, col, range),讀取文件filename 中的數據,起始行為 row,起始列為col,讀取的數據由數組 range 指定,range 的格式為:[R1 C1 R2 C2],其中R1、C1為讀取區域左上角的行和列,R2、C2為讀取區域右下角的行和列。
csvwrite 函數的調用格式如下:
● csvwrite('filename',M),將數組M中的數據保存為文件filename,數據間以逗號分隔。
● csvwrite('filename',M,row,col),將數組M中的指定數據保存在文件中,數據由參數 row和col指定,保存row和col右下角的數據。
● csvwrite寫入數據時每一行以換行符結束。另外,該函數不返回任何值。
7. Git修改提交歷史
Git的一個優勢在於,當你在和別人共享你的工作之前,可以隨便修改你的提交歷史,當然不管在什麼時候,最好不要改動已經推送到central server的commit,否則會產生一次變更的兩個版本。
在推送到central server之前,你可以選取staging area(暫存區)中的任意文件進行提交,也可以通過stash命令決定不與某些內容工作,也可以偷梁換柱地重寫已經發生的commits,這包括:改變commit信息,拆分commit,壓縮多條commit,改變提交的順序,甚至移除某些不再需要的commit等。
為了對於上面的描述有宏觀的了解,筆者在本地新建了一個倉庫且提交了三個commit,it seems like this:
其中SecondCommit中包含兩個txt文件,其餘兩個commit都只包含一個txt文件。
接下來就來篡改一下歷史吧!
(註:--pretty=format 表示格式化輸出,%h 表示提交對象的簡短哈希字串,%s 表示提交說明,為了方便後面的展示,筆者用alias命令簡化上面的命令: git config --global alias.last "log --pretty=format:"%h,%s"",之後如果需要查看log信息,git last 或者 git last -n 即可)
修改最後一條commit是所有修改提交歷史操作中最常見的一個。它的命令很簡單: git commit --amend
從英文單詞amend的字面意思(修改,改正)就可以理解此命令。
執行此命令會進入一個編輯框:
了解vim的同學可能對這不會感覺到陌生,這里簡單介紹一下,首先在鍵盤輸入i(i 表示insert插入,由此進入編輯模式),更改title信息,按Esc退出編輯模式,最後輸入 :wq (保存並且退出),收工。
如果對索引區的內容作了修改,先執行git add 命令,再執行git commit --amend , 效果是一樣的,更改後的log信息如下:
Git提供了互動式變基工具,可以在任何想要修改的commit後停止,做任何想做的事。
通過git rebase -i 命令來互動式的運行變基(註:-i 是--interactive的縮寫)。同時需要指定想要重寫多久遠的歷史,比如修改最近三次提交:git rebase -i HEAD~3 ,因為筆者的FirstCommit是repository裡面的第一個提交,所以筆者採用的是git rebase -i --root命令,有興趣的同學可以看看這個: http://stackoverflow.com/questions/2246208/change-first-commit-of-project-with-git/2309391#2309391.
執行git rebase -i --root命令,彈出如下編輯框:
需要注意的是commit顯示順序剛好和git log的顯示順序相反。尤其注意Commands,之後所有的操作都是基於這些命令, 這里筆者依次解釋一下:
p , pick = use commit: 直接使用commit 不做任何修改,其中p 是pick的縮寫,以下雷同;
r , reword = use commit, but edit the commit message: 使用commit,但是會更改commit 信息;
e , edit = use commit, but stop for amending :使用commit,但是遇到此命令時會停止合並;
s , squash = use commit, but meld into previous commit: 使用commit,但是會合並到前一個commit中;
f , fixup = like "squash", but discard this commit's log message:和squash類似,但是會拋棄commit的log信息
x , exec = run command (the rest of the line) using shell:使用shell運行命令
d , drop = remove commit:丟棄commit
整個其實就是一個腳本,每一行就相當於一個命令,位置可以互換,命令是從上往下執行的。如果移除某一行,對應的commit就丟失了,但是如果把所有的行都移除的話,整個rebase就被終止了。
了解了上面的命令,修改多條commit就顯得很簡單了。
由於篇幅的限制,這里筆者就改變第一條commit的提交信息:
之後便會執行rebase過程,然後彈出一個編輯框,修改commit信息即可。
現在的log信息如下:
上面我們提到SecondCommit中包含兩個文件,現在筆者將其拆分成兩個commit,這個時候需要用到edit命令。
同樣的先進入互動式變基: git rebase -i --root
當從上往下執行rebase的時候,遇到edit命令將會暫停rebase。
此時我們要做的就是reset SecondCommit的提交 git reset HEAD~
這時候用git status 查看最新的狀態:
互動式變基正在執行,SecondCommit的提交已經從索引區變成untrcked的狀態了。我們需要做的就是將這兩個文件分別add到暫存區然後commit到索引區。
$ git add newfile1.txt
$ git commit -m "SecondCommitSplit-1
$ git add newfile2.txt
$git commit -m "SecondCommitSplit-2
完成上面的操作後還是處於rebase的狀態,讓rebase繼續執行即可
git rebase --continue
現在log信息如下:
上面我們說過squash和fixup命令具有合並commit的功能,筆者習慣用的是fixup命令,只保存一個message信息。
比如我們現在要合並最近的兩個commit:
中間還給SecondCommitSplit-2更改了commit 信息
合並後的log信息如下:
上面講過,更改行的位置即可改變commit的提交順序,比如說把第一條和第三條commit的順序互換:
修改順序後的log信息如下:
當然,如果commit之間有相互依賴關系的話就沒有這么簡單了。
其實整篇文章都是圍繞pick,reword,edit,squash,fixup等幾個常用的命令進行的簡單實踐,實際開發過程中的情況還要復雜,具體問題還是要具體分析,筆者只是拋磚引玉,如果有不妥當的地方,還請各位看官指點一二。
8. 徹底清除git所有歷史提交記錄使其為「新」庫
以前開發中未制定、遵循 git 管理項目標准,隨意(不規范)的提交 嚴重「污染了」提交歷史,使開發主線 「臟亂」;
基於以前的倉庫重新開發,這樣可保留以前的配置等文件,但是需要刪除全部的歷史記錄、tag、分支等;
由於自己或其他方面特殊需求,需要保留倉庫的部分屬性(創建時間,說明,主頁等),但需要清除歷史記錄,使其為「新庫」。
基於以上3方面的需求,需要提供一個 在不刪除原倉庫的前提下,清除原倉庫的所有歷史提交記錄(包含:分支、tag) 解決方案。
語法: git checkout --orphan <new_branch>
例句: git checkout --orphan latest_branch
使用 --orphan 選項,可創建1個"清潔"分支(無任何的提交歷史,但是當前分支的內容一應俱全。但嚴格意義上說,這樣創建的分支還不是一個真正的分支,因為HEAD指向的引用中沒有commit值,只有在進行一次提交後,它才算得上真正的分支。
新的分支名可以隨意命名,但不能和以前的分支名沖突。這兒特別強調是因為很多人習慣默認將分支名創建為 master .
本文以 latest_branch 作為新分支名,這個名稱沒有任何特殊含義,你可自定義,只要保證和以後的使用一致即可。
一般倉庫默認的主分支為 master 分支,如果原來的主分支不是 master, 用實際的主分支名代替。
注意 : 有些倉庫有 master 分支保護,不允許強制 push,需要在遠程倉庫項目里暫時把項目保護關掉才能推送。
注意 : 推送前 需要使用 git remote -v 查看關聯的遠程倉庫的信息(主要是遠程庫的別名)。雖然遠程庫的別名默認是 origin ,但你可能設置過其他的別名(而非 origin).
推送前,有的情況需要設置: git branch --set-upstream-to=origin/master master 。
如果別人pull不下來可以敲
可登錄遠程倉庫再次確認。
這兒將上面的步驟封裝為 bat 批處腳本(針對windows),雙擊即可運行。
文件名: fetch_push_clear_all_history.bat
將文本內容保存為 UTF-8 格式,文件最好放在 git 倉庫外。如果放在 git 倉庫內,需要將此文件在 .gitignore 中過濾。
抄襲此篇文章
9. 程序員必備技能-git 不會到還有人不會用吧,不會吧不會吧
版本控制 :版本控制最重要的作用是記錄一個文件的修改 歷史 記錄,並且根據該記錄可以切換到對應的 歷史 版本,這個也是由個人開發到團隊開發重要的工具。
集中式版本控制系統 :具有一個統一的中央伺服器,裡面存放著項目的源碼。各個客戶端都從該伺服器中拉取代碼和上傳自己編寫的代碼到伺服器中。
優點:各個客戶端可以查看其他客戶端在該項目中做了什麼,一定程度上了解項目的進度。同時,管理員可以控制各個程序員的許可權。
缺點:無法應對中央伺服器的單點故障問題,當中央伺服器宕機後,各個客戶端都不能提交代碼和拉取代碼,同時在宕機的期間,做不到版本的 歷史 記錄。
分布式版本控制系統 :每個客戶端都是一個版本庫(本地庫),各個客戶端維護自己的版本 歷史 記錄。各個客戶端的協作是通過使用遠程庫(GitHub等)進行的,push把代碼推送到遠程庫中,pull把遠程庫的代碼拉取下來。
優點:解決了集中式版本控制的缺點。在遠程庫宕機的情況下(雖然說這個概率極低),客戶端還是能進行開發的,因為版本的控制是在本地進行的。同時,每個客戶端保存的是整個項目,包括 歷史 記錄,使得更加安全。
Git的工作機制
代碼託管中心(遠程庫) :
底層:head指針指向分支,分支指針指向版本號。當版本號發生變化時,分支指針指向對應的版本號
(1)配置git的忽略文件
(2)在idea中配置git
(3)初始化項目
10. git 文件移動 修改歷史還在嗎
不會在的了,在新文件夾里看到的歷史是從移動的那一刻起的。
但可以在移動前的歷史里找到,再從日誌文件里查看此文件的日誌。