Git的原理簡介和經常使用命令

Git和SVN是咱們最經常使用的版本控制系(Version Control System, VCS),固然,除了這兩者以外還有許多其餘的VCS,例如早期的CVS等。顧名思義,版本控制系統主要就是控制、協調各個版本的文檔內容的一致性,這些文檔包括但不限於代碼文件、圖片文件等等。早期SVN佔據了絕大部分市場,然後來隨着Git的出現,愈來愈多的人選擇將它做爲版本控制工具,社區也愈來愈強大。相較於SVN,最核心的區別是Git是分佈式的VCS,簡而言之,每個你pull下來的Git倉庫都是主倉庫的一個分佈式版本,倉庫的內容徹底同樣,而SVN則否則,它須要一箇中央版本庫來進行集中控制。採用分佈式模式的好處即是你再也不依賴於網絡,當有更改須要提交的時候而你又沒法鏈接網絡時,你只須要把更改提交到本地的Git倉庫,最後有網絡的時候再把本地倉庫和遠程的主倉庫進行同步便可。固然,分佈式和非分佈式各有各的優缺點,可是目前來看,分佈式的Git正逐漸被愈來愈多的人所接受並推廣。本文主要對Git的基本原理和經常使用命令進行簡介,試圖從底層來講明Git是如何工做的,從而幫助你們理解上層命令在執行的時候背後所產生的動做和變化。原理部分的內容能夠參考Pro Git作進一步的瞭解,而經常使用的命令能夠參考其餘的資料。本文的總結根據本身的理解進行描述,若是錯誤,請不吝賜教。html

Git的基本原理

本質上,Git是一套內容尋址(content-addressable)文件系統,而和咱們直接接觸的Git界面,只不過是封裝在其之上的一個應用層。這個關係很有點相似於計算機網絡中應用層和下屬層的關係。在Git中,那些和應用層相關的命令(也就是咱們最經常使用的命令,如git commit、 git push等),咱們稱之爲porcelain命令(瓷器之意,意爲成品、高級命令);而和底層相關的命令(幾乎不會在平常中使用,如git hash-object、git update-index等),則稱之爲plumbing命令(管道之意,是鏈接git應用界面和git底層實現的一個管道,相似於shell,底層命令)。要了解Git的底層原理,就須要瞭解Git是如何利用底層命令來實現高層命令的。在此以前,讓咱們先來看一下Git的目錄結構,和各個文件在Git中的做用。node

Git的目錄結構

在操做系統中,咱們的倉庫就是一個文件夾。可是爲何這些文件夾就是Git倉庫呢?這是由於Git在初始化的時候會生成一個.git的文件夾,而Git進行版本控制所須要的文件,則都放在這個文件夾中。在桌面上新建一個目錄,而後利用命令行在該目錄下運行git init命令便可完成git倉庫的初始化。若是這個時候你看不到.git目錄,這是由於你的操做系統自動隱藏了該文件夾,須要在系統設置中設置隱藏文件可見。進入.git目錄,即可以看到其中有不少的文件和文件夾,這每個文件都有各自的做用,下面結合圖1來進行說明。git

圖1 .git目錄結構示意圖web

在上圖中,第一排的幾個文件和文件夾是Git的核心,而第二排的則是一些不須要特別關注的。核心文件包括:config文件、objects文件夾、HEAD文件、index文件以及refs文件夾。下面依次對其進行說明。shell

  • config文件:該文件主要記錄針對該項目的一些配置信息,例如是否以bare方式初始化、remote的信息等,經過git remote add命令增長的遠程分支的信息就保存在這裏;
  • objects文件夾:該文件夾主要包含git對象。關於什麼是git對象,將會在下一節進行詳細介紹。Git中的文件和一些操做都會以git對象來保存,git對象分爲BLOB、tree和commit三種類型,例如git commit即是git中的commit對象,而各個版本之間是經過 版本樹來組織的,好比當前的HEAD會指向某個commit對象,而該commit對象又會指向幾個BLOB對象或者tree對象。objects文件夾中會包含不少的子文件夾,其中Git對象保存在以其sha-1值的前兩位爲子文件夾、後38位位文件名的文件中;除此之外,Git爲了節省存儲對象所佔用的磁盤空間,會按期對Git對象進行壓縮和打包,其中pack文件夾用於存儲打包壓縮的對象,而info文件夾用於從打包的文件中查找git對象;
  • HEAD文件:該文件指明瞭git branch(即當前分支)的結果,好比當前分支是master,則該文件就會指向master,可是並非存儲一個master字符串,而是分支在refs中的表示,例如ref: refs/heads/master。
  • index文件:該文件保存了暫存區域的信息。該文件某種程度就是緩衝區(staging area),內容包括它指向的文件的時間戳、文件名、sha1值等;
  • Refs文件夾:該文件夾存儲指向數據(分支)的提交對象的指針。其中heads文件夾存儲本地每個分支最近一次commit的sha-1值(也就是commit對象的sha-1值),每一個分支一個文件;remotes文件夾則記錄你最後一次和每個遠程倉庫的通訊,Git會把你最後一次推送到這個remote的每一個分支的值都記錄在這個文件夾中;tag文件夾則是分支的別名,這裏不須要對其有過多的瞭解;

