前言:
教程來源于Udacity的免費教程-用Git進行版本控制,這里僅是個人的筆記,參考使用。
git tag:給特定提交(commit)添加額外的標簽,額外的提示信息。
git branch:用于創建分支,以此來并行開發項目的不同功能 。
git checkout:在不同的tags和branches之間進行切換。
git merge:合并分支。將不同分支上的修改自動合并。
目前所處的位置
你可以在任一項目中執行這些步驟,我將在 new-git-project 項目中執行。
我們來看看該項目到目前為止的 git log 輸出結果:
以上是已經提交的信息。
git tag 命令
注意所顯示的結果(只需注意 SHA 和 commit 消息)
我們將使用 git tag 命令與倉庫的標簽進行交互:
$ git tag -a v1.0
上述命令將打開代碼編輯器,并等待你為標簽輸入信息。輸入"Ready for content"怎么樣?
注意:在上述命令 (git tag -a v1.0) 中,使用了 -a 選項。該選項告訴 git 創建一個帶注釋的標簽。如果你沒有提供該選項(即 git tag v1.0),那么它將創建一個輕量級標簽。
建議使用帶注釋的標簽,因為它們包含了大量的額外信息,例如:
標簽創建者
標簽創建日期
標簽消息
因此,你應該始終使用帶注釋的標簽。
默認是給最近一次commit添加標簽。
驗證標簽
保存并退出編輯器后,命令行上什么也不會顯示。那么如何知道已經向項目中添加了標簽呢?只需輸入 git tag,命令行會顯示倉庫中的所有標簽。
我們已經驗證了該標簽位于倉庫中,但是我們想知道它位于倉庫的哪個位置。為此,我們需要調用一直在使用的 git log!
git log 的 --decorate 選項
正如你所了解的,git log 是一個非常強大的工具,可以讓我們查看倉庫的 commit。我們已經學習了幾個選項,現在該學習一個新的選項了。--decorate 選項將顯示默認視圖隱藏起來的一些詳情。
馬上去試試運行 git log --decorate 吧!
?? Git 2.13 中 --decorate 選項的變化 ??
在 2.13 版 git 中,log 命令已改為自動啟用 --decorate 選項。這意味著,你不需要在命令中包含 --decorate 選項,因為它已經自動包含了!因此下面的命令輸出結果完全一樣:
git log --decorate git log
標簽信息位于第一行的末尾
HEAD -> master?
你是否注意到,日志中除了顯示標簽信息之外,--decorate 還顯示了 HEAD -> master?這是關于分支的信息!接下來我們將了解 git 中的分支。
刪除標簽
如果將標簽消息中的某個字打錯了,或標簽名稱打錯了(輸入 v0.1,而不是 v1.0),如何修正這個錯誤?最簡單的方法是刪除這個標簽并重新創建。
可以通過輸入 -d 選項 (表示 delete 刪除!)加上標簽名稱來刪除 git 標簽:
$ git tag -d v1.0
向以前的 commit 添加標簽
運行 git tag -a v1.0 將為最近的 commit 添加標簽。但是如果你想向倉庫中很久之前的 Commit 添加標簽呢?
只需提供要添加標簽的 commit 的 SHA 即可!
$ git tag -a v1.0 a87984
向以前的 commit 添加標簽
運行 git tag -a v1.0 將為最近的 commit 添加標簽。但是如果你想向倉庫中很久之前的 Commit 添加標簽呢?
只需提供要添加標簽的 commit 的 SHA 即可!
$ git tag -a v1.0 a87984
(在彈出代碼編輯器以便讓你提供標簽消息之后)此命令將向 SHA 為 a87084 的 commit 添加標簽 v1.0。借助這一技巧,你可以為整個 git 倉庫中的任何 commit 添加標簽!很強大吧?并且只需在你已經知道的 git 標簽命令中加上 commit 的 SHA 即可。
git tag 小結
總結下,git tag 命令用來標記特定的 commit 。當添加新的 commit 時,標簽不會移動。
$ git tag -a beta
此命令將:
向最近的 commit 添加標簽
如果提供了 SHA,則向具體的 commit 添加標簽
分支
Git有個默認的分支叫master,我們提交commit一次,master就會移動到最近的commit上,像游標一樣,而上面提到過的
tag則相當于一個里程碑,是永久不變的,默認分支是master,我們可以通過命令進行切換:
git checkout xxxxx
切換到不同的分支,并進行commit提交后,只會在當前的分支下能夠顯示,比如我在分支branch1下提交了A,那么我切換回
到master分支,我是看不到A的commit的。
我們可以在指定的commit上創建分支,并針對該分支做其它的功能開發。
git branch 命令
git branch 命令用來與 git 的分支進行交互:
$ git branch
它可以用來:
列出倉庫中的所有分支名稱
創建新的分支
刪除分支
如果我們只輸入 git branch,則 git 將列出倉庫中的分支:
創建分支
要創建分支,只需使用 git branch 并提供要創建的分支對應的名稱。因此,如果你想創建一個叫做"sidebar"的分支,只需運行以下命令:
$ git branch sidebar
git checkout 命令
注意,在進行 commit 時,該 commit 將添加到當前分支上。雖然我們創建了新的 sidebar 分支,但是沒有向其添加新的 commit,因為我們尚未切換到該分支。如果我們現在進行 commit 的話,該 commit 將添加到 master 分支,而不是 sidebar 分支。我們已經在演示中看到這一情況,要在分支之間進行切換,我們需要使用 git 的 checkout 命令。
$ git checkout sidebar
請務必了解該命令的工作方式。運行該命令將:
從工作目錄中刪除 git 跟蹤的所有文件和目錄
(git 跟蹤的文件存儲在倉庫中,因此什么也不會丟失)
轉到倉庫,并提取分支指向的 commit 所對應的所有文件和目錄
因此此命令將刪除 master 分支中的 commit 引用的所有文件。它會將這些文件替換為 sidebar 分支中的 commit 引用的文件。理解這一部分十分重要,所以請務必多讀幾遍工作方式。
在上述輸出中,注意我們之前見到的特殊指示符"HEAD"具有一個指向 sidebar 分支的箭頭。它指向 sidebar 是因為 sidebar 分支是當前分支,現在提交的任何 commit 將添加到 sidebar 分支
活躍分支
提示符將顯示活躍分支。但這是我們對提示符進行的特殊自定義,如果你使用的是不同的計算機,判斷活躍分支的最快速方式是查看 git branch 命令的輸出結果。活躍分支名稱旁邊會顯示一個星號。
刪除分支
分支用來進行開發或對項目進行修正,不會影響到項目(因為更改是在分支上進行的)。在分支上做出更改后,你可以將該分支組合到 master 分支上(這種“分支組合過程”叫做“合并”(merge),稍后將詳細講解)
合并了分支的更改后,你可能不再需要該分支了。如果你想刪除分支,可以使用 -d 選項。下面的命令包含 -d 選項,告訴 git 刪掉給出的分支(這里是"sidebar"分支)。
$ git branch -d sidebar
注意,無法刪除當前所在的分支。因此要刪除 sidebar 分支,你需要切換到 master 分支,或者創建并切換到新的分支。
刪除內容讓人比較緊張。但是不用擔心。如果某個分支上有任何其他分支上都沒有包含的 commit(也就是這個 commit 是要被刪除的分支獨有的),git 不會刪除該分支。如果你創建了 sidebar 分支,向其添加了 commit,然后嘗試使用 git branch -d sidebar 刪除該分支,git 不會讓你刪除該分支,因為你無法刪除當前所在的分支。如果你切換到 master 分支并嘗試刪除 sidebar 分支,git 也不會讓你刪除,因為 sidebar 分支上的新 commit 會丟失!要強制刪除,你需要使用大寫的 D 選項 - git branch -D sidebar。
git branch 小結
總結下,git branch 命令用來管理 git 中的分支:
列出所有分支
$ git branch
創建新的"footer-fix"分支
$ git branch footer-fix
刪除"footer-fix"分支
$ git branch -d footer-fix
此命令用來:
列出本地分支
創建新的分支
刪除分支
高效分支
你已經學會了如何創建、列出和刪除分支,我們來運用下所學的知識吧!
首先,確保我們保持相同的進度,并擁有相同的起始代碼。我們將在 new-git-project 項目中進行操作。該項目具有以下文件:
index.html
css/app.css(空文件)
js/app.js(空文件)
CSS 和 JavaScript 文件是空的。確保 index 文件具有以下內容:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Blog Project</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="">
<link rel="stylesheet" href="css/app.css">
</head>
<body>
<header>
<h1>Expedition</h1>
</header>
<div class="container">
<main>
</main>
</div>
<footer>
Made with ? @ Udacity
</footer>
<script src="js/app.js"></script>
</body>
</html>
(這里我建議是重新建立一個repo,然后跟著操作一步一步來,之前的repo我做了太多的操作,比較亂)
完成圖中所必要的每一步操作。
策略
現在,所有代碼都位于 master 分支(默認分支)上。我們通過以下操作利用分支進行工作:
向分支中添加內容
創建新的分支
在分支之間切換
讓我們使用分支完成以下更改:
在 master 分支上 - 向頁面添加默認顏色
創建一個 sidebar 分支 - 為頁面創建側欄
在 master 分支上 - 更改頁面的標題
在 sidebar 分支上 - 向側欄中添加更多內容
創建一個 footer 分支 - 向腳注中添加社交鏈接
更改 1 - 添加頁面顏色
確保位于 master 分支上,并向 css/app.css 添加以下內容:
body {
background-color: #00cae4;
}
保存文件,然后將該文件添加到暫存區,并將其 commit 到倉庫。
更改 2 - 添加側欄
我們向頁面上添加一個側欄。但是假定我們不確定是否喜歡新的背景色。因此我們要將 sidebar 分支放在設置頁面顏色的 commit 之前。你的 SHA 可能會不一樣,但對我來說,在添加顏色的 commit 之前的 commit 具有 SHA 759e74e(已經改為我的分支SHA,即最新的一次commit,我們創建分支,然后在分去上添加側欄的代碼!!!)。因此向該 commit 添加分支的命令是:
$ git branch sidebar 759e74e
現在使用 git checkout 命令切換到新的 sidebar 分支。運行 git log --oneline --decorate 顯示以下結果:
通過向 HTML 文件添加以下 <aside> 代碼添加一個側欄:
<div class="container">
<main>
</main>
</div>
<!-- start of new content -->
<aside>
<h2>About Me</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eos, debitis earum molestias veniam suscipit aliquam totam exercitationem tempore neque vitae. Minima, corporis pariatur facere at quo porro beatae similique! Odit.</p>
</aside>
<!-- end of new content -->
<footer>
Made with ? @ Udacity
</footer>
我將 <aside> 內容添加到 <main> 元素旁邊,作為 <div class="container"> 元素的子級。
(不要直接copy上面的內容復制到index.html中,復制中間的內容到
指定的位置)
你可以在 <aside> 元素中添加任何內容!
更改 3 - 更改 master 上的標題
切換到 master 分支并更新頁面標題。
使用 git checkout 命令切換到 master 分支。(注意,新的側欄的 HTML 不在了!因為所有代碼都妥善地保存在 sidebar 分支上。)
*****!!!切換到master以后,查看index.html后發現中間的內容不見了,因為切換分支后,commit也會切換到當前分支下的,這里我做了好幾次操作,我一直以為是我沒有保存commit成功或是notepad++的鍋......
現在將頁面的 <h1> 標題從"Expedition"改為其他內容。要不改為吸引人的"Adventure"?!
更改 4 - 向側欄中添加更多內容
切換到 sidebar 分支(注意,我們添加到 master 分支的內容在 sidebar 分支上不可見)。
現在,在 <aside> 元素中添加一些內容。添加一些個人信息,例如你喜歡的電影或書籍(我最喜歡的是《指環王》!)。任何內容都可以,只需添加一些內容。
同樣確保不要更改 CSS 文件。
保存 index.html 文件并提交 commit。
更改 5 - 向腳注中添加社交鏈接
我們已經做出了好幾個更改,下面要進行最后一項更改了。我們向頁面腳注中添加一些社交圖標。為了加以區分,我們在基于 master 分支的新 footer 分支上做出這一更改,因此需要先創建一個新的 footer 分支。
但是你知道嗎?git checkout 命令也可以創建一個新的分支。如果你添加 -b 選項,則能夠用一個命令創建分支并切換到該分支。
我們使用新的 git checkout 命令來創建新的 footer 分支并讓此 footer 分支的起點位置與 master 分支的一樣:
$ git checkout -b footer master
(即基于master來創建一個分支,并切換到新的分支)
添加社交鏈接
現在我們已經位于新分支上,我們向頁面腳注中添加一些社交鏈接。我添加了以下內容:
<footer>
<!-- start of new content -->
<section>
<h3 class="visuallyhidden">Social Links</h3>
<a class="social-link" >
<img src="img/social-twitter.png" alt="Twitter">
</a>
<a class="social-link" >
<img src="img/social-instagram.png" alt="Instagram">
</a>
<a class="social-link" >
<img src="img/social-google.png" alt="Google Plus">
</a>
</section>
<!-- end of new content -->
</footer>
你也可以添加你自己的社交帳戶鏈接。
即打開index.html,替換到<footer>..</footer>中間的內容。這時候master,sidebar,footer三個分支均產生了沖突!
同時查看所有分支
我們已經做出了所有需要做出的更改!很棒!
我們已經在三個不同的分支上進行了多項更改。我們在 git log 輸出結果中看不到其他分支,觸發切換到某個分支。如果能在 git log 輸出結果中看到所有分支,是不是很棒?
你到現在為止已經知道,git log 命令非常強大,可以顯示此信息。我們將使用新的 --graph 和 --all 選項:
$ git log --oneline --decorate --graph --all
--graph 選項將條目和行添加到輸出的最左側。顯示了實際的分支。--all 選項會顯示倉庫中的所有分支。
運行此命令將顯示倉庫中的所有分支和 commit:
HEAD當前指向的是活躍分支,目前在footer分支上。
更改小結
我們做出了以下更改:
我們在 master 分支上向頁面添加了默認顏色
我們創建了 sidebar 分支并為側欄添加了代碼
我們在 master 分支上更改了頁面的標題
我們在 sidebar 分支上向側欄添加了更多內容
我們創建了 footer 分支并向腳注中添加了社交鏈接
這些更改都發生在不同的分支上。讓我們用 git 合并所有這些更改吧。將分支組合到一起稱為合并(merge)。
合并
注意,主題分支(例如 sidebar)的作用是讓你做出不影響 master 分支的更改。當你在主題分支上做出更改后,如果覺得不想要該分支上的更改,則可以刪掉該分支,或者你決定要保留更改,則可以將該分支上的更改與其他分支上的更改合并。
將分支組合到一起稱為合并。
注意 git 中的兩種合并:普通合并和快進合并。
快速合并即是MASTER和SOCIAL_LINKS有相同的commit提交,但SOCIAL_LINKS比MASTER分支靠前,SOCIAL_LINKS中有MASTER中未包含的commit提交,如果我們想將這些提交并納入到MASTER分支中,就需要將SOCIAL_LINKS合并到MASTER中,因為HEAD指向提是MASTER,所以合并后,MASTER分支將會向前移動到SOCIAL_LINKS分支的位置。
普通合并即兩條不同的分支進行合并。
?? 了解分支 ??
當你要合并分支時,務必知道當前位于哪個分支上。注意,合并分支會提交 commit。
現在我們不知道如何撤消更改。下節課將介紹這一技巧,但是如果你在錯誤的分支上進行了合并,可以使用以下命令撤消合并:
git reset --hard HEAD^
(確保包含 ^ 字符!它屬于“相對 commit 引用”并表示“父 級 commit”。我們將在下節課學習相對 commit 引用。)
合并指令
git merge 指令用來合并 git 分支:
$ git merge <name-of-branch-to-merge-in>
發生合并時,git 將:
查看將合并的分支
查看分支的歷史記錄并尋找兩個分支的 commit 歷史記錄中都有的單個 commit
將單個分支上更改的代碼行合并到一起
提交一個 commit 來記錄合并操作
當我們合并時,我們將其他分支合并到當前(檢出的)分支上。我們不是將兩個分支合并到一個新的分支上。也不是將當前分支合并到其他分支上。
如我當前的分支在master上,則合并即是合并到master上,合并到當前checkout的分支上。
因為 footer 直接在 master 前面,因此這種合并最簡單。將 footer 合并到 master 中將導致快進合并(Fast-forward merge)。快進合并將使當前檢出的分支向前移動,直到它指向與另一個分支(這里是 footer)指向的 commit 一樣為止。
要合并 footer 分支,運行:
$ git merge footer
進行普通合并
終于完成快進合并流程了!也沒那么難,對吧?
但是你可能會說“當然簡單了,所有 commit 都已經在那,分支指針只是向前移動了!”…說的對,這是最簡單的合并。
現在我們將進行更常見的合并,其中兩個分支完全不一樣。你會驚訝地發現,實際合并 sidebar 這樣的獨特分支,操作是完全一樣的!
要合并 sidebar 分支,確保你位于 master 分支上,并運行:
$ git merge sidebar
因為合并的是兩個完全不一樣的分支,因此將提交 commit。在進行 commit 時,需要提供 commit 消息。因為這是合并 commit,因此已經提供了默認消息。你也可以更改消息,但通常都會直接使用默認的合并 commit 消息。因此當你的代碼編輯器打開并包含該消息時,直接關閉編輯器以確認使用該 commit 消息。
在使用默認 commit 消息后,我的終端如下所示:
(注意:vim的退出要按ESC,然后輸入:wq)
如果合并失敗了呢?
我們剛剛執行的合并能夠成功合并。git 能夠靈活地合并不同分支上的大量工作。但是,有時候也無法合并分支。如果在進行合并時失敗了,則稱為合并沖突(merge conflict)。我們將在下節課了解合并沖突及其背后的原因,并學習如何解決沖突。
合并小結
總結下,git merge 命令用來在 git 中合并分支:
$ git merge <other-branch>
合并有以下兩種類型:
快進合并 – 要合并的分支必須位于檢出分支前面。檢出分支的指針將向前移動,指向另一分支所指向的同一 commit。
普通類型的合并
兩個完全不同的分支被合并
創建一個合并 commit
合并沖突
有時候合并會失敗
大部分情況下,git 將能夠成功地合并分支。但是,有時候 git 無法完全自動地進行合并。合并失敗時,就稱為合并沖突。
如果出現合并沖突,git 將嘗試盡可能合并多的內容,然后將留下特殊選項(例如 >>> 和 <<<),告訴你(沒錯,告訴作為程序員的你!)需要從何處手動修復。
什么導致了合并沖突
正如你所知道的,git 會跟蹤文件中的代碼行。如果完全相同的行在不同的文件中更改了,將產生合并沖突。例如,如果你在 alternate-sidebar-style 分支上并將側欄的標題改為"Information About Me",git 應該選擇哪個標題?你在兩個分支上都更改了標題,因此 git 根本不知道你要保留哪個標題。它肯定不會隨機選擇一個標題!
我們來人為制造一個合并沖突,學習如何解決這種沖突。當你學會了后,就非常簡單!當 git 不確定你要使用即將合并的分支中的哪些行時,就會出現合并沖突。因此我們需要在兩個不同的分支上修改同一行,然后重設合并它們。
人為制造合并沖突!
當同一行在兩個分支中都更改了時,就會出現合并沖突。我們在兩個不同的分支上更改同一頁面的標題:
更改 master 分支上的標題
在最近修改 master 分支的 commit 前面創建一個 heading-update 分支
更改同一標題
切換到 master 分支
合并 heading-update 分支
在分支上更改標題 1
因為 master 分支就像所有其他分支一樣,也是個普通分支,我們在 master 分支上更改標題。將 <h1> 標題改為其他內容。對我來說,標題當前在第 13 行是"Adventure",我將其改為"Quest"。
做出更改后,保存文件并 commit 到倉庫。
在分支上更改標題 2
現在我們需要創建不同的分支并在該分支上更新標題。
現在要注意的是,我們需要創建一個不是從 master 分支上分叉的分支。如果我們在從 master 分支上分叉的分支上做出更改,那么該更改將在此更改前面,git 將直接使用該更改,而不是使用我們剛剛在 master 上做出的更改。因此我們需要將該分支“放在過去”。
(如果我在master上創建了一個分支,那么新的分支是在master前面的,這時候我commit,也都是在master之前,合并的時候,快進合并,將直接使用新的分支commit,所以我們需要在最近的commit之前的某個commit上,創建一個分支,并修改標題,這時候合并分支到master,便會出現合并沖突的現象!
我們創建一個位于最近 commit 之前的 commit 上的分支。使用 git log 獲取上一個 commit 的 SHA,并在該 commit 上創建一個分支。在創建 heading-update 分支后,我的 git log 輸出結果如下所示:
現在已經創建好分支heading_update,我們只需再次在該分支上更新標題。確保更改在 master 分支上做出更改的同一行。我將第 13 行的"Adventure"改為"Crusade"。
然后保存文件,并將文件 commit 到倉庫
下面我們要進行合并分支,將分支heading_update合并到master,所以我們必須首先要切換到master分支下
git checkout master
確保你位于 master 分支(我們也可以在另一分支上操作,但是我習慣將 master 分支作為主分支,讓另一個分支合并到該分支上)上,并合并 heading-update 分支:
$ git merge heading-update
你應該看到以下結果:
合并沖突輸出結果解釋
終端中顯示的輸出結果為:
$ git merge heading-update
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
注意在 git merge heading-update 命令之后,git 嘗試合并在兩個分支上都更改了的文件 (index.html),但是出現沖突。此外,它告訴你發生了什么:"Automatic merge failed; fix conflicts and then commit the result"。
還記得 git status 命令嗎?在處理合并沖突時,該命令將非常有用。
git status 的輸出結果告訴我們 index.html 中存在合并沖突。因此在代碼編輯器中查看該文件!
合并沖突指示符解釋
編輯器具有以下合并沖突指示符:
1.<<<<<<< HEAD 此行下方的所有內容(直到下個指示符)顯示了當前分支上的行
2.||||||| merged common ancestors 此行下方的所有內容(直到下個指示符)顯示了原始行的內容
3.======= 表示原始行內容的結束位置,之后的所有行(直到下個指示符)是被合并的當前分支上的行的內容
4.>>>>>>> heading-update 是要被合并的分支(此例中是 heading-update 分支)上的行結束指示符
解決合并沖突
git 使用合并沖突指示符來告訴你兩個不同分支上的哪些行導致了合并沖突,以及原始行是什么。要解決合并沖突,你需要:
選擇保留哪些行
刪掉所有帶指示符的行
因為某種原因,我不太喜歡現在的"Crusade"一詞,但是"Quest"也不太合適。要不將標題設為"Adventurous Quest"?
修改為:
刪掉所有包含合并沖突指示符的行并選擇保留哪個標題后,直接保存文件,并將其添加到暫存區,然后 commit!就像普通合并一樣,代碼編輯器會彈出,并讓你提供 commit 消息。和之前一樣,我們經常會使用自動生成的合并 commit 消息,因此在編輯器打開后,直接關閉編輯器并使用自動生成的 commit 消息。
就這些內容!當你明白合并指示符所顯示的內容后,合并沖突就沒那么復雜了。
合并沖突小結
當相同的行在要合并的不同分支上做出了更改時,就會出現合并沖突。git 將在合并途中暫停,并告訴你存在沖突,以及哪些文件存在沖突。要解決文件中的沖突:
找到并刪掉存在合并沖突指示符的所有行
決定保留哪些行
保存文件
暫存文件
提交 commit
注意一個文件可能在多個部分存在合并沖突,因此檢查整個文件中的合并沖突指示符,搜索 <<< 能夠幫助你找到所有這些指示符。