Как сделать источник открытым, когда git история включает в себя личную информацию? - PullRequest
0 голосов
/ 05 февраля 2020

Мы удалили личную информацию из нашей программы и добавили эти файлы в наш git файл игнорирования. Мы хотим, чтобы наше репо публиковалось c сейчас, но я боюсь, что посетители смогут восстановить конфиденциальную информацию из истории git. Какое решение?

1 Ответ

2 голосов
/ 05 февраля 2020

Как все говорили в комментариях, вы хотите переписать историю. Обычный инструмент для этого - git filter-branch, который немного сложен в использовании, потому что у него так много опций. Посмотрите любое количество существующих публикаций StackOverflow, чтобы узнать о многих способах его использования (и некоторых альтернативах).

Что такое переписывание истории о

Помните, что репозиторий Git это в основном две базы данных:

  • Большая база данных состоит из Git объектов . Существует четыре вида объектов, о которых мы подробнее расскажем ниже. Каждый объект имеет свой уникальный идентификатор ha sh, который указывает c для этого одного объекта.

  • Меньшая база данных состоит из имен: имен ветвей, имен тегов и других подобных имен , Каждое имя содержит один объект ha sh ID.

Клонирование репозитория Git состоит из копирования некоторых или всех его объектов из большой базы данных, что можно найти при поиске ha sh идентификаторы в меньшей базе данных; и копирование некоторых имен из небольшой базы данных.

History в репозитории Git - это просто коммит объектов в этом репозитории. В зависимости от того, насколько щедрым вы хотите быть с определением, вы также можете добавить аннотированных теговых объектов к этому. Имена, такие как имена веток и тегов, позволяют находить коммиты. Аннотированные объекты тегов позволяют находить коммиты. Коммиты позволяют вам находить коммиты ... и это почти все: вы начинаете - вы находите коммит с sh ID - начиная с имени. Вам также нужно имя, чтобы найти аннотированный теговый объект, поэтому даже если мы используем расширенное определение, вы начинаете с имени.

Четыре типа объекта

Итак, теперь давайте посмотрим на четыре типа объектов. Это:

  • Аннотированные метки. Мы уже упоминали аннотированные объекты тегов. Они содержат ваше сообщение тега и, возможно, ключ подписи GPG или аналогичный, а также целевой объект тега, имеющий sh ID. Обычно это будет идентификатор коммита, хотя здесь разрешен любой из четырех типов объектов.

  • Коммит объектов. В коммите содержится метаданных , то есть информация о коммите, например, кто его сделал и когда, и их сообщение журнала, а также идентификатор ha sh дерева дерева объект. Объект дерева представляет данные в go с коммитом: снимок. Другими словами, вместо удержания снимка напрямую, фиксация содержит только га sh ID снимка. Это означает, что если два коммита содержат одно и то же дерево исходных текстов, они могут делиться им - есть только один снимок.

    Каждый коммит также может перечислять идентификатор ha sh одного или нескольких предшественников («родитель») совершает. Это то, где история действительно живет; мы вернемся к этому через мгновение.

  • Объекты дерева. Мы упоминали об этом чуть выше. Они содержат небольшие структуры, каждая из которых состоит ровно из трех значений:

    • a mode , что представляет собой значение цифры c из небольшого набора допустимых значений;
    • a name , которое является именем компонента, например file.c или subdir; и
    • a га sh ID .


    Идентификатор ha sh в некоторых случаях является идентификатором другого дерева или blob объект в большинстве других случаев. (Оставшийся случай заключается в том, что они могут содержать идентификатор ha sh некоторого коммита в некотором другом репозитории, который является особым случаем, называемым gitlink , разрешенным только тогда, когда режим установить 160000. Вот как работают подмодули: коммит суперпроекта содержит коммит репозитория подмодуля ha sh ID, в некотором объекте дерева.)

  • Последний тип объекта объект blob . Он содержит данные файла или - для записи дерева символов c (режим 120000) - имя файла, которое является целью ссылки.

Следовательно, объекты часть репозитория Git - это место, где хранятся все ваши файлы. Каждая зафиксированная версия каждого файла появляется в этой базе данных в виде больших двоичных объектов, которые перечислены в деревьях, которые перечислены в коммитах, которые перечислены в других коммитах. Время от времени - редко или никогда - большой двоичный объект или дерево перечисляются непосредственно по объекту тега или имени тега, и нередко идентификатор ha sh ID указывается непосредственно по объекту тега или имени ветви.

Объединение двух баз данных приводит к полезному хранилищу

Имя ветви по определению содержит идентификатор ha sh последнего коммита в ветви. Оттуда Git находит каждый более ранний (родительский) коммит. Это создает трассировку через часть фиксации базы данных объекта.

В имени тега обычно указывается либо объект тега, либо коммит. «Снятие» тега путем поиска лежащего в его основе коммита приводит вас к коммиту. У этого коммита есть родительский (-ые) родительский (-ые) родительский (-ые) родительский (-ые) родительский (-ые) родительский (-ые) домен (-ы), и, следуя им, так же, как вы делаете это с именами ветвей, создается трассировка в коммит-части базы данных объектов.