除此之外,.git目錄下還有不少其餘的文件和文件夾,這些文件和文件夾會額外支撐一些其餘的功能,可是不是Git的核心部分,所以稍做了解便可。hooks主要定義了客戶端或服務端鉤子腳本,這些腳本主要用於在特定的命令和操做以前或者以後進行特定的處理,好比:當你把本地倉庫push到服務器的遠程倉庫時,能夠在服務器倉庫的hooks文件夾下定義post_update腳本,在該腳本中能夠經過腳本代碼將最新的代碼部署到服務器的web服務器上,從而將版本控制和代碼發佈無縫鏈接起來;description文件僅供GitWeb程序使用,這裏不須要過多的關心;logs則記錄了本地倉庫和遠程倉庫的每個分支的提交記錄,即全部的commit對象(包括時間、做者等信息)都會被記錄在這個文件夾中,所以這個文件夾中的內容是咱們查看最頻繁的,無論是Git log命令仍是tortoiseGit的show log,都須要從該文件夾中獲取提交日誌;info文件夾保存了一份不但願在.gitignore 文件中管理的忽略模式的全局可執行文件,基本也用不上;COMMIT_EDITMSG文件則記錄了最後一次提交時的註釋信息。從以上的描述中咱們能夠發現,.git文件夾中包含了衆多功能不一的文件夾和文件,這些文件夾和文件是描述Git倉庫所必不可少的信息,不能夠隨意更改或刪除;尤爲須要注意的是,.git文件夾隨着項目的演進,可能會變得愈來愈大,由於任何文件的任何一個變更,都須要Git在objects文件夾下將其從新存儲爲一個新的對象文件,所以若是一個文件很是大,那麼你提交幾回改動就會形成.git文件夾容量成倍增加。所以,.git文件夾更像是一本書,每個版本的每個變更都存儲在這本書中,並且這本書還有一個目錄,指明瞭不一樣的版本的變更內容存儲在這本書的哪一頁上,這就是Git的最基本的原理。數據庫

從底層命令理解Git

上節中咱們講到,Git分爲porcelain命令和plumbing命令,而porcelain命令是基於plumbing來實現的。爲了進一步的理解Git的底層原理,咱們將在這一節中詳細的探討Git對象的存儲格式以及plumbing命令。若是把Git比做Linux操做系統,那plumbing命令就有點相似於shell命令,而上層的procelain命令即是利用shell命令編寫的一系列的系統功能或工具,如你自定義的自動化運維工具等。在接下來的介紹中,咱們將試着如何利用plumbing命令,而不是porcelain命令,來完成Git的暫存和提交工做,並利用log查看提交記錄。首先,咱們從Git的對象介紹開始。服務器

Git對象

在以前咱們提到過,Git是一套內容尋址(content-addressable)文件系統,那麼Git是怎麼進行尋址呢?其實,尋址無非就是查找,而Git採用HashTable的方式進行查找,也就是說,Git只是經過簡單的存儲鍵值對(key-value pair)的方式來實現內容尋址的,而key就是文件(頭+內容)的哈希值(採用sha-1的方式,40位),value就是通過壓縮後的文件內容。所以,在接下來的實踐中,咱們會常常經過40位的hash值來進行plumbing操做,幾乎每個plumbing命令都須要經過key來指定所要操做的對象。網絡

