簡體   English   中英

克隆 git-svn 存儲庫會導致“消失”分支

[英]Cloning a git-svn repository leads to “disappearing” branches

前言

我們有一個大的 SVN 存儲庫(200k+ 提交和數百個分支和標簽)。 一個巨大的、不祥的、無法維護的、令人沮喪的混亂。 為了更高效地工作,大約一年前,我在我的開發機器上做了一個 git svn 克隆,所以我在 GIT 上進行本地開發,然后推送到 GIT 然后推送到 svn

我們現在正在考慮拆分存儲庫並將主要開發分支移動到 git,或者至少將我們的開發分支移動到 git。

由於我有我的本地 git 存儲庫,我想通過克隆其中的一部分來進行一些測試,並將其推送到我們公司的 GitLab,但沒有太大的成功,可能是因為我缺乏一些 Z0BCC70105AD279503EZ1FE7B7B 機制的知識

開始吧

為了在不推送整個 30GB 存儲庫的情況下進行一些快速測試,我想對本地 Git 存儲庫進行淺層克隆,並使用以下命令推送克隆:

git clone --depth=1 --no-single-branch file:///path/to/repo

我想克隆每個分支的 HEAD 修訂版,但克隆只包含主分支和我們的開發分支,沒有其他內容(我不確定標簽,我沒有檢查)。 過了一會兒,我意識到克隆只包含我們的 dev 分支,因為它是我檢查過的唯一一個(盡管 git svn 存儲庫是 SVN 存儲庫的完整克隆)。

然后我試着做一個

git clone file:///path/to/repo

我又只得到了主人和我的開發分支,沒有別的。

在這兩次嘗試中,我注意到克隆比原始的 git 存儲庫 (30GB) 小得多 (200-700MB)。 在第二次嘗試中,我期待一個與原始存儲庫相同大小的存儲庫。

