1. 程式人生 > >Git 沙盒模擬實戰(遠端篇)

Git 沙盒模擬實戰(遠端篇)

# Git 沙盒模擬實戰(遠端篇) **\>---**[基礎篇](https://www.cnblogs.com/l1ng14/p/13656059.html) ## 遠端倉庫 遠端倉庫並不複雜, 在如今的雲端計算盛行的世界很容易把遠端倉庫想象成一個富有魔力的東西, 但實際上它們只是你的倉庫在另個一臺計算機上的拷貝。你可以通過因特網與這臺計算機通訊 —— 也就是增加或是獲取提交記錄 話雖如此, 遠端倉庫卻有一系列強大的特性 - 首先也是最重要的的點, 遠端倉庫是一個強大的備份。本地倉庫也有恢復檔案到指定版本的能力, 但所有的資訊都是儲存在本地的。有了遠端倉庫以後,即使丟失了本地所有資料, 你仍可以通過遠端倉庫拿回你丟失的資料。 - 還有就是, 遠端讓程式碼社交化了! 既然你的專案被託管到別的地方了, 你的朋友可以更容易地為你的專案做貢獻(或者拉取最新的變更) 現在用網站來對遠端倉庫進行視覺化操作變得越發流行了(像 [Github](https://github.com/) ), 但遠端倉庫**永遠**是這些工具的頂樑柱, 因此理解其概念非常的重要! --- 使用`clone`命令將遠端倉庫中的專案克隆到本地。 ~~~shell $ git clone [remoteName] ~~~ > `remoteName` : 遠端倉庫的地址(名稱) ## Git Fetch `Git` 遠端倉庫相當的操作實際可以歸納為兩點:向遠端倉庫傳輸資料以及從遠端倉庫獲取資料。既然我們能與遠端倉庫同步,那麼就可以分享任何能被 `Git` 管理的更新(因此可以分享程式碼、檔案、想法、情書等等)。 本節課我們將學習如何從遠端倉庫獲取資料 —— 命令如其名,它就是 `git fetch`。 你會看到當我們從遠端倉庫獲取資料時, 遠端分支也會更新以反映最新的遠端倉庫。在上一了我們已經提及過這一點了。 ![image-20210119114118304](https://i.loli.net/2021/01/20/FXKDZIz7CxNaOdf.png) > 虛線為遠端倉庫,實線為本地倉庫。 ~~~shell $ git fetch ~~~ ![image-20210119114211342](https://i.loli.net/2021/01/20/d5Z3wazRKToUbiP.png) ### git fetch 做了些什麼 `git fetch` 完成了僅有的但是很重要的兩步: - 從遠端倉庫下載本地倉庫中缺失的提交記錄 - 更新遠端分支指標(如 `o/master`) `git fetch` 實際上將本地倉庫中的遠端分支更新成了遠端倉庫相應分支最新的狀態。 如果你還記得上一節課程中我們說過的,遠端分支反映了遠端倉庫在你**最後一次與它通訊時**的狀態,`git fetch` 就是你與遠端倉庫通訊的方式了!希望我說的夠明白了,你已經瞭解 `git fetch` 與遠端分支之間的關係了吧。 `git fetch` 通常通過網際網路(使用 `http://` 或 `git://` 協議) 與遠端倉庫通訊。 ### git fetch 不會做的事 `git fetch` 並==不會==改變你本地倉庫的狀態。它==不會==更新你的 `master` 分支,也==不會==修改你磁碟上的檔案。 理解這一點很重要,因為許多開發人員誤以為執行了 `git fetch` 以後,他們本地倉庫就與遠端倉庫同步了。它可能已經將進行這一操作所需的所有資料都下載了下來,但是**並沒有**修改你本地的檔案。我們在後面的課程中將會講解能完成該操作的命令。 所以, 你可以將 `git fetch` 的理解為單純的下載操作。 ## Git Pull 既然我們已經知道了如何用 `git fetch` 獲取遠端的資料, 現在我們學習如何將這些變化更新到我們的工作當中。 其實有很多方法的 —— 當遠端分支中有新的提交時,你可以像合併本地分支那樣來合併遠端分支。也就是說就是你可以執行以下命令: - `git cherry-pick o/master` - `git rebase o/master` - `git merge o/master` - 等等 實際上,由於先抓取更新再合併到本地分支這個流程很常用,因此 `Git` 提供了一個專門的命令來完成這兩個操作。它就是我們要講的 `git pull`。 ![image-20210120104624095](https://i.loli.net/2021/01/20/B4PQajhkYVn6ibm.png) ~~~shell $ git pull ~~~ ![image-20210120104640123](https://i.loli.net/2021/01/20/nLN4Wx5FVk7rMBy.png) `git pull` 就是 `git fetch` 和 `git merge ` 的縮寫! ## 模擬團隊合作 這裡有一件棘手的事 —— 為了接下來的課程, 我們需要先教你如何製造遠端倉庫的變更。 這意味著,我們需要“假裝”你的同事、朋友、合作伙伴更新了遠端倉庫,有可能是某個特定的分支,或是幾個提交記錄。 為了做到這點,我們引入一個自造命令 `git fakeTeamwork`!它的名稱已經說明了一切,先看演示.. ![image-20210120104701712](https://i.loli.net/2021/01/20/HzEnYAtVSX67Ifb.png) ~~~shell $ git clone local branch "master" set to track remote branch "o/master" $ git commit -m "c2" $ git commit -m "c3" $ git push $ git checkout c1 $ git commit -m "c4" 警告!現在是分離 HEAD 狀態 $ git checkout master $ git merge c4 ~~~ ![image-20210120104712094](https://i.loli.net/2021/01/20/JQRnruzIt8eDXhN.png) ## Git Push OK,我們已經學過了如何從遠端倉庫獲取更新併合併到本地的分支當中。這非常棒……但是我如何與大家分享**我的**成果呢? 嗯,上傳自己分享內容與下載他人的分享剛好相反,那與 `git pull` 相反的命令是什麼呢?`git push`! `git push` 負責將**你的**變更上傳到指定的遠端倉庫,並在遠端倉庫上合併你的新提交記錄。一旦 `git push` 完成, 你的朋友們就可以從這個遠端倉庫下載你分享的成果了! 你可以將 `git push` 想象成釋出你成果的命令。它有許多應用技巧,稍後我們會了解到,但是咱們還是先從基礎的開始吧…… *注意 —— `git push` 不帶任何引數時的行為與 Git 的一個名為 `push.default` 的配置有關。它的預設值取決於你正使用的 Git 的版本,但是在教程中我們使用的是 `upstream`。 這沒什麼太大的影響,但是在你的專案中進行推送之前,最好檢查一下這個配置。* ![image-20210120104724490](https://i.loli.net/2021/01/20/scPDtZ8E4eipLHm.png) ~~~shell $ git push ~~~ 過去了, 遠端倉庫接收了 `C2`,遠端倉庫中的 `master` 分支也被更新到指向 `C2` 了,我們的遠端分支 (``o/master`) 也同樣被更新了。所有的分支都同步了! ![image-20210120104748698](https://i.loli.net/2021/01/20/QKD3Iz2XULe7EaJ.png) ## 衝突處理 ![image-20210120104759464](https://i.loli.net/2021/01/20/VAnXzKBC8aQp9Yt.png) `git push` 失敗是因為你最新提交的 `C3` 基於遠端分支中的 `C1`。而遠端倉庫中該分支已經更新到 `C2` 了,所以 `Git` 拒絕了你的推送請求。 ~~~shell $ git fakeTeamwork $ git fetch $ git commit -m "c3" $ git rebase c2 $ git push ~~~ ![image-20210120104831530](https://i.loli.net/2021/01/20/NasGcJAHe9MUBbi.png) 用 `git fetch` 更新了本地倉庫中的遠端分支,然後用 `rebase` 將我們的工作移動到最新的提交記錄下,最後再用 `git push` 推送到遠端倉庫。 ## origin 和它的周邊 ## 合併特性分支 既然你應該很熟悉 `fetch`、``pull`、`push` 了,現在我們要通過一個新的工作流來測試你的這些技能。 在大型專案中開發人員通常會在(從 `master` 上分出來的)特性分支上工作,工作完成後只做一次整合。這跟前面課程的描述很相像(把 `side` 分支推送到遠端倉庫),不過本節我們會深入一些. 但是有些開發人員只在 `master` 上做 `push`、`pull` —— 這樣的話 `master` 總是最新的,始終與遠端分支 (`o/master`) 保持一致。 對於接下來這個工作流,我們集成了兩個步驟: - 將特性分支整合到 `master` 上 - 推送並更新遠端分支 ![image-20210120104841787](https://i.loli.net/2021/01/20/eFQT5oWJZhOqcSa.png) ~~~shell $ git pull -rebase $ git push ~~~ 執行了兩個命令: - 將我們的工作 `rebase` 到遠端分支的最新提交記錄 - 向遠端倉庫推送我們的工作 ![image-20210120104850824](https://i.loli.net/2021/01/20/tJHwSzPRxejZYAX.png) `fetch`遠端倉庫的更新到本地倉庫,進行`rebase`合併,最後`push`到遠端倉庫中。 ![image-20210120104910537](https://i.loli.net/2021/01/20/N2mbDa7ElhUHJYe.png) ~~~shell $ git fetch $ git rebase o/master side1 $ git rebase side1 side2 $ git rebase side2 side3 $ git rebase side3 master $ git push ~~~ ![image-20210120104953469](https://i.loli.net/2021/01/20/tahC4wk1DBVQZSY.png) ## 為什麼不用 merge 呢? 為了 `push` 新變更到遠端倉庫,你要做的就是**包含**遠端倉庫中最新變更。意思就是隻要你的本地分支包含了遠端分支(如 `o/master`)中的最新變更就可以了,至於具體是用 `rebase` 還是 `merge`,並沒有限制。 那麼既然沒有規定限制,為何前面幾節都在著重於 `rebase` 呢?為什麼在操作遠端分支時不喜歡用 `merge` 呢? 在開發社群裡,有許多關於 `merge` 與 `rebase` 的討論。以下是關於 `rebase` 的優缺點: 優點: - `Rebase` 使你的提交樹變得很乾淨, 所有的提交都在一條線上 缺點: - `Rebase` 修改了提交樹的歷史 比如, 提交 `C1` 可以被 `rebase` 到 `C3` 之後。這看起來 `C1` 中的工作是在 `C3` 之後進行的,但實際上是在 `C3` 之前。 一些開發人員喜歡保留提交歷史,因此更偏愛 `merge`。而其他人(比如我自己)可能更喜歡乾淨的提交樹,於是偏愛 `rebase`。仁者見仁,智者見智。 ![image-20210120105007891](https://i.loli.net/2021/01/20/fhoHD72Y5WvIzXt.png) ~~~shell $ git fetch $ git checkout side1 $ git merge o/master $ git merge side2 $ git merge side3 $ git checkout master $ git merge side1 $ git push ~~~ ![image-20210120105017867](https://i.loli.net/2021/01/20/OkU7RSAh2EjKVnX.png) ### 遠端跟蹤分支 在前幾節課程中有件事兒挺神奇的,`Git` 好像知道 `master` 與 `o/master` 是相關的。當然這些分支的名字是相似的,可能會讓你覺得是依此將遠端分支 `master` 和本地的 `master `分支進行了關聯。這種關聯在以下兩種情況下可以清楚地得到展示: - `pull` 操作時, 提交記錄會被先下載到 `o/master` 上,之後再合併到本地的 `master` 分支。隱含的合併目標由這個關聯確定的。 - `push` 操作時, 我們把工作從 `master` 推到遠端倉庫中的 `master` 分支(同時會更新遠端分支 `o/master`) 。這個推送的目的地也是由這種關聯確定的! ## 遠端跟蹤 直接了當地講,`master` 和 `o/master` 的關聯關係就是由分支的“`remote tracking`”屬性決定的。`master` 被設定為跟蹤 `o/master` —— 這意味著為 `master` 分支指定了推送的目的地以及拉取後合併的目標。 你可能想知道 `master` 分支上這個屬性是怎麼被設定的,你並沒有用任何命令指定過這個屬性呀!好吧, 當你克隆倉庫的時候, `Git` 就自動幫你把這個屬性設定好了。 當你克隆時, `Git` 會為遠端倉庫中的每個分支在本地倉庫中建立一個遠端分支(比如 `o/master`)。然後再建立一個跟蹤遠端倉庫中活動分支的本地分支,預設情況下這個本地分支會被命名為 `master`。 克隆完成後,你會得到一個本地分支(如果沒有這個本地分支的話,你的目錄就是“空白”的),但是可以檢視遠端倉庫中所有的分支(如果你好奇心很強的話)。這樣做對於本地倉庫和遠端倉庫來說,都是最佳選擇。 這也解釋了為什麼會在克隆的時候會看到下面的輸出: ```shell local branch "master" set to track remote branch "o/master" ``` ### 我能自己指定這個屬性嗎? 當然可以啦!你可以讓任意分支跟蹤 `o/master`, 然後該分支會像 `master` 分支一樣得到隱含的 `push` 目的地以及 merge 的目標。 這意味著你可以在分支 `totallyNotMaster` 上執行 `git push`,將工作推送到遠端倉庫的 `master` 分支上。 有兩種方法設定這個屬性,第一種就是通過遠端分支檢出一個新的分支,執行: ``` git checkout -b totallyNotMaster o/master ``` 就可以建立一個名為 `totallyNotMaster` 的分支,它跟蹤遠端分支 `o/master`。 --- 在**不**檢出 `master` 分支的情況下將工作推送到的遠端倉庫中的 `master` 分支上。 ![image-20210120105052237](https://i.loli.net/2021/01/20/myAzl6HFWp3VOQx.png) ~~~shell $ git branch side $ git checkout side $ git commit -m "c3" $ git fetch $ git rebase c2 side $ git push ~~~ ## Git Push 的引數 很好! 既然你知道了遠端跟蹤分支,我們可以開始揭開 `git push`、`fetch` 和 `pull` 的神祕面紗了。我們會逐個介紹這幾個命令,它們在理念上是非常相似的。 首先來看 `git push`。在遠端跟蹤課程中,你已經學到了 `Git` 是通過當前檢出分支的屬性來確定遠端倉庫以及要 `push` 的目的地的。這是未指定引數時的行為,我們可以為 `push` 指定引數,語法是: ```shell git push ``` `` 引數是什麼意思呢?我們稍後會深入其中的細節, 先看看例子, 這個命令是: ``` git push origin master ``` 把這個命令翻譯過來就是: *切到本地倉庫中的“master”分支,獲取所有的提交,再到遠端倉庫“origin”中找到“master”分支,將遠端倉庫中沒有的提交記錄都新增上去,搞定之後告訴我。* 我們通過`place`引數來告訴 `Git` 提交記錄來自於 `master`, 要推送到遠端倉庫中的 `master`。它實際就是要同步的兩個倉庫的位置。 需要注意的是,因為我們通過指定引數告訴了 `Git` 所有它需要的資訊, 所以它就忽略了我們所檢出的分支的屬性! ![image-20210120105126285](https://i.loli.net/2021/01/20/RAikJKupfomLwMO.png) ~~~shell $ git push origin master $ git push origin foo ~~~ ![image-20210120105134365](https://i.loli.net/2021/01/20/ftwQFjCvzirR8gD.png) ## place 引數詳解 還記得之前課程說的吧,當為 `git push` 指定 `place` 引數為 `master` 時,我們同時指定了提交記錄的來源和去向。 你可能想問 —— 如果來源和去向分支的名稱不同呢?比如你想把本地的 `foo` 分支推送到遠端倉庫中的 `bar` 分支。 哎,很遺憾 `Git` 做不到…… 開個玩笑,別當真!當然是可以的啦 :) `Git` 擁有超強的靈活性(有點過於靈活了) 接下來咱們看看是怎麼做的…… 要同時為源和目的地指定 `` 的話,只需要用冒號 `:` 將二者連起來就可以了: ```shell git push origin : ``` 這個引數實際的值是個 `refspec`,`refspec` 是一個自造的詞,意思是 `Git` 能識別的位置(比如分支 `foo` 或者 `HEAD~1`) 一旦你指定了獨立的來源和目的地,就可以組織出言簡意賅的遠端操作命令了,讓我們看看演示! ![image-20210120105142621](https://i.loli.net/2021/01/20/2qcZ8dXuT6Ixfko.png) ~~~shell $ git push origin foo^:master ~~~ ![image-20210120105152060](https://i.loli.net/2021/01/20/U2zfTa5ktdp7Bov.png) 如果你要推送到的目的分支不存在會怎麼樣呢?沒問題!`Git `會在遠端倉庫中根據你提供的名稱幫你建立這個分支! ![image-20210120105201822](https://i.loli.net/2021/01/20/3cPNlSrCj8gt1Ko.png) ~~~shell $ git push origin master^:foo $ git push origin foo:master ~~~ ![image-20210120105210579](https://i.loli.net/2021/01/20/EGmQY5B2riy8b1D.png) ## Git fetch 的引數 我們剛學習了 `git push` 的引數,很酷的 `` 引數,還有用冒號分隔的 `refspecs`(`:`)。 這些引數可以用於 `git fetch` 嗎? 你猜中了!`git fetch` 的引數和 `git push` 極其相似。他們的概念是相同的,只是方向相反罷了(因為現在你是下載,而非上傳) 讓我們逐個討論下這些概念…… ### place 引數 如果你像如下命令這樣為 `git fetch` 設定 `place` 的話: ```shell $ git fetch origin foo ``` `Git` 會到遠端倉庫的 `foo` 分支上,然後獲取所有本地不存在的提交,放到本地的 `o/foo` 上。 來看個例子(還是前面的例子,只是命令不同了) 我們只下載了遠端倉庫中 `foo` 分支中的最新提交記錄,並更新了 `o/foo`。 你可能會好奇 —— 為何 Git 會將新提交放到 `o/foo` 而不是放到我本地的 `foo` 分支呢?之前不是說這樣的 `place` 引數就是同時應用於本地和遠端的位置嗎? 好吧, 本例中 `Git` 做了一些特殊處理,因為你可能在 `foo` 分支上的工作還未完成,你也不想弄亂它。還記得在 `git fetch` 課程裡我們講到的嗎 —— 它不會更新你的本地的非遠端分支, 只是下載提交記錄(這樣, 你就可以對遠端分支進行檢查或者合併了)。 “如果我們指定 `:` 會發生什麼呢?” 如果你覺得直接更新本地分支很爽,那你就用冒號分隔的 `refspec` 吧。不過,你不能在當前檢出的分支上幹這個事,但是其它分支是可以的。 這裡有一點是需要注意的 —— `source` 現在指的是遠端倉庫中的位置,而 `` 才是要放置提交的本地倉庫的位置。它與 git push 剛好相反,這是可以講的通的,因為我們在往相反的方向傳送資料。 理論上雖然行的通,但開發人員很少這麼做。我在這裡介紹它主要是為了從概念上說明 `fetch` 和 `push` 的相似性,只是方向相反罷了。 --- 使用 `fetch` 時, 你必須指定`source `和 `destination`。 注意一下目標視窗, 因為提交物件的 `ID` 可能會變哦! ![image-20210120105218686](https://i.loli.net/2021/01/20/zGOKkgB4mDEsqR5.png) ~~~shell $ git fetch origin master^1:foo $ git fetch origin foo:master $ git checkout foo $ git merge master ~~~ ![image-20210120105227744](https://i.loli.net/2021/01/20/VGwX79AHumcxDaN.png) ### 會讓人捱揍的 source `Git` 有兩種關於 `source` 的用法是比較詭異的,即你可以在 `git push` 或 `git fetch` 時不指定任何 `source`,方法就是僅保留冒號和 `destination` 部分,`source` 部分留空。 - `git push origin :side` 如果 `push` 空 `source` 到遠端倉庫會如何呢?它會刪除遠端倉庫中的分支!==慎重使用,刪除別人的遠端分支可能會捱揍。== - `git fetch origin :bugFix` 如果 `fetch` 空 `source` 到本地,會在本地建立一個新分支。 ## Git pull 引數 既然你已經掌握關於 `git fetch` 和 `git push` 引數的方方面面了,關於 `git pull` 幾乎沒有什麼可以講的了 :) 因為 `git pull` 到頭來就是 `fetch` 後跟 `merge` 的縮寫。你可以理解為用同樣的引數執行 `git fetch`,然後再 `merge`你所抓取到的提交記錄。 還可以和其它更復雜的引數一起使用, 來看一些例子: 以下命令在 `Git` 中是等效的: `git pull origin foo` 相當於: ```shell $ git fetch origin foo $ git merge o/foo ``` 還有... `git pull origin bar~1:bugFix` 相當於: ```shell $ git fetch origin bar~1:bugFix $ git merge bugFix ``` 看到了? `git pull` 實際上就是 `fetch` + `merge` 的縮寫, `git pull` 唯一關注的是提交最終合併到哪裡(也就是為 `git fetch` 所提供的 `destination` 引數)。 通過指定 `master` 我們更新了 `o/master`。然後將 `o/master` `merge` 到我們的檢出位置,**無論**我們當前檢出的位置(`checkout`的位置、`HEAD`所在位置)是哪。 --- 需要下載一些提交,然後建立一些新分支,再合併這些分支到其它分支, 但這用不了幾個命令。 ![image-20210120105251136](https://i.loli.net/2021/01/20/Kf93FIO4HyqvBem.png) ~~~shell $ git pull origin bar:foo $ git pull origin master:side ~~~ ![image-20210119160554842](https://i.loli.net/2021/01/19/HFVOcEz1u4SeMlt.png) 內容來源:[碼雲 --- Learn Git Branching(遠端篇)](https://oschina.gitee.io/learn-git-bra