Git對象的類型包括:BLOB、tree對象、commit對象。BLOB對象能夠存儲幾乎全部的文件類型,全稱爲binary large object,顧名思義,就是大的二進制表示的對象,這種對象類型和數據庫中的BLOB類型(常常用來在數據庫中存儲圖片、視頻等)是同樣的,看成一種數據類型便可;tree對象是用來組織BLOB對象的一種數據類型,你徹底能夠把它想象成二叉樹中的樹節點,只不過Git中的樹不是二叉樹,而是"多叉樹";commit對象表示每一次的提交操做,由tree對象衍生,每個commit對象表示一次提交,在建立的過程當中能夠指定該commit對象的父節點,這樣全部的commit操做即可以鏈接在一塊兒,而這些commit對象便組成了提交樹,branch只不過是這個樹中的某一個子樹罷了。若是你能理解commit樹,那Git幾乎就已經理解了一半了。數據結構

Git對象的存儲方式也很簡單,基本能夠用以下表達式來表示:app

Key = sha1(file_header + file_content)

Value = zlib(file_content)

簡單來講,Git 將文件頭與原始數據內容拼接起來,並計算拼接後的新內容的 40位的sha-1校驗和,將該校驗和的前2位做爲object目錄中的子目錄的名稱,後38位做爲子目錄中的文件名;而後,Git 用zlib的方式對數據內容進行壓縮,最後將用 zlib 壓縮後的內容寫入磁盤。文件頭的格式爲 "blob #{content.length}\0",例如"blob 16\000",這種文件頭格式也是常常採用的格式。對於tree對象和commit對象,文件頭的格式都是同樣的,可是其文件數據倒是有固定格式的,鑑於本次只是Git原理的基本介紹,這裏再也不詳細描述,有興趣的能夠去Git的官網查找相關文檔進行了解;其實也能夠本身按照理解構思一下,若是讓你來設計這種格式,應該如何設計:tree對象相似於樹中節點的定義,在tree對象中要包含對鏈接的BLOB對象的引用,而commit對象與tree對象相似,要包含提交的tree對象的引用,想到這裏,我以爲文檔的閱讀大概也就能夠省去了。

對象暫存區

在procelain命令中,爲了將修改的文件加入暫存區(也叫索引庫,將修改的文件key-value化,.git根目錄下的index文件記錄該暫存區中的文件索引),咱們會使用git add filename命令。那麼在git add這個命令的背後,Git是如何使用plumbing命令來完成文件的索引操做呢?其實,git add命令對應着兩個基本的plumbing命令:

git hash-object #獲取指定文件的key,若是帶上-w選項,則會將該對象的value進行存儲

git update-index #將指定的object加入索引庫,須要帶上—add選項

所以,git add命令在plumbing命令中實際上是分紅了兩步:首先,經過hash-object命令將須要暫存的文件進行key-value化轉換成Git對象,並進行存儲,拿到這些文件的key;而後,經過update-index命令將這些對象加入到索引庫進行暫存,這樣便完成了Git文件的暫存操做。若是要根據Git對象的key來查看文件的信息,還須要涉及下面的一個plumbing命令:

git cat-file –p/-t key #獲取指定key的對象信息,-p打印詳細信息,-t打印對象的類型

利用該命令能夠查看已經key-value化的Git對象的詳細信息。

    接下來,咱們利用plumbing命令來進行git add的實踐。首先,新建一個Git倉庫,經過在新建的文件夾中利用git init命令來初始化,這裏再也不詳述,以下圖所示:

初始化以後,會在當前目錄下生成.git目錄,進入該目錄,就會發現咱們上述的目錄結構。而後,咱們新建一個version.txt文件並在文件中寫入"version 1"字符串,這是version.txt的第一個版本,而後利用git hash-object –w命令將該文件轉換爲Git的對象並存儲,以下圖:

這裏hash-objec命令會返回該Git對象的key值,這時到.git目錄的objects目錄下會發現,多了一個6c子目錄,該目錄中的文件名稱爲58b76a52188643965f3a6704166e8e0424b7fe,也就是該key值的後38位。記下該key值,由於咱們要根據該key值將該對象加入索引庫。接着,咱們利用update-index命令進行索引化操做,以下圖:

