Git原理與高級使用(3)

遠程版本庫

相信你們對遠程版本庫都有所瞭解,而且也都有在使用相似github,gitlab或者bitbucket之類的服務,那咱們這裏就主要來講一下本地版本庫與遠程版本庫交互時的一些注意點,一般在咱們建立好了遠程倉庫而且打算將本地同步上去時會先執行git remote add origin https://github.com/test/test.git,這裏其實就能夠當作咱們給遠程版本庫取了一個別名origin,其實origin這個名字是能夠隨便定的,固然服從規範的話咱們一般不會去用其餘的名字。接着咱們就會執行git push -u origin master來將咱們本地版本庫推送到遠程,這裏注意咱們加-u實際上是創建了一個origin和master之間的關聯,以後咱們就只要簡單執行git push,git就會自動去選擇將master分支推送給遠程,一樣git pull也是。這時候咱們能夠經過執行git remote show來查看現有的遠程地址,git

因此其實本地的一個版本庫是能夠同時有多個遠程地址的,例如origin2,origin3。若是想要只查看某個遠程地址的信息和通本地的關聯可使用git remote show origingithub

這裏還有一點要注意的是,有時候在提交時會出現這樣一段警告服務器

這裏就涉及了git push的一個默認行爲模式,在git 2.0以前默認是使用matching的,也就是它會將如今本地關聯的這條分支推送到遠程存在而且同名的分支,例如如今咱們將master與origin關聯,那麼當咱們使用matching模式去提交時,就會去找遠程是否有一個也叫作master的分支,若是有而後就將咱們本地分支推送到遠程這個master分支,而在git 2.0以後就採起了一個相對保守的策略,也就是simple模式,它在push時用的遠程分支會是咱們執行git pull時的那個遠程分支,例如說我git pull是默認是一個叫作dev的分支,那麼git push時就會推送去這個dev分支,爲何說simple是更保守呢,就是由於matching實際上是一種git大膽的猜想咱們默認推送和拉取用的是同名的遠程分支和本地分支。咱們能夠像圖中所說使用git config去設置或改變默認行爲模式。app

在咱們將本地版本庫與遠程版本庫作了關聯以後,每次咱們調用git status就會看到這樣一條信息gitlab

這裏背後其實在本地咱們會多了一個叫作origin/master的分支,它被用來追蹤遠程的master分支,每次當咱們執行git pull時,其實背後是跑了兩條指令git fetch與git merge,git fetch會先同步遠程的master分支與本地的origin/master分支,而後再將origin/master分支merge到本地的master分支,而在咱們執行git push時,也是先將本地分支與origin/master分支同步,再將本地master分支推送到遠程分支測試

要查看遠程分支時執行git branch -a便可fetch

分支實踐

分支的實踐我的以爲沒有所謂的最佳,其實都是要根據不一樣項目的狀況來決定,這裏就介紹一個本身日常比較多使用的方式,分爲3個主要分支加一種類型的分支:版本控制

  1. develop分支 (頻繁變化的一個分支,主要用於開發人員每日開發所用)
  2. test分支 (供測試和產品等人員使用的一個分支,變化不是特別頻繁)
  3. master分支 (生產發佈分支,變化很是不頻繁的一個分支,一般會加予權限來管理)
  4. bugfix(hotfix)分支 (這個表明的是一個類型的分支,也就是當生產系統中出現了緊急的bug,用於緊 急修復的分支)

裸庫

git裸庫實際上就是一個沒有工做區的git倉庫,那它的做用是什麼呢,其實就是用來作一個存放於中轉的倉庫,一般咱們會把它放到本身的服務器上,由於咱們不須要在服務器上去作文件的操做,因此其實咱們只須要有.git那個文件夾的內容便可,根據前面幾篇的內容咱們知道其實全部版本信息都在這個.git文件夾下。建立裸庫的話只要咱們執行git init --bare便可code

Submodule

在開發中,有時候咱們可能有多個項目會依賴於一個庫,那若是咱們把項目放在不一樣的版本控制系統,那麼每當咱們的庫有更新時咱們就須要從新把最新的庫拷貝過來從新部署升級到各個項目中,試想若是這個庫一天有屢次的改動,那項目也要跟着去修改屢次就顯得很沒效率,因而就有了git的submodule來專門解決這種問題,git submodule可讓咱們在項目中引用另一個版本庫的項目,使它在咱們當前的項目中可見,而且只要另外那個版本庫一旦有更新,咱們要作的只是使用一條git指令而後就會自動更新最新的代碼了。咱們經過在項目中執行git submodule add git@github.com:desmond/git_child.git mymodule就能夠將git_child這個項目以submodule的形式加入到咱們當前項目的mymodule文件夾下,注意的是這個目錄事先不能夠存在,若是已存在的話git會報錯orm

