簡體   English   中英

當git歷史包含私人信息時如何使源代碼開放?

[英]How to make source open when git history includes private information?

我們已經從我們的程序中刪除了私人信息,並將這些文件添加到我們的 git ignore 文件中。 我們現在想公開我們的 repo,但恐怕訪問者可以從 git 歷史記錄中恢復機密信息。 解決辦法是什么?

正如每個人在評論中所說的,你想改寫歷史。 常用的工具是git filter-branch ,它使用起來有點復雜,因為它有很多選項。 查看任意數量的現有 StackOverflow 帖子,了解使用它的多種方法(以及一些替代方法)。

歷史改寫是關於什么的

請記住,一個 Git 存儲庫主要是兩個數據庫:

  • 大數據庫由 Git對象組成。 有四種對象,我們將在下面詳細介紹。 每個對象都有自己唯一的哈希 ID,特定於該對象。

  • 較小的數據庫由名稱組成:分支名稱、標記名稱和其他此類名稱。 每個名稱都包含一個對象哈希 ID。

克隆 Git 存儲庫包括從大數據庫中復制部分或全部對象,如通過在較小數據庫中查找哈希 ID 所發現的那樣; 並從較小的數據庫中復制一些名稱。

Git 存儲庫中的歷史記錄只是該存儲庫中的提交對象 根據您希望定義的慷慨程度,您還可以向其添加帶注釋的標簽對象 名稱,如分支和標簽名稱,可讓您找到提交。 帶注釋的標記對象可讓您找到提交。 提交讓你可以找到提交……差不多就是這樣:你開始——你找到一個提交對象哈希 ID——從一個名字開始。 您還需要一個名稱來查找帶注釋的標簽對象,因此即使我們使用擴展定義,您也需要從名稱開始。

四種對象類型

所以,現在讓我們看看這四種對象類型。 這些是:

  • 帶注釋的標簽。 我們已經提到了帶注釋的標簽對象。 它們包含您的標簽消息,可能還有 GPG 簽名密鑰或類似密鑰,以及標簽目標對象哈希 ID。 通常這將是提交的 ID,盡管此處允許使用四種對象類型中的任何一種。

  • 提交對象。 提交包含元數據,它是關於提交的信息,例如提交者、時間和他們的日志消息,以及對象的哈希 ID。 樹對象代表提交的數據:快照。 換句話說,提交只保存快照的哈希 ID ,而不是直接保存快照。 這意味着如果兩個提交擁有相同的源代碼樹,他們可以共享它——只有一個快照。

    每個提交還可以列出一個或多個前驅(“父”)提交的哈希 ID。 這就是歷史真正存在的地方; 我們稍后會回到這個話題。

  • 樹對象。 我們在上面提到了這些。 它們包含小型結構,每個結構都精確地包含三個值:

    • 一個mode ,它是一小組允許值中的數值;
    • 一個名稱,它是一個組件名稱,如file.csubdir
    • 一個哈希 ID


    在某些情況下,哈希 ID 是另一棵樹的 ID,或者在大多數其他情況下是blob對象的哈希 ID。 (剩下的情況是他們可以在其他存儲庫中保存某個提交的哈希 ID,這是一種稱為gitlink的特殊情況,僅當模式設置為160000時才允許。這就是子模塊的工作方式:超級項目提交持有一個子模塊存儲庫的提交哈希 ID,在某個樹對象中。)

  • 最后一種對象是blob 對象 它保存文件的數據,或者——對於符號鏈接(模式120000 )樹條目,作為鏈接目標的文件名。

因此,Git 存儲庫的對象部分是存儲所有文件的地方。 每個文件的每個提交版本以 blob 的形式出現在此數據庫中,這些 blob 列在樹中,這些樹列在其他提交中列出的提交中。 偶爾——很少或從來沒有——一個 blob 或樹直接按標簽對象或標簽名稱列出,而不那么偶爾,提交哈希 ID 直接按標簽對象或分支名稱列出。

結合兩個數據庫會產生一個有用的存儲庫

根據定義,分支名稱包含分支中最后一次提交的哈希 ID。 從那里,Git 找到每個較早的(父)提交。 這會通過對象數據庫的提交部分生成跟蹤。

標簽名稱通常列出標簽對象或提交。 通過查找其底層提交來“剝離”標簽會引導您進行提交。 該提交具有它所擁有的任何父項,並且按照與分支名稱相同的方式,通過對象數據庫的提交部分生成跟蹤。

每個名稱進行此過程“到達”某些提交集。 根據定義,對象數據庫中的任何剩余提交都是無法訪問的 可到達的提交是git clone將復制的提交; 無法到達的將被丟棄。 1

你可能想知道為什么我一直在這里提到克隆 我們將在下一節中討論。


1這里有一些關於reflogs 的麻煩。 每個名字都有,或者可以有,一個 reflog。 Reflogs 有時間和日期標記的條目; 每個條目存儲為哈希 ID。 運行git clone不會復制或使用引用日志,但git gc使用它們來避免過快扔掉東西。 reflog 條目讓原本已死的對象(通常是提交)保持不變,因此默認情況下您可以使它們恢復活力至少 30 天。 我們已經知道一個引用名稱——比如一個分支名稱——包含一個對象的哈希 ID。 例如,當我們進行新的提交時,分支名稱會定期更新以存儲新的哈希 ID。 這時,Git 將名稱的值寫入分支的 reflog。