注意,這裏必定要帶上—add選項,而—cacheinfo選項則指出該文件的文件類型,100644表示普通文件,與之相關的還有可執行文件等等;而且,除了指定key值,還須要指定文件名,代表要把哪一個文件的哪一個版本加入索引庫。該命令執行完成後,能夠發現.git目錄下多了index文件,而且在之後每次update-index命令執行以後,該index文件的內容都會發生變化。至此,git add的主要過程也便完成了。

    這裏咱們簡單談一下index文件。index是一個索引文件,存放的是暫存區的整個目錄樹的信息,而且爲目錄樹中的每一個文件都保存了時間戳和長度。若是用UltraEdit打開使用過程當中的index文件,能夠發現index的格式爲如下形式:

Index魔數(DIRC) + 版本號 + 暫存的文件個數 + 每一個文件的時間戳和長度

Index索引庫記錄從項目初始化到目前爲止,項目倉庫中全部文件最後一次修改時刻的時間戳以及對應的長度信息,所以隨着加入倉庫中的文件不斷增多,index文件也會不斷增大。每次調用git add命令,都會把add的文件的索引信息(時間戳和大小)進行更新,而咱們所使用的git status命令,則會把每個文件的索引信息和上次提交的索引信息進行比較,若是發生了變化,就會顯示出來。Pro git 中是這樣描述暫存操做的:暫存操做會對每個文件計算校驗和(即第一章中提到的 SHA-1 哈希字串),而後把當前版本的文件快照保存到 Git 倉庫中(Git 使用 blob 類型的對象存儲這些快照),並將校驗和加入暫存區域。意思很明確,也就是每一個文件對應的當前版本的key也會加入到index文件中,這個我沒有進行驗證,不過理論上講應該是正確的。

建立樹節點

在Git中,全部的內容以tree或者BLOB對象進行存儲,若是把Git比做UNIX的文件系統,則tree對象對應於UNIX文件系統中的目錄,而BLOB對象則對應於inodes或文件內容。在Git對象小節中,咱們大體猜測了tree對象的存儲格式。其實,一個單獨的tree對象包含一條或多條tree記錄,每一條記錄含有一個指向BLOB對象或子tree對象的sha-1指針(也就是一個40位的key值),並附有該對象的權限模式 、類型和文件名信息,所以,咱們的猜測也是八九不離十的。爲何要建立tree對象呢?咱們都知道,在Git中,咱們add完已修改的文件以後,通常就直接commit暫存區中的內容到本地倉庫了,彷佛並無tree這個概念。其實,建立tree對象只是add和commit中間的一個緩衝步驟,由於commit對象要根據tree對象來建立。那麼如何建立tree對象呢?只須要以下命令便可:

git write-tree #根據索引庫中的信息建立tree對象

該命令返回所建立的tree對象的key值,經過git cat-file能夠查看該對象的詳細信息。建立過程以下圖:

從圖中能夠看出,cat-file –t顯示該對象的類型爲tree,代表該tree對象建立成功了,至此,樹節點便建立完成了。

    實際上,因爲index暫存區包括了項目倉庫中全部的文件,所以commit對象所對應的tree對象,永遠都是工做目錄的根tree對象。也就是說,每次commit,都是把工做目錄的根目錄所對應的tree對象,連接給這次的commit對象;並且,在Git中,每一個子目錄都對應一個tree對象,每一個文件對應一個BLOB對象,所以整個工做目錄對應一棵Git對象樹,根節點就是commit對象所引用的tree節點,而每一個子文件夾又分別對應一棵子樹。因此任何一個文件的更改,都會致使其上層全部父對象的更改和從新存儲。這裏再也不進行演示,你能夠經過git add和git commit進行屢次提交,並在每次提交以後使用git log查看commit對象的key,使用cat-file獲取對應的tree對象的key,並再次使用cat-file獲取該tree對象下全部的子對象,這時你能夠發現,子文件夾都對應一個tree節點,文件都對應一個BLOB節點。

Commit對象

在Git中,每一次commit都對應一個commit對象,而一個commit對象對應一個tree對象。爲了建立commit對象,須要使用以下命令:

git commit-tree key –p key2 #根據tree對象建立commit對象,-p表示前繼commit對象

該方法有點相似於數據結構中樹的增長節點操做:都是向父節點中增長子節點。其中,-p選項指明瞭前繼commit對象的key值,也就是父節點的key值,這樣,這兩個commit節點便鏈接在了一塊兒,而不斷的鏈接便構成了一棵樹,也就是咱們接下來要講的提交樹。Commit對象的建立過程以下所示:

在該命令中,咱們只須要指定key的前六位便可,因爲這是第一次提交,所以不須要帶上-p選項來指明父節點。經過cat-file命令能夠看到,commit對象已建立成功,該commit對象中包含了與之關聯的tree對象的key值,以及author和committer的信息。若是要查看完整的提交記錄,能夠經過git log –stat key命令,該命令會打印指定commit對象以前的全部提交記錄。至此,commit對象已經建立完成,而咱們也利用plumbing命令,完整的實現了Git的add和commit操做,Cool。到目前爲止,所建立的全部對象的關係以下圖所示:

圖2 第一次提交後Git對象關係圖

提交樹Commit Tree

接下來,咱們在第一次提交的基礎上完成第二次提交和第三次提交。第二次提交咱們會提交version.txt的第二個版本,並增長一個新的文件;第三次提交會演示在tree對象中構造子tree對象並提交。在下面的每一次提交中,咱們還須要指定每一次提交的前繼提交對象,這樣commit對象便鏈接在一塊兒,造成一棵提交樹。首先,咱們進行第二個版本的修改和提交。以下圖,修改version.txt並添加一個new.txt文件,而後利用上面的方法進行key-value化和索引更新:

而後進行索引的更新:

而後咱們利用暫存區建立tree對象,並根據該tree對象建立commit對象,以下圖所示。注意,本次commit須要利用-p選項指定這次commit對象的前繼commit對象,能夠看到,經過git log命令打印出來的commit對象,鏈接在了一塊兒。

本次提交完成後,Git中的對象關係以下圖所示:

圖3 第二次提交後Git對象關係圖

緊接着,咱們來進行第三次提交。首先,利用read-tree命令將第一個版本中的tree對象讀入暫存區。以下圖所示:

注意,在讀取的過程當中,須要加上—prefix選項,不然沒法成功讀取,這是由於在index中相同路徑的文件只能出現一次,因爲version.txt已經存在於index索引庫了,所以若是想把第一個版本的tree對象讀取進來,須要將該版本的version.txt放在文件夾bak中。而後建立tree對象並進行第三次提交,以下圖所示:

經過git log能夠查看全部的commit對象。這個時候,經過cat-file命令查看這次建立的tree對象所包含的內容:

能夠看到,所建立的tree對象還不只包括以上的兩個BLOB對象,還包括剛纔讀取的子tree對象,這個時候若是把這個tree再導出成工做目錄的話,則在根目錄會多出一個bak子文件夾。通過第三次提交後,Git中的全部對象的關係如圖4所示。

    注意,這裏加上這樣的步驟只是爲了讓你們明白tree對象中的子tree對象的存在,正如上面上節所說的,整個工做目錄對應一個tree對象,而且其下的每個子文件夾都是一個tree對象,每次的commit對象都對應着根tree對象,而任何一個對象的改變都會致使其上層全部tree對象的從新存儲。

    以上,即是咱們利用plumbing命令完成的三次提交的過程,但願經過這幾個步驟,能讓你簡單的理解porcelain命令和plumbing命令之間的聯繫,爲接下來的Git學習作鋪墊。

圖4 第三次提交後Git對象關係圖

Git的經常使用命令

本節的目的在於對Git中比較重要可是不太會常用的命令進行一個簡要的介紹,從而讓你們對Git中大部門命令都有一個總體的瞭解。Git中的基本命令的使用這裏再也不贅述,總體的工做流程如圖所示。若是對Git的分支還不是很瞭解的話,建議去仔細閱讀下Pro Git的第三章。Git的基本工做流程如圖5所示。其中,git pull、git push、git fetch、git remote等基本命令的使用這裏再也不進行贅述,這些基本的命令是最重要的命令,請務必緊緊掌握。建議經過以上的基本原理的講解和Pro Git的描述對各個基礎命令背後所發生的變化進行詳細的思考,以加深本身對Git應用層命令的認識。不要僅僅把本身侷限於tortoiseGit的GUI的使用中,只有深刻的理解了工具,纔有可能用好它。

