摘要:還可以通過檢查對象內容的的哈希值和對象名是否相同,來判斷對象內容是否正確。對象對象和其它所有的對象一樣,都用其內容的哈希值來命名的只有當兩個對象的內容完全相同包括其所指向所有子對象時,它的名字才會一樣,反之亦然。
git是什么
簡單來說,Git,它是一個快速的 分布式版本控制系統 (Distributed Version Control System,簡稱 DVCS) 。
同傳統的 集中式版本控制系統 (Centralized Version Control Systems,簡稱CVCS) 不同,Git的分布式特性使得開發者間的協作變得更加靈活多樣。
這時候我們會想到:
什么又是版本控制呢?
什么是分布式什么是集中式?
我們帶著問題往下走。
版本控制版本控制是一種記錄一個或若干文件內容變化,以便將來查閱特定版本修訂情況的系統。
比如:有一位程序員他可能需要保存一個代碼文件的所有的修訂版本,這樣就可以
將某個文件回溯到之前的狀態
甚至將整個項目都回退到過去某個時間點的狀態
比較文件的變化細節,查出最后是誰修改了哪個地方,從而找出導致怪異問題出現的原因
這時候采用版本控制就是一個非常明智的選擇,使用版本控制系統通常還意味著,就算你亂來一氣把整個項目中的文件改的改刪的刪,你也照樣可以輕松恢復到原先的樣子。 但額外增加的工作量卻微乎其微。
版本控制的成長兒童:人們通過復制整個項目的方式來保存不同的版本,或許還會改名加上備份時間以示區別。好處就是簡單,但是特別容易犯錯,一不小心會寫錯文件或者覆蓋意想外的文件。
少年:人們為了上面的問題,很久以前就開發了許多種本地版本控制系統,大多是采用某種簡單的數據庫來記錄文件的歷次更新差異,比如其中比較流行的 RCS 。
青年:人們又遇到一個問題,如何讓在不同系統上的開發者協同工作? 于是,集中化的版本控制系統( CVCS)應運而生。 這類系統,諸如 CVS 、 Subversion ,都有一個單一的集中管理的服務器,保存所有文件的修訂版本,而協同工作的人們都通過客戶端連到這臺服務器,取出最新的文件或者提交更新。現在,每個人都可以在一定程度上看到項目中的其他人正在做些什么。 而管理員也可以輕松掌控每個開發者的權限,并且管理一個 CVCS。
事分兩面,有好有壞。 這么做最顯而易見的缺點是中央服務器的單方面故障。 如果關機一小時,那么在這一小時內,誰都無法提交更新,也就無法協同工作。 如果中心數據庫所在的磁盤發生損壞,又沒有做恰當備份,毫無疑問你將丟失所有數據——包括項目的整個變更歷史,只剩下人們在各自機器上保留的多帶帶快照。
壯年:于是分布式版本控制系統面世了。 在這類系統中,像 Git 、 Mercurial 等,客戶端并不只提取最新版本的文件快照,而是把代碼倉庫完整地鏡像下來。 這么一來,任何一處協同工作用的服務器發生故障,事后都可以用任何一個鏡像出來的本地倉庫恢復。 因為每一次的克隆操作,實際上都是一次對代碼倉庫的完整備份。
許多這類系統都可以指定和若干不同的遠端代碼倉庫進行交互。籍此,你就可以在同一個項目中,分別和不同工作小組的人相互協作。 你可以根據需要設定不同的協作流程,比如層次模型式的工作流,而這在以前的集中式系統中是無法實現的。
git誕生史記很多人都知道, Linus 在1991年創建了開源的 Linux ,從此,Linux 系統不斷發展,已經成為最大的服務器系統軟件了。
Linus 雖然創建了 Linux,但 Linux 的壯大是靠全世界熱心的志愿者參與的,這么多人在世界各地為 Linux 編寫代碼,那 Linux 的代碼是如何管理的呢?
事實是,在2002年以前,世界各地的志愿者把源代碼文件通過 diff 的方式發給 Linus,然后由 Linus 本人通過手工方式合并代碼!
你也許會想,為什么 Linus 不把 Linux 代碼放到版本控制系統里呢?不是有 CVS、SVN這些免費的版本控制系統嗎?因為 Linus 堅定地反對 CVS 和 SVN,這些集中式的版本控制系統不但速度慢,而且必須聯網才能使用。有一些商用的版本控制系統,雖然比 CVS 、 SVN 好用,但那是付費的,和 Linux 的開源精神不符。
不過,到了2002年,Linux 系統已經發展了十年了,代碼庫之大讓 Linus 很難繼續通過手工方式管理了,社區的弟兄們也對這種方式表達了強烈不滿,于是 Linus 選擇了一個商業的版本控制系統 BitKeeper,BitKeeper 的東家 BitMover 公司出于人道主義精神,授權 Linux 社區免費使用這個版本控制系統。
安定團結的大好局面在2005年就被打破了,原因是 Linux 社區牛人聚集,不免沾染了一些梁山好漢的江湖習氣。開發 Samba 的 Andrew 試圖破解 BitKeeper 的協議(這么干的其實也不只他一個),被 BitMover 公司發現了(監控工作做得不錯!),于是 BitMover 公司怒了,要收回 Linux 社區的免費使用權。
Linus 可以向 BitMover 公司道個歉,保證以后嚴格管教弟兄們,嗯,這是不可能的。實際情況是這樣的:
Linus 花了兩周時間自己用 C 寫了一個分布式版本控制系統,這就是 Git!一個月之內,Linux 系統的源碼已經由 Git 管理了!牛是怎么定義的呢?大家可以體會一下。
Git 迅速成為最流行的分布式版本控制系統,尤其是2008年,GitHub 網站上線了,它為開源項目免費提供 Git 存儲,無數開源項目開始遷移至 GitHub,包括 jQuery,PHP,Ruby等等。
歷史就是這么偶然,如果不是當年 BitMover 公司威脅 Linux 社區,可能現在我們就沒有免費而超級好用的 Git 了。
git的優點在集中式系統中,每個開發者就像是連接在集線器上的節點,彼此的工作方式大體相像。 而在 Git 中,每個開發者同時扮演著節點和集線器的角色——也就是說,每個開發者既可以將自己的代碼貢獻到其他的倉庫中,同時也能維護自己的公開倉庫,讓其他人可以在其基礎上工作并貢獻代碼。 由此,Git 的分布式協作可以為你的項目和團隊衍生出種種不同的工作流程。
速度快
簡單的設計,易用
對非線性開發模式的強力支持(允許成千上萬個并行開發的分支)
完全分布式
有能力高效管理類似 Linux 內核一樣的超大規模項目(速度和數據量)
git實現原理從根本上來講 Git 是一個內容尋址 (content-addressable) 文件系統,并在此之上提供了一個版本控制系統的用戶界面,Git 的核心部分是一個簡單的鍵值對數據庫 (key-value data store) 。 你可以向該數據庫插入任意類型的內容,它會返回一個鍵值,通過該鍵值可以在任意時刻再次檢索 (retrieve) 該內容。
初始化的git目錄當在一個新目錄或已有目錄執行 git init 時,Git 會創建一個 .git 目錄。 這個目錄包含了幾乎所有 Git 存儲和操作的對象。 如若想備份或復制一個版本庫,只需把這個目錄拷貝至另一處即可。
$ ls -F1 HEAD config* description hooks/ info/ objects/ refs/
這是一個全新的 git init 版本庫,這將是你看到的默認結構。
description 文件僅供 GitWeb 程序使用,我們無需關心。
config 文件包含項目特有的配置選項。
info 目錄包含一個全局性排除(global exclude)文件,用以放置那些不希望被記錄在 .gitignore 文件中的忽略模式(ignored patterns)。
hooks 目錄包含客戶端或服務端的鉤子腳本 (hook scripts)。
objects 目錄存儲所有數據內容。
refs 目錄存儲指向數據(分支)的提交對象的指針
HEAD 文件指示目前被檢出的分支
index 文件保存暫存區信息。
git對象模型所有用來表示項目歷史信息的文件,是通過一個40個字符的 (40-digit) “對象名”來索引的,對象名看起來像這樣:
6ff87c4664981e4397625791c8ea3bbb5f2279a3
你會在Git里到處看到這種“40個字符”字符串。每一個“對象名”都是對“對象”內容做 SHA1 哈希計算得來的,( SHA1 是一種密碼學的哈希算法)。這樣就意味著兩個不同內容的對象不可能有相同的“對象名”。
這樣做會有幾個好處:
Git 只要比較對象名,就可以很快的判斷兩個對象是否相同。
因為在每個倉庫 (repository) 的“對象名”的計算方法都完全一樣,如果同樣的內容存在兩個不同的倉庫中,就會存在相同的“對象名”下。
Git 還可以通過檢查對象內容的 SHA1 的哈希值和“對象名”是否相同,來判斷對象內容是否正確。
對象每個對象 (object) 包括三個部分:類型,大小和內容。大小就是指內容的大小,內容取決于對象的類型,有四種類型的對象:"blob" 、 "tree" 、 "commit" 和 "tag" 。
“blob” 用來存儲文件數據,通常是一個文件。
“tree” 有點像一個目錄,它管理一些“tree”或是 “blob”(就像文件和子目錄)。
一個“commit”只指向一個"tree",它用來標記項目某一個特定時間點的狀態。它包括一些關于時間點的元數據,如時間戳、最近一次提交的作者、指向上次提交 (commits) 的指針等等。
一個 “tag” 是來標記某一個提交 (commit) 的方法。
幾乎所有的 Git 功能都是使用這四個簡單的對象類型來完成的。它就像是在你本機的文件系統之上構建一個小的文件系統。
Blob對象一個 blob 通常用來存儲文件的內容。
Tree 對象一個 tree 對象可以指向一個包含文件內容的 blob 對象, 也可以是其它包含某個子目錄內容的其它 tree 對象,它一般用來表示內容之間的目錄層次關系。 Tree 對象、blob 對象和其它所有的對象一樣,都用其內容的 SHA1 哈希值來命名的;只有當兩個 tree 對象的內容完全相同(包括其所指向所有子對象)時,它的名字才會一樣,反之亦然。這樣就能讓Git 僅僅通過比較兩個相關的 tree 對象的名字是否相同,來快速的判斷其內容是否不同。
Commit對象commit 對象指向一個 tree 對象,并且帶有相關的描述信息。
一個提交 commit 由以下的部分組成:
一個 tree 對象:tree 對象的 `SHA1簽名, 代表著目錄在某一時間點的內容。
父對象 (parent(s)): 提交 (commit) 的SHA1簽名代表著當前提交前一步的項目歷史。合并的提交 (merge commits) 可能會有不只一個父對象。如果一個提交沒有父對象,那么我們就叫它“根提交" (root commit) ,它就代表著項目最初的一個版本 (revision)。 每個項目必須有至少有一個“根提交"(root commit)。
作者 (author) :做了此次修改的人的名字,還有修改日期。
提交者(committer):實際創建提交(commit)的人的名字, 同時也帶有提交日期。
注釋:用來描述此次提交。
注意:一個提交(commit)本身并沒有包括任何信息來說明其做了哪些修改; 所有的修改(changes)都是通過與父提交(parents)的內容比較而得出的。 值得一提的是, 盡管git可以檢測到文件內容不變而路徑改變的情況, 但是它不會去顯式(explicitly)的記錄文件的更名操作(可以看一下 git diff )。
一般用 git commit 來創建一個提交 (commit), 這個提交 (commit) 的父對象一般是當前分支 (current HEAD) ,同時把存儲在當前索引 (index) 的內容全部提交。
對象模型:如果我們把它提交 (commit) 到一個 Git 倉庫中, 在 Git 中它們也許看起來就如下圖:
你可以看到:每個目錄都創建了 tree對象 (包括根目錄), 每個文件都創建了一個對應的 blob對象。最后有一個 commit 對象 來指向根 tree 對象 (root of trees) , 這樣我們就可以追蹤項目每一項提交內容.
標簽對象:一個標簽對象包括一個對象名(SHA1簽名), 對象類型, 標簽名, 標簽創建人的名字(tagger), 還有一條可能包含有簽名(signature)的消息.
回到我們的問題 強大的git分支有人把 Git 的分支模型稱為它的必殺技特性,也正因為這一特性,使得它 從眾多版本控制系統中脫穎而出。
Git 保存的不是文件的變化或者差異,而是一系列不同時刻的文件快照。
在進行提交操作時,Git 會保存一個提交對象(commit object)。知道了 Git 保存數據的方式,該提交對象會包含一個指向暫存內容快照的指針。 但不僅僅是這樣,該提交對象還包含了作者的姓名和郵箱、提交時輸入的信息以及指向它的父對象的指針。首次提交產生的提交對象沒有父對象,普通提交操作產生的提交對象有一個父對象,而由多個分支合并產生的提交對象有多個父對象,
當使用 git commit 新建一個提交對象前,Git 會先計算每一個子目錄的校驗和(40 個字符長度 SHA-1 字串),然后在 Git 倉庫中將這些目錄保存為樹(tree)對象。之后 Git 創建的提交對象,除了包含相關提交信息以外,還包含著指向這個樹對象(項目根目錄)的指針,如此它就可以在將來需要的時候,重現此次快照的內容了。
Git 中的分支,其實本質上僅僅是個指向 commit 對象的可變指針。Git 會使用 master 作為分支的默認名字。在若干次提交后,你其實已經有了一個指向最后一次提交對象的 master 分支,它在每次提交的時候都會自動向前移動。
Git 是如何知道你當前在哪個分支上工作的呢?其實答案也很簡單,它保存著一個名為 HEAD 的特別指針。在 Git 中,它是一個指向你正在工作中的本地分支的指針,我們可以將 HEAD 想象為當前分支的別名。
由于 Git 中的分支實際上僅是一個包含所指對象校驗和的文件,所以創建和銷毀一個分支就變得非常廉價。說白了,新建一個分支就是向一個文件寫入 41 個字節(外加一個換行符)那么簡單,當然也就很快了。
大多數版本控制系統它們管理分支大多采取備份所有項目文件到特定目錄的方式,所以根據項目文件數量和大小不同,可能花費的時間也會有相當大的差別,快則幾秒,慢則數分鐘。
而 Git 的實現與項目復雜度無關,它永遠可以在幾毫秒的時間內完成分支的創建和切換。同時,因為每次提交時都記錄了祖先信息(parent 對象),將來要合并分支時,尋找恰當的合并基礎(譯注:即共同祖先)的工作其實已經自然而然地擺在那里了,所以實現起來非常容易。Git 鼓勵開發者頻繁使用分支,正是因為有著這些特性作保障。
分支的新建與合并新建分支并進入
$ git checkout -b iss53
根據需求寫代碼并提交
$ git commit -a -m "new text"
接到線上問題需要并且修改bug
$ git checkout master $ git checkout -b hotfix $ git commit -a -m "fixed bug"
合并修改完bug的代碼進master(暫無沖突)
$ git checkout master $ git merge hotfix
解決問題后刪除hotfix分支并返回原來的iss53分支繼續工作
$ git branch -d hotfix $ git checkout iss53 $ git commit -a -m "finished"
合并iss53分支進主分支
$ git checkout master $ git merge iss53
請注意,這次合并操作的底層實現,并不同于之前 hotfix 的并入方式。因為這次你的開發歷史是從更早的地方開始分叉的。由于當前 master 分支所指向的提交對象(C4)并不是 iss53 分支的直接祖先,Git 不得不進行一些額外處理。就此例而言,Git 會用兩個分支的末端(C4 和 C5)以及它們的共同祖先(C2)進行一次簡單的三方合并計算。
這次,Git 沒有簡單地把分支指針右移,而是對三方合并后的結果重新做一個新的快照,并自動創建一個指向它的提交對象(C6)。這個提交對象比較特殊,它有兩個祖先(C4 和 C5)。
有時候合并操作并不會如此順利。如果在不同的分支中都修改了同一個文件的同一部分,Git 就無法干凈地把兩者合到一起。如果你在解決問題 #53 的過程中修改了 hotfix 中修改的部分,將會出現問題。
Git 作了合并,但沒有提交,它會停下來等你解決沖突。
任何包含未解決沖突的文件都會以未合并 unmerged 的狀態列出。Git 會在有沖突的文件里加入標準的沖突解決標記,可以通過它們來手工定位并解決這些沖突。
rebase 變基最容易的整合分支的方法是 merge 命令,它會把兩個分支最新的快照(C3 和 C4)以及二者最新的共同祖先(C2)進行三方合并,合并的結果是產生一個新的提交對象(C5)。:
但是,如果你想讓 experiment分支歷史看起來像沒有經過任何合并一樣,還有另外一個選擇:你可以把在 C3 里產生的變化補丁在 C4 的基礎上重新打一遍。在 Git 里,這種操作叫做變基 (rebase)。有了 rebase 命令,就可以把在一個分支里提交的改變移到另一個分支里重放一遍。
$ git checkout experiment $ git rebase master
它的原理是回到兩個分支最近的共同祖先,根據當前分支(也就是要進行變基的分支 experiment )后續的歷次提交對象(這里只有一個 C3),生成一系列文件補丁,然后以基底分支(也就是主干分支 master)最后一個提交對象(C4)為新的出發點,逐個應用之前準備好的補丁文件,最后會生成一個新的合并提交對象(C3"),從而改寫 experiment 的提交歷史,使它成為 master 分支的直接下游
簡單講他就是把你的 experiment 分支里的每個提交 commit 取消掉,并且把它們臨時 保存為補丁 patch (這些補丁放到".git/rebase"目錄中),然后把 experiment 分支更新 到最新的 origin 分支,最后把保存的這些補丁應用到 experiment 分支上。
現在的 C3" 對應的快照,其實和普通的三方合并,即上個例子中的 C5 對應的快照內容一模一樣了。雖然最后整合得到的結果沒有任何區別,但變基能產生一個更為整潔的提交歷史。如果視察一個變基過的分支的歷史記錄,看起來會更清楚:仿佛所有修改都是在一根線上先后進行的,盡管實際上它們原本是同時并行發生的。
在 rebase 的過程中,也許會出現沖突 conflict。在這種情況,Git 會停止 rebase 并會讓你去解決 沖突;在解決完沖突后,用 git-add 命令去更新這些內容的索引 index, 然后,你無需執行 git-commit ,只要執行:
$ git rebase --continue
這樣git會繼續應用 apply 余下的補丁。在任何時候,你可以用 --abort 參數來終止 rebase 的行動,并且 experiment 分支會回到 rebase 開始前的狀態。
$ git rebase --abort
git merge 應該只用于為了保留一個有用的,語義化的準確的歷史信息,而希望將一個分支的整個變更集成到另外一個 branch 時使用 rebase。這樣形成的清晰版本變更圖有著重要的價值。
所有其他的情況都是以不同的方式使用 rebase 的適合場景:經典型方式,三點式,interactive 和 cherry-picking。
我們使用變基的目的:是想要得到一個能在遠程分支上干凈應用的補丁 — 比如某些項目你不是維護者,但想幫點忙的話,最好用變基:先在自己的一個分支里進行開發,當準備向主項目提交補丁的時候,根據最新的 origin/master 進行一次變基操作然后再提交,這樣維護者就不需要做任何整合工作(實際上是把解決分支補丁同最新主干代碼之間沖突的責任,化轉為由提交補丁的人來解決。),只需根據你提供的倉庫地址作一次快進合并,或者直接采納你提交的補丁。
需要注意,合并結果中最后一次提交所指向的快照,無論是通過變基,還是三方合并,都會得到相同的快照內容,只不過提交歷史不同罷了。變基是按照每行的修改次序重演一遍修改,而合并是把最終結果合在一起。
有趣的變基我在不同的topic之間來回切換,這樣會導致我的歷史中不同topic互相交叉,邏輯上組織混亂;
我們可能需要多個連續的commit來解決一個bug;
我可能會在commit中寫了錯別字,后來又做修改;
甚至我們在一次提交時純粹就是因為懶惰的原因,我可能吧很多的變更都放在一個commit中做了提交。
rebase可以合并commit
rebase可以用來修改commit信息
rebase可以用來拆分commit
git rebase -i HEAD~3
變基也可以放到其他分支進行,并不一定非得根據分化之前的分支。
變基的風險要用它得遵守一條準則:
不要在公共分支上使用rebase。
“No one shall rebase a shared branch”?—?Everyone about rebase
如果你遵循這條金科玉律,就不會出差錯。
在進行變基的時候,實際上拋棄了一些現存的提交對象而創造了一些類似但不同的新的提交對象。如果你把原來分支中的提交對象發布出去,并且其他人更新下載后在其基礎上開展工作,而稍后你又用 git rebase 拋棄這些提交對象,把新的重演后的提交對象發布出去的話,你的合作者就不得不重新合并他們的工作,這樣當你再次從他們那里獲取內容時,提交歷史就會變得一團糟。
注意rebase往往會重寫歷史,所有已經存在的commits雖然內容沒有改變,但是commit本身的hash都會改變。
結論:只要你的分支上需要rebase的所有commits歷史還沒有被push過(比如上例中rebase時從分叉處開始有兩個commit歷史會被重寫),就可以安全地使用git rebase來操作。
上述結論可能還需要修正:對于不再有子分支的branch,并且因為rebase而會被重寫的commits都還沒有push分享過,可以比較安全地做rebase
思考下它的功能吧 git pull --rebase
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/94909.html
摘要:打開是個構造函數,定義了一些靜態屬性和方法我們先看在插件下地址上面寫的解釋就跟沒寫一樣在文件下我們看到輸出的一些對象方法每一個對應一個模塊而在下引入的下面,我們先研究引入的對象的英文單詞解釋,除了最常用的點擊手勢之外,還有一個意思是水龍頭進 打開compile class Compiler extends Tapable { constructor(context) { ...
摘要:淺析筆者在此整理了常見的命令,的重要性無需多言,與其再百度海中搜索命令,不妨嘗試收藏筆者的此篇作品。旨在快速高效地處理無論規模大小的任何軟件工程。其最大特色就是分支及合并操作非常快速簡便。 淺析git 筆者在此整理了常見的git命令,git的重要性無需多言,與其再百度海中搜索git命令,不妨嘗試收藏筆者的此篇作品。希望對你的學習有所幫助。 版本控制系統之git Git: (一)簡介:G...
摘要:淺析筆者在此整理了常見的命令,的重要性無需多言,與其再百度海中搜索命令,不妨嘗試收藏筆者的此篇作品。旨在快速高效地處理無論規模大小的任何軟件工程。其最大特色就是分支及合并操作非常快速簡便。 淺析git 筆者在此整理了常見的git命令,git的重要性無需多言,與其再百度海中搜索git命令,不妨嘗試收藏筆者的此篇作品。希望對你的學習有所幫助。 版本控制系統之git Git: (一)簡介:G...
摘要:淺析參數說明對于所有列表里提到的純模塊做處理需要在腳本里有一個包名到目錄的映射。闡明包名到目錄的映射,見鍵代表了包的名字,空的包名則代表不在任何包中的頂層包。最終會在下生成可執行文件,調用制定的函數實例分析 python setup.py 淺析 setuptools.setup() 參數說明 packages 對于所有 packages 列表里提到的純 Python 模塊做處理 需要...
閱讀 2787·2021-11-17 09:33
閱讀 2169·2021-09-03 10:40
閱讀 522·2019-08-29 18:45
閱讀 2956·2019-08-29 16:21
閱讀 613·2019-08-29 11:11
閱讀 3394·2019-08-26 12:00
閱讀 2947·2019-08-23 18:19
閱讀 1094·2019-08-23 12:18