讀了「扔物線」老師的小冊《Git 原理詳解及實用指南》感受收穫良多,因而想寫點東西作一個總結,即加深本身的印象也但願能給社區小夥伴一點幫助,寫的不對的地方還請多多指導。身爲一個初入前端半年的菜鳥,由伊始的只知道git是用來託管代碼的工具到逐步瞭解中央版本控制系統與分佈式版本控制系統(git)的原理與區別;從以前只會基本的add、commit、pull、push操做到使用stash、merge、reset方便得不亦樂乎,都得益於對git原理的深刻理解,逼話少說,咋們直接進入正題。前方長篇預警...前端
所謂版本控制,就是在文件修改的歷程中保留修改歷史,能夠方便的撤銷(如同文本編輯的撤銷操做通常,只是版本控制會複雜的多)以前對文件的修改。一個版本控制系統的三個核心內容:版本控制(最基本的功能),主動提交(commit歷史)和遠程倉庫(協同開發)。git
工做模型安全
- 主工程師搭好項目框架
- 在公司服務器建立一個遠程倉庫,並提交代碼
- 其餘人拉取代碼,並行開發
- 每一個人獨立負責一個功能,開發完成提交代碼
- 其餘人隨時拉取代碼,保持同步
分佈式與中央式的區別主要在於,分佈式除了遠程倉庫以外團隊中每個成員的機器上都有一份本地倉庫,每一個人在本身的機器上就能夠進行提交代碼,查看版本,切換分支等操做而不須要徹底依賴網絡環境。
工做模型服務器
- 主工程師搭好項目框架 ,並提交代碼到本地倉庫
- 在公司服務器建立一個遠程倉庫,並將1的提交推送到遠程倉庫
- 其餘人把遠程倉庫全部內容克隆到本地,擁有了各自的本地倉庫,開始並行開發
- 每一個人獨立負責一個功能,能夠把每個小改動提交到本地(因爲本地提交無需當即上傳到遠程倉庫,因此每一步提交沒必要是一個完整功能,而能夠是功能中的一個步驟或塊)
- 功能開發完畢,將和這個功能相關的全部提交從本地推送到遠程倉庫
- 每次當有人把新的提交推送到遠程倉庫的時候,其餘人就能夠選擇把這些提交同步到本身的機器上,並把它們和本身的本地代碼合併
假設你已經安裝好了git並將代碼clone到了本地,新手移步git安裝與代碼拷貝指南。markdown
首先理解三個基本概念:
網絡
stage 這個詞在 Git 裏,是「集中收集改動以待提交」的意思;而 staging area ,就是一個「聚集待提交的文件改動的地方」。簡稱「暫存」和「暫存區」。至於 staged 表示「已暫存」,就不用再解釋了吧?3.如今文件已經放入暫存區,能夠用commit命令提交: 在這裏你也能夠直接commit提交會進入commit信息編輯頁面,而加上-m參數能夠快捷輸入簡短的提交備註信息,這樣你就完成了一次提交(能夠經過
git log
查看提交歷史)
git status
能夠看到,該文件 又變紅了,不過此次它左邊的文字不是 "New file:" 而是 "modified:",並且上方顯示它的狀態也不是 "Untracked" 而是 "not staged for commit",意思很明確:Git 已經認識這個文件了,它不是個新文件,但它有了一些改動。因此雖然狀態的顯示有點不一樣,但處理方式仍是同樣的:
接下來再次將該文件add、commit,查看log能夠看到已經存在兩條提交記錄
4.最後經過push把本地的全部commit上傳到遠程倉庫:
工做模型
1.在上面基本操做的基礎上,同事 commit 代碼到他的本地,並 push 到遠程倉庫
2.你把遠程倉庫新的提交經過 pull指令拉取到你的本地
經過這個流程,你和同事就能夠簡單地合做了:你寫了代碼,commit,push 到遠程倉庫,而後他 pull 到他的本地;他再寫代碼,commit, push 到遠程倉庫,而後你再 pull 到你的本地。你來我往,配合得不亦樂乎。(可是有時候push會失敗)app
爲何會失敗?
由於 Git 的push 實際上是用本地倉庫的commit記錄去覆蓋遠程倉庫的commit記錄(注:這是簡化概念後的說法,push 的實質和這個說法略有不一樣),而若是在遠程倉庫含有本地沒有的commit的時候,push (若是成功)將會致使遠端的commit被擦掉。這種結果固然是不可行的,所以 Git 會在 push 的時候進行檢查,若是出現這樣的狀況,push 就會失敗框架
這時只須要先經過git pull
(實爲fetch和merge的組合操做)將本地倉庫的提交和遠程倉庫的提交進行合併,而後再push就能夠了分佈式
核心:
(1)任何新的功能(feature)或 bug 修復全都新建一個 branch 來寫;
(2)branch 寫完後,合併到 master,而後刪掉這個 branch(可以使用git origin -d 分支名
刪除遠程倉庫的分支)。 工具
當前 commit 在哪裏,HEAD 就在哪裏,這是一個永遠自動指向當前 commit 的引用,因此你永遠能夠用 HEAD 來操做當前 commit,
HEAD 是 Git 中一個獨特的引用,它是惟一的。而除了 HEAD 以外,Git 還有一種引用,叫作 branch(分支)。HEAD 除了能夠指向 commit,還能夠指向一個branch,當指向一個branch時,HEAD會經過branch間接指向當前commit,HEAD移動會帶着branch一塊兒移動:
branch 包含了從初始 commit 到它的全部路徑,而不是一條路徑。而且,這些路徑之間也是彼此平等的。
像上圖這樣,master 在合併了 branch1 以後,從初始 commit 到 master 有了兩條路徑。這時,master 的串就包含了 1 2 3 4 7 和 1 2 5 6 7 這兩條路徑。並且,這兩條路徑是平等的,1 2 3 4 7 這條路徑並不會由於它是「原生路徑」而擁有任何的特別之處git branch 名稱
git checkout 名稱
(將HEAD指向該branch)
git checkout -b 名稱
git branch -d 名稱
所謂引用,其實就是一個個的字符串。這個字符串能夠是一個 commit 的 SHA-1 碼(例:c08de9a4d8771144cd23986f9f76c4ed729e69b0),也能夠是一個 branch(例:ref: refs/heads/feature3)。
Git 中的 HEAD 和每個 branch 以及其餘的引用,都是以文本文件的形式存儲在本地倉庫 .git 目錄中,而 Git 在工做的時候,就是經過這些文本文件的內容來判斷這些所謂的「引用」是指向誰的。
(1)把當前branch位置上傳到遠程倉庫,並把它路徑上的commits一併上傳
(2)git中(2.0及之後版本),git push
不加參數只能上傳到從遠程倉庫clone或者pull下來的分支,如需push在本地建立的分支則需使用git push origin 分支名
的命令
(3)遠端倉庫的HEAD並不隨push與本地一致,遠端倉庫HEAD永遠指向默認分支(master),並隨之移動(可使用git br -r
查看遠程分支的HEAD指向)。
含義:從目標 commit 和當前 commit (即 HEAD 所指向的 commit)分叉的位置起,把目標 commit 的路徑上的全部 commit 的內容一併應用到當前 commit,而後自動生成一個新的 commit。
當執行git merge branch1
操做,Git 會把 5 和 6 這兩個 commit 的內容一併應用到 4 上,而後生成一個新的提交 7 。
git merge --abort
放棄解決衝突,取消merge
有些人不喜歡 merge,由於在 merge 以後,commit 歷史就會出現分叉,這種分叉再匯合的結構會讓有些人以爲混亂而難以管理。若是你不但願 commit 歷史出現分叉,能夠用 rebase 來代替 merge。
能夠看出,經過 rebase,5 和 6 兩條 commits 把基礎點從 2 換成了 4 。經過這樣的方式,就讓原本分叉了的提交歷史從新回到了一條線。這種「從新設置基礎點」的操做,就是 rebase 的含義。另外,在 rebase 以後,記得切回 master 再 merge 一下,把 master 移到最新的 commit。爲何要從 branch1 來 rebase,而後再切回 master 再 merge 一下這麼麻煩,而不是直接在 master 上執行 rebase?
這就致使 master 上以前的兩個最新 commit (3和4)被剔除了。若是這兩個 commit 以前已經在遠程倉庫存在,這就會致使無法 push : 因此,爲了不和遠程倉庫發生衝突,通常不要從 master 向其餘 branch 執行 rebase 操做。而若是是 master 之外的 branch 之間的 rebase(好比 branch1 和 branch2 之間),就沒必要這麼多費一步,直接 rebase 就好。
從圖中能夠看出,rebase 後的每一個 commit 雖然內容和 rebase 以前相同,但它們已是不一樣的 commit 了(每一個commit有惟一標誌)。若是直接從 master 執行 rebase 的話,就會是下面這樣:
須要說明的是,rebase 是站在須要被 rebase 的 commit 上進行操做,這點和 merge 是不一樣的。
stash 指令能夠幫你把工做目錄的內容所有放在你本地的一個獨立的地方,它不會被提交,也不會被刪除,你把東西放起來以後就能夠去作你的臨時工做了,作完之後再來取走,就能夠繼續以前手頭的事了。
操做步驟:
(1)git stash
能夠加上save參數後面帶備註信息(git stash save '備註信息'
)
(2)此時工做目錄已經清空,能夠切換到其餘分支幹其餘事情了
(3)git stash pop
彈出第一個stash(該stash從歷史stash中移除);或者使用git stash apply
達到相同的效果(該stash仍存在stash list中),同時可使用git stash list
查看stash歷史記錄並在apply後面加上指定的stash返回到該stash。
注意:沒有被track的文件會被git忽略而不被stash,若是想一塊兒stash,加上-u參數。
能夠查看git的引用記錄,不指定參數,默認顯示HEAD的引用記錄;若是不當心把分支刪掉了,可使用該命令查看引用記錄,而後使用checkout切到該記錄處重建分支便可。
注意:再也不被引用直接或間接指向的 commits 會在必定時間後被 Git 回收,因此使用 reflog 來找回被刪除的 branch 的操做必定要及時,否則有可能會因爲 commit 被回收而再也找不回來。
git log -p
能夠查看每一個commit的改動細節(到改動文件的每一行)
git log --stat
查看簡要統計(哪幾個文件改動了)
git show 指定commit 指定文件名
查看指定commit的指定文件改動細節
git diff --staged
能夠顯示暫存區和上一條提交之間的不一樣。換句話說,這條指令可讓你看到「若是你當即輸入 git commit,你將會提交什麼」
git diff
能夠顯示工做目錄和暫存區之間的不一樣。換句話說,這條指令可讓你看到「若是你如今把全部文件都 add,你會向暫存區中增長哪些內容」
git diff HEAD
能夠顯示工做目錄和上一條提交之間的不一樣,它是上面這兩者的內容相加。換句話說,這條指令可讓你看到「若是你如今把全部文件都 add 而後 git commit,你將會提交什麼」(不過須要注意,沒有被 Git 記錄在案的文件(即歷來沒有被 add 過的文件,untracked files 並不會顯示出來。由於對 Git 來講它並不存在)實質上,若是你把 HEAD 換成別的commit,也能夠顯示當前工做目錄和這條 commit 的區別。
再提一個修復了錯誤的commit?能夠是能夠,不過還有一個更加優雅和簡單的解決方法:commit --amend。
具體作法:
(1)修改好問題
(2)將修改add到暫存區
(3)使用git commit --amend
提交修改,結果以下圖:
使用rebase -i(交互式rebase):
所謂「交互式 rebase」,就是在 rebase 的操做執行以前,你能夠指定要 rebase 的 commit 鏈中的每個 commit 是否須要進一步修改,那麼你就能夠利用這個特色,進行一次「原地 rebase」。
操做過程:
(1)git rebase -i HEAD^^
說明:在 Git 中,有兩個「偏移符號」: ^ 和 ~。
^ 的用法:在 commit 的後面加一個或多個 ^ 號,能夠把 commit 往回偏移,偏移的數量是 ^ 的數量。例如:master^ 表示 master 指向的 commit 以前的那個 commit; HEAD^^ 表示 HEAD 所指向的 commit 往前數兩個 commit。
~ 的用法:在 commit 的後面加上 ~ 號和一個數,能夠把 commit 往回偏移,偏移的數量是 ~ 號後面的數。例如:HEAD~5 表示 HEAD 指向的 commit往前數 5 個 commit。
上面這行代碼表示,把當前 commit ( HEAD 所指向的 commit) rebase 到 HEAD 以前 2 個的 commit 上:
(2)進入編輯頁面,選擇commit對應的操做,commit爲正序排列,舊的在上,新的在下,前面黃色的爲如何操做該commit,默認pick(直接應用該commit不作任何改變),修改第一個commit爲edit(應用這個 commit,而後停下來等待繼續修正)而後:wq退出編輯頁面,此時rebase停在第二個commit的位置,此時能夠對內容進行修改: (3)修改完後使用add,commit --amend將修改提交git rebase --continue
繼續 rebase 過程,把後面的 commit 直接應用上去,此次交互式 rebase 的過程就完美結束了,你的那個倒數第二個寫錯的 commit 就也被修正了:
git reset --hard HEAD^
HEAD^ 表示 HEAD 往回數一個位置的 commit ,上節剛說過,記得吧?
操做步驟與修改歷史提交相似,第二步把須要撤銷的commit修改成drop,其餘步驟再也不贅述。
git rebase --onto HEAD^^ HEAD^ branch1
上面這行代碼的意思是:以倒數第二個 commit 爲起點(起點不包含在 rebase 序列裏),branch1 爲終點,rebase 到倒數第三個 commit 上。
有的時候,代碼 push 到了遠程倉庫,才發現有個 commit 寫錯了。這種問題的處理分兩種狀況:
假如是某個你本身獨立開發的 branch 出錯了,不會影響到其餘人,那不要緊用前面幾節講的方法把寫錯的 commit 修改或者刪除掉,而後再 push 上去就行了。可是此時會push報錯,由於遠程倉庫包含本地沒有的 commits(在本地已經被替換或被刪除了),此時直接使用git push origin 分支名 -f
強制push。
(1)增長新提交覆蓋以前內容
(2)使用git revert 指定commit
它的用法很簡單,你但願撤銷哪一個 commit,就把它填在後面。如:git revert HEAD^
上面這行代碼就會增長一條新的 commit,它的內容和倒數第二個 commit 是相反的,從而和倒數第二個 commit 相互抵消,達到撤銷的效果。在 revert 完成以後,把新的 commit 再 push 上去,這個 commit 的內容就被撤銷了。它和前面所介紹的撤銷方式相比,最主要的區別是,此次改動只是被「反轉」了,並無在歷史中消失掉,你的歷史中會存在兩條 commit :一個原始 commit ,一個對它的反轉 commit。
git reset --hard 指定commit
你的工做目錄裏的內容會被徹底重置爲和指定commit位置相同的內容。換句話說,就是你的未提交的修改會被所有擦掉。
git reset --soft 指定commit
會在重置 HEAD 和 branch 時,保留工做目錄和暫存區中的內容,並把重置 HEAD 所帶來的新的差別放進暫存區。
什麼是「重置 HEAD 所帶來的新的差別」?就是這裏:
git reset --mixed(或者不加參數) 指定commit
保留工做目錄,而且清空暫存區。也就是說,工做目錄的修改、暫存區的內容以及由 reset 所致使的新的文件差別,都會被放進工做區。簡而言之,就是「把全部差別都混合(mixed)放在工做區中」。
checkout的本質是簽出指定的commit,不止能夠切換branch還能夠指定commit做爲參數,把HEAD移動到指定的commit上;與reset的區別在於只移動HEAD不改變綁定的branch;git checkout --detach
能夠把 HEAD 和 branch 脫離,直接指向當前 commit。
但願個人總結能給你們帶來些許幫助,也但願和你們一塊兒學以至用,一塊兒成長。最後,萬分感謝扔老師的小冊,強勢安利《git原理詳解與實用指南》,認準扔物線。