寫在前面
有關(guān)Git的誕生故事以及Git的強大,這里無須贅述滓侍。寫這篇文章的原因是因為乖仇,習慣了用Git桌面工具向Github提交代碼的我,換了一臺筆記本后突然發(fā)現(xiàn)我連最基本的Git命令行都不會了碰声。。熬甫。羞愧至極胰挑,所以就開始我的學習之路。
我的學習方法是椿肩,先大概了解Git的相關(guān)指令瞻颂,然后簡單試驗之后,開始搜集各類博客資料郑象,匯總之后贡这,再次對試驗過程進行整理,我整理了一些高質(zhì)量的博文在附錄中或者在文中厂榛。
本文是一篇階段性的記錄文章盖矫,只記錄我容易遺忘的知識點丽惭,引用的文章圖片都標注了出處,好了下面開始本文
為什么很多人推薦從svn轉(zhuǎn)向git
從使用者角度分析:
- svn下載源代碼慢炼彪。在git中一個幾個G的版本庫吐根,一般一二十分鐘就能下載完畢,但是在svn中要一個小時左右辐马;
- svn隨時都得要與服務器交互拷橘,無論是查看log,還是查看以往的版本你必須跟服務器相連喜爷,并且速度奇慢無比冗疮,而git做這些幾乎是瞬間的事;
- 各個分支之間的補丁遷移麻煩檩帐,在git上只要兩三個命令就可以完事的(其實一個命令术幔,因為需要查找與分支切換),但是在svn上你必須要下載每個分支的代碼湃密,然后比較修改诅挑,再上傳;
從服務器角度說為什么要用git:
- git版本庫占用空間蟹涸础(幾乎是svn的分支數(shù)之一也就是說如果有四個分支拔妥,svn的版本庫的體積將接近git的四倍),SVN每個分支都是一份代碼的copy达箍,而git每個分支只是各個提交點的hash值的集合没龙。分支幾乎不占用什么空間;
- git是分布式管理系統(tǒng)缎玫,完全可以不對代碼進行備份硬纤,但SVN不行,一旦服務器的硬盤掛掉整個代碼庫就完了赃磨;
- git不用時時聯(lián)網(wǎng)查詢筝家,并且對文件進行壓縮,使得文件體積大大減小邻辉,并且傳輸速度快肛鹏,svn是單個文件,git是壓縮后的恩沛,在使用svn時我已經(jīng)碰到過好幾次服務器無響應了。由于git很多都可以在本地操作的缕减,所以大大降低了客戶端對服務器的連接雷客,出現(xiàn)這種情況的概率會大大減小桥狡;
- 如果客戶端離服務器端非常遠搅裙,在網(wǎng)速糟糕的情況下皱卓,用svn下載代碼速度遠不上git.
推薦文章 http://www.cnblogs.com/common1140/p/3952948.html
git 工作原理
git跟蹤并管理的是修改漱逸,而非文件团秽。而且git只能文本信息的修改和恢復架诞,對于二進制文件床牧,比如word或者圖片婆誓,只能監(jiān)聽到改動卻無法對改動進行恢復锣杂。
git 分支概念
每次提交(git commit
)未玻,git都會把這些提交串成一個時間線(后文中管這個時間線叫做分支)
默認情況下辉浦,使用git init
初始化的項目只有一條分支颅和,叫做master
分支傅事。可以理解成有一個叫做master
的指針指向著當前最新一次提交峡扩。還有一個概念就是存在一個HEAD
指針蹭越,而且請記住HEAD
永遠指向當前的分支。HEAD指向的是哪個分支教届,當前我們就向那分支提交代碼响鹃。
下圖展示了一個普通git項目的分支結(jié)構(gòu)
可見,當前項目只有一個分支案训,就是master
分支买置,有三次提交。然后HEAD
指針萤衰,指向當前的master
分支堕义。
因分支必須指向某一次commit,所以必須先有commit才有后來的分支脆栋。某個分支必須至少有一次提交
git commit
之后倦卖,才會在git branch
指令中顯示出來
隨著你的每次提交,master
分支都會向前移動椿争,HEAD
指針也同樣跟著向前移動怕膛。
當某個場景中,我們從當前分支master
創(chuàng)建了新的分支秦踪,比如叫做dev
褐捻。下面這條指令是創(chuàng)建并切換到dev分支。
git branch -b dev
背地里椅邓,Git會新建一個指針叫做dev
柠逞,指向master
指向的相同的提交,然后再把HEAD
指向dev
,就表示當前分支在dev
上景馁。
可以看出板壮,git創(chuàng)建一個分支很快,因為除了增加一個dev指針合住,改動HEAD指針指向以外绰精,工作區(qū)的文件沒有變化撒璧。
從現(xiàn)在開始,對工作區(qū)的修改和提交就是針對dev分支了笨使。比如新提交一次后卿樱,dev指針就會往后移動一步,但是注意硫椰,master指針不變繁调。
git分支合并
假如我們在dev分支上的開發(fā)工作完成了,就可以吧dev分支上的代碼合并到master分支上最爬。有兩種情況
-
快進模式 Fast forward 簡稱FF模式
本模式下的前提是:master分支涉馁,在dev分支拉出后,沒有過提交爱致。這樣的合并烤送,Git直接就把master
指向dev
指向的當前分提交即可,完成了快速合并git checkout master git merge dev
合并完dev分支后糠悯,dev分支可以刪掉了帮坚,刪掉dev分支的操作,其實就是把dev指針給刪掉就好了』グ現(xiàn)在就只剩下一個master
指針指向當前提交了
git branch -d dev
如果沒有提交试和,就刪除分支,git會提示你纫普,你需要先提交在刪除阅悍。但是可以強制刪除,使用
git branch -D dev
- 非快進模式
本模式發(fā)生的情況就是昨稼,分支dev對某個文件节视,比如readme.txt文件修改后并提交,此時切換回master假栓,發(fā)現(xiàn)master分支也修改了readme.txt并提交寻行。 這樣master分支和dev分支都對同一個文件進行了修改并提交,在master分支合并dev分支時匾荆,會提示沖突拌蜘。如下所示
這個時候Git無法實現(xiàn)快速合并,只能先嘗試著將各自的修改合并后提交牙丽,但是這個時候經(jīng)常伴隨著沖突简卧,比如readme.txt文件出現(xiàn)了沖突,文件被修改成了如下內(nèi)容
<<<<<<< HEAD
Creating a new branch is quick
Creating a new branch is quick & simple.
=======
Creating a new branch is quick AND simple.
>>>>>>> dev
=== 分割沖突代碼烤芦,上面HEAD標記的是當前分支的內(nèi)容举娩,下面dev標記的是dev分支合并過來的內(nèi)容,自己進行取舍
解決完沖突后,將沖突文件 git add
git commit
提交晓铆,這時我們的分支圖變成這個樣子
最后查看一下分支的合并情況
git log --graph --pretty=oneline --abbrev-commit
上面的截圖可以看出,解決沖突的部分被單獨劃歸出來了
禁用快速合并模式(簡稱no-ff模式)保留分支commit信息
前面提到過快速合并绰播,快速合并的場景下骄噪,快速合并沒有沖突。但是有個缺點就是蠢箩,在合并后的master的log日志中看不到本次合并的dev分支的commit的id和描述信息
即链蕊,合并完成后,一旦刪除了dev分支谬泌,我們既無法知道分支存在過滔韵,也無法區(qū)分那些修改是在分支上進行的。下面看一個禁止掉快速合并的情況
git merge --no-ff -m "merge with no-ff" dev
上面可以看到 分支合并歷史中記錄了合并過來的分支的commitid
如果不適用no-ff模式掌实,單純使用git reflog查看一下日志陪蜻,你能看出來的,那些分支合并過來了嗎
git stash
當前情況:此時有兩個分支贱鼻,master和dev宴卖,dev編輯到一半,并未成功邻悬,所以不能提交症昏。但此時master有一個bug需要馬上去修復,但因為dev無法提交父丰,所以用stash保存現(xiàn)場肝谭。
git stash
轉(zhuǎn)去master去把bug修復完后,checkout到dev開發(fā)分支,應該先merge master 然后再
git stash pop
恢復dev開發(fā)現(xiàn)場
多人協(xié)作
當你從遠程倉庫克隆時蛾扇,實際上Git自動把本地的master分支和遠程的master分支關(guān)聯(lián)起來攘烛,并且,遠程倉庫的默認名稱是origin屁桑。
-
查看遠程倉庫的名稱
git remote
-
推送分支
推送分支医寿,就是把該分支上的所有本地提交推送到遠程庫。推送時蘑斧,要指定本地分支靖秩,這樣,Git就會把該分支推送到遠程庫對應的遠程分支上:
git push origin master
-
抓取分支
git clone git@github.com:yourname/learngit.git
從遠程庫中獲取分支時竖瘾,默認只能獲取到master分支沟突,使用下面指令創(chuàng)建并關(guān)聯(lián)遠程的dev分支
git checkout -b dev origin/dev
然后提交dev分支
git push origin dev
-
解決遠程沖突
如果你提交之前,恰好有小伙伴對當前的dev的分支也做了某些修改捕传,則可能提示你提交失敗惠拭,這個時候,你需要使用
git pull
來把最新的提交從origin/dev上抓取下來,然后本地合并职辅,解決沖突中棒呛,再次提交
-
刪除遠程庫
git push origin :<branch name>
因此,多人協(xié)作的工作模式通常是這樣:
可以試圖用
git push origin branch-name
推送自己的修改域携;如果推送失敗簇秒,則因為遠程分支比你的本地更新,需要先用
git pull
試圖合并秀鞭;如果合并有沖突趋观,則解決沖突,并在本地提交锋边;
沒有沖突或者解決掉沖突后皱坛,再用
git push origin branch-name
推送就能成功!
注:如果
git pull
提示“no tracking information”豆巨,則說明本地分支和遠程分支的鏈接關(guān)系沒有創(chuàng)建剩辟,用命令git branch --set-upstream-to=origin/dev dev
更多內(nèi)容參考阮一峰老師的這篇討論git工作流程的文章
經(jīng)典文章
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 #是工作區(qū)(work dict)和暫存區(qū)(stage)的比較
git diff --cached #是暫存區(qū)(stage)和分支(master)的比較
git 錯誤回退
-
將還在工作區(qū)內(nèi)未添加到緩存區(qū)的文件恢復到上次add之前的狀態(tài)
git checkout -- filename
-
將提交到緩存區(qū)的文件回退到工作區(qū),將緩存區(qū)的該文件恢復到上次commit之前的狀態(tài)
git reset HEAD filename
-
已經(jīng)commit了搀矫,想回退到之前的某次提交
-
回退至某次快照(commit提交)
git reset --hard 3628164(此數(shù)字為某次commit的log日志前七位)
-
commitid可以通過如下命令查找,翻看歷史操作記錄
git reflog
-
-
已經(jīng)提交到遠程庫了
無法回退
git 刪除文件
使用 git rm filename
刪除指定文件
執(zhí)行完上面這條指令抹沪,相當于先刪掉了文件,然后執(zhí)行了git add瓤球,將刪除的結(jié)果添加到了暫存區(qū)融欧,所以如果想恢復文件的話,需要進行如下操作
git reset HEAD filename #恢復暫存區(qū)
git checkout -- filename #恢復工作區(qū)
刪除歷史文件(它會從HEAD所在的分支歷史中查詢并刪除<FILE>文件)
git filter-branch --tree-filter 'rm -f <FILE>' HEAD
關(guān)聯(lián)github
先建立本地庫卦羡,關(guān)聯(lián)遠程庫
-
生成秘鑰
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文件的內(nèi)容:
-
將本地git項目關(guān)聯(lián)遠程倉儲
$ git remote add origin https://github.com/yourname/learngit.git
-
將本地改動推送到遠程倉庫
git push -u origin master #將當前分支推送到遠端,第二次開始不用使用-u
先建立遠程庫葛碧,用本地庫關(guān)聯(lián)
-
克隆遠程庫
git clone https://github.com/yourname/learngit.git
GitHub給出的地址不止一個,還可以用https://github.com/yourname/gitskills.git這樣的地址过吻。實際上进泼,Git支持多種協(xié)議,默認的git://使用ssh,但也可以使用https等其他協(xié)議乳绕。
使用https除了速度慢以外绞惦,還有個最大的麻煩是每次推送都必須輸入口令,但是在某些只開放http端口的公司內(nèi)部就無法使用ssh協(xié)議而只能用https洋措。
git 標簽處理
發(fā)布版本時翩隧,我們通常會給版本庫打一個標簽(tag)。標簽是以一個更讓人理解的方式來標記commit呻纹,
比如說請給我找出4.5.6版本的提交,而不是請給我找出commit id為5f54f5sd的提交专缠±桌遥“找到了:git show v4.5.6”
類似IP和域名的關(guān)系。
git中標簽雖然是版本庫的快照涝婉,也是一個指向當前commit的指針哥力。(和分支很像,但是分支可以移動墩弯,標簽不能移動)吩跋,所以創(chuàng)建和刪除標簽都是瞬間完成的。
-
在當前分支直接使用如下指令創(chuàng)建tag
git tag v1.0#可以是任意有意義的字符串
注:然后使用 git tag查詢所有標簽渔工。tag不是按時間順序排布的锌钮,而是按照名字排序的
-
標簽是默認打在最新一次提交上的,添加commitid來對任意提交打tag
git tag v1.0 commitid
-
使用下面的指令可以用來創(chuàng)建帶有說明的tag
git tag -a v0.1 -m "version 0.1 released" 3628164
使用 git show <tagname>可以查看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的精確位數(shù)(一般前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.pubwindows
clip < ~/.ssh/id_rsa.publinux
sudo apt-get install xclip
xclip -sel clip < ~/.ssh/id_rsa.pub
精彩理解
我覺得這svn和git兩個工具主要的區(qū)別在于歷史版本維護的位置
Git本地倉庫包含代碼庫還有歷史庫,在本地的環(huán)境開發(fā)就可以記錄歷史
而SVN的歷史庫存在于中央倉庫旺韭,每次對比與提交代碼都必須連接到中央倉庫才能進行
這樣的好處在于:
1氛谜、自己可以在脫機環(huán)境查看開發(fā)的版本歷史
2、多人開發(fā)時如果充當中央倉庫的Git倉庫掛了区端,任何一個開發(fā)者的倉庫都可以作為中央倉庫進行服務
svn中央服務器掛了值漫,那我一樣可以將本地的項目重新搭建一個服務器呢?
答:不行织盼,你的本地沒有歷史版本
答Q詈巍! 斷網(wǎng)了悔政,看看能不能查看歷史版本晚吞?看看能不能提交代碼?
關(guān)于git commit
git commit
命令確認的是最近的一次git add
谋国,如果文件最近的內(nèi)容修改沒有被git add
槽地,那么在git commit
時,最近的文件修改內(nèi)容不會被提交。
指定文件可以提交未保存到暫存區(qū)(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"不使用其他參數(shù)不會提交unstaged modify
git commit -m "commit without param will not commit unstaged modify"
是否可以把公鑰私鑰一起給別人呢
只需要給公鑰捌蚊。
原始數(shù)據(jù)經(jīng)過私鑰加密后只能用公鑰解密集畅,換句話說,別人收到經(jīng)過加密的數(shù)據(jù)后缅糟,如果用你的公鑰能夠解密挺智,那么他就可以確認這些數(shù)據(jù)是你發(fā)送的
如果把私鑰給別人的話,別人就可以冒充你給別人發(fā)東西了
關(guān)于回退操作
對于沒提交到stage的修改窗宦;
刪除后赦颇,重新恢復,修改的內(nèi)容是會直接消失的赴涵;比如你在文件中添加一個字符:‘1’媒怯;不用git add file
添加到stage;直接用rm刪除后髓窜,再用git checkout -- file
恢復扇苞;恢復過來后,去看文件是沒有這個字符:‘1’的寄纵。
印證了
git checkout -- file
恢復的是已經(jīng)添加到stage的內(nèi)容鳖敷;
而使用git rm
刪除的就是stage的內(nèi)。git reset HEAD -- file
會從master中將被刪的stage的內(nèi)容拷貝過去程拭。如果你使用了git rm
之后接著使用git commit -m “remove file”
則會刪除master里的內(nèi)容定踱;
所以,關(guān)于一次回退流程是這樣的
-
git reset --h(huán)ard HEAD^
可以將刪除的master從回收站恢復過來恃鞋; - 然后利用
git reset HEAD -- file
從master中拷貝到stage中屋吨; - 最后再用
git checkout -- file
從stage中拷貝到工作目錄中。
關(guān)于未提交的修改
現(xiàn)象:
在分支修并提交后山宾,切到主干至扰,主干的工作區(qū)是干凈的;在分支修改不提交资锰,切回主干敢课,主干工作區(qū)是被修改過未提交的狀態(tài)
解釋:
這樣做的好處可能是你本來想對DEV分支進行想改,但是你忘了切換到dev你還在master就已經(jīng)改了工作區(qū)绷杜,如果這時切換到dev修改的工作區(qū)內(nèi)容沒了直秆,豈不是很操蛋。只有commit之后才確定修改的內(nèi)容屬于哪個分支鞭盟。
未commit的工作區(qū)文件和stage文件是可以靈活地在且僅在任一branch存在的圾结。這是前提。
-
在工作區(qū)做了修改齿诉,提交到DEV的分支筝野,再切換回master
這時候晌姚,對master來說,工作區(qū)沒有任何未提交的修正(因為所有修正都已經(jīng)commit)歇竟。則工作區(qū)內(nèi)容應該是與master分支最后一次提交的內(nèi)容一致挥唠。(處于任何其他時間點,都意味著工作區(qū)可能存在修正焕议,這就出現(xiàn)了矛盾)
在工作區(qū)做了修改宝磨,沒有提交到DEV分支,即切換回master
這個時候盅安,對master來說唤锉,工作區(qū)有了修正,那么就保持工作區(qū)的現(xiàn)有狀態(tài)即可别瞭。
pull&& push
pull:本地 <-- 遠程
push:本地 --> 遠程
本質(zhì)上都是同步commit
如果你本地落后遠程腌紧,必然要pull
如果你本地超前遠程,必然要push
課后查看
- 為什么會產(chǎn)生沖突畜隶?什么時候產(chǎn)生沖突
- 上面的截圖可以看出,解決沖突的部分被單獨劃歸出來了
- 圖形界面操作
- 斷網(wǎng)情況下查看一下svn的操作