(一個標記,無論是否帶注釋,直接進入樹或 blob 對象,也使該對象保持活動狀態。但通常情況下,您沒有樹或 blob 對象的標簽。此外,索引中的條目將使 blob 保持活動狀態, 因為那是你有git add -ed 但還沒有git commit -ed 的文件被存儲的地方。雖然這些都不會被克隆。)


重寫歷史是關於復制提交

沒有提交——事實上,任何類型的 Git 對象——都不能改變,一點也不能改變。 這樣做的原因是對象的哈希 ID 是對象內容的(加密)校驗和。 改變一點,你得到的是一個新的、不同的對象,具有不同的校驗和。 2

為了“重寫歷史”,這正是我們想要的:我們遍歷存儲庫中所有可訪問的提交。 對於每個這樣的提交,我們決定:復制這個提交,還是不? 對於每一個我們決定答案是:是的,復制它,我們也決定:在我們做的時候做一些改變,還是不做?

如果我們制作的副本與原始副本逐位相同,那么副本就是原始副本。 它保持不變,我們實際上只是重新使用原始提交。 但是如果我們改變任何東西——包括快照——我們會得到一個新的、不同的提交,帶有一個新的唯一哈希 ID。 通過確保以正確的順序復制提交——從有史以來的第一次提交開始並向前工作,而不是 Git 首選的向后順序——我們確保當我們復制提交時,以后的提交將使用不同的父級散列 ID,我們將這些后來的提交復制到新的和改進的提交,這些提交背后有新的和改進的歷史。

最好通過示例來查看此過程。 假設我們有這個現有的歷史:

A--B--C--D--E--H--I--L--M--N--O--P   <-- master
       \               /
        F--G-------J--K

作為對象數據庫中的整個提交集,一個名稱master查找最后一個提交P 我們將進行復制,在復制期間,我們將保留提交B但將其更改為刪除文件,保持提交C原樣,保留提交JKM ,將D放到LJK除外)完全,保留N ,刪除O ,並保留P 生成的副本如下所示:

A--B--C--D--E--H--I--L--M--N--O--P   <-- refs/original/refs/heads/master
       \               /
        F--G-------J--K

B'-C'-----M'-N'-P'   <-- master
    \    /
     J'-K'

我們刪除了A ,因此我們必須以兩種方式更改B :新副本沒有父副本,並且省略了我們不想要的文件。 這意味着我們必須復制C以僅以一種方式更改它:副本將B'作為其父項。 我們必須將J復制到J'才能使用C'作為父級; 我們必須同樣將K復制到K' 我們必須將合並提交M復制到M'以使其具有C'K'作為其兩個父項,依此類推。

復制選定的提交后,在此過程中進行了一些更改,我們讓 Git 存儲庫更改名稱master以指向新的提交P' 請注意,通過從master開始並向后工作,我們永遠不會訪問任何原始提交 但是,如果我們保持A不變,我們將有這個:

A--B--C--D--E--H--I--L--M--N--O--P   <-- refs/original/refs/heads/master
 \     \               /
  \     F--G-------J--K
   \
    B'-C'-----M'-N'-P'   <-- master
        \    /
         J'-K'

也就是說,我們只會以一種方式更改B ,以刪除不需要的文件。 我們仍然有B'但它會指向現有的提交A ,並且從master開始,我們只會訪問新副本,直到到達B' ,然后返回提交A

另一個時髦的名字,這個refs/original/refs/heads/master怎么樣? 這個名字——以及腳注 1 中提到的 reflogs——會讓我們看到最初的歷史。 但是名稱不會被git clone ,引用日志也不會。 時髦的名稱本身是git filter-branch的副產品,當我們告訴它復制master並刪除或修改一些提交時,它將原始名稱保存在這個新的refs/original/名稱集下。

因此,使用git filter-branch來“重寫”歷史實際上意味着:通過復制大多數提交,同時更改有關它們的某些內容,我的存儲庫數據庫的大小大約增加了一倍。 新的和改進的副本與原件相鄰 他們甚至可能共享一些提交,在歷史的最早部分,這取決於您選擇復制的內容和您選擇更改的內容。

如果這兩個歷史記錄沒有共享任何內容,則您的新歷史記錄是獨立的。 如果他們確實共享了某些內容,那么您的新歷史記錄與您選擇的一樣干凈:它僅共享第一個(歷史上最早的)提交,在復制時,您說不要管這些,它們仍然很好

您現在已准備好使用git clone復制復制的提交。 由於git clone忽略refs/original/名稱,並忽略 reflog,因此將當前版本的存儲庫復制到新版本時會得到以下結果:

B'-C'-----M'-N'-P'   <-- master (HEAD), origin/master
    \    /
     J'-K'

(假設您沒有告訴 filter-branch 保留A ;如果您這樣做了,請在左側插入A )。 名稱master出現在這里只是因為git clone在將存儲庫復制到新的數據庫對后自己創建了它。 原始存儲庫中的分支名稱已全部替換為origin/ whatever ,這是任何git clone的常用方式。


2這其中的“加密”部分僅意味着設計散列沖突非常困難。 哈希沖突導致 Git 撅嘴並拒絕創建新對象,或者至少在理論上,這是應該發生的。 實際上,哈希沖突從未真正發生過。 另請參閱git 中的哈希沖突

暫無
暫無

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

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