Git 使用

AlenQi發表於2018-10-24

從版本控制說起

版本控制最主要的功能就是追蹤檔案的變更。它將什麼時候、什麼人更改了檔案的什麼內容等資訊忠實地了已錄下來。每一次檔案的改變,檔案的版本號都將增加。除了記錄版本變更外,版本控制的另一個重要功能是並行開發。軟體開發往往是多人協同作業,版本控制可以有效地解決版本的同步以及不同開發者之間的開發通訊問題,提高協同開發的效率。並行開發中最常見的不同版本軟體的錯誤(Bug)修正問題也可以通過版本控制中分支與合併的方法有效地解決。

版本控制包括:檢入檢出控制、分支和合並、歷史記錄。

  1. 檢入檢出控制:開發人員能夠從庫中取出對應專案的配置項進行修改,並檢入到軟體配置庫中,對版本進行“升級”;配置管理人員可以確定多餘配置項並刪除。同步控制的實質是版本的檢入檢出控制。檢入就是把軟體配置項從使用者的工作環境存入到軟體配置庫的過程,檢出就是把軟體配置項從軟體配置庫中取出的過程。檢人是檢出的逆過程。同步控制可用來確保由不同的人併發執行的修改不會產生混亂。
  2. 分支和合並:版本分支(以一個已有分支的特定版本為起點,但是獨立發展的版本序列)的人工方法就是從主版本——稱為主幹上拷貝一份,並做上標記。在實行了版本控制後,版本的分支也是一份拷貝,這時的拷貝過程和標記動作由版本控制系統完成。版本合併(來自不同分支的兩個版本合併為其中一個分支的新版本)有兩種途徑,一是將版本A的內容附加到版本B中;另一種是合併版本A和版本B的內容,形成新的版本C。
  3. 歷史記錄:版本的歷史記錄有助於對軟體配置項進行稽核,有助於追蹤問題的來源。歷史記錄包括版本號、版本修改時間、版本修改者、版本修改描述等最基本的內容,還可以有其他一些輔助性內容,比如版本的檔案大小和讀寫屬性。

集中式VS分散式

集中式

集中式版本控制系統,版本庫是集中存放在中央伺服器的,而在開發的時候,先要從中央伺服器取得最新的版本,然後開始開發,開發完成後,再把自己的改動推送給中央伺服器。

中央伺服器就好比是一個圖書館,你要改一本書,必須先從圖書館借出來,然後回到家自己改,改完了,再放回圖書館。集中式版本控制系統最大的毛病就是必須聯網才能工作,如果在區域網內還好,頻寬夠大,速度夠快,可如果在網際網路上,遇到網速慢的話,可能提交一個10M的檔案就需要5分鐘。

Git 使用

分散式

分散式版本控制系統根本沒有類似中央伺服器的概念,每個人的電腦上都是一個完整的版本庫。這樣,你工作的時候,就不需要聯網了,因為版本庫就在你自己的電腦上。既然每個人電腦上都有一個完整的版本庫,

那多個人如何協作呢?比方說你在自己電腦上改了檔案A,你的同事也在他的電腦上改了檔案A,這時,你們倆之間只需把各自的修改推送給對方,就可以互相看到對方的修改了。和集中式版本控制系統相比,分散式版本控制系統的安全性要高很多,因為每個人電腦裡都有完整的版本庫,某一個人的電腦壞掉了不要緊,隨便從其他人那裡複製一個就可以了。而集中式版本控制系統的中央伺服器要是出了問題,所有人都會沒法開發。

在實際使用分散式版本控制系統的時候,其實很少在兩人之間的電腦上推送版本庫的修改,因為可能你們倆不在一個區域網內,兩臺電腦互相訪問不了,也可能今天你的同事病了,他的電腦壓根沒有開機。因此,分散式版本控制系統通常也有一臺充當中央伺服器的電腦,但這個伺服器的作用僅僅是用來方便交換同步大家的修改,沒有它大家也一樣可以開發,只是同步修改不方便而已。

Git 使用

Git簡史

Linux 核心開源專案有著為數眾廣的參與者。 絕大多數的 Linux 核心維護工作都花在了提交補丁和儲存歸檔的繁瑣事務上(1991-2002年間)。到2002年,整個專案組開始啟用一個專有的分散式版本控制系統 BitKeeper 來管理和維護程式碼。

Git 使用
(BitKeeper 是開源在 Git 上的。。。)

到了2005年,開發 BitKeeper 的商業公司同 Linux 核心開源社群的合作關係結束,他們收回了 Linux 核心社群免費使用 BitKeeper 的權力。 這就迫使 Linux 開源社群(特別是 Linux 的締造者 Linus Torvalds)基於使用 BitKeeper 時的經驗教訓,開發出自己的版本系統———Git.他們對新的系統制訂了若干目標:

- 速度
- 簡單的設計
- 對非線性開發模式的強力支援(允許成千上萬個並行開發的分支)
- 完全分散式
- 有能力高效管理類似 Linux 核心一樣的超大規模專案(速度和資料量)
複製程式碼

Git原理

直接記錄快照,而非差異比較

Git 和其它版本控制系統(包括 Subversion 和近似工具)的主要差別在於 Git 對待資料的方法。 概念上來區分,其它大部分系統以檔案變更列表的方式儲存資訊。 這類系統(CVS、Subversion、Perforce、Bazaar 等等)將它們儲存的資訊看作是一組基本檔案和每個檔案隨時間逐步累積的差異。

儲存每個檔案與初始版本的差異

Git 不按照以上方式對待或儲存資料。 反之,Git 更像是把資料看作是對小型檔案系統的一組快照。 每次你提交更新,或在 Git 中儲存專案狀態時,它主要對當時的全部檔案製作一個快照並儲存這個快照的索引。 為了高效,如果檔案沒有修改,Git 不再重新儲存該檔案,而是隻保留一個連結指向之前儲存的檔案。 Git 對待資料更像是一個 快照流。

儲存專案隨時間改變的快照

近乎所有操作都是本地執行

在 Git 中的絕大多數操作都只需要訪問本地檔案和資源,一般不需要來自網路上其它計算機的資訊。因為你在本地磁碟上就有專案的完整歷史,所以大部分操作看起來瞬間完成。

例如,要瀏覽專案的歷史,Git 不需外連到伺服器去獲取歷史,然後再顯示出來——它只需直接從本地資料庫中讀取。 你能立即看到專案歷史。

Git 保證完整性

Git 中所有資料在儲存前都計算校驗和,然後以校驗和來引用。 這意味著不可能在 Git 不知情時更改任何檔案內容或目錄內容。

Git使用了SHA-1並非是為了安全性,而是為了資料的完整性;它可以保證,在很多年後,你重新checkout某個commit時,一定是它多年前的當時的狀態,完全一摸一樣,完全值得信任。

Git 用以計算校驗和的機制叫做 SHA-1 雜湊(hash,雜湊)。 這是一個由 40 個十六進位制字元(0-9 和 a-f)組成字串,基於 Git 中檔案的內容或目錄結構計算出來。 SHA-1 雜湊看起來是這樣:

24b9da6552252987aa493b52f8696cd6d3b00373

實際上,Git 資料庫中儲存的資訊都是以檔案內容的雜湊值來索引,而不是檔名。

三種狀態

Git 有三種狀態,你的檔案可能處於其中之一:已提交(committed)、已修改(modified)和已暫存(staged)。 已提交表示資料已經安全的儲存在本地資料庫中。 已修改表示修改了檔案,但還沒儲存到資料庫中。 已暫存表示對一個已修改檔案的當前版本做了標記,使之包含在下次提交的快照中。

這裡涉及到Git 專案的三個工作區域的概念:Git 倉庫、工作目錄以及暫存區域。

工作目錄、暫存區域以及 Git 倉庫

  1. Git 倉庫目錄是 Git 用來儲存專案的後設資料和物件資料庫的地方。 這是 Git 中最重要的部分,從其它計算機克隆倉庫時,拷貝的就是這裡的資料。

  2. 工作目錄是對專案的某個版本獨立提取出來的內容。 這些從 Git 倉庫的壓縮資料庫中提取出來的檔案,放在磁碟上供你使用或修改。

  3. 暫存區域是一個檔案,儲存了下次將提交的檔案列表資訊,一般在 Git 倉庫目錄中。 有時候也被稱作`‘索引’',不過一般說法還是叫暫存區域。

Git的資料流

Git 使用

Git工作流

  1. Feature 分支的工作流

Git 使用

這種封裝使得多個開發者專注自己的功能而不會打擾主程式碼庫。它還保證 master 分支永遠不會包含損壞的程式碼,給持續整合環境帶來了是很大的好處。

封裝功能的開發使得 Pull Request 的使用成為可能,用來啟動圍繞一個分支的討論。它給了其他開發者在功能併入主專案之前參與決策的機會。或者,如果你開發功能時卡在一半,你可以發起一個 Pull Request,向同事尋求建議。重點是,Pull Request 使得你的團隊在評論其他人的工作時變得非常簡單。

  1. GitFlow 工作流

GitFlow 工作流圍繞專案釋出定義了一個嚴格的分支模型。有些地方比功能分支工作流更復雜,為管理大型專案提供了可控的框架。

和功能分支工作流相比,這種工作流沒有增加任何新的概念或命令。它給不同的分支指定了特定的角色,定義它們應該如何、什麼時候交流。除了功能分支之外,它還為準備釋出、維護髮布、記錄釋出分別使用了單獨的分支。當然,你還能享受到功能分支工作流帶來的所有好處:Pull Request、隔離實驗和更高效的協作。

Git 使用

和單獨的 master 分支不同,這種工作流使用兩個分支來記錄專案歷史。master 分支儲存官方釋出歷史,develop 分支用來整合功能分支。同時,這還方便了在 master 分支上給所有提交打上版本號標籤。

Git 使用

每個新功能都放置在自己的分支中,可以在備份/協作時推送到中央倉庫。但是,與其合併到 master,功能分支將開發分支作為父分支。當一個功能完成時,它將被合併回 develop。功能永遠不應該直接在 master 上互動

Git 使用

一旦 develop分支的新功能足夠釋出(或者預先確定的釋出日期即將到來),你可以從 develop 分支 fork 一個釋出分支。這個分支的建立開始了下個釋出週期,只有和釋出相關的任務應該在這個分支進行,如修復 bug、生成文件等。一旦準備好了釋出,釋出分支將合併進 master,打上版本號的標籤。另外,它也應該合併回 develop,後者可能在釋出啟動之後有了新的進展。

使用一個專門的分支來準備釋出確保一個團隊完善當前的釋出,其他團隊可以繼續開發下一個釋出的功能。它還建立了清晰的開發階段(比如說,「這周我們準備 4.0 版本的釋出」,而我們在倉庫的結構中也能看到這個階段)。

Git 使用

維護或者「緊急修復」分支用來快速給產品的釋出打上補丁。這是唯一可以從 master 上 fork 的分支。一旦修復完成了,它應該被併入 master 和 develop 分支(或者當前的釋出分支),master 應該打上更新的版本號的標籤。

有一個專門的 bug 修復開發線使得你的團隊能夠處理 issues,而不打斷其他工作流或是要等到下一個釋出週期。你可以將維護分支看作在 master 分支上工作的臨時釋出分支。

  1. Fork 工作流

Fork 工作流並沒有唯一的服務端倉庫作為「中央」程式碼庫,它給予每個開發者一個服務端倉庫。也就是說每個貢獻者都有兩個 Git 倉庫,而不是一個:一個私有的本地倉庫和一個公開的服務端倉庫。

Git 使用

Fork 工作流的主要優點在於貢獻可以輕易地整合進專案,而不需要每個人都推送到單一的中央倉庫。開發者推送到他們 自己的 服務端倉庫,只有專案管理者可以推送到官方倉庫。這使得管理者可以接受任何開發者的提交,卻不需要給他們中央倉庫的許可權。

這種分散式的工作流為大型、組織性強的團隊(包括不可信的第三方)提供了安全的協作方式。它同時也是開源專案理想的工作流。例如GitHub.

Git 使用

開發者們fork官方倉庫。

Git 使用

在他們剛克隆的本地倉庫中,開發者可以編輯程式碼、提交更改。

Git 使用

一旦開發者準備好共享他們的新功能,他們需要做兩件事情。第一,他們必須將貢獻的程式碼推送到自己的公開倉庫,讓其他開發者能夠訪問到。第二,他們需要通知專案維護者,他們想要將功能併入官方程式碼庫。也就是我們說的提 PR .

Git 使用

專案維護者整合他們的功能,接受或者拒絕 PR .

Git 使用

開發者和中央倉庫保持同步。

Git小技巧

  1. Git 別名配置

git config --globalalias.st status

  1. 設定git為大小寫敏感

git config --globalcore.ignorecasefalse

  1. 儲存當前追蹤的檔案修改狀態而不提交,並使得工作空間恢復乾淨

git stash

  1. 使用 nrm 切換倉庫地址

https://github.com/Pana/nrm

  1. 移除所有在遠端已經被刪除的遠端分支

git fetch -p

  1. 恢復某個檔案到某個Commit時候的狀態

git checkout <commit-ish> -- <file_path>

  1. 修改上一個提交的資訊

git commit -v --amend

  1. 修改所有提交的作者、郵箱資訊
$ git filter-branch --commit-filter '
      if [ "$GIT_AUTHOR_EMAIL" = "schacon@localhost" ];
      then
              GIT_AUTHOR_NAME="Scott Chacon";
              GIT_AUTHOR_EMAIL="schacon@example.com";
              git commit-tree "$@";
      else
              git commit-tree "$@";
      fi' HEAD
複製程式碼
  1. 修改多個提交說明
$ git rebase -i HEAD~3

pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
  
# Rebase 710f0f8..a5f4a0d onto 710f0f8
#
# Commands:
#  p, pick = use commit
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted. 
#
複製程式碼
  1. 快速切換到上一個分支

git checkout -

  1. 修改當前分支名

git branch -m <new-branch-name>

  1. 在Pull時候強制用變基進行操作

git config --global branch.autosetuprebase always

  1. 同時在多個分支上開發
git worktree add ../new-feature-or-bug-fix

git worktree list

rm -rf new-feature-or-bug-fix

git worktree prune
複製程式碼