本節重點對如下幾個git命令進行介紹,重點在於對這些命令的基本使用的普及,包括:git log、git fork、git rebase、git reset、git reverse和git stash。大多數狀況下,咱們在開發中小型項目的時候,若是團隊成員不是不少,則只須要開一個分支就夠了。在這種狀況下,只要你操做規範,在push以前注意pull最新的代碼,則基本不會出現比較嚴重的衝突或者問題,這時候以上命令基本都用不上,可是在多分支的狀況下,咱們可能會使用以上的命令來進行分支合併或者版本回退等,所以,咱們有必要對這些命令作一個簡單的瞭解,知道在何時去使用它們。

圖5 Git的基本工做流程圖

Git log

在提交了若干更新以後,又或者克隆了某個項目,想回顧下提交歷史,可使用 git log 命令查看。默認不用任何參數的話,git log會按提交時間列出全部的更新,最近的更新排在最上面。通常狀況下,我會使用以下命令來打印log中的提交日誌記錄:

git log --pretty=format:"%h %s" --graph

其中。--pretty選項指定打印的格式,%h表示列出每一個提交對象的短的sha1值(40位中的前6位);--graph選項表示使用圖的方式來打印日誌記錄。打印的結果以下圖所示:

也可使用Git的GUI來顯示Git的提交歷史,在倉庫中右鍵選擇Git GUI,而後選擇菜單欄上的 repository-->visual all branch history 選項,便可以顯示全部分支的提交記錄。以下圖所示:

Git fork

Git fork不是一個Git命令,而是一種工做流。它不是使用單個服務端倉庫做爲『中央』代碼基線,而讓各個開發者都有一個服務端倉庫,這意味着各個代碼貢獻者有2個Git倉庫而不是1個:一個本地私有的,另外一個服務端公開的,以下圖所示。

Forking工做流的一個主要優點是,貢獻的代碼能夠被集成,而不須要全部人都能push代碼到僅有的中央倉庫中。 開發者push到本身的服務端倉庫,而只有項目維護者才能push到正式倉庫。 這樣項目維護者能夠接受任何開發者的提交,但無需給他正式代碼庫的寫權限。

Git rebase