這時候能夠看到child就成功加入到了當前項目中,而且git還會建立一個.gitmodules的文件,裏面記錄了submodule的信息

當child發生了更新時,咱們只要在mysubmodule中執行git pull就能夠獲取到最新的提交信息了,那假設咱們項目中有多個submodule的話那更新不就很麻煩了嗎,git其實也提供了一個git submodule foreach git pull,當咱們在項目根目錄下執行後,git就會循環的給每一個submodule都執行一次pull操做

固然在pull完以後,咱們還要記得對submodule執行一次git push纔會真正把本地項目中submodule的改動推送到遠程版本庫中

另外要注意的是,若是咱們是第一次從遠程版本庫clone下來時,git是不會幫咱們把submodule中的內容也一塊兒獲取的,這時候咱們就要先執行git submodule init,再執行git submodule update --recursive,此時本地就會獲取到submodule中的內容。不過git clone也提供了一個選項幫咱們簡化上面的操做,咱們只需執行git clone git地址 --recursive就能作到上面的效果了

若是咱們clone完以後進入submodule的文件夾,就會看到如今的分支會變成一個遊離的狀態指向submodule的最新commit,不過本質上它就是咱們submodule上master的最新commit,因此咱們能夠直接執行git checkout master切換回去

Subtree

subtree和submodule其實解決的都是同一類問題,但當咱們在使用submodule時一般是經過更新被依賴的module而後再在使用這個module的項目中去更新,可是若是反過來想作到修改項目中的module也能夠更新module自己的話submodule就會出現些bug,因此咱們就引入了subtree。也就是說subtree能夠用來完成雙向修改。因此在咱們掌握了subtree以後,其實就徹底能夠代替本來的submodule了,git官方也是這麼推薦的。

首先咱們執行git remote add subtree-origin git地址在本地項目中添加module的遠程地址,接着執行git subtree add --predix=subtree subtree-origin master --squash就能夠在本支的subtree文件夾下加入module的master分支內容,這裏squash的意思是在加入module內容時,會將module的全部commit合併成一個commit合併進本地的內容,這樣在本地的提交記錄中咱們就只會看到一條關於module庫的commit。而若是不加squash的話,就能夠看到module的每一條commit都會被合併到本地

下圖能夠看到若是使用squash的狀況下產生的commit,其實就兩條,一條就是把全部的commit合併成一個,另外一條就是將這條commit合併成一個subtree

當咱們建立submodule時,其實那個文件夾表明的是指向module地址的一個引用,而subtree是真正的在項目中放入了module的內容,這是它與submodule不一樣的地方

那麼此時若是module自己更新了,咱們能夠在項目中執行git subtree pull --prefix=subtree subtree-origin master --squash來更新項目

那若是咱們要經過更新項目自身來後將修改apply到module時要怎麼作呢,咱們只須要執行git subtree push --prefix=subtree subtree-origin master便可,可是這裏若是咱們使用squash選項的話實際上是會失敗的,這裏就牽扯到了squash這個選項的問題,當咱們使用squash時其實咱們等因而產生了一個新的commit,即使咱們知道這個commit其實就是把三個commit合併在一塊兒,可是在commit對象鏈中經過該commit是沒法與module裏面的commit聯結上的,這就會致使咱們在git pull和git push時因爲沒有共同的父commit對象而出現conflict的狀況,須要咱們手動去處理,那若是不加squash呢,那這個問題就能夠解決了,由於兩邊其實都是擁有同樣的commit對象鏈,可是不用squash的話,咱們就會在項目自己中看到module的commit,這部分commit其實咱們大多數時候是不關心的,同時若是項目中有除了subtree修改提交外仍是其餘文件的修改,那當咱們同步推送回module自己時,也會把這些不屬於module的commit給同步過去,這樣就會形成兩邊都被污染。因此你們能夠根據實際狀況來選擇使用哪種,可是一個宗旨就是若是一開始就使用squash選項,那麼務必要確保以後全部subtree操做都要使用squash,反之亦然。

