Когда вы переписываете историю таким образом, вы можете - и ваш случай это сделал - получить то, что составляет новый и другой репозиторий .В этом случае все существующие клоны старого репозитория могут использоваться только со старым репозиторием .Теперь вы просто создаете новые клоны из нового репозитория, который является новым проектом, который вы никогда не должны подключать к старому проекту: эти два больше не совместимы, и коммиты больше не могут передаваться из одного в другой.
Это упрощенный взгляд на сложную реальность, но этого должно быть достаточно для вашего случая.Если вы хотите понять реальность, читайте дальше.
Git в основном заботится о коммитах, но что такое коммит?
Суть репозитория Git - это пара баз данных.Большая база данных - это та, которая содержит все коммиты или, точнее, все объекты Git.(Существует четыре типа объектов Git: коммит, дерево, блоб и аннотированный тег. Деревья и блобы - это то, как коммиты хранят файлы внутри себя, в то время как аннотированные объекты тегов предназначены только для хранения аннотированных данных тегов.) Каждый уникальный объект Git имеет уникальныйхэш-идентификатор, поэтому каждый коммит имеет свой уникальный хэш-идентификатор, отличный от любого другого коммита.
Мало того, что все эти хэш-идентификаторы уникальны, они также универсальные .(Это глобально универсальные идентификаторы или GUID, также называемые UUID .) Это означает, что каждый Git во всем юниверсе использует одинаковый GUID для этого коммита.
Git на самом деле достигает этого, это то, что ID является криптографической контрольной суммой content коммита.Это означает, что буквально невозможно изменить что-либо в коммите: если вам действительно удастся что-то изменить, то вы получите новый и другой коммит с новым и другим хеш-идентификатором.Учитывая идентификатор хэша, Git может проверить, есть ли у него объект.Если это так, он может получить объект.Если нет, ваш Git может запросить у другого Git (у которого есть объект) полный объект и вставить полученный объект в его большую базу данных.
Всякий раз, когда у нас есть хэш-идентификатор и фактический объект находится вВ базе данных мы говорим, что у нас есть указатель на объекта.Эти указатели позволяют нам находить коммиты (или другие объекты Git, но в основном мы работаем с коммитами).
В любом случае реальное содержимое коммита обычно довольно короткое: каждый коммит содержит хеш-идентификаторснимок файлов для этого коммита - это данные, которые вы хотите сохранить навсегда - плюс набор метаданных , таких как ваше имя и адрес электронной почты.Тем не менее, одним из фрагментов метаданных для каждого коммита является хеш-идентификатор коммита parent (или множественного числа, если коммит является коммитом слияния).Таким образом, каждый коммит указывает на своего родителя по хэш-идентификатору.
Мы можем нарисовать это, и если мы будем использовать одну заглавную букву для замены коммитов, это даже выглядит довольно разумно.(Конечно, мы быстро исчерпаем буквы, поэтому Git использует эти большие уродливые хеш-идентификаторы.) Вот пример репозитория с master
и восемью коммитами, чьи хеш-идентификаторы от A
до H
:
A <-B <-C ... <-F <-G <-H <--master
последний коммит в ветви с именем master
имеет хэш-идентификатор H
.Commit H
сам хранит хэш-идентификатор commit G
, в котором хранится идентификатор F
, и так далее.В конце концов мы работаем вплоть до самого первого коммита, коммита A
.У него нет родителя, потому что он не может иметь его: это был первый коммит.Это позволяет нам (и Git) остановиться.
НетТо есть Git должен работать назад все время. Мы всегда начинаем с конца - tip commit некоторой ветви - как найдено по некоторому имени ветви. Следовательно, вторая меньшая база данных Git представляет собой таблицу имен - имен веток, имен тегов и других ссылок , каждая из которых содержит ровно один хэш-идентификатор. Когда ссылка является именем ветви, идентификатор хеша - это фиксация, и, следуя всем стрелкам, направленным назад, мы находим все коммиты, которые достижимы из ветви.
Когда мы создаем новую ветку, мы просто создаем новое имя , которое указывает на какой-то существующий коммит:
...--F--G--H <-- master, develop
Теперь обе ветви указывают на фиксацию H
. Мы выбираем одну ветку для включения и используем git checkout
, чтобы прикрепить нашу HEAD
к ветви:
...--F--G--H <-- master, develop (HEAD)
Теперь мы можем сделать новый коммит обычным способом. Когда мы это делаем, Git упаковывает все наши файлы, присоединяет наши метаданные - наше сообщение журнала, имя, адрес электронной почты, отметку времени и т. Д. - и записывает новый коммит. Родителем нового коммита является текущий коммит H
. Данные нового коммита хешируются в какую-то большую уродливую строку случайного вида, которая отличается от всех остальных коммитов, но мы просто вызовем I
:
...--F--G--H <-- master, develop (HEAD)
\
I
и теперь происходит действительно умный бит. Теперь Git записывает хэш-идентификатор I
в любое имя ветви HEAD
, к которому прикреплено:
...--F--G--H <-- master
\
I <-- develop (HEAD)
Если мы вернемся к master
и сделаем там новые коммиты, две ветви расходятся.
фильтр-ветвь копирует коммиты
Что делает git filter-branch
, так это перечисляет каждый коммит - или каждый коммит из некоторого подмножества, в зависимости от ваших параметров - и начинает извлекать каждый из них, запуская указанные вами фильтры - плюс еще один, хотя вы также можете указать и этот. - и делать новые коммиты из результата. Всякий раз, когда фильтр изменяет что-либо, по определению новый коммит не будет побитовым идентичным старому, поэтому он получит другой хэш-идентификатор. 1 дополнительный фильтр - тот, который делает новые коммиты, и он автоматически заменяет родительский хэш-идентификатор на результат от внесения любых предыдущих изменений. Итак, предположим, у вас есть:
D--E <-- master
/
A--B--C
\
F--G <-- feature
и ваш фильтр изменяет информацию об авторе. Фиксация A
становится новой фиксацией A'
:
D--E <-- master
/
A--B--C
\
F--G <-- feature
A' [in progress]
Теперь фильтрующая ветвь должна копировать B
. Даже если ваш фильтр изменяет без , новый коммит должен иметь A'
в качестве родителя, а не A
, поэтому конечный создатель изменений меняет родительский хеш (и, возможно, более ранний фильтр меняет автора информация тоже) и мы получаем:
D--E <-- master
/
A--B--C
\
F--G <-- feature
A'-B' [in progress]
Это повторяется до E
и G
:
D--E <-- master
/
A--B--C
\
F--G <-- feature
D'-E' <-- (replacement for master)
/
A'-B'-C'
\
F'-G' <-- (replacement for feature)
Как только git filter-branch
проходит через каждый коммит, он заменяет имя: он вставляет идентификатор E'
в master
и идентификатор G'
в feature
, и теперь ваша база данных имен больше не помнит оригинальные E
и G
, и все, на что вы смотрите, будет начинаться с E'
или G'
. Эти новые и (по крайней мере, предположительно) улучшенные коммиты - это те, которые вам нужны; Вы хотите забыть старые.
Старые коммиты все еще там - и фактически ветвь фильтра копирует исходную master
ссылку, например, на refs/original/refs/heads/master
, но новый набор коммитов - это новый репозиторий. Клонирование этого хранилища не копирует оригиналы, а только достижимые новые и улучшенные коммиты. Удаление имен refs/original/
в конечном итоге приведет к тому, что Git соберет мусор для старых коммитов (обычно через 30 дней, хотя именно через какое время, зависит от множества других факторов).
1 Если фильтры делают буквально нет изменений, новый коммит будет бит-за-битом идентичен оригиналу и, следовательно, имеет исходный коммит ID хеша и буквально является исходным коммитом. Но последний фильтр, который сам выполняет коммит, часто что-то меняет.