朋友整理的,放這里偶爾過來看看
一、基本介紹
首先,Git作為版本控制系統,他的原理與SVN為首的集中式版本控制系統差別較大。
簡史
很多人都知道,Linus在1991年創建了開源的Linux,從此,Linux系統不斷發展,已經成為最大的服務器系統軟件了。
Linus Torvalds在2002年起,使用BitMover的版本控制軟件BitKeeper管理Linux核心開發,而因為BitKeeper除商業付費版本,僅提供可免費使用但不允許修改重新編譯的精簡版本,引起了開源社區的不滿,如自由軟件之父Richard stallman也敢嚴厲批評Linux Torvalds使用非自由軟件開發Linux核心。
在2005年,Samba檔案服務器開發人Andrew Tridgell寫了連接BitKeeper存儲庫的簡單程序,被BitMover創辦人Larry McVoy指控對BitKeeper進行逆向工程,因為決定停止BitKeeper對Linux的支持。
頓時Linux核心開發受到了嚴峻的挑戰,而Linus Torvalds秉承自己的版本自己寫的精神,整個周末都不見人影,隔周卻如變戲法般帶著Git出現。
Linus花了近10天時間自己用C寫了一個分布式版本控制系統,這就是Git!一個月之內,Linux系統的源碼已經由Git管理了!牛是怎么定義的呢?大家可以體會一下。
Git迅速成為最流行的分布式版本控制系統,尤其是2008年,GitHub網站上線了,它為開源項目免費提供Git存儲,無數開源項目開始遷移至GitHub,包括jQuery,PHP,Ruby等等。
1.集中式版本控制系統
集中式版本控制系統,其將版本庫集中存放在中央服務器上,所有的版本控制也都在中央服務器,所以在項目分支功能上略顯無力,所以對于這類項目的版本控制,只有通過多次的項目、文件網絡提交來形成版本控制。并且在中央服務器上發生故障時,對整個團隊的協同工作效率略有下降。甚至在最壞的情況下發生項目丟失現象。
當然,他的強大也是毋庸置疑的,在統一的版本控制器上可以清楚看到該項目上的所有開發者在做什么,集中化項目也可以很好的管理項目版本,所以集中式版本控制系統是以項目的視角進行控制版本。管理員也輕松掌控著每個開發者的權限。
2.分布式版本控制系統
相對于上述系統來講,Git則以分布式的形式完成版本控制功能。開發者并不是在原始代碼上進行加工并上傳形成版本樹,而是將整個項目鏡像下來,將項目分布式管理。所以在發生上述故障時,其他開發者也有相對完整項目版本庫。而其強大的分支管理更是Git的版本控制特點。Git作為版本控制是以開發者的視角進行版本控制,形成與開發者相匹配的版本樹。
二、工作特性
1.保存方式
首先,Git保存數據是以快照的形式展現。如果此次版本中的文件沒有做任何修改,則會保存上個版本的地址鏈接。他與SVN的記錄文件差異的方法不同。當保存一個文件的時候,Git把這個文件的信息和內容存儲在一個文件里,然后求得這個文件的sha-1作為文件名(.git/objects)。在保存在Git中的數據都是用這些sha-1算法生成的哈希值作為唯一索引進行引用,這一方式可以節省項目存儲的空間。
sha-1算法是將文件中的內容計算生成一個40位的哈希值,sha-1的前兩位為文件夾名/sha1剩余部分為該文件的文件名。他就像每一個文件的指紋記錄,在文件進入暫存區中生成,當該文件進入本地倉庫后,指針便指向該文件。
0c85f9f5ce1d60376130469d4e455166c041ea
Git更像是把數據看作是對小型文件系統的一組快照,將所有數據以哈希值作為引用key,合理保存并使用我們的項目文件。
2.區域劃分
其次需要清除Git的工作區、暫存區、本地倉庫和遠程倉庫概念。
工作區:項目目錄
暫存區:在英文解釋中使用index/stage來表示暫存區(在.git/index中),此時會將文件生成快照,在工作區的文件與本地最新的版本庫文件發生變化后,便用指針指向這個文件。
本地倉庫:相對于每個開發者本地Git的項目版本(在git/objects中)
遠程倉庫*:個人理解為項目的統一版本庫
3.文件分類
我們所有的項目文件就在這四個域中進行各種操作。而不同的域中會以不同的文件狀態展示已提交(committed),已修改(modified)和已暫存(staged)。
已修改:表示修改了某個文件,但還沒有提交保存,處于修改后的工作區中
已暫存:已暫存表示把已修改的文件放在下次提交時要保存的清單上
已提交:表示該文件已經被安全地保存在本地數據庫,處于本地倉庫中
4.git對象
從根本上講,git是一套內容尋址的文件系統,存儲的是key-value鍵值對,然后根據key值來查找value的,說到尋址就會想到指針,git也是根據指針來尋址的,這些指針就存儲在git的對象中。git一共有3種對象,commit對象,tree對象和blob對象,最后應該還有一個tag
commit 該對象指向一個"tree"對象,并且帶有相關的描述信息,標記項目某一個特定時間點的狀態
tree 像一個目錄,管理一些"tree"對象或是"blob"對象
blob 一個"blob"通常用來存儲文件的內容
tag 給某個提交增添一個標記。一個"tag"對象包括一個對象名(SHA1簽名)、對象類型、標簽名、標簽創建人的名字("tagger"), 還有一條可能包含有簽名(signature)的消息
5.目錄介紹
打開".git"目錄可以看到
COMMIT_EDITMSG 保存最新的commit message,git系統不會用到這個文件,只是給用戶一個參考
config 這個是git倉庫的配置文件
description 倉庫的描述信息,主要給gitweb等git托管系統使用
HEAD 這個文件包含了一個檔期分支(branch)的引用,通過這個文件Git可以得到下一次commit的parent
hooks 這個目錄存放一些shell腳本,可以設置特定的git命令后觸發相應的腳本;在搭建gitweb系統或其他git托管系統會經常用到hook script
index 這個文件就是我們前面提到的暫存區(stage),是一個二進制文件
info 包含倉庫的一些信息
logs 保存所有更新的引用記錄
objects 所有的git對象都會存放在這個目錄中,對象的SHA1哈希值的前兩位是文件夾名稱,后38位作為對象文件名
refs 這個目錄一般包括三個子文件夾,heads、remotes和tags,heads中的文件標識了項目中的各個分支指向的當前commit
git中的引用是一個非常重要的概念,對于理解分支、HEAD指針以及reflog非常有幫助。
認識HEAD
HEAD是一個引用,一般情況下間接指向你當前所在的分支的最新的commit上。HEAD跟Git中一般的引用不同,它并不包含某個commit的SHA1哈希值,而是包含當前所在的分支,所有HEAD直接執行當前所在的分支,然后間接指向當前所在分支的最新提交。
可以看一下".git/HEAD"的內容:
ref: refs/heads/master
這表示HEAD是一個指向master分支的引用,根據路徑查看到一下內容:
74be835ef696faed6ca9e7ee43474253b695a4bf
一個哈希值,前兩位為文件夾名,后38位及時文件名,根據上述講到文件存儲都在object目錄下可以看到
三、基本用法
1.基本配置
git config --list 顯示所有配置信息
git config --global user.name yourname 配置全局用戶名,如果不要"--global"或者使用"--local",則表示配置為局部用戶名
git config --global user.email youremail 配置全局電子郵箱
git config --global alias.cm commit 為git命令配置別名。他可以降低工作繁瑣性,還可以配置復合操作
git help 顯示幫助信息
當然,你也可以生成你的ssh公鑰來配置你的git本地與github遠程倉庫。
2.創建及基本本地操作版本庫
1.基本指令
- git init <dir> 在當前目錄(不添加dir參數)或dir目錄下創建為Git倉庫,初始化后會在指定目錄下生成一個.git的隱藏目錄
上面的4條指令實在工作區、暫存區和本地倉庫之間進行復制文件。
- git add <files> 將此文件添加至暫存區
. :提交新文件(new)和被修改(modified)文件,不包括被刪除(deleted)文件
** -u** :提交被修改(modified)和被刪除(deleted)文件,不包括新文件(new)
-A :是上面兩個功能的合集
- git commit 將暫存區中文件生成快照,提交至本地倉庫
-m "提交內容描述" :如果這里不用-m參數的話,git將調到一個文本編譯器(通常是vim)來讓你輸入提交的描述信息
-a :可只將所有被修改或者已刪除的且已經被git管理的文檔提交倒倉庫中
--amend :對于已提交過文件形成版本后,如過需要在該版本上添加或修改文件,則需要使用,該提交會使就提交取消
- git reset -- <files> 用來完成版本回退工作
--hard :stage與工作區都將回退,該命令危險度較高,因為在回退版本之后會很難找回回退前的版本,之后可以選擇文件、HEAD、HEAD~版本號、提交的sha-1值
--soft :工作區回退,而不會恢復到stage中,所以如果將數據還原只需將stage中的數據commit就可以了
--mixed :此為默認方式,將stage和本地倉庫代碼還原至上個版本,但將還原前的內容保留在工作區中
- git checkout -- <files> 將stage的文件版本取出并覆蓋至工作區)('--'表示為在此版本分支下的文件,或使用版本號及commitID代替,表示為使用該版本文件或該次提交時的文件來覆蓋工作區)。如果files是版本名的話就會將當前分支切換到該分支上。
. :將本地倉庫中的文件來覆蓋工作區與stage區域
-b:生成一個版本分支,并切換到該分支上,類似git branch branchname & git checkout branchname的合并命令
2.查閱指令
- git status 在進行文件的上傳后,可以查看暫存區文件的狀態
Untracked files 未被跟蹤的文件,路徑為工作目錄
Changes to be committed 已添加至暫存區
Changes not staged for commit 跟蹤的已發生改變的文件
此時可以清楚看到在git add test3.txt后,Changes to be committed下就有個該文件,表示該文件已添加至暫存區;而在刪除文件后也會在此提示。
- **git log <files> **此時可以清楚看到每次提交的日志信息
commit*為此次提交的sha-1唯一碼
Author*為此次提交的用戶信息
Date*為此次提交的日期
最后下方為此次提交的日志。
- git log --pretty=oneline <files>
但是每次顯示的信息太多,我們也并不關心這些,所以我們也可以使用這個指令來查看提交日志。
- git ls-files --stage
這里可以查看index暫存區的文件名與其快照
- git reflog
可以查看項目此時head指針指向的版本(當前版本),及所有的版本號
3.差異指令
- git diff <files>
可以查看工作區與暫存區快照之間的差異,也就是修改文件還沒有暫存起來變化的內容。
第一行顯示為變化前后的文件名稱
第二行顯示變化前后的快照sha-1ID號
@@中間為文件-修改前版本與+修改后版本
---為修改前的文件名;+++為修改后的文件名
最后顯示未修改內容(白)、刪除內容(紅)及添加內容(綠)。顏色不同的地方可能是我使用的是Cmder來運行git
git diff --cached <files>這里可以查看暫存區與最新版本庫的代碼差異,顯示也同上。
git diff --staged <files>這里可以查看下一次提交到本地代碼庫的內容
git diff head <files>這里可以查看工作區與最新版本庫的代碼差異,如果此處的head指向master分支,則可以使用master代替head。
git diff <branch1> <branch2> <files>
git diff <branch1> <branch2> --stat <files>
而這里是比較不同版本或單獨文件的差異詳情,而后者則不會顯示差異詳情內容
4.文件刪除
- git rm <files>
用于刪除版本庫中的文件,在刪除之后會在stage中形成差異,可以在git status中查看。在需要將文件徹底刪除則使用git commit指令;如需恢復則可以用上述的git checkout指令
- --cached :當我們需要刪除暫存區或分支上的文件, 但本地又需要使用, 只是不希望這個文件被版本控制
這里可以看到使用git rm刪除后git commit就完成版本文件的刪除。
而在下面因為在刪除后本地的情況與stage中一樣,所以直接使用git check是無效的,而是先使用git reset將本地倉庫中的文件目錄與stage區域同步,再使用git checkout將stage區域與工作區同步,完成文件恢復。
3.分支
我們知道在使用git的時候,每次提交(commit)都會形成一個版本。git將他們串成一條時間線,而作為初始化版本的master就是一條時間線。而HEAD嚴格來說不是指向提交,而是指向master,master才是指向提交的,所以HEAD指向的就是當前分支。
1.基本操作
- git branch 查看本地分支
-a :查看所有分支
-r :查看遠程分支
-d :刪除已經參與了合并的分支
-D :強制刪除分支
-m :重命名當前分支
-v :查看各個分支最后一個提交對象的信息
--no-merged :查看尚未合并的分支。在刪除這些尚未合并的分支可能會需要-D參數,來保護分支中還包含著尚未合并進來的工作成果
在此之前,我們先創建一個分支:
git branch <branchname> 創建一個分支。當然,如果想創建分支并切換到那個分支上,可以使用命令git checkout -b <branchname>來完成
git checkout <branchname> 切換分支
git merge <branchname> 合并分支,將branchname分支與所在分支合并成一次新的提交
--abort :中斷一次合并,會嘗試恢復到你運行合并前的狀態。但當運行命令前,在工作目錄中有未儲藏、未提交的修改時它不能完美處理,除此之外它都工作地很好。
-Xignore-space-change :忽略所有空白修改。如果你的團隊中的某個人可能不小心重新格式化空格為制表符或者相反的操作,這會是一個救命稻草。
完成分支工作后,我們就可以合并分支了,合并后我們可以查看
git 作了合并,但沒有提交,它會停下來等你解決沖突。vim打開文件可以看到下文件:
可以看到 =======隔開的上半部分,是 HEAD(即 master 分支,在運行 merge 命令時所切換到的分支)中的內容,下半部分是在test2分支中的內容。解決沖突的辦法無非是二者選其一或者由你親自整合到一起。
當然,git允許你使用其他的合并工具或圖形化界面。在合并之后可以使用下面的命令開啟圖形化沖突解決:
- git mergetool
這里顯示默認的合并工具是tortoisemerge,在后面可以輸入opendiff,就可以打開一個圖形合并工具。
在完成合并后,如何沒有沖突則不需要提交,如果沖突解決后就可以git add和git commit來完成此次的合并提交工作。
- git log --graph 可以看到分支合并圖
2.merge與rebase
首先,git merge與git rebase所做的事是一樣的。都被設計來將一個分支的更改并入另一個分支,只不過方式有些不同。
經常會在工作中創建分支:
現在,如果master中新的提交和你的工作是相關的。為了將新的提交并入你的分支,你有兩個選擇:merge或rebase。
2.merge
關于merge合并的方法我們也已經知道,命令git checkout feature和git merge master 或者 **git merge master feature **可以在feature分支中生成新的合并提交。得到以下結構:
Merge是一個安全的操作。現有的分支不會被更改,避免了rebase潛在的缺點。
但是這意味著每次合并上游更改時feature分支都會引入一個外來的合并提交。如果master非?;钴S的話,這或多或少會污染你的分支歷史。當大量分支合并時就需要高級git log來查看項目歷史提交。
fast-forward和no fast forward
當前分支合并到另一分支時,如果沒有分歧解決,就會直接移動文件指針。這個過程叫做fastforward。。屬于快進方式,不過這種情況如果刪除分支,則會丟失分支信息。因為在這個過程中沒有創建commit。
可以看到藍框就是master的直接后代(無分叉)的結果,這時git會默認在merge時是執行一個fast-forward的merge策略,git并不會創建一個merge commit而是簡單地把T1分支標簽移動到master分支tip所指向的commit;這時合并后master分支就變換成一個透明分支,我們在提交圖譜中無法得知其存在(途中綠色部分),并且一旦這個branch被刪除,那么從歷史上我們再也無法看到任何關于這個開發分支曾經存在的歷史淵源。
這不是我們所想要的,所以我們通過強制git產生一個真正的merge---通過使用--no-ff參數(no fast forward的意思)。
git merge --no-ff brenchname no fast forward的意思,使得每一次的合并都創建一個新的commit記錄。即使這個commit只是fast-foward,用來避免丟失信息。
git merge --squash brenchname 是用來把一些不必要commit進行壓縮。需要進行一次額外的commit來“總結”一下,然后完成最終的合并。
3.rebase
作為merge的替代選擇,你可以像merge一樣使用rebase。
git checkout branch 和 git rebase basebranch topicbranch
merge試講兩個分支進行合并并完成一次提交,而rebase是在當前分支上重演另一個分支的歷史。會聯想到一次剪切效果。
其最大特點就是會使你的項目歷史非誠干凈,呈現出一條線性提交,因為它不會引入合并提交,因此在合并后顯示的提交日志和 gitk 結構清晰。但是你需要注意使用rebase的黃金定律:
永遠不要rebase一個已經分享的分支(到非remote分支,比如rebase到master,develop,release分支上),也就是說永遠不要rebase一個已經在中央庫中存在的分支.只能rebase你自己使用的私有分支。
因為在進行多人并行開發時,在rebase后沒有合并提交歷史,這樣會給整個項目歷史帶來災難性的影響。
在下圖可以看到有兩個分支,master和t1;兩個分支都對其父節點做了commit提交。
首先切換至t1分支,并開始于master分支rebase;在完成分支衍合后,可以看到我們目前進入游離分支,一旦切換分支該分支則會消失。
git status查看工作空間后可以看到已添加至f9d1184...的commitid上,查看上圖的分支圖就知道這個分支是master分支。
最后命令git add 和 git rebase --continue就完成了rebase的衍合操作。再進入master分支merge,git checkout master 和 git merge branchname就完成了在主分支上的衍合。查看log日志可以看到記錄和merge合并完全不一樣。
交互式rebase
git rebase -i <master> 該功能可以在rebase的基礎上進行歷史重寫效果。
下圖中可以看到兩條分支:master和t,兩者的分支都對文件有修改,現在開始對t分支進行歷史修改并rebase到master分支上:
可以看到這里會給出t分支上的歷史版本:
pick: 正常選中
reword: 選中,并且修改提交信息;
edit: 選中,rebase時會暫停,允許你修改這個commit(參考這里)
squash: 選中,會將當前commit與上一個commit合并
fixup: 與squash相同,但不會保存當前commit的提交信息
exec: 執行其他shell命令
這里使用fixup測試一下:
在修改完成并提交后,完成相應修改后就可以完成合并。
最后將分支合并至master上,查看就可以看到將t分支的修改版修改完成后,rebase到master分支上。
4.標簽
git可以給歷史中的某一個提交打上標簽,表示及其重要。
git使用兩種主要類型的標簽:輕量標簽(lightweight)與附注標簽(annotated)。
一個輕量標簽很像一個不會改變的分支 - 它只是一個特定提交的引用。
git tag -a V1.4 -m "message" 這里創建一個附注標簽。-m 選項指定了一條將會存儲在標簽中的信息。如果沒有為附注標簽指定一條信息,Git 會運行編輯器要求你輸入信息
git show <tagname> 這條命令可以看到標簽信息與對應的提交信息。其中會顯示打標簽者、日期、附注信息等
git tag <CreateTagName>創建輕量標簽,只需要提供標簽名字
下圖可以看到兩個tag詳情,左側為附注標簽,右側為輕量標簽??梢院芮宄吹絻烧叩牟顒e;當然差別不知是表面。輕量標簽本質上是將提交校驗和存儲到一個文件中,沒有保存任何其他信息。而附注標簽是存儲在 git 數據庫中的一個完整對象。
git show <commitID> 在我們過去的版本中也可以打標簽,尋找到提交ID就可以完成
git tag 列出已有的標簽列表。