指令

  1. 查看全部分支(包括在本地的遠程關聯分支)

    git branch -a,若是想顯示每條分支的最後一條commit,能夠執行git branch -av

  2. clone遠程項目並自定義文件夾名

    git clone 遠程地址 文件夾名

  3. 爲分支建立一個與遠程的關聯

    git push --set-upstream origin 分支名,這時候關聯就會創建而且在本地多了一個'origin/分支名'的branch,其實這條命令與git push -u origin master是達到同樣的效果,不過git新版本推薦是使用--set-upstream的作法

  4. git拉取以後怎麼建立遠程的分支

    一般在咱們拉取了遠程倉庫以後會在本地建立一個關聯分支

    那在咱們獲取到關聯分支後如何建立一個本地的與之對應的分支呢,咱們能夠執行git checkout -b test origin/test來建立,這樣就會多出一個本地的test分支而且commit對象鏈與origin/test同樣,另外,咱們也可使用git checkout --track origin/test來建立一個本地分支,它與前者的區別就在於它會自動用origin/後面的這個遠程名字來做爲本地建立的分支名

  5. 如何刪除遠程的一個分支

    第一種方法咱們能夠經過執行git push origin :test,這裏其實表明的意思是咱們將一個空分支推送到遠程的test分支,就變相作了一個刪除的動做,第二種方法比較直接,就是執行git push origin --delete test

  6. 建立一個與本地分支名不一樣的遠程分支

    git push --set-upstream origin develop:develop2這條指令就表明在遠程建立一個develop2分支而且與本地develop分支關聯,可是若是這個時候咱們在本地develop分支調用git push會受到這樣一個錯誤警告

    也就是說在遠程與本地分支不一樣名時,咱們只能經過git push origin HEAD:develop2或者git push origin develop:develop2的方式來推送,固然其實這兩條命令背後是同樣的,由於當前HEAD指向的就是develop分支

  7. 修改遠程倉庫的名字

    git remote rename origin origin2,這樣就把遠程倉庫名從origin重命名爲origin2了

  8. 刪除遠程倉庫名

    git remote rm origin

場景

  1. 如何刪除submodule呢

    git並無提供直接的指令幫助咱們刪除submodule,因此咱們就須要先執行git rm --cached mymodule將submodule從暫存區中移除,再之心rm -rf mymodule將文件夾完全刪除,接着提交咱們的修改,同時.gitmodule這個文件也沒有用了,因此也能夠經過上述方法將其刪除,這樣咱們就成功的刪除了submodule

  2. 怎樣跟上當初fork的項目記錄

    咱們能夠經過在本地fork的版本庫中加入一個原做的遠程分支,而後執行git fetch將原做的最新代碼拉取下來,一般咱們會把這個分支名叫作upstream,接着咱們能夠執行merge操做,將原做的upstream分支merge到咱們當前分支,而後提交推送,就跟上最新記錄了。

Q&A

  1. 使用subtree時,有什麼方法能夠既不會污染項目自己又能夠不會常常出現須要解決衝突的狀況

    這裏要先說的一個概念是squash其實並非subtree這個指令專有的,git merge一樣也能夠指定這個選項而且做用是相同的

    咱們能夠經過另外創建一個相似叫no-squash的分支而後不加squash選項更新subtree,這樣就保留了module的歷史記錄,沒有煩人的反覆衝突問題,而後再將no-squash分支合併到主分支,可是這裏合併時用的是squash的方式git merge --squash, 這樣項目的主分支上只會體現一個commit,比直接git subtree add/pull --squash還要簡潔(本來是兩個commit)。

    固然這種作法也有缺點,首先新開的分支歷史記錄就會稍顯混亂,另外就是每次新分支作subtree的操做就要記得merge一次回master,但我本身以爲仍是在可以接受的範圍內得。

  2. 在使用git pull時常常會出現多了一個merge commit的狀況,這是爲何呢

    其實常常會出現別人推送到遠程後咱們又作了一些修改,這時候當咱們執行git pull時,會先執行git fetch,接着執行git merge,這時候其實不少時候會由於合併產生額外的commit,這時候咱們就能夠經過執行git pull --rebase來解決這個問題

  3. 假設沒有github,要怎麼在兩個host之間傳修改呢

    這個算是比較冷門一點的問題,不過git也是有方法幫咱們來作的,咱們能夠經過執行git format-patch -n -o path這裏n只的是最近的n條commit,而後咱們使用-o後面接一個路徑,接着git就會在指定路徑中建立每一個commit對應的一個patch文件,接着咱們能夠把這些patch文件經過email等方式傳給另一個host,而後在那個host的項目中執行git am path,這裏的path就是放傳過來的這些文件,這樣git就會幫咱們更新了

相關文章
相關標籤/搜索