教程地址,動畫學git,太直觀了,爽!
https://learngitbranching.js.org/
感謝作者!
本地
/********************************************************/
/********************************************************/
基礎篇
git branch bugFix 建立bugFix分支
git checkout bugFix 轉換"當前分支"為bugFix
git commit 提交,生成節點
git checkout master
git commit
git merge bugFix 把bugFix合並到"當前分支master" 【merge from】
git rebase master 把"當前分支bugFix"rebase到master下邊!【rebase to】
git rebase bugFix 分支已經是最新!
/********************************************************/
高級篇
1.HEAD
git checkout C1
git checkout master
git commit
git checkout C2
HEAD一開始指向master,后指向C1,后指向master
master提交后,HEAD隨着master一起移動,並繼續指向master
后指向C2,這一步叫做"分離的HEAD狀態"!!!
想完成此關,從 bugFix 分支中分離出 HEAD 並讓其指向一個提交記錄。
答案:
git checkout bugFix
git checkout C4
2.相對引用
實際中,提交的節點哈希值不會是C0/C1/C2這么簡單的數值,而是40位的...
所以還是用相對引用簡單。
git checkout master^ 尋找指定提交記錄的父提交
git checkout master^^ 尋找指定提交記錄的父父提交
此外還可以把HEAD作為,相對引用的參照。
git checkout HEAD^
要完成此關,切換到 bugFix 的父節點。這會進入"分離 HEAD 狀態"。
答案:
git checkout bugFix
git checkout C4
git checkout HEAD^
3.相對引用2
git branch -f master HEAD~3
將master分支強制指向 HEAD 的第 3 級父提交。
答案:
git checkout C1 HEAD指向C1
git branch -f bugFix HEAD^ 分支bugFix指向HEAD的上一級
git branch -f master C6 分支master直接指向C6
4.版本回退
#git reset HEAD~1
Git 把 master 分支移回到 C1;現在我們的本地代碼庫根本就不知道有 C2 這個提交了。
在reset后, C2 所做的變更還在,但是處於未加入暫存區狀態。
#git revert HEAD
雖然在你的本地分支中使用 git reset 很方便,但是這種“改寫歷史”的方法對大家一起使用的遠程分支是無效的!
為了撤銷更改並分享給別人,我們需要使用 git revert。
在我們要撤銷的提交記錄后面多出一個新提交!
這是因為新提交記錄 C2' 引入了更改 —— 這些更改剛好是用來撤銷 C2 這個提交的。也就是說 C2' 的狀態與 C1 是相同的。
revert 之后就可以把你的更改推送到遠程倉庫與別人分享啦。
如果C2后邊還有C3,C4等等提交,C3,C4的修改不會動,僅僅是撤銷C2的這次提交修改的內容!並在C4后邊生成新的提交(撤銷C2的修改)。
要完成此關,分別撤銷 local 分支和 pushed 分支上的最近一次提交。共需要撤銷兩個提交(每個分支一個)。
記住 pushed 是遠程分支,local 是本地分支 —— 這么說你應該知道用分別哪種方法了吧?
git reset HEAD^
git checkout pushed
git revert HEAD

