git查看 对比未提交_30分钟让你掌握Git的黑魔法
擔憂
很多人怕使用 git,我個人覺得主要可能是兩部分的原因:
沒接觸過:平時接觸的代碼還托管在 SVN 或 CVS 等工具上。
不太熟悉:可能對 git 的使用還不太熟悉和全面,導致在使用 git 時步步為營。
Never Be Afraid To Try Something New.
代碼對于開發者是勞作成果的結晶,對于公司而言是核心資產,有一些擔憂也是正常的。但git 也并沒有我們想象中的那么復雜,讓我們每次使用都心有余悸,其實我們只需要稍微花一點時間嘗試多多了解它,在很多時候你會發現,非但 git 不會讓你產生擔憂,反而會讓自己的交付過程更加高效。
Version Control
談及 git 就不得不提到版本控制,我們不妨先來看下版本控制是做什么的,這將有助于后續對 git 的理解。
當你在工作中面對的是一些經常變化的文檔、代碼等交付物的時候,考慮如何去追蹤和記錄這些 changes 就變得非常重要,原因可能是:
對于頻繁改動和改進的交付物,非常有必要去記錄下每次變更的內容,每次記錄的內容匯成了一段修改的歷史,有了歷史我們才知道我們曾經做了什么。
記錄的歷史中必須要包含一些重要的信息,這樣追溯才變得有意義,比如:
Who: 是誰執行的變更?
When: 什么時候做出的變更?
What: 這次變更做了什么事情?
最好可以支持撤銷變更,不讓某一個提交的嚴重問題,去污染整個提交歷史。
版本控制系統(VCS: Version Control System),正會為你提供這種記錄和追溯變更的能力。
1
大多數的 VCS 支持在多個使用者之間共享變更的提交歷史,這從實質上讓團隊協同變為了可能,簡單說來就是:
你可以看到我的變更提交。
]我也可以看到你的變更提交。
如果雙方都進行了變更提交,也可以以某種方式方法進行比對和合并,最終作出統一的變更版本。
VCS 歷經多年的發展,目前業界中有許多VCS 工具可供我們選擇。在本文中,我們將會針對目前最流行的 git 來介紹。
git是黑魔法么?
剛接觸 git 時,git 確實有讓人覺得有點像黑魔法一樣神秘,但是又有哪個技術不是這樣呢?當我們了解其基本的數據結構后,會發現 git 從使用角度來講其實并不復雜,我們甚至可以更進一步的學習 git 的一些先進的軟件設計理論,從中獲益。首先,讓我們先從 commit 說起。
git commit object
提交對象(git commit object): 每一個提交在 git 中都通過 git commit object 存儲,對象具有一個全局唯一的名稱,叫做revision hash。它的名字是由 SHA-1 算法生成,形如 "998622294a6c520db718867354bf98348ae3c7e2",我們通常會取其縮寫方便使用,如 "9986222"。對象構成: commit 對象包含了 author + commit message 的基本信息。對象存儲:git commit object 保存一次變更提交內的所有變更內容,而不是增量變化的數據 delta (很多人都理解錯了這一點),所以 git 對于每次改動存儲的都是全部狀態的數據。大對象存儲:因對于大文件的修改和存儲,同樣也是存儲全部狀態的數據,所以可能會影響 git 使用時的性能(glfs 可以改進這一點)。提交樹:多個 commit 對象會組成一個提交樹,它讓我們可以輕松的追溯 commit 的歷史,也能對比樹上 commit 與 commit 之間的變更差異。
git commit 練習
讓我們通過實戰來幫助理解,第一步我們來初始化一個repository(git倉庫),默認初始化之后倉庫是空的,其中既沒有保存任何文本內容也沒有附帶任何提交:
第二步,讓我們來看下執行過后 git 給出的輸出內容,它會指引我們進行進一步的了解:
output 1: On branch master
對于剛剛創建空倉庫來說,master 是我們的默認分支,一個 git 倉庫下可以有很多分支(branches),具體某一個分支的命名可以完全由你自己決定,通常會起便于理解的名字,如果用 hash 號的話肯定不是一個好主意。
branches 是一種引用(ref),他們指向了一個確定的 commit hash 號,這樣我們就可以明確我們的分支當前的內容。
除了 branches 引用以外,還有一種引用叫做tags,相信大家也不會陌生。
master 通常被我們更加熟知,因為大多數的分支開發模式都是用 master 來指向“最新”的 commit。
On branchmaster 代表著我們當前是在 master 分支下操作,所以每次當我們在提交新的 commit 時,git 會自動將master 指向我們新的 commit,當工作在其他分支上時,同理。
有一個很特殊的 ref 名稱叫做"HEAD",它指向我們當前正在操作的 branches 或 tags (正常工作時),其命名上非常容易理解,表示當前的引用狀態。
通過 git branch(或 gittag) 命令你可以靈活的操作和修改 branches 或 tags。
output 2: No commits yet
對于空倉庫來說,目前我們還沒有進行任意的提交。
nothing to commit (create/copy files anduse "git add" to track)
output 中提示我們需要使用 git add 命令,說到這里就必須要提到暫存或索引(stage),那么如何去理解暫存呢?
一個文件從改動到提交到git 倉庫,需要經歷三個狀態:
[if !supportLists]· [endif]工作區:工作區指的是我們本地工作的目錄,比如我們可以在剛才創建的 hackers 目錄下新增一個readme文件,readme 文件這時只是本地文件系統上的修改,還未存儲到 git。
暫存(索引)區: 暫存實際上是將我們本地文件系統的改動轉化為 git 的對象存儲的過程。
倉庫:git commit 后將提交對象存儲到 git 倉庫。
2
git add 的幫助文檔中很詳細的解釋了暫存這一過程:
對 git 暫存有一定了解后,其相關操作的使用其實也非常簡單,簡要的說明如下:
1、暫存區操作
通過 git add 命令將改動暫存;
可以使用 git add -p 來依次暫存每一個文件改動,過程中我們可以靈活選擇文件中的變更內容,從而決定哪些改動暫存;
如果 git add 不會暫存被 ignore 的文件改動;
通過 git rm 命令,我們可以刪除文件的同時將其從暫存區中剔除;
2、暫存區修正
通過 git reset 命令進行修正,可以先將暫存區的內容清空,在使用git add -p 命令對改動 review 和暫存;
這個過程不會對你的文件進行任何修改操作,只是 git 會認為目前沒有改動需要被提交;
如果我們想分階段(or 分文件)進行 reset,可以使用 git reset FILE or git reset -p 命令;
3、暫存區狀態
可以用 git diff --staged 依次檢查暫存區內每一個文件的修改;
用 git diff 查看剩余的還未暫存內容的修改;
4、Just Commit!
當你對需要修改的內容和范圍滿意時,你就可以將暫存區的內容進行 commit 了,命令為 git commit;
如果你覺得需要把所有當前工作空間的修改全部commit,可以執行 git commit -a ,這相當于先執行 git add 后執行 git commit,將暫存和提交的指令合二為一,這對于一些開發者來說是很高效的,但是如果提交過大這樣做通常不合適;
我們建議一個提交中只做一件事,這在符合單一職責的同時,也可以讓我們明確的知道每一個 commit 中做了一件什么事情而不是多個事情。所以通常我們的使用習慣都是執行 git add -p 來 review 我們將要暫存內容是否合理?是否需要更細的拆分提交?這些優秀的工程實踐,將會讓代碼庫中的 commits 更加優雅??;
ok,我們已經在不知不覺中了解了很多內容,我們來回顧下,它們包括了:
包含的信息是什么?
commit 是如何表示的?
暫存區是什么?如何全部添加、一次添加、刪除、查詢和修正?
如何將暫存區的改動內容 commit?
不要做大提交,一個提交只做一件事;
附帶的,在了解 commit 過程中我們知道了從本地改動到提交到 git 倉庫,經歷的幾個關鍵的狀態:
工作區(Working Directory)
暫存區(Index)
Git 倉庫(Git Repo)
下圖為上述過程中各個狀態的轉換過程:
本地改動文件時,此時還僅僅是工作區內的改動;
當執行 git add 之后,工作區內的改動被索引在暫存區;
當執行 git commit 之后,暫存區的內容對象將會存儲在 git 倉庫中,并執行更新 HEAD 指向等后續操作,這樣就完成了引用與提交、提交與改動快照的一一對應了。
3
正是因為 git 本身對于這幾個區域(狀態)的設計,為 git 在本地開發過程帶來了靈活的管理空間。我們可以根據自己的情況,自由的選擇哪些改動暫存、哪些暫存的改動可以 commit、commit 可以關聯到那個引用,從而進一步與其他人進行協同。
提交之后
我們已經有了一個 commit,現在我們可以圍繞 commit 做更多有趣的事情:
查看commit 歷史: git log(or git log --oneline)。
在commit 中查看改動的 diff:git log -p。
查看ref 與提交的關聯關系,如當前 master 指向的commit: git show master。
檢出覆蓋: git checkout NAME(如果NAME是一個具體的提交哈希值時,git 會認為狀態是“detached(分離的)”,因為 gitcheckout 過程中重要的一步是將 HEAD 指向那個分支的最后一次 commit。所以如果這樣做,將意味著沒有分支在引用此提交,所以若我們這時候進行提交的話,沒有人會知道它們的存在)。
使用git revert NAME 來對 commit 進行反轉操作。
使用git diff NAME.. 將舊版本與當前版本進行比較,查看 diff。
使用git log NAME, 查看指定區間的提交。
使用git reset NAME 進行提交重置操作。
使用git reset --hard NAME:將所有文件的狀態強制重置為 NAME 的狀態,使用上需要小心。
引用基本操作
引用(refs)包含兩種分別是branches 和 tags, 我們接下來簡單介紹下相關操作:
git branch b 命令可以讓我們創建一個名稱為 b 的分支;
當我們創建了一個 b 分支后,這也相當于意味著 b 的指向就是 HEAD 對應的 commit;
我們可以先在 b 分支上創建一個新的 commitA ,然后假如切回 master 分支上,這時再提交了一個新的 commitB,那么 master 和 HEAD 將會指向了新的commit __B,而 b 分支指向的還是原來的 commit A;
git checkout b 可以切換到 b 分支上,切換后新的提交都會在 b 分支上,理所應當;
git checkout master 切換回 master 后,b分支的提交也不會帶回 master 上,分支隔離;
分支上提交隔離的設計,可以讓我們非常輕松的切換我們的修改,非常方便的做各類測試。
tags 的名稱不會改變,而且它們有自己的描述信息(比如可以作為 release note 以及標記發布的版本號等)。
做好你的提交
可能很多人的提交歷史是長這個樣子的:
單就git 而言,這看上去是沒有問題而且合法的,但對于那些對你修改感興趣的人(很可能是未來的你!),這樣的提交在信息在追溯歷史時可能并沒有多大幫助。但是如果你的提交已經長成這個樣子,我們該怎么辦?
沒關系,git 有辦法可以彌補這一些:
git commit --amend
我們可以將新的改動提交到當前最近的提交上,比如你發現少改了什么,但是又不想多出一個提交時會很有用。
如果我們認為我們的提交信息寫的并不好,我要修改修改,這也是一種辦法,但是并不是最好的辦法。
這個操作會更改先前的提交,并為其提供新的hash值。
git rebase -i HEAD~13
這個命令非常強大,可以說是 git 提交管理的神器,此命令含義是我們可以針對之前的 13 次的提交在 VI 環境中進行重新修改設計:
[if !supportLists]· [endif]操作選項 p 意味著保持原樣什么都不做,我們可以通過 vim 中編輯提交的順序,使其在提交樹上生效;
[if !supportLists]· [endif]操作選項 r: 我們可以修改提交信息,這種方式比 commit --amend 要好的多,因為不會新生成一個 commit;
[if !supportLists]· [endif]操作選項 e: 我們可以修改commit,比如新增或者刪除某些文件改動;
[if !supportLists]· [endif]操作選項 s: 我們可以將這個提交與其上一次的提交進行合并,并重新編輯提交信息;
[if !supportLists]· [endif]操作選項 f: f 代表著"fixup"。例如我們如果想針對之前一個老的提交進行 fixup,又不想做一次新的提交破壞提交樹的歷史的邏輯含義,可以采用這種方式,這種處理方式非常優雅;
關于git
版本控制的一個常見功能是允許多個人對一組文件進行更改,而不會互相影響。或者更確切地說,為了確保如果他們不會踩到彼此的腳趾,不會在提交代碼到服務端時偷偷的覆蓋彼此的變化。
在 git 中我們如何保證這一點呢?
git 與svn不同,git 不存在本地文件 lock 的情況,這是一種避免出現寫作問題的方式,但是并不方便,而 git 與 svn 最大的不同在于它是一個分布式 VCS,這意味著:
每個人都有整個存儲庫的本地副本(其中不僅包含了自己的,也包含了其他人的提交到倉庫的所有內容)。
一些VCS 是集中式的(例如,svn):服務器具有所有提交,而客戶端只有他們“已檢出”的文件。所以基本上在本地我們只有當前文件,每次涉及本地不存在的文件操作時,都需要訪問服務端進行進一步交互。
每一個本地副本都可以當作服務端對外提供 git 服務。
我們可以用 git push 推送本地內容到任意我們有權限的 git 遠端倉庫。
不管是集團的 force、github、gitlab等工具,其實本質上都是提供的 git 倉庫存儲的相關服務,在這一點上其實并沒有特別之處,針對 git 本身和其協議上是透明的。
4
git沖突解決
沖突的產生幾乎是不可避免的,當沖突產生時你需要將一個分支中的更改與另一個分支中的更改合并,對應 git 的命令為 git merge NAME ,一般過程如下:
找到HEAD 和 NAME 的一個共同祖先(commonbase);
嘗試將這些 NAME 到共同祖先之間的修改合并到 HEAD 上;
新創建一個 merge commit 對象,包含所有的這些變更內容;
HEAD 指向這個新的 mergecommit;
git 將會保證這個過程改動不會丟失,另外一個命令你可能會比較熟悉,那就是 git pull 命令,git pull 命令實際上包含了 git merge 的過程,具體過程為:
it fetch REMOTE
git merge REMOTE/BRANCH
和git push一樣,有的時候需要先設置 "tracking"(-u) ,這樣可以將本地和遠程的分支一一對應。
如果每次 merge 都如此順利,那肯定是非常完美的,但有時候你會發現在合并時產生了沖突文件,這時候也不用擔心,如何處理沖突的簡要介紹如下:
沖突只是因為 git 不清楚你最終要合并后的文本是什么樣子,這是很正常的情況;
產生沖突時,git 會中斷合并操作,并指導你解決好所有的沖突文件;
打開你的沖突文件,找到 <<<<<<< ,這是你需要開始處理沖突的地方,然后找到=======,等號上面的內容是 HEAD 到共同祖先之間的改動,等號下面是 NAME 到共同祖先之間的改動。用 git mergetool 通常是比較好的選擇,當然現在大多數 IDE 都集成了不錯的沖突解決工具;
當你把沖突全部解決完畢,請用 git add . 來暫存這些改動;
最后進行 git commit,如果你想放棄當前修改重新解決可以使用 git merge--abort ,非常方便;
當你完成了以上這些艱巨的任務,最后 git push 吧!
push失敗?
排除掉遠端的 git 服務存在問題以外,我們 push 失敗的大多數原因都是因為我們在工作的內容其他人也在工作的關系。
Git 是這樣判斷的:
1、會判斷 REMOTE 的當前 commit 是不是你當前正在 pushing commit 的祖先。
2、如果是的話,代表你的提交是相對比較新的,push 是可以成功的(fast-forwarding)。
3、否則push 失敗并提示你其他人已經在你 push 之前執行更新(push is rejected)。
當發生push is rejected 后我們的幾個處理方法如下:
使用git pull 合并遠程的最新更改(git pull相當于git fetch + git merge) ;
使用--force 強制推送本地變化到遠端飲用進行覆蓋,需要注意的是 這種覆蓋操作可能會丟失其他人的提交內容;
可以使用 --force-with-lease 參數,這樣只有遠端的 ref 自上次從 fetch 后沒有改變時才會強制進行更改,否則 reject the push,這樣的操作更安全,是一種非常推薦使用的方式;
如果rebase 操作了本地的一些提交,而這些提交之前已經 push 過了的話,你可能需要進行 force push 了,可以想象看為什么?
本文只是選取部分 Git 基本命令進行介紹,目的是拋磚引玉,讓大家對 git 有一個基本的認識。當我們深入挖掘 Git 時,你會發現它本身有著如此多優秀的設計理念,值得我們學習和探究。
不要讓 Git 成為你認知領域的黑魔法,而是讓 Git 成為你掌握的魔法。
總結
以上是生活随笔為你收集整理的git查看 对比未提交_30分钟让你掌握Git的黑魔法的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python面向对象生动讲解_Pytho
- 下一篇: c语言实现python列表_用C语言实现