把一個分支中的修改整合到另外一個分支的辦法有兩種,第一種是咱們經常使用的git merge操做,而第二種即是本節要講的rebase(中文翻譯爲衍合)。該命令的原理是,回到兩個分支最近的共同祖先,根據當前分支(也就是要進行衍合的分支experiment)後續的歷次提交對象(這裏只有一個 C3),生成一系列文件補丁,而後以基底分支(也就是主幹分支master)最後一個提交對象(C4)爲新的出發點,逐個應用以前準備好的補丁文件,最後會生成一個新的合併提交對象(C3'),從而改寫 experiment 的提交歷史,使它成爲 master 分支的直接下游。以下圖所示:

通常咱們使用rebase的目的,是想要獲得一個能在遠程分支上乾淨應用的補丁,好比某些項目你不是維護者,但想幫點忙的話,最好用rebase:先在本身的一個分支裏進行開發,當準備向主項目提交補丁的時候,根據最新的 origin/master 進行一次衍合操做而後再提交,這樣維護者就不須要作任何整合工做(其實是把解決分支補丁同最新主幹代碼之間衝突的責任,化轉爲由提交補丁的人來解決),只需根據你提供的倉庫地址做一次快進合併,或者直接採納你提交的補丁。

在rebase的過程當中,也許會出現衝突。在這種狀況,Git會中止rebase並會讓你去解決衝突;在解決完衝突後,用git add命令去更新這些內容的索引, 而後,你無需執行git-commit,只要執行git rebase –continue,這樣git會繼續應用(apply)餘下的補丁。若是要捨棄本次衍合,只須要git rebase --abort便可。切記,一旦分支中的提交對象發佈到公共倉庫,就千萬不要對該分支進行rebase操做

咱們在使用git pull命令的時候,可使用--rebase參數,即git pull --rebase。這裏表示把你的本地當前分支裏的每一個提交取消掉,而且把它們臨時保存爲補丁(這些補丁放到.git/rebase目錄中),而後把本地當前分支更新爲最新的origin分支,最後把保存的這些補丁應用到本地當前分支上。在使用tortoise的pull的過程當中,若是你留意tortoiseGit的日誌的話,你就會發現,它使用的就是這種方式來pull最新的提交的。

Git reset

在使用Git的過程當中,因爲操做不當,做爲初學者的咱們可能常常要去解決衝突。某些時候,當你不當心改錯了內容,或者錯誤地提交了某些commit,咱們就須要進行版本的回退。版本回退最經常使用的命令包括git reset和git revert。這兩個命令容許咱們在版本的歷史之間穿梭。

下面就幾種比較經典的場景進行總結:

  • 場景1:當你改亂了工做區某個文件的內容,想直接丟棄工做區的修改時,用命git checkout -- filename;
  • 場景2:當你不但改亂了工做區某個文件的內容,還添加到了暫存區時,想丟棄修改,分兩步,第一步用命令git reset HEAD file,就回到了場景1,第二步按場景1操做;
  • 場景3:已經提交了不合適的修改到版本庫時,想要撤銷本次提交,使用git reset --hard commit_id,不過前提是沒有推送到遠程庫。

穿梭前,用git log能夠查看提交歷史,以便肯定要回退到哪一個版本;要重返將來,用git reflog查看命令歷史,以便肯定要回到將來的哪一個版本。

Git revert

Git revert用來撤銷某次操做,這次操做以前和以後的commit和history都會保留,而且把此次撤銷做爲一次最新的提交。git revert是提交一個新的版本,將須要revert的版本的內容再反向修改回去,版本會遞增,不影響以前提交的內容。

Git revert和git reset均可以進行版本的回退,將工做區回退到歷史的某個狀態,兩者有以下的區別:

  • git revert是用一次新的commit來回滾以前的commit,而git reset是直接刪除指定的commit(並無真正的刪除,經過git reflog能夠找回),這是兩者最顯著的區別;
  • git reset 是把HEAD向後移動了一下,而git revert是HEAD繼續前進,只是新的commit的內容和要revert的內容正好相反,可以抵消要被revert的內容;
  • 在回滾這一操做上,效果差很少。可是在往後繼續merge之前的老版本時有區別。由於git revert是用一次逆向的commit"中和"以前的提交,所以往後合併老的branch時,致使這部分改變不會再次出現;可是git reset是之間把某些commit在某個branch上刪除,於是和老的branch再次merge時,這些被回滾的commit應該還會被引入。

Git stash

Git stash用來暫存當前正在進行的工做, 將工做區還沒加入索引庫的內容壓入本地的Git棧中,在須要應用的時候再彈出來。好比想pull 最新代碼,又不想加新commit;或者爲了修復一個緊急的bug,先stash,使返回到本身上一個commit,改完bug以後再stash pop,繼續原來的工做。Git stash可讓本地倉庫返回到上一個提交狀態,而本地的還未提交的內容則被壓入Git棧。Git stash的基本使用流程以下:

git stash #暫存工做區還沒有提交的內容

Do your work #在上一個提交的狀態之上完成你的操做

git stash pop #將暫存的內容彈出並應用

    當你屢次使用git stash命令後,你的棧裏將充滿了未提交的代碼,這時候你會對將哪一個版本應用回來有些困惑,這時git stash list命令能夠將當前的Git棧信息打印出來,你只須要將找到對應的版本號,例如使用 git stash apply stash@{1} 就能夠將你指定版本號爲stash@{1}的暫存內容取出來,當你將全部的棧都應用回來的時候,可使用git stash clear來將棧清空。TortoiseGit中的stash save菜單就對應該命令。

總結

本文主要對Git的基本原理和經常使用命令進行介紹和知識普及。從Git的目錄結構,到porcelain命令和plumbing命令,到利用plumbing命令完成commit實踐,最後對一些比較重要的命令進行說明,但願閱讀完本文,你能對Git的原理有總體的認識,同時可以靈活的使用Git的各類命令。本文大多數內容來源於互聯網,是一個知識收集和理解總結性的文章,但願能真正幫助到你們。

相關文章
相關標籤/搜索