Выполнение этого процесса для каждое имя "достигает" некоторого набора коммитов. Любые оставшиеся коммиты в базе данных объектов по определению недоступны . Достижимые коммиты - это те, которые git clone скопирует; недоступные будут выброшены. 1

Вы можете удивиться, почему я продолжаю упоминать клон здесь; мы вернемся к этому в следующем разделе.


1 Здесь есть некоторая суета с reflogs. Каждое имя имеет или может иметь reflog , Reflogs имеют отметки даты и времени; каждая запись хранится как хах sh ID. Запуск git clone не копирует и не использует reflogs, но git gc использует их, чтобы не выбрасывать вещи слишком быстро. Записи reflog позволяют в противном случае мертвым объектам - обычно зафиксированным - сохраняться, так что вы можете вернуть их к жизни как минимум на 30 дней по умолчанию. Мы уже знаем, что имя ссылки, такое как имя ветви, содержит идентификатор объекта ha sh. Имена ветвей регулярно обновляются для хранения новых га sh идентификаторов, когда мы, например, делаем новые коммиты. В это время Git записывает old значение имени в reflog ветви.

(Тег, аннотированный или нет, который идет непосредственно в объект дерева или блоб, сохраняет этот объект тоже жив. Как правило, у вас нет тегов для объектов дерева или больших объектов. Кроме того, записи в index будут поддерживать живые объекты BLOB-объектов, так как именно здесь у вас есть файлы, * git add -ed но еще не git commit -эд сохранен. Однако ни один из них не клонируется.)


История перезаписи - о копировании коммитов

Нет коммита - фактически, нет Git объект любого типа - может быть изменен, ни одного бита. Причина этого заключается в том, что идентификатор ha sh объекта представляет собой (cryptographi c) контрольную сумму содержимого объекта. Измените один бит, и вы получите новый, другой объект с другой контрольной суммой. 2

Чтобы «переписать историю», это именно то, что мы хотим: мы go через все достижимые коммиты в хранилище. Для каждого такого коммита мы решаем: Копировать этот коммит или нет? Для каждого, где мы решаем, что ответом является: Да, копировать его, мы также решаем: Внесите какие-то изменения, пока мы это делаем, или нет?

Если копия, которую мы делаем, является бит-за-битом, идентичной оригиналу, то копия будет оригиналом , Он остается неизменным, и мы фактически просто повторно используем исходный коммит. Но если мы изменим что-нибудь - включая снимок - мы получим новый, другой коммит с новым уникальным идентификатором ha sh. Удостоверившись, что вы копируете коммиты в правильном порядке - начиная с самого первого из когда-либо сделанных коммитов и работая вперед, вместо предпочтительного обратного порядка Git - мы гарантируем, что когда мы не будем копировать коммит , позднее коммиты будут использовать другой набор родительских идентификаторов ha sh, и мы скопируем эти более поздние коммиты в новые и улучшенные коммиты, у которых есть новая и улучшенная история.

Этот процесс, вероятно, лучше всего рассматривать на примере. Предположим, у нас есть эта существующая история:

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

как полный набор коммитов в базе данных объектов, с одним именем master, находящим last commit, 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', затем go вернемся к фиксации A.

А как насчет этого другого прикольного имени, этого refs/original/refs/heads/master? Это имя и пометки, упомянутые в сноске 1, позволят нам увидеть оригинальную историю. Но это имя не копируется git clone, и также не являются reflogs. Само название в стиле фанк является побочным продуктом git filter-branch, который сохраняет исходные имена под этим новым refs/original/ набором имен, когда мы говорим ему скопировать master и удалить или изменить некоторые коммиты по пути.

Таким образом, использование git filter-branch для «переписывания» истории действительно означает: Приблизительно удваивает размер моей базы данных репозитория, копируя большинство коммитов, изменяя что-то о них. Новые и улучшенные копии живут рядом с оригиналами. Они могут даже разделить несколько коммитов, относящихся к самой ранней части истории, в зависимости от того, что вы решите скопировать и что вы решите изменить.

Если две истории ничего не делят, ваша новая история остается неизменной. один. Если они что-то делят, ваша новая история так же чиста, как вы решили сделать это: она разделяет только первый (самый ранний в истории) коммит, который, когда вы копировали, вы сказали, что оставьте их в покое, они хороши так же, как и .

Теперь вы готовы использовать git clone до copy скопированных коммитов. Поскольку git clone игнорирует имена refs/original/ и игнорирует повторные журналы, то, что вы получаете, когда копируете текущую версию репозитория в новую, это:

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

(при условии, что вы этого не сделали попросите фильтр-ветку оставить A; если вы это сделали, вставьте A слева). Имя master появляется здесь только потому, что git clone само создало его после копирования репозитория в новую пару баз данных. Все имена веток из вашего исходного репозитория были заменены на origin/<em>whatever</em> обычным способом для любой git clone.


2 Часть "cryptographi c" это просто означает, что очень сложно спроектировать столкновение ха sh. Ха sh столкновения приводят к Git надуванию и отказу от создания нового объекта, или, по крайней мере, теоретически, это то, что должно произойти. На практике столкновения ха sh никогда не происходят. См. Также Ха sh столкновение в git.

...