所以我意識到 git 只克隆簽出的分支,而不是遠程分支(remotes/svn/*)。 為什么,因為 git svn 存儲庫是 svn 存儲庫的完整副本? 為什么不克隆所有分支? 它們在那里(否則 git svn 存儲庫不會那么大),它們只是沒有被檢出。 而且...我們如何談論“遠程”分支? 它們不是 git svn 存儲庫的一部分,應該被視為本地的嗎?

那么我如何告訴 git 在克隆 git svn 存儲庫時考慮所有這些分支? 我不想對 git svn 存儲庫中的所有分支進行大量檢查,這對我來說聽起來像是一個笨拙和凌亂的解決方案。

更新

感謝您的回復。 很抱歉沒有早點回復你,但是你給我留下了很多文件要閱讀,而且我必須自己做一些其他的研究!

所以,如果我的理解是正確的,我的 git-svn 存儲庫包含原始 svn 存儲庫的所有提交,並且它知道 svn 存儲庫包含分支和標簽,但在本地它沒有提交的 SHA1 和 svn 存儲庫之間的關聯。這是分支名稱,我必須手動添加這些關聯。

您的片段是一個非常有用的起點,謝謝!

我還發現了 clone 命令的神奇參數--mirror ,它還導入了遙控器,所以我不必接觸 git-svn 存儲庫,但后來我直接在克隆的 git 存儲庫上創建了分支。

TL; DR:您需要為要作為分支的每個分支創建實際的分支名稱。 克隆時(嗯,通常),遠程跟蹤名稱不計算在內。 這可能非常便宜。 請繼續閱讀詳細說明。

這是一種從每個refs/remotes/svn/*名稱創建本地分支的廉價方法:

git for-each-ref --format='%(refname)' refs/remotes/svn |
    while read name; do
        local=${name#refs/remotes/svn/}  # remove the icky part from the name
        [ "$local" == HEAD ] && continue
        git branch $local $name
    done

這(注意:未經測試,可能有一些小錯誤)將為那些具有相應本地分支名稱的名稱打印錯誤消息; 大概你可以忽略它。

...所以我意識到 git 僅克隆已簽出的分支,而不是遠程分支...

實際上沒有“遠程分支”之類的東西。 好吧,除非您以某種方式定義“遠程分支”。 這最終給我們留下了首先定義“分支”的問題:請參閱“分支”到底是什么意思? 當小心這一點時——與日常對話相反——我喜歡確保使用兩個詞的短語分支名稱來指代諸如master之類的名稱,這些名稱實際上已經縮短了:見下文。

Git 處理的是提交,由名稱和其他提交找到。 請參閱Think Like (a) Git以了解可達性和許多相關內容的正確定義, 1但總體思路是名稱——全名,如refs/heads/masterrefs/remotes/svn/foo foo——每個都包含hash 一次提交的 ID。 那個提交會記住哪個提交就在它之前。 這些提交——提交——記住他們的前輩提交,祖父母記住他們的前輩,等等。

git clone的作用是:

  1. 創建一個新的空目錄(或使用您告訴它使用的目錄);
  2. 在該目錄中創建一個新的空存儲庫,使用git init
  3. 添加一個remote ,它由一個簡單的名稱組成,例如origin和一個 URL (以及一些配置——這可以滑到第 4 步,或者被認為是第 3 步的一部分);
  4. 做任何額外的必要配置;
  5. 運行git fetch 最后
  6. 對您提供的名稱或其他 Git 提供的名稱運行git checkout ,或者 — 最壞的后備情況 — 嘗試使用git checkout master

此處的第 5 步對您來說是最重要的一步,因為git fetch是所有主要操作所在。

為什么不克隆所有分支?

git fetch運行時,它會從另一個Git 獲得一個列表,其中另一個 Git 告訴它它的所有名稱。 另一個 Git 會說,例如,我有refs/heads/master ,即提交a123456... 我有refs/remotes/svn/foo ,那是提交b789abc...等等。

您的Git 然后會拋出任何refs/heads/refs/tags/開頭的名稱。 生成的名稱列表是他們的 Git 的分支名稱標簽名稱 所有其他名稱都屬於其他類別。 特別是,任何以refs/remotes/開頭的名稱都是遠程跟蹤名稱2所以它會被丟棄。

您的 Git 然后向他們的 Git 詢問提交(通過 hash ID)以及使提交完整和有用所需的任何其他對象。 您的 Git 還會詢問通過標簽名稱識別的對象,只要您使用標簽即可 - 盡管在變得非常復雜時會使用哪些標簽,具體取決於git fetch選項。

一旦您的 Git 具有提交對象和其他內部對象(如果需要),您的 Git 然后將它們的分支名稱(它們的refs/heads/master等)復制到您的遠程跟蹤名稱。 他們的refs/heads/master成為你的refs/remotes/origin/master 他們的refs/heads/develop (如果存在)成為您的refs/remotes/origin/develop

所有這些都發生在git fetch步驟(步驟 5)期間。 --single-branch--no-single-branch這樣的選項會影響它們的哪個分支名稱匹配,但不會影響從分支名稱到遠程跟蹤名稱的轉換。 --mirror選項確實會影響轉換,完全消除它,但有時也會產生暗示--bare的副作用。

最后一步,第 6 步中的git checkout ,有一個非常大的副作用。 您剛剛創建的新克隆沒有分支名稱。 3所以git checkout master或任何其他名稱顯然注定要失敗,對吧? 但它不會失敗。 相反,Git 使用了一個聰明的 (?) 技巧:當您要求檢查一個不存在的分支名稱時,Git 會查看遠程跟蹤名稱,看看是否有一個匹配的名稱。 如果是這樣,Git 將使用存儲在相應遠程跟蹤名稱中的提交 hash ID創建(本地)分支名稱。

因此,這將創建您要求的任何分支 - 或者在這種情況下,由於您沒有指定一個分支,因此另一個 Git 告訴您的 Git 哪個分支名稱是另一個 Git 推薦的。 (無論如何,這通常只是master 。)第 6 步就是創建它的原因。

如果您在origin存儲庫中有標簽,那么新克隆中也會有一些標簽(介於零和全部之間)。 您可以稍后使用git fetch顯式請求標簽。 您可以明確要求在克隆時不要在新克隆中包含標簽。 您此時擁有的標簽只是從其他存儲庫中的標簽復制而來。 這里的想法是 - 與每個存儲庫完全私有的分支名稱不同 - 標簽名稱將在所有存儲庫之間共享,通過存儲庫連接傳播,幾乎就像某種病毒一樣。 4

由於您的源存儲庫大多只有遠程跟蹤名稱,而不是分支,因此您的克隆(無論是否淺)都會忽略那些名稱只能這些名稱訪問的提交。


1這與 SVN 有很大不同,其中有一個中央服務器可以簡單地按順序對每個修訂進行編號。 Git 字面上不能依賴順序編號,因為可能有單獨的克隆是順序但並行的(對於此處的非單詞道歉)獲取不同的提交。 也就是說,假設克隆 A 和 B 是相同的,並且每個都有 500 次提交。 然后,在克隆 A 中工作的 Alice 創建提交 #501。 與此同時,在克隆 B 中工作的 Bob 創建了提交 #501。 這兩個提交是不同的——可能在不同的分支上——它們都是 #501。 序號不能在這里工作。

2 Git 將此稱為遠程跟蹤分支名稱 我曾經使用過這個短語,但我現在認為這里的分支這個詞更容易誤導而不是有用。 你可以隨心所欲地調用它:只要記住它不是分支名稱,因為它們實際上以refs/heads/開頭。

注意: Git 通常在打印名稱時去掉refs/heads/refs/tags/refs/remotes/部分,假設 output 仍然足夠清晰。 有時 Git 只剝離refs/雖然:嘗試git branch -r ,然后嘗試git branch -a (為什么這些不同?這是一個謎。)

3如果您使用--mirror ,您的新克隆具有所有分支名稱,但是git clone跳過第 6 步。您的新克隆是裸露的,因此沒有工作樹,並且無法使用git checkout

4這也是提交的傳播方式。 假設您連續提交了 W、X 和 Y,而他們沒有。 您連接到他們的 Git 作為push操作,並為他們提供所有這三個提交並要求他們設置其中一個名稱來記住提交Y ,它記住X ,它記住W ,它記住他們已經擁有的提交。

或者:他們有這些提交,而你沒有。 您連接到他們的 Git 作為fetch操作,他們給您所有三個,並且您的 Git 設置您的origin/whatever記住現在提交Y

基本上,您可以獲得兩個 Git 存儲庫進行配對。 一個發送,另一個接收。 接收者得到發送者發送的所有接收者要求的新內容,即使最終接收者並不真正想要它:此時,接收者可以拒絕更新某些名稱以記住提交鏈中的最后一次提交。 接收器因此保留其舊名稱和舊 hash ID,或者沒有名稱(也沒有 hash ID)。

提交或其他 Git object 的 hash ID 無法找到它最終被垃圾收集並丟棄。 對於裸存儲庫,這往往更快,並且由於 Git 2.11,服務器“接收提交和其他 Git 對象”過程首先將它們粘在隔離區,然后再決定它們是好的並接受它們,或者決定它們是不好並拒絕他們。 然后被接受的從隔離區遷移到真正的存儲庫數據庫,被拒絕的被迅速丟棄。 Pre-2.11 接收到的對象立即進入,暫時使服務器膨脹,例如拒絕大文件(想想 GitHub 的 100MB 文件大小限制)。

淺克隆修改(部分)這些規則:使用淺克隆,接收 Git 有一個特殊文件,其中包含 hash ID。 它缺少那些實際的提交,但假裝它有它們,所以當發件人問“你有提交 X”時,答案是“是”,這樣發件人就永遠不會發送提交 X。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM