寫在前面
有關Git的誕生故事以及Git的強大,這裡無須贅述。寫這篇文章的原因是因為,習慣了用Git桌面工具向Github提交程式碼的我,換了一臺筆記本後突然發現我連最基本的Git命令列都不會了。。。羞愧至極,所以就開始我的學習之路。
我的學習方法是,先大概瞭解Git的相關指令,然後簡單試驗之後,開始蒐集各類部落格資料,彙總之後,再次對試驗過程進行整理,我整理了一些高質量的博文在附錄中或者在文中。
本文是一篇階段性的記錄文章,只記錄我容易遺忘的知識點,引用的文章圖片都標註了出處,好了下面開始本文
為什麼很多人推薦從svn轉向git
從使用者角度分析:
- svn下載原始碼慢。在git中一個幾個G的版本庫,一般一二十分鐘就能下載完畢,但是在svn中要一個小時左右;
- svn隨時都得要與伺服器互動,無論是檢視log,還是檢視以往的版本你必須跟伺服器相連,並且速度奇慢無比,而git做這些幾乎是瞬間的事;
- 各個分支之間的補丁遷移麻煩,在git上只要兩三個命令就可以完事的(其實一個命令,因為需要查詢與分支切換),但是在svn上你必須要下載每個分支的程式碼,然後比較修改,再上傳;
從伺服器角度說為什麼要用git:
- git版本庫佔用空間小(幾乎是svn的分支數之一也就是說如果有四個分支,svn的版本庫的體積將接近git的四倍),SVN每個分支都是一份程式碼的copy,而git每個分支只是各個提交點的hash值的集合。分支幾乎不佔用什麼空間;
- git是分散式管理系統,完全可以不對程式碼進行備份,但SVN不行,一旦伺服器的硬碟掛掉整個程式碼庫就完了;
- git不用時時聯網查詢,並且對檔案進行壓縮,使得檔案體積大大減小,並且傳輸速度快,svn是單個檔案,git是壓縮後的,在使用svn時我已經碰到過好幾次伺服器無響應了。由於git很多都可以在本地操作的,所以大大降低了客戶端對伺服器的連線,出現這種情況的概率會大大減小;
- 如果客戶端離伺服器端非常遠,在網速糟糕的情況下,用svn下載程式碼速度遠不上git.
推薦文章 http://www.cnblogs.com/common1140/p/3952948.html
git 工作原理
git跟蹤並管理的是修改,而非檔案。而且git只能文字資訊的修改和恢復,對於二進位制檔案,比如word或者圖片,只能監聽到改動卻無法對改動進行恢復。
git 分支概念
每次提交(git commit
),git都會把這些提交串成一個時間線(後文中管這個時間線叫做分支)
預設情況下,使用git init
初始化的專案只有一條分支,叫做master
分支。可以理解成有一個叫做master
的指標指向著當前最新一次提交。還有一個概念就是存在一個HEAD
指標,而且請記住HEAD
永遠指向當前的分支。HEAD指向的是哪個分支,當前我們就向那分支提交程式碼。
下圖展示了一個普通git專案的分支結構
可見,當前專案只有一個分支,就是master
分支,有三次提交。然後HEAD
指標,指向當前的master
分支。
因分支必須指向某一次commit,所以必須先有commit才有後來的分支。某個分支必須至少有一次提交
git commit
之後,才會在git branch
指令中顯示出來
隨著你的每次提交,master
分支都會向前移動,HEAD
指標也同樣跟著向前移動。
當某個場景中,我們從當前分支master
建立了新的分支,比如叫做dev
。下面這條指令是建立並切換到dev分支。
git branch -b dev
複製程式碼
背地裡,Git會新建一個指標叫做dev
,指向master
指向的相同的提交,然後再把HEAD
指向dev
,就表示當前分支在dev
上。
可以看出,git建立一個分支很快,因為除了增加一個dev指標,改動HEAD指標指向以外,工作區的檔案沒有變化。
從現在開始,對工作區的修改和提交就是針對dev分支了。比如新提交一次後,dev指標就會往後移動一步,但是注意,master指標不變。
git分支合併
假如我們在dev分支上的開發工作完成了,就可以吧dev分支上的程式碼合併到master分支上。有兩種情況
-
快進模式 Fast forward 簡稱FF模式 本模式下的前提是:master分支,在dev分支拉出後,沒有過提交。這樣的合併,Git直接就把
master
指向dev
指向的當前分提交即可,完成了快速合併git checkout master git merge dev 複製程式碼
合併完dev分支後,dev分支可以刪掉了,刪掉dev分支的操作,其實就是把dev指標給刪掉就好了。現在就只剩下一個master
指標指向當前提交了
git branch -d dev
複製程式碼
如果沒有提交,就刪除分支,git會提示你,你需要先提交在刪除。但是可以強制刪除,使用
git branch -D dev
複製程式碼
- 非快進模式
本模式發生的情況就是,分支dev對某個檔案,比如readme.txt檔案修改後並提交,此時切換回master,發現master分支也修改了readme.txt並提交。 這樣master分支和dev分支都對同一個檔案進行了修改並提交,在master分支合併dev分支時,會提示衝突。如下所示
這個時候Git無法實現快速合併,只能先嚐試著將各自的修改合併後提交,但是這個時候經常伴隨著衝突,比如readme.txt檔案出現了衝突,檔案被修改成瞭如下內容
<<<<<<< HEAD
Creating a new branch is quick
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> dev
複製程式碼
=== 分割衝突程式碼,上面HEAD標記的是當前分支的內容,下面dev標記的是dev分支合併過來的內容,自己進行取捨
解決完衝突後,將衝突檔案 git add
git commit
提交,這時我們的分支圖變成這個樣子
最後檢視一下分支的合併情況
git log --graph --pretty=oneline --abbrev-commit
複製程式碼
上面的截圖可以看出,解決衝突的部分被單獨劃歸出來了
禁用快速合併模式(簡稱no-ff模式)保留分支commit資訊
前面提到過快速合併,快速合併的場景下,快速合併沒有衝突。但是有個缺點就是,在合併後的master的log日誌中看不到本次合併的dev分支的commit的id和描述資訊
即,合併完成後,一旦刪除了dev分支,我們既無法知道分支存在過,也無法區分那些修改是在分支上進行的。下面看一個禁止掉快速合併的情況
git merge --no-ff -m "merge with no-ff" dev
複製程式碼
上面可以看到 分支合併歷史中記錄了合併過來的分支的commitid
如果不適用no-ff模式,單純使用git reflog檢視一下日誌,你能看出來的,那些分支合併過來了嗎
git stash
當前情況:此時有兩個分支,master和dev,dev編輯到一半,並未成功,所以不能提交。但此時master有一個bug需要馬上去修復,但因為dev無法提交,所以用stash儲存現場。
git stash
複製程式碼
轉去master去把bug修復完後,checkout到dev開發分支,應該先merge master 然後再
git stash pop
複製程式碼
恢復dev開發現場
多人協作
當你從遠端倉庫克隆時,實際上Git自動把本地的master分支和遠端的master分支關聯起來,並且,遠端倉庫的預設名稱是origin。
-
檢視遠端倉庫的名稱
git remote 複製程式碼
-
推送分支
推送分支,就是把該分支上的所有本地提交推送到遠端庫。推送時,要指定本地分支,這樣,Git就會把該分支推送到遠端庫對應的遠端分支上:
git push origin master 複製程式碼
-
抓取分支
git clone git@github.com:yourname/learngit.git 複製程式碼
從遠端庫中獲取分支時,預設只能獲取到master分支,使用下面指令建立並關聯遠端的dev分支
git checkout -b dev origin/dev 複製程式碼
然後提交dev分支
git push origin dev 複製程式碼
-
解決遠端衝突
如果你提交之前,恰好有小夥伴對當前的dev的分支也做了某些修改,則可能提示你提交失敗,這個時候,你需要使用
git pull 複製程式碼
來把最新的提交從origin/dev上抓取下來,然後本地合併,解決衝突中,再次提交
-
刪除遠端庫
git push origin :
因此,多人協作的工作模式通常是這樣:
-
可以試圖用
git push origin branch-name
推送自己的修改; -
如果推送失敗,則因為遠端分支比你的本地更新,需要先用
git pull
試圖合併; -
如果合併有衝突,則解決衝突,並在本地提交;
-
沒有衝突或者解決掉衝突後,再用
git push origin branch-name
推送就能成功!
注:如果
git pull
提示“no tracking information”,則說明本地分支和遠端分支的連結關係沒有建立,用命令git branch --set-upstream-to=origin/dev dev
更多內容參考阮一峰老師的這篇討論git工作流程的文章 經典文章
git 後悔藥系列
git diff 分析程式碼差異
diff --git a/readme.txt b/readme.txt
index 46d49bf..9247db6 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,2 +1,2 @@
-Git is a version control system.
+Git is a distributed version control system.
Git is free software.
複製程式碼
git diff #是工作區(work dict)和暫存區(stage)的比較 git diff --cached #是暫存區(stage)和分支(master)的比較
git 錯誤回退
-
將還在工作區內未新增到快取區的檔案恢復到上次add之前的狀態
git checkout -- filename 複製程式碼
-
將提交到快取區的檔案回退到工作區,將快取區的該檔案恢復到上次commit之前的狀態
git reset HEAD filename 複製程式碼
-
已經commit了,想回退到之前的某次提交
-
回退至某次快照(commit提交)
git reset --hard 3628164(此數字為某次commit的log日誌前七位) 複製程式碼
-
commitid可以通過如下命令查詢,翻看歷史操作記錄
git reflog 複製程式碼
-
-
已經提交到遠端庫了
無法回退
git 刪除檔案
使用 git rm filename
刪除指定檔案
執行完上面這條指令,相當於先刪掉了檔案,然後執行了git add,將刪除的結果新增到了暫存區,所以如果想恢復檔案的話,需要進行如下操作
git reset HEAD filename #恢復暫存區
git checkout -- filename #恢復工作區
複製程式碼
刪除歷史檔案(它會從HEAD所在的分支歷史中查詢並刪除檔案)
git filter-branch --tree-filter 'rm -f <FILE>' HEAD
複製程式碼
關聯github
先建立本地庫,關聯遠端庫
-
生成祕鑰
ssh-keygen -t rsa -C "youremail@example.com" 複製程式碼
以在使用者主目錄裡找到.ssh目錄,裡面有id_rsa和id_rsa.pub兩個檔案,這兩個就是SSH Key的祕鑰對,id_rsa是私鑰,不能洩露出去,id_rsa.pub是公鑰,可以放心地告訴任何人。
-
在github填寫公鑰
登陸GitHub,開啟“Account settings”,“SSH Keys”頁面:然後,點“Add SSH Key”,填上任意Title,在Key文字框裡貼上id_rsa.pub檔案的內容:
-
將本地git專案關聯遠端倉儲
$ git remote add origin https://github.com/yourname/learngit.git 複製程式碼
-
將本地改動推送到遠端倉庫
git push -u origin master #將當前分支推送到遠端,第二次開始不用使用-u 複製程式碼
先建立遠端庫,用本地庫關聯
-
克隆遠端庫
git clone https://github.com/yourname/learngit.git 複製程式碼
GitHub給出的地址不止一個,還可以用https://github.com/yourname/gitskills.git這樣的地址。實際上,Git支援多種協議,預設的git://使用ssh,但也可以使用https等其他協議。
使用https除了速度慢以外,還有個最大的麻煩是每次推送都必須輸入口令,但是在某些只開放http埠的公司內部就無法使用ssh協議而只能用https。
git 標籤處理
釋出版本時,我們通常會給版本庫打一個標籤(tag)。標籤是以一個更讓人理解的方式來標記commit,
比如說請給我找出4.5.6版本的提交,而不是請給我找出commit id為5f54f5sd的提交。“找到了:git show v4.5.6”
類似IP和域名的關係。
git中標籤雖然是版本庫的快照,也是一個指向當前commit的指標。(和分支很像,但是分支可以移動,標籤不能移動),所以建立和刪除標籤都是瞬間完成的。
-
在當前分支直接使用如下指令建立tag
git tag v1.0#可以是任意有意義的字串 複製程式碼
注:然後使用 git tag查詢所有標籤。tag不是按時間順序排布的,而是按照名字排序的
-
標籤是預設打在最新一次提交上的,新增commitid來對任意提交打tag
git tag v1.0 commitid 複製程式碼
-
使用下面的指令可以用來建立帶有說明的tag
git tag -a v0.1 -m "version 0.1 released" 3628164 複製程式碼
-
使用 git show 可以檢視tag的說明
-
git push origin <tagname>
可以推送一個本地標籤(標籤預設儲存在本地) -
git push origin --tags
可以推送全部未推送過的本地標籤 -
git tag -d <tagname>
可以刪除一個本地標籤; -
git push origin :refs/tags/<tagname>
可以刪除一個遠端標籤。
提高效率的小指令
-
彩色的git輸出
git config color.ui true 複製程式碼
-
讓快照的log資訊緊湊輸出
git log --pretty=oneline 複製程式碼
-
以圖表的形式檢視提交歷史
git log --graph 複製程式碼
-
只檢視commit id的精確位數(一般前7位就能識別)
git log --abbrev-commit 複製程式碼
-
給常常的指令設定別名
git config --global alias.st status git st git config --global alias.unstage 'reset HEAD' git unstage test.py 複製程式碼
-
只檢視最近一次的log
git log -1 複製程式碼
-
獲取本地公鑰的快捷方式(文字軟體開啟有可能出錯!)
mac pbcopy < ~/.ssh/id_rsa.pub
windows clip < ~/.ssh/id_rsa.pub
linux sudo apt-get install xclip xclip -sel clip < ~/.ssh/id_rsa.pub
精彩理解
我覺得這svn和git兩個工具主要的區別在於歷史版本維護的位置
Git本地倉庫包含程式碼庫還有歷史庫,在本地的環境開發就可以記錄歷史 而SVN的歷史庫存在於中央倉庫,每次對比與提交程式碼都必須連線到中央倉庫才能進行
這樣的好處在於: 1、自己可以在離線環境檢視開發的版本歷史 2、多人開發時如果充當中央倉庫的Git倉庫掛了,任何一個開發者的倉庫都可以作為中央倉庫進行服務
svn中央伺服器掛了,那我一樣可以將本地的專案重新搭建一個伺服器呢?
答:不行,你的本地沒有歷史版本
答!! 斷網了,看看能不能檢視歷史版本?看看能不能提交程式碼?
關於git commit
git commit
命令確認的是最近的一次git add
,如果檔案最近的內容修改沒有被git add
,那麼在git commit
時,最近的檔案修改內容不會被提交。
-
指定檔案可以提交未儲存到暫存區(unstaged) vi readme.txt git commit readme.txt -m "commit with unstaged modify" vi readme.txt git commit -a -m "commit all file will commit unstaged modify"
-
不使用其他引數不會提交unstaged modify git commit -m "commit without param will not commit unstaged modify"
是否可以把公鑰私鑰一起給別人呢
只需要給公鑰。
原始資料經過私鑰加密後只能用公鑰解密,換句話說,別人收到經過加密的資料後,如果用你的公鑰能夠解密,那麼他就可以確認這些資料是你傳送的 如果把私鑰給別人的話,別人就可以冒充你給別人發東西了
關於回退操作
對於沒提交到stage的修改;
刪除後,重新恢復,修改的內容是會直接消失的;比如你在檔案中新增一個字元:‘1’;不用git add file
新增到stage;直接用rm刪除後,再用git checkout -- file
恢復;恢復過來後,去看檔案是沒有這個字元:‘1’的。
印證了
git checkout -- file
恢復的是已經新增到stage的內容;
而使用git rm
刪除的就是stage的內。git reset HEAD -- file
會從master中將被刪的stage的內容拷貝過去。如果你使用了git rm
之後接著使用git commit -m “remove file”
則會刪除master裡的內容;
所以,關於一次回退流程是這樣的
git reset --hard HEAD^
可以將刪除的master從回收站恢復過來;- 然後利用
git reset HEAD -- file
從master中拷貝到stage中; - 最後再用
git checkout -- file
從stage中拷貝到工作目錄中。
關於未提交的修改
現象: 在分支修並提交後,切到主幹,主幹的工作區是乾淨的;在分支修改不提交,切回主幹,主幹工作區是被修改過未提交的狀態
解釋: 這樣做的好處可能是你本來想對DEV分支進行想改,但是你忘了切換到dev你還在master就已經改了工作區,如果這時切換到dev修改的工作區內容沒了,豈不是很操蛋。只有commit之後才確定修改的內容屬於哪個分支。
未commit的工作區檔案和stage檔案是可以靈活地在且僅在任一branch存在的。這是前提。
-
在工作區做了修改,提交到DEV的分支,再切換回master
這時候,對master來說,工作區沒有任何未提交的修正(因為所有修正都已經commit)。則工作區內容應該是與master分支最後一次提交的內容一致。(處於任何其他時間點,都意味著工作區可能存在修正,這就出現了矛盾)
-
在工作區做了修改,沒有提交到DEV分支,即切換回master 這個時候,對master來說,工作區有了修正,那麼就保持工作區的現有狀態即可。
pull&& push
pull:本地 <-- 遠端 push:本地 --> 遠端
本質上都是同步commit
如果你本地落後遠端,必然要pull 如果你本地超前遠端,必然要push
課後檢視
- 為什麼會產生衝突?什麼時候產生衝突
- 上面的截圖可以看出,解決衝突的部分被單獨劃歸出來了
- 圖形介面操作
- 斷網情況下檢視一下svn的操作