chapter 1: 如何創(chuàng)建版本庫
- 初始化一個(gè)倉庫
$ git init
- 添加文件到Git倉庫的過程:
$ git add readme.txt
(沒有消息提示, Unix哲學(xué): 沒有消息就是好消息)
$git commit -m 'write a readme file'
[master (root-commit) cb926e7] wrote a readme file
1 file changed, 2 insertions(+)
create mode 100644 readme.txt
chapter 2: 時(shí)光穿梭機(jī)
- 修改完文件后, 查看結(jié)果:
$ git status
$ git status
On branch master
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: readme.txt
no changes added to commit (use "git add" and/or "git commit -a")
git status命令可以讓我們時(shí)刻掌握倉庫當(dāng)前的狀態(tài)迫筑,上面的命令告訴我們宪赶,readme.txt被修改過了,但還沒有準(zhǔn)備提交的修改
"Changes not staged for commit": 表示修改沒有add到暫存區(qū)中等待提交, 也就是修改還只存在工作區(qū)
經(jīng)過'$ git add readme.txt'后, 執(zhí)行'$ git status', 輸出了如下日志:
$ git status
On branch master
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
modified: readme.txt
"Changes to be committed": 表示修改已經(jīng)add到了staged(暫存區(qū)), 等待被commit到HEAD master.
- 執(zhí)行完'$ git commit - m 'add something'', 再執(zhí)行"$ git status", 輸出如下日志:
$ git status
On branch master
nothing to commit (working directory clean)
Git告訴我們當(dāng)前沒有需要提交的修改脯燃,而且搂妻,工作目錄是干凈(working directory clean)的
- 查看修改了什么內(nèi)容
$ git diff readme.txt
section 1: 版本回退
- 查看歷史記錄
$ git log
$ git log
commit 3628164fb26d48395383f8f31179f24e0882e1e0
Author: Michael Liao askxuefeng@gmail.com
Date: Tue Aug 20 15:11:49 2013 +0800
append GPL
commit ea34578d5496d7dd233c827ed32a8cd576c5ee85
Author: Michael Liao askxuefeng@gmail.com
Date: Tue Aug 20 14:53:12 2013 +0800
add distributed
commit cb926e7ea50ad11b8f9e909c05226233bf755030
Author: Michael Liao askxuefeng@gmail.com
Date: Mon Aug 19 17:51:55 2013 +0800
wrote a readme file
git log命令顯示從最新到最久的提交日志
如果想輸出信息更見簡潔:
$ git log --pretty=oneline
$ git log --pretty=oneline
3628164fb26d48395383f8f31179f24e0882e1e0 append GPL
ea34578d5496d7dd233c827ed32a8cd576c5ee85 add distributed
cb926e7ea50ad11b8f9e909c05226233bf755030 wrote a readme file
3628164fb26d48395383f8f31179f24e0882e1e0 是commit id(版本號).
每提交一個(gè)新版本, 實(shí)際上Git就會(huì)把它們自動(dòng)串成一條時(shí)間線
- 如何將文件回退到上一個(gè)版本
Git中使用HEAD表示當(dāng)前版本, 也就是最新提交的commit id 所在的版本. 上一個(gè)版本就是"HEAD^", 上上個(gè)版本就是"HEAD^^".
上100個(gè)版本就是"HEAD~100"
$ git reset --hard HEAD^
$ git reset --hard HEAD^
HEAD is now at ea34578 add distributed
此時(shí), 你如果想回到最新的commit去. 只要找到那個(gè)版本的commit id(一般取前7位):
$ git reset --hard 3628164
就可以指定回到未來的某個(gè)版本!
Git的版本回退速度非常快辕棚,因?yàn)镚it在內(nèi)部有個(gè)指向當(dāng)前版本的HEAD指針欲主,當(dāng)你回退版本的時(shí)候邓厕,Git僅僅是把HEAD從指向“append GPL”:
git-head
改為指向“add distributed”:
git-head-move
然后順便把工作區(qū)的文件更新了。所以你讓HEAD指向哪個(gè)版本號扁瓢,你就把當(dāng)前版本定位在哪详恼。
如果, 你在當(dāng)前的命令行中找不到最新版本的commit id. 可以通過如下命令:
$ git reflog
$ git reflog
ea34578 HEAD@{0}: reset: moving to HEAD^
3628164 HEAD@{1}: commit: append GPL
ea34578 HEAD@{2}: commit: add distributed
cb926e7 HEAD@{3}: commit (initial): wrote a readme file
3628164就是你要找的那個(gè)版本commit id.
section 2: 工作區(qū)和暫存區(qū)
工作區(qū): working directory
版本庫: Reposity, 工作區(qū)中的那個(gè)'.git'目錄, 版本庫中存了許多東西, 最重要的就是被稱為'stage(or index)'的暫存區(qū), 還有Git為我們自動(dòng)創(chuàng)建的第一個(gè)分支master, 以及指向master的一個(gè)指針叫HEAD
git-repo
暫存區(qū): stage(或index), 作為版本庫的最重要的部分存在與'.git'目錄中.
把文件往Git版本庫里添加的時(shí)候,是分兩步執(zhí)行的:
第一步是用“git add”把文件添加進(jìn)去引几,實(shí)際上就是把文件修改添加到暫存區(qū)单雾;
第二步是用“git commit”提交更改,實(shí)際上就是把暫存區(qū)的所有內(nèi)容提交到當(dāng)前分支;
因?yàn)槲覀儎?chuàng)建Git版本庫時(shí)她紫,Git自動(dòng)為我們創(chuàng)建了唯一一個(gè)master分支硅堆,所以,現(xiàn)在贿讹,commit就是往master分支上提交更改
從來沒有add過的文件, 使用'git status'查看, 顯示的是'Untracked files'
執(zhí)行了git add命令后, 暫存區(qū)的狀態(tài)如圖:
git-stage
所以渐逃,git add命令實(shí)際上就是把要提交的所有修改放到暫存區(qū)(Stage),然后民褂,執(zhí)行g(shù)it commit就可以一次性把暫存區(qū)的所有修改提交到分支
執(zhí)行了git commit命令后, 暫存區(qū)就沒有內(nèi)容了, 修改的文件已經(jīng)被commit到了master上:
現(xiàn)在版本庫變成了這樣茄菊,暫存區(qū)就沒有任何內(nèi)容了:
git-stage-after-commit
section 3: 管理修改
為什么Git比其他版本控制系統(tǒng)設(shè)計(jì)得優(yōu)秀,因?yàn)镚it跟蹤并管理的是修改赊堪,而非文件面殖。
你會(huì)問,什么是修改哭廉?比如你新增了一行脊僚,這就是一個(gè)修改,刪除了一行遵绰,也是一個(gè)修改辽幌,更改了某些字符,也是一個(gè)修改椿访,刪了一些又加了一些乌企,也是一個(gè)修改,甚至創(chuàng)建一個(gè)新文件成玫,也算一個(gè)修改
如果你進(jìn)行了如下操作:
第一次修改 -> git add -> 第二次修改 -> git commit
則只有第一次修改的內(nèi)容被提交到了reposity中, 因?yàn)橹挥械谝淮涡薷谋籥dd到stage中
每次修改加酵,如果不add到暫存區(qū),那就不會(huì)加入到commit中
section 4: 撤銷修改
- 丟棄工作區(qū)的修改:
$ git checkout -- readme.txt
命令git checkout -- readme.txt意思就是哭当,把readme.txt文件在工作區(qū)的修改全部撤銷猪腕,這里有兩種情況:
一種是readme.txt自修改后還沒有被放到暫存區(qū),現(xiàn)在荣病,撤銷修改就回到和版本庫一模一樣的狀態(tài)码撰;
一種是readme.txt已經(jīng)添加到暫存區(qū)后,又作了修改个盆,現(xiàn)在脖岛,撤銷修改就回到添加到暫存區(qū)后的狀態(tài)朵栖。
總之,就是讓這個(gè)文件回到最近一次git commit或git add時(shí)的狀態(tài)柴梆。
注意: '--'很重要, 如果沒有, 將變成了"創(chuàng)建一個(gè)新分支"的命令
- 撤銷掉暫存區(qū)的修改(unstage) , 重新放回到工作區(qū)
$ git reset HEAD readme.txt
Unstaged changes after reset:
M readme.txt
git reset命令既可以回退版本, 也可以把暫存區(qū)中的修改回退到工作區(qū)
還記得如何回退版本嗎?
section 5: 刪除文件
在本地刪除了文件"rm text.txt", 立刻可以通過"git status"看到變化
- 如果你要從版本庫中刪除文件
$ git rm test.txt
rm 'test.txt'
$ git commit -m "remove test.txt"
[master d17efd8] remove test.txt
1 file changed, 1 deletion(-)
delete mode 100644 test.txt
- 如果你錯(cuò)刪了文件, 把誤刪的文件恢復(fù)到最新版本:
$ git checkout -- test.txt
git checkout其實(shí)是用版本庫里的版本替換工作區(qū)的版本陨溅,無論工作區(qū)是修改還是刪除,都可以“一鍵還原”绍在。
chapter 3: 遠(yuǎn)程倉庫
- 配置github
第1步:創(chuàng)建SSH Key门扇。在用戶主目錄下,看看有沒有.ssh目錄偿渡,如果有臼寄,再看看這個(gè)目錄下有沒有id_rsa和id_rsa.pub這兩個(gè)文件,如果已經(jīng)有了溜宽,可直接跳到下一步吉拳。如果沒有,打開Shell(Windows下打開Git Bash)适揉,創(chuàng)建SSH Key:
$ ssh-keygen -t rsa -C "youremail@example.com"
如果一切順利的話留攒,可以在用戶主目錄里找到.ssh目錄,里面有id_rsa和id_rsa.pub兩個(gè)文件嫉嘀,這兩個(gè)就是SSH Key的秘鑰對炼邀,id_rsa是私鑰,不能泄露出去剪侮,id_rsa.pub是公鑰拭宁,可以放心地告訴任何人.
第2步:登陸GitHub,打開“Account settings”票彪,“SSH Keys”頁面:
然后红淡,點(diǎn)“Add SSH Key”,填上任意Title降铸,在Key文本框里粘貼id_rsa.pub文件的內(nèi)容
chapter: 創(chuàng)建和合并分支
git中默認(rèn)有一個(gè)主分支, 叫做master. HEAD嚴(yán)格來說不是指向提交, 而是指向master分支, 而master才是指向提交的. 所以HEAD就是指向當(dāng)前的分支.
每一次提交, master分支都會(huì)向前移動(dòng)一步
當(dāng)你創(chuàng)建新的分支如dev時(shí), Git新建了一個(gè)指針叫dev, 指向master相同的提交, 再把HEAD指向dev, 就表示當(dāng)前分支在dev上:
git-br-create
從現(xiàn)在開始, 對工作區(qū)的修改和提交都市針對dev分支了, 比如新提交一次后, dev分支就向前移動(dòng)一步, 而master指針不變:
git-br-dev-fd
假如我們在dev中的工作完成了, 就可以把dev合并到master上.
創(chuàng)建分支, 并切換到分支:
$ git checkout -b 15_learn_git_flow -t master
git checkout命令加上-b參數(shù)表示創(chuàng)建并切換,相當(dāng)于以下兩條命令:
$ git branch dev
$ git checkout dev
Switched to branch 'dev'
然后摇零,用git branch命令查看當(dāng)前分支:
$ git branch
- dev
master
git branch命令會(huì)列出所有分支推掸,當(dāng)前分支前面會(huì)標(biāo)一個(gè)*號。
然后驻仅,我們就可以在dev分支上正常提交谅畅,比如對readme.txt做個(gè)修改,加上一行:
Creating a new branch is quick.
然后提交:
$ git add readme.txt
$ git commit -m "branch test"
[dev fec145a] branch test
1 file changed, 1 insertion(+)
現(xiàn)在噪服,dev分支的工作完成毡泻,我們就可以切換回master分支:
$ git checkout master
Switched to branch 'master'
切換回master分支后,再查看一個(gè)readme.txt文件粘优,剛才添加的內(nèi)容不見了仇味!因?yàn)槟莻€(gè)提交是在dev分支上呻顽,而master分支此刻的提交點(diǎn)并沒有變:
git-br-on-master
現(xiàn)在,我們把dev分支的工作成果合并到master分支上:
$ git merge dev
Updating d17efd8..fec145a
Fast-forward
readme.txt | 1 +
1 file changed, 1 insertion(+)
git merge命令用于合并指定分支到當(dāng)前分支丹墨。合并后廊遍,再查看readme.txt的內(nèi)容,就可以看到贩挣,和dev分支的最新提交是完全一樣的喉前。
注意到上面的Fast-forward信息,Git告訴我們王财,這次合并是“快進(jìn)模式”卵迂,也就是直接把master指向dev的當(dāng)前提交,所以合并速度非橙蘧唬快狭握。
當(dāng)然,也不是每次合并都能Fast-forward疯溺,我們后面會(huì)將其他方式的合并论颅。
合并完成后,就可以放心地刪除dev分支了:
$ git branch -d dev
Deleted branch dev (was fec145a).
刪除后囱嫩,查看branch恃疯,就只剩下master分支了:
$ git branch
- master
因?yàn)閯?chuàng)建、合并和刪除分支非衬校快今妄,所以Git鼓勵(lì)你使用分支完成某個(gè)任務(wù),合并后再刪掉分支鸳碧,這和直接在master分支上工作效果是一樣的盾鳞,但過程更安全。
小結(jié)
Git鼓勵(lì)大量使用分支:
查看分支:git branch
創(chuàng)建分支:git branch name
切換分支:git checkout name
創(chuàng)建+切換分支:git checkout -b name
合并某分支到當(dāng)前分支:git merge name
刪除分支:git branch -d name
問題: 如何創(chuàng)建遠(yuǎn)程的分支?
- 先在本地創(chuàng)建分支
$ git checkout -b 18_learn_git_flow -t master
- 將本地的分支push到遠(yuǎn)程
$ git push origin 18_learn_git_flow
section 4: Bug分支
修復(fù)bug時(shí)瞻离,我們會(huì)通過創(chuàng)建新的bug分支進(jìn)行修復(fù)腾仅,然后合并,最后刪除套利;
當(dāng)手頭工作沒有完成時(shí)推励,先把工作現(xiàn)場git stash一下,然后去修復(fù)bug肉迫,修復(fù)后验辞,再git stash pop,回到工作現(xiàn)場喊衫。
查看全部的工作現(xiàn)場 :
$ git stash list
恢復(fù)工作現(xiàn)場:
方式一: 通過git stash apply stash@{0}, 但恢復(fù)后, stash內(nèi)容并不刪除, 需要使用git stash drop來刪除
方式二: 通過 git stash pop, 恢復(fù)的同時(shí)把stash內(nèi)容也刪掉.
section 5: Feature分支
刪除分支:
$ git branch -d feature1 --- 刪除本地分支1, 如果該分支沒有被merge request, 則不能刪除
$ git branch -D feature1 --- 刪除本地分支, 不管該branch有沒有merge request
刪除遠(yuǎn)程分支:
$ git push origin --delete 18_learn_git_flow
或者, 通過推送一個(gè)空的branch到遠(yuǎn)程達(dá)到刪除的目的:
$ git branch -d branchName
$ git push origin :branchName
刪除不存在對應(yīng)遠(yuǎn)程分支的本地分支:
假設(shè)這樣一種情況:
1. 我創(chuàng)建了本地分支b1并pull到遠(yuǎn)程分支 origin/b1跌造;
2. 其他人在本地使用fetch或pull創(chuàng)建了本地的b1分支;
3. 我刪除了 origin/b1 遠(yuǎn)程分支族购;
4. 其他人再次執(zhí)行fetch或者pull并不會(huì)刪除這個(gè)他們本地的 b1分支壳贪,運(yùn)行 git branch -a 也不能看出這個(gè)branch被刪除了陵珍,如何處理
使用$ git remote show origin 查看b1的狀態(tài)
如果顯示b1是stable, 使用 $ git remote prune origin 可以將其從本地版本庫中刪除
或者, 更簡單的方式是, 使用 $ git fetch -p , 它在fetch之后刪除掉沒有與遠(yuǎn)程分支對應(yīng)的本地分支
重命名本地分支:
$ git branch -m oldName newName
重命名遠(yuǎn)程分支, 其實(shí)也就是先刪除遠(yuǎn)程分支, 然后將重命名后的本地分支推送上去:
$ git push --delete origin oldName
$ git branch -m oldName newName
$ git push origin newName
chapter: 多人協(xié)作
因此,多人協(xié)作的工作模式通常是這樣:
首先撑碴,可以試圖用git push origin branch-name推送自己的修改撑教;
如果推送失敗,則因?yàn)檫h(yuǎn)程分支比你的本地更新醉拓,需要先用git pull試圖合并伟姐;
如果合并有沖突,則解決沖突亿卤,并在本地提交愤兵;
沒有沖突或者解決掉沖突后,再用git push origin branch-name推送就能成功排吴!
如果git pull提示“no tracking information”秆乳,則說明本地分支和遠(yuǎn)程分支的鏈接關(guān)系沒有創(chuàng)建,用命令git branch --track branch-name origin/branch-name钻哩。
這就是多人協(xié)作的工作模式屹堰,一旦熟悉了,就非常簡單街氢。
小結(jié)
查看遠(yuǎn)程庫信息扯键,使用git remote -v;
本地新建的分支如果不推送到遠(yuǎn)程珊肃,對其他人就是不可見的荣刑;
從本地推送分支,使用git push origin branch-name伦乔,如果推送失敗厉亏,先用git pull抓取遠(yuǎn)程的新提交;
在本地創(chuàng)建和遠(yuǎn)程分支對應(yīng)的分支烈和,使用git checkout -b branch-name origin/branch-name爱只,本地和遠(yuǎn)程分支的名稱最好一致;
建立本地分支和遠(yuǎn)程分支的關(guān)聯(lián)斥杜,使用git branch --track branch-name origin/branch-name虱颗;
從遠(yuǎn)程抓取分支,使用git pull蔗喂,如果有沖突,要先處理沖突高帖。
chapter: 標(biāo)簽管理
發(fā)布一個(gè)版本時(shí)缰儿,我們通常先在版本庫中打一個(gè)標(biāo)簽,這樣散址,就唯一確定了打標(biāo)簽時(shí)刻的版本乖阵。將來無論什么時(shí)候宣赔,取某個(gè)標(biāo)簽的版本,就是把那個(gè)打標(biāo)簽的時(shí)刻的歷史版本取出來瞪浸。所以儒将,標(biāo)簽也是版本庫的一個(gè)快照。
Git的標(biāo)簽雖然是版本庫的快照对蒲,但其實(shí)它就是指向某個(gè)commit的指針(跟分支很像對不對钩蚊?但是分支可以移動(dòng),標(biāo)簽不能移動(dòng))蹈矮,所以砰逻,創(chuàng)建和刪除標(biāo)簽都是瞬間完成的
命令git tag name用于新建一個(gè)標(biāo)簽,默認(rèn)為HEAD泛鸟,也可以指定一個(gè)commit id蝠咆;
-a tagname -m "blablabla..."可以指定標(biāo)簽信息;
-s tagname -m "blablabla..."可以用PGP簽名標(biāo)簽北滥;
命令git tag可以查看所有標(biāo)簽刚操;
$ git checkout master
$ git tag v1.0
$ git tag v1.0 36564232
$ git tag -a v1.0 -m "version 1.0 released" 36564232
查看tag的詳情內(nèi)容:
$ git show tagname
刪除標(biāo)簽:
$ git tag -d v1.0
因?yàn)閯?chuàng)建的標(biāo)簽都只存儲(chǔ)在本地, 不會(huì)自動(dòng)推送到遠(yuǎn)程, 所以, 打錯(cuò)了標(biāo)簽可以在本地安全刪除.
如果要推送某個(gè)標(biāo)簽到遠(yuǎn)程, 使用命令:
$ git push origin tagname
或者, 一次性推送全部尚未推送到遠(yuǎn)程的本地標(biāo)簽:
$ git push origin --tags
刪除遠(yuǎn)程標(biāo)簽(其實(shí)是推送一個(gè)空的tag到遠(yuǎn)程tag達(dá)到刪除目的):
1) 先刪除本地的標(biāo)簽: $ git tag -d v.0
2) 然后從遠(yuǎn)程刪除: $ git push origin :refs/tags/v1.0
或者, 1.7以后可以這么做:
$ git push origin --delete tag v1.0
獲取遠(yuǎn)程的tag:
$ git fetch origin tag <tagname>
小結(jié)
命令git push origin tagname可以推送一個(gè)本地標(biāo)簽;
命令git push origin --tags可以推送全部未推送過的本地標(biāo)簽再芋;
命令git tag -d tagname可以刪除一個(gè)本地標(biāo)簽菊霜;
命令git push origin :refs/tags/tagname可以刪除一個(gè)遠(yuǎn)程標(biāo)簽。