<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Git還能這樣用?一文看懂Git最佳實踐!

          共 19218字,需瀏覽 39分鐘

           ·

          2024-06-03 09:15


          ??目錄


          1 什么是 git

          2 什么不是 git

          3 選對工具

          4 盡量在本地

          5 分支策略

          6 Merge 還是 rebase

          7 處理合并沖突

          8 不要 pull,要 fetch

          9 小而完整的 commit

          10 LFS 技巧

          11 Git 的缺點

          12 總結(jié)




          很多 Git 的操作,都有多種方法達到目的,但其中往往只有一種方法是最佳路徑。

          Git 是個超級強大也非常流行的版本控制系統(tǒng)(VCS)。它的設(shè)計理念和其他VCS非常不同。縱觀整個業(yè)界,很多人在用舊的思維方式來解決 Git 的使用問題,有 svn 方式的、p4 方式的、奇怪方式的、錯誤方式的,等等,而不是更新成 Git 的思維方式。雖然 Git 非常靈活,確實可以用這些方式來使用,但其實操作起來反而更難,而且效率更低,吃力不討好。這里我打算把二十多年的各種版本控制系統(tǒng)的使用經(jīng)驗和十多年 Git 的使用經(jīng)驗,總結(jié)出一些 Git 的最佳實踐。其實很多時候,正確的做法比錯誤的更簡單,更不容易出錯。





          01



          什么是 Git

          不開玩笑。最常見的 git 錯誤使用,正是來自于沒意識到 Git 是什么。大部分 Git 的屬性,可以從定義用邏輯推導出來。邏輯是最重要的,只要邏輯錯了,就一定是錯了。哪怕所有人都這么做,也是錯的。


          Git 是一個分布式版本控制系統(tǒng),跟蹤目錄里的修改。它的工作流是非線性的(不同電腦上的平行分支形成了一個 graph)。和主從式的系統(tǒng)不一樣的是,每臺電腦上的每個 Git 目錄都是一個完整的 repo,包含全部歷史完整的版本跟蹤能力。(LFS 是個例外,后面會提到。)

          因為 Git 的本質(zhì)是一個基于目錄的分布式 VCS,這里面并沒有中心服務(wù)器的角色。去中心化是未來。同個項目的所有 repo 都是平等的端點。一個 repo 可以在服務(wù)器、本地目錄、其他人的電腦上。只是為了團隊協(xié)作的目的,會認為指定一個或多個端點作為“服務(wù)器”。是的,可以同時有多個上游服務(wù)器。很多時候這么做很有必要。比如對內(nèi)開發(fā)的 repo 和對外開源的 repo,就是兩個不同的端點。可以有不同的分支和推送頻率。本地只要一個 repo 就都管理了。

          非線性的工作流表示提交和分支操控是一個常規(guī)的操作。建立分支、rebase、修訂 commit、強制推送、cherry-pick、分支復位,在 Git 都是很正常的使用方式。



          02



          什么不是 Git

          很多東西經(jīng)常和 Git 一起出現(xiàn),但是并不是 Git 的一部分。

             2.1 GitHub/GitLab


          這些都不是 git,而是提供 git 服務(wù)和社區(qū)的網(wǎng)站。Git 是個基于目錄的 VCS,并不需要網(wǎng)站服務(wù)或者網(wǎng)絡(luò)訪問才能工作。早期經(jīng)常有人沒法區(qū)分 GitHub 和 git。當要說 git 的時候,會說 GitHub,制造的混亂不是一星半點。

             2.2 Fork


          Fork 仍然也是 Git 服務(wù)網(wǎng)站的功能,用來簡化協(xié)作流程。在沒有 fork 的時候,如果你想往開源項目里修 bug 或者加 feature,會需要這樣的流程:
          1. 克隆 repo;
          2. 修改代碼;
          3. 生成補丁;
          4. 發(fā)到論壇或者支持的郵件列表;
          5. 找作者來 review,合并補丁。

          很多項目到現(xiàn)在還是這么做的。如果有了 fork,可以簡化成:
          1. Fork 并克隆 repo;
          2. 修改代碼;
          3. 發(fā)出 merge request 或者 pull request。

          雖然 fork 很有用,但這仍然不是 git 的一部分。它用到的是 git 的分布式能力。本質(zhì)上,在 fork 的時候,它會克隆一份 repo,把原來的 repo 設(shè)置成上游。所以其實如果你的目標不是為了繼續(xù)把 repo 放在網(wǎng)絡(luò)服務(wù)上,那就克隆到本地就是了。太多的人把 fork 當作 like 來用,根本就是錯的。如果沒打算改代碼,fork 是沒意義的。機器學習界這個問題尤其嚴重。經(jīng)常放一個 README 就假開源了,還有幾百個 fork,都不知道能 fork 到什么。

             2.3 Merge request/Pull request


          GitHub 上叫 Pull Request,GitLab 上叫 merge request,其實是一個東西的不同視角。這些都是 code review 和合并的流程,不是 git 的一部分。

          需要注意的是,它們的重點在“request”,而不是 merge 或者 pull。如果你要把一個分支 merge 到你自己的,沒必要開一個 MR 然后自己給自己通過。在本地 merge 就是了,更簡單更快。

             2.4 Import


          很多 git 服務(wù)支持“Import”,用來從別的 git、svn、cvs、p4 等 VCS 導入一個庫。如果原本的 repo 已經(jīng)是 git,那直接 push 到新的地方就是了,比 import 更簡單。而且這樣絕對不會丟失歷史記錄或者搞錯文件。如果是其他 VCS 的 repo,那也可以用插件或腳本來先轉(zhuǎn)成一個本地的 git repo,然后再 push 到新的地方。



          03



          選對工具

          Git 本身是個命令行工具。但是,非線性工作流的本質(zhì)就讓它沒可能在字符界面顯示出分支的 graph。選個好的 GUI 非常關(guān)鍵。不但可以大幅度增加工作效率,更重要的是,減少出錯的機會。第二個常見的 git 使用錯誤來源,正是因為用錯了工具造成了。

          Windows 上最好的 git GUI 是 TortoiseGit,沒有之一。它只是個 GUI,git 命令行需要事先安裝。和其他 Tortoise 打頭的工具(TortoiseCVS、TortoiseSVN)一樣,它的風格是沒有主 UI,而集成到 Windows 的文件管理器里面。Repo 里的文件(也就是目錄里的)圖標上會覆蓋上狀態(tài)。右鍵點擊這個目錄,菜單里可以看到 TortoiseGit 的子菜單,包含 git 的一些操作。大部分 VCS 的 GUI 工具,比如 P4V、SourceTree,UGit,都有個主 UI 顯示映射了的工作空間,而不是目錄本身。對于 git 來說,這其實是個錯誤,因為 git 是基于目錄的,不存在工作空間這個概念。而且,這種情況下非常常見的錯誤就是忘記提交新增的文件。在 TortoiseGit 里,除了蓋在圖標上的狀態(tài)之外,提交窗口也可以顯示出哪些文件還沒添加,不會出現(xiàn)遺漏的情況。


          另外,TortoiseGit 有一個獨特的版本 graph 查看器,里面可以顯示出 repo 的整個分支結(jié)構(gòu)。通過這個查看器,可以很方便地看出來 repo 是怎么成長的,有那些不必要的分支,如何從一個分支跳到另一個,等等。這是 TortoiseGit 比其他 git UI 好的一個重要原因。不管是 Visual Studio 里的、SourceTree、還是 UGit,在 UI 設(shè)計上都像用傳統(tǒng)的 VCS 思路來套用到 git 上,而不是 git 的思路。主它們的共同問題就是,基本只關(guān)注于當前分支。而有能力同時看所有分支,對 git 來說非常重要,因為 git 的工作流是非線性的。


          其他高級功能,比如打補丁、處理 submodule(非常重要),都可以在 TortoiseGit 的 GUI 里完成。但它沒法覆蓋所有的功能。有些很少用的,還是得通過命令行。



          04



          盡量在本地

          所有的 git 操作都可以在本地 repo 上完成,因為服務(wù)端的并沒有更高優(yōu)先級。雖然大部分提供 git 服務(wù)的網(wǎng)站都在網(wǎng)頁界面里有 cherry-pick、新建分支、合并這些操作,但是在本地執(zhí)行更容易,而且比在服務(wù)端執(zhí)行了再拉下來要更快。



          05



          分支策略

          Git 的工作流是基于分支的。不但每個 repo 是平等的,每個分支也是。Master/main、develop 這些只是為了簡化管理而人工指定的有特殊含義的分支。這里的分支策略是為了更好地協(xié)作而產(chǎn)生的習慣規(guī)范,不是 git 的工作流本身必須定義的。分支可以分為幾個層次。

             5.1 Main 分支


          這是整個項目的穩(wěn)定分支,里面的內(nèi)容可能相對較老,但是這個分支里的內(nèi)容都是經(jīng)過測試和驗證的。原先都叫 master,因為政治正確的要求,最近越來越多新項目開始用 main。有些快速開發(fā)的項目甚至不采用 main 分支。

             5.2 Develop 分支


          開發(fā)主要發(fā)生在 develop 分支。新特性先放到這個分支,再去優(yōu)化和增強穩(wěn)定性。


             5.3 大項目可選的團隊 Develop 分支


          對于跨團隊的大項目,每個團隊都有自己的興趣點和發(fā)布周期。很常見的做法是,每個團隊有自己的 develop 分支。每過一段時間合并到總的 develop 分支。 一般來說,中等大小的團隊,專注于 repo 的某一部分,可以采取這樣的分支形式。小團隊或者個人沒有必要有自己的 develop 分支。那樣反而會浪費時間和增加合并過程中的風險。


             5.4 Feature 分支


          Feature 分支是生命期很短的分支,專注于單個特性的開發(fā)。和其他 VCS 不一樣的是,在 git 里開分支開銷非常低,所以可以高頻地開分支和合并分支。在做一個特性的時候,常規(guī)的流程是這樣的:
          1. 從 develop 分支上新建一個 feature 分支;
          2. 提交一些關(guān)于這個 feature 的代碼;
          3. 合并回去;
          4. 刪除這個 feature 分支。

          對于本地 repo 里的 feature 分支,你可以做任何事。常見的用法是在開發(fā)過程中非常頻繁地提交,走一小步就提交一次。在發(fā)出 MR 之前,先合并成一個 commit,把這個分支變整潔,方便后續(xù)操作。

          當 feature 分支合并之后,絕對不存在任何理由讓這個分支仍然存在于服務(wù)器上。WOA 現(xiàn)在有自動刪除的選項,可以設(shè)置成默認開啟。但有時候仍然會出些問題,這個選項會消失,需要手工刪除分支(其實就是在 MR 頁面上點一下的事)。記住:服務(wù)器上只是一個端點,刪掉那邊的一個分支不會影響你的本地 repo。如果你有后續(xù)工作需要在那個分支上做,就繼續(xù)在你本地的分支上完成就是了。這和服務(wù)端有沒有這個分支一點關(guān)系都沒有。


          因為每個分支都是平等的,可以推出在任何一個分支上都可以新建分支。比如,如果特性 B 依賴于特性 A,你不用等特性 A 合并了才開始做特性 B。只要在特性 A 的分支上建立一個特性 B 的分支就可以了,即便特性 A 不是你的分支也可以。等到特性 A 合并了,把特性 B 的分支 rebase 一下就是了。少了等待環(huán)節(jié),效率提高很多,也不必催人做 code review。

          能建立大量 feature 分支,對于提高工作效率非常關(guān)鍵。每個特性建立一個 feature 分支,在上面完成特性,發(fā)出 MR。在 code review 通過之前,已經(jīng)可以新建另一個特性專用的 feature 分支,切換過去,開始做另一個特性。在 code review 過程中還能來回切換,同時做多個特性。其他 VCS 是做不到這一點的,效率也自然低很多。

             5.5 Release 分支群


          Release 不只是一個分支,而是一群以“release/”打頭的分支。就好像一個目錄,包含了不同版本給不同產(chǎn)品線的 release 分支。一般來說他們從 main 或者 develop 分支出來。當發(fā)現(xiàn)一個 bug 的時候,在 main 或者 develop 分支修好,然后 cherry-pick 到 release 分支里。這種單向的處理可以方便管理,并且不用擔心某個 commit 是不是只有 release 分支有。Release 分支經(jīng)常在每個 sprint 的開頭創(chuàng)建,包含這個 sprint 要發(fā)布的東西;或者在每個 sprint 的結(jié)尾創(chuàng)建,包含下一個 sprint 要發(fā)布的東西。



          06



          Merge 還是 rebase

          雖然在提及把 commit 從 feature 分支放到 develop 分支的時候,我們一直說“合并,但其實這里存在兩個維度。是的,不是有兩個操作,是有兩個維度。

          第一個維度,是 merge 還是 rebase。這是兩種合并的方式。第一種是普通的合并,和傳統(tǒng)的 VCS 一樣。它會把一個分支合并到目標分支,在頂上建立一個 commit 用來合并,兩個分支里已有的 commit 不會有變化。


          另一個就是 rebase。它會從分支分出來的地方切開,嫁接到目標分支的頂端上。(我一直認為 rebase 應(yīng)該翻譯成嫁接,而不是“變基”。)


          第二個維度是是否 squash,也就是選擇一個分支里的一些 commit,壓扁成一個 commit。這個任何時間都能做,即便不是為了合并也行。在 TortoiseGit 里,這叫“combine into one commit”。


          兩個維度組合之后,我們就得到了4個操作。但是“squash 再 merge”沒有任何意義,所以就剩下“不 squash 就 merge, “不 squash 就 rebase”,以及“squash 再 rebase。(微軟的 DevOps 文檔曾經(jīng)有個嚴重的錯誤。里面描述成 merge 表示不 squash 就 merge、rebase 表示 squash 在 rebase,而沒有把它們當作兩個維度來看。是我在2018年左右提出了這個問題,并且要求他們修改,還提供了多個圖片解釋它們到底有什么區(qū)別。過了大概半年之后才改成對的。但很多人就是從那里學的 git,都被帶壞了。)

          其實還可以有第三個維度,修訂與否。但這個更多的是發(fā)生在 merge 之前的過程。修訂,amend,表示當提交的時候,是不是要覆蓋掉上一個 commit。打開的話,提交之后還會只有一個 commit,而不是兩個。

          關(guān)閉 amend

          打開 amend

          現(xiàn)在的問題就是,什么時候用什么。要是要處理的是長生命周期的分支,比如團隊的 develop 分支、develop 分支、main 分支,合乎邏輯的選擇是 merge。因為它們的結(jié)構(gòu)需要保留,而且合并后分支也不打算消失。

          對于 feature 分支,不同團隊可以有不同選擇。這里我只說最高效,開銷最低的。一個 feature 分支里可以有多個 commits,但它們只有合在一起的時候才會成為一個 feature。中間的 commit 以后就再也用不到了。留著只會浪費空間和時間。所以邏輯上,這些 commit 就需要被 squash。這時候如果 merge 一個只包含一個 commit 的分支,就會出現(xiàn)這樣的 graph:


          這里有個什么都不做的 commit,只是把兩個分支抓在一起,以及一個永遠掛在外面的 commit。即便 git 里開分支和合并的開銷很低,但這會一直積累的。這里用 merge,就完全是在浪費時間和空間。對于 feature 到 develop 的合并來說,rebase 是最佳選擇。

          現(xiàn)在,如果早晚需要把多個 commit 合成一個,那就該用 amend。是的,大部分時候,一路 amend 過去,比最后才來 squash 更好。首先,rebase 一個 commit,會比 rebase 一串來得容易得多,特別是有代碼沖突的時候。其次,如果 MR 的最后才 squash & merge,那 commit 的消息就是沒有經(jīng)過 review 的,增加了犯錯的風險。(是的,非常經(jīng)常發(fā)生)

          所有這些操作都可以在本地完成。這比在 Web UI 上操作遠程的 repo 要容易而且高效。總結(jié)起來,這里的最佳實踐是:
          1. 在開發(fā)過程中可以用 commit 或者 amend commit;
          2. 在發(fā)出 MR 的時候 squash 成一個 commit;
          3. 在 MR 的迭代內(nèi)持續(xù)用 amend commit;
          4. 在 MR 通過后用 rebase 進行合并。

          (其實,p4 里面的每一次 submit,都是 amend + rebase。之前只是因為沒有人告訴你這個事實。而且 p4 里只有一種 submit 的方式,沒有思考和選擇的空間,做就是了。但這絕不代表不需要思考“有沒有更好的做法”這個問題,這非常重要。)

          更復雜的情況是在跨公司的 repo 上工作,比如 UE。這時候規(guī)則需要做一些改變。一般來說,這種情況下你的 feature 分支是從 release 分支上建出來的,而不是 develop 分支。而且這種 feature 分支其實是作為 develop 分支來用,有長的生命周期。這時候,如果你要把一個特性從比如 UE 5.1移植到5.2,rebase 就不是最佳選擇了。因為那樣的話會把5.1 release 分支里的所有 commit 和你的所有 feature commit 一起 rebase。而你真正想要的是只把你的 commit 給 cherry-pick 過去。這其實還是因為工具。如果用的是 TortoiseGit,就不會有這個疑惑。因為里面 rebase 默認是交互式的。你可以精確選擇哪些 commit 需要操作。這就讓 rebase 和 cherry-pick 變成一樣的東西。唯一的區(qū)別,是 rebase 是讓 git 選一個 commit 的列表,讓你從中選哪個要哪個不要。而 cherry-pick 是讓你直接選 commit 的列表。



          07



          處理合并沖突

          當出現(xiàn)合并沖突的時候,最好的方式是先把你的 feature 分支 rebase 到目標分支的頂端,這時候解決沖突,然后 force push。如果用 WOA 的沖突解決(可能有些別的基于 web 的 git 服務(wù)也有),它會每次都做 merge。結(jié)果經(jīng)常把簡單的單個 commit rebase,變成了復雜的三分支合并。

             7.1 常見錯誤:解決合并沖突后建了個新的 MR


          因為沖突解決的錯誤行為,有可能在解決之后,修改被提交到了一個新的分支。這時候應(yīng)該把你的分支 reset 到新的去,force push,再刪掉新的;而不是關(guān)掉原先的 MR,在新分支上開個新 MR。

             7.2 常見錯誤:把分支搞亂


          如果真的遇到了多分支復雜交錯的情況,有兩個方法可以嘗試清理出來。
          1. 強制 rebase。Fetch 一下整個 repo;把你的分支 rebase 到目標分支上的時候勾選 force;這時候在列表里選要拿去 rebase 的 commit。大部分時候這都能行。但有時候 git 因為分支太錯綜復雜而搞不清楚 commit,在列表里會有遺漏。
          2. Cherry-pick。在目標分支上新建一個臨時分支;把有用的 commit 都 cherry-pick 過去;把你的分支 reset 到那個臨時分支上;最后刪掉那個臨時分支。

          兩個方法最后都需要 force push。



          08



          不要 pull,要  fetch

          很多教程都說 push 和 pull 是在本地和遠程 repo 之間同步的指令。但是其實 push 是基礎(chǔ)指令,pull 不是。它是 fetch 當前分支->和本地分支合并->reset 到合并后的頂端。這里就產(chǎn)生了不必要的合并。你可以打開 rebase pull,這就簡化成 fetch 當前分支->rebase 本地分支。


          好一些,但是每次 pull 的時候都會開啟 rebase 的窗口,即便沒什么好 rebase 的。其實如果改用手動運行 fetch 和 rebase,同樣的工作量可以獲得更多。因為默認的 fetch 可以拿到所有分支,而不是只有當前分支。然后你可以決定哪個分支 rebase 到哪里。整個過程中都可以保證沒有錯誤的 merge 發(fā)生。



          09



          小而完整的 commit

          每個 commit 都該小而完整,有些人把這個叫做“原子性。不要把多個特性壓到一個 commit 里,同時不要有一堆必須合起來才能用的 commit。

             9.1 常見錯誤:一個 commit 里做多件事情

          這是一個非常常見的錯誤。一個大的 commit 包含多個任務(wù)的代碼。這樣的 commit 必須要拆成多個才行。在 git 里,這樣的拆分比較容易。如果一個分支“Feature”包含了特性 A 和特性 B 的代碼,那么,
          1. 在“Feature”的頂端建立“Feature A”和“Feature B”兩個分支;
          2. 切換到“Feature A”分支,刪掉其中特性 B 的代碼,開 amend 提交;
          3. 把“Feature B”分支 rebase 到新的“Feature A”分支。

          這就行了。現(xiàn)在兩個分支都分別只包含一個特性。如果特性 B 不依賴于特性 A,它還可以繼續(xù) rebase 到 develop 分支去。

             9.2 常見錯誤:多個不完整的 commit

          另一個非常常見的錯誤是不完整的 commit,比如不能編譯、不能運行、只包含瑣碎的修改、或者僅僅為了未來的使用而做的修改。這樣的 commit 只是中間結(jié)果,沒法單獨存在,需要和其他 commit 合起來才變成一個完整的 commit。那它們就需要合并之后才發(fā) MR。

             9.3 拆分大的 commit

          是的,有時候是需要把一個大的 commit 拆分成多個,讓 MR 更容易看。但是這里的拆分并不能讓 commit 變得不完整。如果一個大 commit 中的一部分,本身就能對現(xiàn)在的代碼庫有幫助,拿著就能提出來變成一個獨立的 commit。常見的是獨立的 bug 修復、代碼整理、或者重構(gòu)。



          10



          LFS 技巧

          LFS 是 git 里蠻特殊的一部分。為了讓 git 更好地支持大(二進制)文件,LFS 其實讓 git 的設(shè)計做了一些妥協(xié)。LFS 比 git 晚了9年發(fā)布,而且花了好多年才讓主流 git 服務(wù)都提供支持。

             10.1 LFS 是怎么回事

          保存完整歷史的大文件,特別是大的二進制文件超級占空間和處理時間。在 LFS 里,默認子保存一個版本的大文件,歷史則放在另一個端點,一般是服務(wù)器。本地其實也可以這樣拉取完整的歷史:

              
          git lfs fetch --all

          當從一個 git 轉(zhuǎn)移到另一個的時候,會要求做這件事情。其他時候一個版本就夠了。

          另外,LFS 有加鎖解鎖的功能。但是和主從式的 VCS 不同的是,加鎖解鎖不會自動擴散到所有端點。這還是因為并不存在中心服務(wù)器的概念。

             10.2 常見錯誤:沒開 LFS

          非常重要的一件事情是,LFS 不負責鑒別哪些文件是大文件。在添加大文件之前,它們路徑需要加到 .gitattributes 里,可以用通配符。一旦路徑在 .gitattributes 里了,文件操作就會自動通過 LFS 過濾,不需要額外的手工操作。

          但是,如果一個文件在沒有改 .gitattributes 之前就添加了,那它會被當作普通文件。要糾正這個,需要把文件路徑放到 .gitattributes,然后執(zhí)行:

              
          git add --renormalize .

          才能把當前目錄下的 LFS 狀態(tài)修正過來。但歷史里面的沒法改,一旦提交了,大文件就會永遠在那邊。除非用我的另一篇文章提到的方法來精簡。通過那樣的方法過濾 git 庫,刪除不小心提交的大文件非常痛苦。過程中會有很多手工操作和確認,但至少這件事情是可做的。在實際項目中,我曾經(jīng)把一個野蠻生長到 1.6GB 的 git 庫,通過去掉沒開 LFS 的情況下提交的第三方依賴和數(shù)據(jù),精簡到了 10MB,而且所有歷史記錄都在。其他 VCS 甚至不會有機會這么做,只能無限增長下去,或者砍掉一段歷史記錄。

             10.3 濫用 LFS

          另一個極端就是濫用 LFS。把所有的文件都當做大文件來添加,這樣 git repo 就表現(xiàn)成了個 svn。當然,git 相對 svn 的大部分優(yōu)點也沒了,開發(fā)效率下降5-10倍。要進一步把效率下降10倍,可以鎖上所有的文件。這樣所有人都需要 checkout 文件才能編輯。這樣的 git repo 就退化成了一個 p4 庫。(要再次把效率下降10倍,就在同個項目上混合使用 git 和 p4。可以肯定,到不了10次 commit,就會有人搞錯,把文件同時放到兩邊,造成兩邊都混亂。)

             10.4 封裝 LFS 鎖

          剛提到,LFS 鎖所有的東西可以很容易把開發(fā)效率下降2個數(shù)量級。但是,對于非編程的工作流,比如美術(shù)工作,反正是沒有 diff 的操作。這就會變成加鎖->check out->修改->提交->解鎖,和主從式 VCS 的工作流一樣。一個常見的解決方法是寫一個腳本來加鎖、擴散鎖的狀態(tài),另一個腳本來做提交、解鎖、擴散鎖的狀態(tài)。把 LFS 鎖封裝之后,工作流既可以符合美術(shù)類,也同時保持編程類工作流的效率。從另一個角度想這個問題:git 有機會封裝成同時符合編程類和非編程類工作流,保證兩邊的效率;但是 svn/p4 卻沒可能封裝成提高編程類工作流效率的。



          11



          Git 的缺點

          當然,git 不是完美的,有些地方仍然比其他 VCS 有些缺點。解決這些問題的辦法,有,但支持并不廣泛。

             11.1 缺乏分支權(quán)限管理

          Git 沒有內(nèi)建權(quán)限管理(來自于 Linus Torvalds 的設(shè)計理念)。當一個人獲得訪問 repo 的權(quán)限,所有的分支都能訪問到。有些服務(wù)通過控制“.git/refs/heads”下的文件訪問,提供了基于分支的權(quán)限管理。這就能有基本的權(quán)限管理,又不需要修改 git。

             11.2 巨型庫(單一庫)

          當 Linus Torvalds 設(shè)計 git 的時候,首要目標是支持 Linux 內(nèi)核的開發(fā),需求限于這樣的中等規(guī)模。對于一個巨大的項目,git 的性能并不好。想想在“git status”的時候,git 需要窮舉目錄下的所有文件,比較當前的和 repo 里的區(qū)別。這肯定會花不少時間。

          這幾年,git 也在這上面做了一些改進。Git 2.25里引入的部分 clone 和稀疏 checkout 可以讓你不需要把整個 repo 都 clone 或者 checkout,只要你需要的一部分子目錄就行。但這些還比較新,不是所有服務(wù)提供方都支持。

          要解決存放 Android 源代碼的需求,Google 有個工具叫“repo”。它可以管理多個 git repo,就好像一個巨大的 repo 一樣。這個工具支持 Linux 和 macOS,但是 Windows 上基本沒法用。同時,因為本質(zhì)上其實還是一堆git庫的集合,把文件從一個 git 挪到另一個,就會丟失歷史。Google 的另一個工作是 Git protocol v2。它可以加速 repo 之間傳輸?shù)乃俣取?/span>

          微軟的 Windows 長期以來一直用的 fork 的 p4,叫做 source depot(SD),作為版本控制。在2015年的某個時候,p4 已經(jīng)無法滿足現(xiàn)代的敏捷開發(fā)和協(xié)作的需求,于是考慮切換到 git。即便代價非常大(切換了一個用了20年以上的系統(tǒng),大量修改 bug 跟蹤、自動編譯、測試、部署系統(tǒng),培訓部門里的每個人,配發(fā)大容量 SSD。),也要堅持去做,因為都知道這才是未來。直接轉(zhuǎn)的話,單個 git 庫的大小是270GB,clone 一次得花12小時,checkout 花3小時,甚至連“git status”都要10分鐘,簡直沒法用。于是有人開始考慮通過引入一些主從的特性來改進 git。但因為他們對開源社區(qū)的無知,甚至連搜索一下都不,就給這個東西起名叫 gvfs(git virtual files ystem),全然不顧已經(jīng)有叫這個名字的知名項目 GNOME virtual file system。被詬病了幾年才改名叫 VFSForGit。

          它不是 git 的直接替代。首先是引入了一個新的協(xié)議,用于虛擬化 repo 里的文件。在克隆的時候,不用 git clone,而用 gvfs clone。在 .git 和工作目錄下的所有文件都只是個符號鏈接,指向服務(wù)器上的真實文件(有了中心服務(wù)器的概念),在本地硬盤上不占空間。然后有個后臺駐留程序在監(jiān)視這個虛擬化。讀文件的時候,它就把文件內(nèi)容從服務(wù)器取到本地的 cache,修改文件的時候,它就把符號鏈接替換成硬盤上的普通文件(相當于自動 checkout)。同時這個駐留程序還監(jiān)控文件讀寫的操作。如果文件沒有被寫過,就認為內(nèi)容不變。這樣就只需要比較被寫過的文件,而不是目錄下所有文件(相當于不按內(nèi)容判斷是否相同)。然而,這其實破壞了 git 的很多設(shè)計原則,以及放棄了按文件內(nèi)容決定是否發(fā)生改變的規(guī)則。顯而易見沒可能被官方的 git 采納。這些對規(guī)則的破壞,這也使得 VFSForGit 無法和很多 git GUI 很好地配合使用,包括 TortoiseGit。

          因此,微軟換了個方向,新做了一個叫做 Scalar 的系統(tǒng)。這個就不用虛擬化了,也不會改變 git 的工作流。它是以擴展的形式,優(yōu)化原有 git 的部分 clone 和稀疏 checkout,不再修改 git 的基礎(chǔ)。但它的適用性仍然是個問題。目前只有微軟 fork 的 git 和 Azure devops 支持這個。實際上 meta 和 google 也一直在等待著 git 能更好地支持單一巨型庫,并時不時嘗試從自己開發(fā)的系統(tǒng)里切換過去。

          但是隨著時間的發(fā)展,總會有更多改進被合并到官方的 git 去。這個問題會慢慢改善。對絕大部分項目來說,這些問題并不會遇到,也不會是問題。



          12



          總結(jié)

          像 git 這樣靈活的系統(tǒng),達到同個目的往往存在多條路徑。這里提到的這些 git 最佳實踐,希望能幫助朋友們找到路徑中最優(yōu)的一條。你越是了解 git,越能明白邏輯正確的版本控制應(yīng)該是什么樣的,越會支持 git 的使用。而正好相反的是 p4。你越是不了解 p4,越會支持 p4 的使用,因為它并沒有給人思考的余地,所以用再久也沒法了解什么是版本控制,也無法發(fā)現(xiàn) p4 是多么難用。(僅代表作者個人觀點)

          -End-
          原創(chuàng)作者|minmingong

          往期推薦


          阿里:寫一個倒計時功能刷掉了80% 的人
          經(jīng)五輪面試終于拿到微信的offer,卻只能無奈放棄
          萬萬沒想到,用瀏覽器打開終端竟這么容易實現(xiàn)

          最后


          • 歡迎加我微信,拉你進技術(shù)群,長期交流學習...

          • 歡迎關(guān)注「前端Q」,認真學前端,做個專業(yè)的技術(shù)人...

          點個在看支持我吧

          瀏覽 50
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  午夜大鸡巴 | 伊人操操| 一道本免费无码在线视频 | 97人妻视频 | 中文字幕成人在线观看 |