[英]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。 這就是歷史真正存在的地方; 我們稍后會回到這個話題。
樹對象。 我們在上面提到了這些。 它們包含小型結構,每個結構都精確地包含三個值:
file.c
或subdir
; 和
在某些情況下,哈希 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
原樣,保留提交J
和K
和M
,將D
放到L
( J
和K
除外)完全,保留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.