/********************************************************/
移動提交記錄篇
"我想要把這個提交放到這里,那個提交放到剛才那個提交的后面"
1.將一些提交復制到當前所在的位置(HEAD)下面。
git cherry-pick <提交號>
git cherry-pick C2 C4
就會把他們抓過來放到當前分支下。
答案:
git cherry-pick C3 C4 C7
2.圖形交互式rebase
你不清楚你想要的提交記錄的哈希值時,rebase 加參數 --interactive (-i)
Git 會打開一個 UI 界面,並列出將要被復制到目標分支的備選提交記錄,
它還會顯示每個提交記錄的哈希值和提交說明,提交說明有助於你理解這個提交進行了哪些更改。
當 rebase UI界面打開時, 你能做3件事:
#調整提交記錄的順序(通過鼠標拖放來完成)
#刪除你不想要的提交(點擊切換 pick 的狀態來完成,關閉就意味着你不想要這個提交記錄)
#合並提交。它允許你把多個提交記錄合並成一個。
git rebase -i HEAD~4
可以對提交記錄做排序,也可以刪除某些提交。
(命令的動作:從master到HEAD~4,這整串分支,從HEAD~4位置復制了一份)
要通過本關, 做一次交互式的 rebase,整理成目標窗口中的提交順序。
記住,你隨時都可以用 undo、reset 修正錯誤。
答案:
git rebase -i HEAD~4
之后刪除C2,調整C4C5的順序
/********************************************************/
雜項
1.只取一個提交記錄
本地棧式提交
我正在解決某個特別棘手的 Bug,為了便於調試而在代碼中添加了一些調試命令並向控制台打印了一些信息。
這些調試和打印語句都在它們各自的提交記錄里。
最后我終於找到了造成這個 Bug 的根本原因,解決掉以后覺得沾沾自喜!
最后就差把 bugFix 分支里的工作合並回 master 分支了。
你可以選擇通過 fast-forward 快速合並到 master 分支上,但這樣的話 master 分支就會包含我這些調試語句了。
你肯定不想這樣,應該還有更好的方式……
實際我們只要讓 Git 復制解決問題的那一個提交記錄就可以了。
跟之前我們在“整理提交記錄”中學到的一樣,我們可以使用
git rebase -i
git cherry-pick
來達到目的。
$ git rebase -i HEAD~3 忽略debug/print分支,只把bugFix分支移動到master下
$ git merge master 這個時候,bugFix是"當前分支",但master是bugFix的父節點...
分支已經是最新啦
$ git checkout master
$ git merge master 寫錯
分支已經是最新啦
$ git merge bugFix 必須把"當前分支"調整為master之后,再操作合並。
2.提交技巧 #1:使用rebase -i
你之前在 newImage 分支上進行了一次提交,然后又基於它創建了 caption 分支,然后又提交了一次。
此時你想對的某個以前的提交記錄進行一些小小的調整。
比如設計師想修改一下 newImage 中圖片的分辨率,盡管那個提交記錄並不是最新的了。
->先用 git rebase -i 將提交重新排序,然后把我們想要修改的提交記錄挪到最前
->然后用 git commit --amend 來進行一些小修改
->接着再用 git rebase -i 來將他們調回原來的順序
->最后我們把 master 移到修改的最前端(用你自己喜歡的方法),就大功告成啦!
當然完成這個任務的方法不止上面提到的一種(我知道你在看 cherry-pick 啦),
最后有必要說明一下目標狀態中的那幾個' —— 我們把這個提交移動了兩次,每移動一次會產生一個 ';
而 C2 上多出來的那個是我們在使用了 amend 參數提交時產生的,所以最終結果就是這樣了。
也就是說,我在對比結果的時候只會對比提交樹的結構,
對於 ' 的數量上的不同,並不納入對比范圍內。
只要你的 master 分支結構與目標結構相同,我就算你通過。
$ git rebase -i HEAD^^ 調整c2c3,得到c3' c2'
$ git commit --amend (修改c2',得到c2'')
$ git rebase -i HEAD^^ 調整c2'' c3',得到c2''' c3''
$ git checkout master
$ git merge caption caption指向c3'',合並到master
3.提交技巧 #2:使用cherry-pick實現相同效果
$ git checkout master
$ git cherry-pick C2 把C2轉到master分支下,得到C2'
$ git commit --amend (修改c2',得到c2'')
$ git cherry-pick C3 把C3轉到master分支下(c2''下),得到C3'
4.git Tag
相信通過前面課程的學習你已經發現了:
分支很容易被人為移動,並且當有新的提交時,它也會移動。
分支很容易被改變,大部分分支還只是臨時的,並且還一直在變。
有沒有什么可以永遠指向某個提交記錄的標識呢,
比如軟件發布新的大版本,或者是修正一些重要的 Bug 或是增加了某些新特性,
有沒有比分支更好的可以永遠指向這些提交的方法呢?
更難得的是,它們並不會隨着新的提交而移動。
你也不能檢出到某個標簽上面進行修改提交,
它就像是提交樹上的一個錨點,標識了某個特定的位置。
git tag v1 C1
如果不指定C1,會加到HEAD指向的位置。
在這個關卡中,按照目標建立兩個標簽,然后檢出到 v1 上面,
要注意你會進到分離 HEAD 的狀態————這是因為不能直接在v1 上面做 commit。
5.git describe
Git Describe
標簽在代碼庫中起着“錨點”的作用,
Git 為此專門設計的一個命令用來描述離你最近的錨點(也就是標簽)。
Git Describe 能幫你在提交歷史中移動了多次以后找到方向;
當你用 git bisect(一個查找產生 Bug 的提交記錄的指令)找到某個提交記錄時,
或者是當你坐在你那剛剛度假回來的同事的電腦前時,可能會用到這個命令。
git describe 的??語法是:git describe <ref>
<ref> 可以是任何能被 Git 識別成提交記錄的引用,
如果你沒有指定的話,Git 會以你目前所檢出的位置(HEAD)。
它輸出的結果是這樣的:
<tag>_<numCommits>_g<hash>
tag 表示的是離 ref 最近的標簽
numCommits 是表示這個 ref 與 tag 相差有多少次提交記錄
hash 表示的是你所給定的 ref 所表示的提交記錄哈希值的前幾位。
當 ref 提交記錄上有某個標簽時,則只輸出標簽名稱
/********************************************************/
1.多分支rebase
2.選擇父提交記錄
操作符 ^ 與 ~ 符一樣,后面也可以跟一個數字。
但是該操作符后面的數字與 ~ 后面的不同,並不是用來指定向上返回幾代,
而是指定合並提交記錄的某個父提交。
還記得前面提到過的一個合並提交有兩個父提交吧,所以遇到這樣的節點時該選擇哪條路徑就不是很清晰了。
Git 默認選擇合並提交的“第一個”父提交,在操作符 ^ 后跟一個數字可以改變這一默認行為。
git checkout HEAD~
git checkout HEAD^2
git checkout HEAD~2
鏈式:git checkout HEAD~^2~2
git branch bugWork master^^2^ 在那個地方創建分支
3.糾纏不清的分支
現在我們的 master 分支是比 one、two 和 three 要多幾個提交。
出於某種原因,我們需要把 master 分支上最近的幾次提交做不同的調整后,分別添加到各個的分支上。
one 需要重新排序並刪除 C5
two 僅需要重排排序
three 只需要提交一次。
通關之后用 show solution 看看我們的答案哦。
我的:
$ git checkout three
$ git merge C2
$ git checkout C1
$ git cherry-pick C4 C3 C2
$ git checkout two
$ git cherry-pick C5
$ git cherry-pick C4' C3' C2'
答案:
$ git checkout one
$ git cherry-pick C4 C3 C2
$ git checkout two
$ git cherry-pick C5 C4 C3 C2
$ git branch -f three C2
不用merge,而是用強制指向即可。
遠程
/********************************************************/
/********************************************************/
1. git clone 在本地創建一個遠程倉庫的拷貝
在我們的本地倉庫多了一個名為 origin/master 的分支, 這種類型的分支就叫遠程分支。
遠程分得有一個特別的屬性,在你檢出時自動進入分離 HEAD 狀態。
Git 這么做是出於不能直接在這些分支上進行操作的原因, 你必須在別的地方完成你的工作,
(更新了遠程分支之后)再用遠程分享你的工作成果。
用 git clone 某個倉庫時,Git 已經幫你把遠程倉庫的名稱默認設置為 origin 了.
git checkout origin/master
git commit
正如你所見,Git 變成了分離 HEAD 狀態,當添加新的提交時 origin/master 也不會更新。
這是因為 origin/master 只有在遠程倉庫中相應的分支更新了以后才會更新。
2. git fetch
從遠程倉庫獲取一次提交記錄,並且遠程分支origin/master會更新
它不會更新你的 master 分支,也不會修改你磁盤上的文件。
理解這一點很重要,因為許多開發人員誤以為執行了 git fetch 以后,他們本地倉庫就與遠程倉庫同步了。
它可能已經將進行這一操作所需的所有數據都下載了下來,但是並沒有修改你本地的文件。
所以, 你可以將 git fetch 的理解為單純的下載操作。
3. git pull
用 git fetch 獲取遠程的數據,
再將這些變化更新到我們的工作當中。
git cherry-pick o/master
git rebase o/master
git merge o/master
等等
由於先抓取更新再合並到本地分支,這個流程很常用,
因此 Git 提供了一個專門的命令來完成這兩個操作。
它就是 git pull。
4.模擬團隊合作
克隆一個遠程倉庫(用 git clone),
再在剛創建的遠程倉庫中模擬一些修改,【模擬遠程修改指令git fackTeamwork <分支名><提交次數>】
然后在你自己的本地分支上做一些提交,
再拉取遠程倉庫的變更。
這看起來像是包含了好幾節的課程。
$ git clone
local branch "master" set to track remote branch "o/master"
$ git fakeTeamwork master 2
$ git commit
$ git fetch
$ git merge o/master
5. git push
git push 負責將你的變更上傳到指定的遠程倉庫,並在遠程倉庫上合並你的新提交記錄。
你可以將 git push 想象成發布你成果的命令!!!
注意 —— git push 不帶任何參數時的行為與 Git 的一個名為 push.default 的配置有關。
它的默認值取決於你正使用的 Git 的版本,但是在教程中我們使用的是 upstream。
這沒什么太大的影響,但是在你的項目中進行推送之前,最好檢查一下這個配置。
遠程倉庫接收了 C2,
遠程倉庫中的 master 分支也被更新到指向 C2 了,
我們的遠程分支 (o/master) 也同樣被更新了。
所有的分支都同步了!
6.遠程庫提交歷史的偏離
假設你周一克隆了一個倉庫,然后開始研發某個新功能。
到周五時,你新功能開發測試完畢可以發布了。
但是你的同事這周寫了一堆代碼,
還改了許多你的功能中使用的 API,
這些變動會導致你新開發的功能變得不可用。
但是他們已經將那些提交推送到遠程倉庫了,
因此你的工作就變成了基於項目舊版的代碼,
與遠程倉庫最新的代碼不匹配了。
這種情況下, git push 就不知道該如何操作了。
如果你執行 git push,Git 應該讓遠程倉庫回到星期一那天的狀態嗎?
還是直接在新代碼的基礎上添加你的代碼,
異或由於你的提交已經過時而直接忽略你的提交?
因為這情況(歷史偏離)有許多的不確定性,Git 是不會允許你 push 變更的。
實際上它會強制你先合並遠程最新的代碼,然后才能分享你的工作。
方法:
git fetch
git rebase origin/master
git push
我們用 git fetch 更新了本地倉庫中的遠程分支,
然后用 rebase 將工們的工作移動到最新的提交記錄下,
最后再用 git push 推送到遠程倉庫。
其它的方法,merge
盡管 git merge 不會移動你的工作(它會創建新的合並提交),
但是它會告訴 Git 你已經合並了遠程倉庫的所有變更。
這是因為遠程分支現在是你本地分支的祖先,
也就是說你的提交已經包含了遠程分支的所有變化。
git fetch
git merge origin/master
git push
git fetch 更新了本地倉庫中的遠程分支,
然后合並了新變更到我們的本地分支(為了包含遠程倉庫的變更),
最后我們用 git push 把工作推送到遠程倉庫
簡化操作:
git pull 就是 fetch 和 merge 的簡寫
git pull --rebase 就是 fetch 和 rebase 的簡寫!
要完成本關,你需要完成以下幾步:
克隆你的倉庫
模擬一次遠程提交(fakeTeamwork)
完成一次本地提交
用 rebase 發布你的工作
/********************************************************/
1.推送主分支
在大型項目中開發人員通常會在(從 master 上分出來的)特性分支上工作,工作完成后只做一次集成。這跟前面課程的描述很相像(把 side 分支推送到遠程倉庫),不過本節我們會深入一些.
但是有些開發人員只在 master 上做 push、pull —— 這樣的話 master 總是最新的,始終與遠程分支 (o/master) 保持一致。
對於接下來這個工作流,我們集成了兩個步驟:
將特性分支集成到 master 上
推送並更新遠程分支
這個關卡的 Boss 很厲害 —— 以下是通關提示:
這里共有三個特性分支 —— side1 side2 和 side3
我需要將這三分支按順序推送到遠程倉庫
因為遠程倉庫已經被更新過了,所以我們還要把那些工作合並回來
$ git fetch C8拿回本地
$ git rebase o/master side1 rebase to o/master from side1
$ git rebase side1 side2
$ git rebase side2 side3
$ git rebase side3 master
快速前進...
$ git push
2.合並遠程倉庫
在開發社區里,有許多關於 merge 與 rebase 的討論。
以下是關於 rebase 的優缺點:
優點: Rebase 使你的提交樹變得很干凈, 所有的提交都在一條線上
缺點: Rebase 修改了提交樹的歷史
比如, 提交 C1 可以被 rebase 到 C3 之后。
這看起來 C1 中的工作是在 C3 之后進行的,
但實際上是在 C3 之前。
一些開發人員喜歡保留提交歷史,因此更偏愛 merge。
而其他人(比如我自己)可能更喜歡干凈的提交樹,於是偏愛 rebase。
使用merge實現相同功能:
$ git checkout master
$ git pull C8拿回本地,並且origin/master指向它,master也指向它!
快速前進...
$ git merge side1
$ git merge side2
$ git merge side3
$ git push
3.遠程追蹤
pull 操作時, 提交記錄會被先下載到 o/master 上,之后再合並到本地的 master 分支。
隱含的合並目標由這個關聯確定的。
push 操作時, 我們把工作從 master 推到遠程倉庫中的 master 分支(同時會更新遠程分支 o/master) 。
這個推送的目的地也是由這種關聯確定的!
master 和 o/master 的關聯關系就是由分支的“remote tracking”屬性決定的。
master 被設定為跟蹤 o/master
這意味着為 master 分支指定了推送的目的地以及拉取后合並的目標。
當你克隆倉庫的時候, Git 就自動幫你把這個屬性設置好了。
有兩種方法設置這個屬性,
第一種就是通過遠程分支檢出一個新的分支,執行:
git checkout -b totallyNotMaster o/master
就可以創建一個名為 totallyNotMaster 的分支,它跟蹤遠程分支 o/master。
另一種方法就是使用:(這個命令更明確!)
git branch -u o/master foo
這樣 foo 就會跟蹤 o/master 了。
如果當前就在 foo 分支上, 還可以省略 foo:
git branch -u o/master
答案:
$ git checkout -b side o/master
local branch "side" set to track remote branch "o/master"
$ git commit
$ git pull --rebase
$ git push
4.git push 參數
在遠程跟蹤中,Git通過當前檢出分支的屬性來確定遠程倉庫以及要 push 的目的地。
這是未指定參數時的行為,我們可以為 push 指定參數,語法是:
git push <remote> <place>
git push origin master
切到本地倉庫中的“master”分支,
獲取所有的提交,
再到遠程倉庫“origin”中找到“master”分支,
將遠程倉庫中沒有的提交記錄都添加上去,
搞定之后告訴我。
“place”參數告訴 Git 提交記錄來自於 master, 要推送到遠程倉庫中的 master。
它實際就是要同步的兩個倉庫的位置。
例子:
git checkout C0
git push
命令失敗了,什么也沒有發生!
因為檢出 HEAD 沒有跟蹤任何分支。
git checkout C0
git push origin master
可以的
5.git push 參數2
如果來源和去向分支的名稱不同呢?
比如你想把本地的 foo 分支推送到遠程倉庫中的 bar 分支。
git push origin <source>:<destination>
這個參數實際的值是個 refspec,“refspec” 是一個自造的詞,
意思是 Git 能識別的位置(比如分支 foo 或者 HEAD~1)
如果你要推送到的目的分支不存在,Git 會在遠程倉庫中根據你提供的名稱幫你創建這個分支!
$ git push origin master^:foo
$ git push origin foo:master
6.git fetch 參數
git fetch 的參數和 git push 極其相似。
他們的概念是相同的,只是方向相反罷了(因為現在你是下載,而非上傳)
git fetch origin foo
為何 Git 會將新提交放到 o/foo 而不是放到我本地的 foo 分支呢?
本例中 Git 做了一些特殊處理,因為你可能在 foo 分支上的工作還未完成,你也不想弄亂它。
fetch它不會更新你的本地的非遠程分支, 只是下載提交記錄
(這樣, 你就可以對遠程分支進行檢查或者合並了)。
如果你覺得直接更新本地分支很爽,那你就用冒號分隔的 refspec 吧。
不過,你不能在當前檢出的分支上干這個事,但是其它分支是可以的。
注意:
source 現在指的是遠程倉庫中的位置,而 <destination> 才是要放置提交的本地倉庫的位置。
它與 git push 剛好相反,這是可以講的通的,因為我們在往相反的方向傳送數據。
理論上雖然行的通,但開發人員很少這么做。
如果參數分支不存在,就會新建。
如果 git fetch 沒有參數,它會下載所有的提交記錄到各個遠程分支……
答案:
$ git fetch origin master~1:foo
$ git fetch origin foo:master
$ git checkout foo
$ git merge master
7.沒有source的source
如果 push 空 <source> 到遠程倉庫會如何呢?它會刪除遠程倉庫中的分支!
git push origin :foo
我們通過給 push 傳空值 source,成功刪除了遠程倉庫中的 foo 分支, 這真有意思...
如果 fetch 空 <source> 到本地,會在本地創建一個新分支。
git fetch origin :bar
很神奇吧!但無論怎么說, 這就是 Git!
8.git pull 參數
一樣的。。。
留白
