Вы начинаете с неправильного представления: Git не держит commits как deltas. Любая заданная фиксация хранит полное дерево каталогов со всеми его файлами в целом. Учитывая коммита SHA-1 ID, чтобы увидеть содержимое файла, имя которого, например, top/mid/bottom.ext
, вы:
- извлечь дерево для фиксации (путем извлечения фиксации, которая имеет SHA-1 этого дерева в ID) и найти его поддерево под названием
top
. Это включает в себя идентификатор SHA-1 этого поддерева.
- экстракт что дерево и найти поддерево с именем
mid
(что дает еще один алгоритм SHA-1)
- экстракт что дерево и найти двоичный объект (файл) с именем
bottom.ext
, который дает вам последнюю SHA-1
- экстракта blob с использованием SHA-1. Это полное содержимое файла.
Это отличается от многих других систем управления версиями, которые хранят изменения как дельта и должны восстанавливать «новый» файл (первая версия хранится напрямую, дифференцируется вперед во времени) или любой «более старый» файл (последняя версия хранится напрямую, дифференциалы перемещаются назад во времени).
Помимо этого, каждый фиксатор имеет вместе с деревом идентификатор SHA-1 набор «parent commit». Выполнение транзитивного закрытия над родителями каждого коммита создает направленный ациклический граф (или, возможно, несколько групп DAG). Края на этом графике - это то, что люди любят думать как «ветви» (хотя git вычисляет их динамически, «имя ветки» просто называет узел на графике).
Все, что сказано, когда вы делаете git push
, вы связываетесь с удаленным репо и получаете его представление о том, какие имена ветвей соответствуют идентификаторам фиксации, и предлагать, чтобы какое-либо конкретное имя (ы) идентификатор (ы).Вы также отправляете все «отсутствующие» идентификаторы SHA-1 и данные, необходимые для восстановления деревьев, файлов, тегов и/или коммитов для них. Удаленное репо рассматривает ваш запрос («пожалуйста, измените develop
с фиксацией ID 1234567, чтобы зафиксировать ID ba98765») и принимает или отклоняет его, как правило, исходя из того, добавляет ли он новые коммиты в ветку, не удаляя старые.
Если разработчик 1 сначала подталкивает и добавляет новые фиксации к ветке develop
, все идет хорошо. Затем, когда разработчик 2 толкает, ее коммиты добавляются в develop
, но они инструктируют удаленное репо удалять фиксацию разработчика 1. Когда все начиналось у них было что-то вроде этого (клонированный с центрального сервера):
...<--B<--C<--D<--E <-- develop
где B
, C
, D
и E
представляют фиксации узлов (идентифицированные этих идентификаторов SHA-1, которые являются слишком болезненным для людей, поэтому у нас есть имя develop
, чтобы отслеживать идентификатор SHA-1 для E
).
Когда разработчик 1 добавляет коммит, это становится (по его собственной репо):
...<--B<--C<--D<--E<--F <-- develop
Если он толкает это на центральный сервер, а затем добавить F
прекрасно, это новое обязательство, что является «вниз по течению ». Таким образом, сервер добавляет F
и изменяет develop
, чтобы получить этот идентификатор.
Между тем разработчик 2 добавляет коммит, но она получает это:
...<--B<--C<--D<--E<--G <-- develop
Ее SHA-1 ID не соответствует (поскольку идентификаторы SHA-1 являются глобально уникальными: они криптографической хэш коммита, включая все его деревья и файлы). Когда она пойдет на это, центральный сервер увидит, что она предлагает добавить G
, но для этого удалите F
. (Помните, что идентификатор фиксации содержит родительский идентификатор, поэтому G
должен указать на E
. Его нельзя изменить: изменение даже одного бита в любом месте фиксации или его содержимое изменяет идентификатор SHA-1.)
С обычным (не «силовым») нажатием, сервер отклонит это.
Developer 2 должен затем git fetch
(или эквивалент), чтобы забрать совершить F
, давая ей это:
...<--B<--C<--D<--E<--G <-- develop
\
`-F <-- origin/develop
(origin
быть "удаленный", что имена на центральный сервер).
Теперь ей предстоит выяснить, как объединить F
и G
. Два простых и автоматизированных альтернативы:
- перебазироваться
G
на F
- сделать «слиться совершить» объединение
G
и F
в M
перебазироваться G
на F
ей нужно только запустить git rebase
(при условии обычной настройки ветвления отслеживания).Это будет diff G
против E
(для получения delta-git не храните дельта!), Затем попытайтесь применить дельту к F
. Если автоматизированная дельта применить работы, она не получает измененную копию G
-Call него G'
:
...<--B<--C<--D<--E<--G
\
F <-- origin/develop
\
G' <-- develop
Старая G
больше не имеет метки, поэтому она отказалась и в итоге сборщиком мусора. Новый G'
является прямым потомком F
и теперь может быть нажат.
другой ее вариант, слияние, создает новое обязательство M
, выполнив стандартный трехстороннее слияние:
...-B--C--D--E--G--M <-- develop
\ /
F <-- origin/develop
Новый фиксации может быть перенесен на сервер, потому что M
имеет F
предком , поэтому на сервере сохраняется фиксация F
.
Опция принудительного толчка (вместо перезагрузки или слияния) по-прежнему является опцией, но, как правило, не является хорошей, поскольку она удаляет фиксацию F
из цепочки фиксаций на ветке, наконечник которой обозначен как develop
.
Вопрос о том, следует ли переустанавливать или объединять, является одним из предпочтений. Слияние добавляет дополнительные узлы фиксации и затрудняет просмотр того, что произошло, но главная причина, по которой это сложнее, состоит в том, что он изобразил действительно. Rebasing делает его намного проще - он «выглядит», разработчик 2 ждал, пока разработчик 1 завершит свою работу, а затем написал ее на основе его. Но это не совсем то, что произошло, и достаточно часто, фиксация временных марок покажет это.
Git делает сделать дельта-сжатия внутри, но совсем по-другому. Теоретически git может сжимать содержимое файла против сообщения в фиксации, например (или наоборот). Это дельта-сжатие сохраняет размер того, что git вызывает «пакетный» файл. Деревья могут быть сжаты против других деревьев, поэтому, если вы добавляете или удаляете один файл в большой директории, соответствующее поддерево может иметь дельта-сжатие. Для производительности и удобства объекты git сохраняются «де-deltified» как «свободные объекты» и «повторно упакованы» в новые пакеты автоматически. Свободные объекты - deflate-compressed, а сжатие также используется в упаковках.
Сбор мусора также делает переупаковку, указанную выше. Большинство коммитов сохраняются на некоторое время (по 90 дней по умолчанию) через механизм «reflog» git, который позволяет вам (1) найти идентификаторы фиксации филиала по дате и (2) восстановить фиксацию удаленных случайно, до истечения срока действия логина
О, мой бог, это так блестяще и имеет смысл. Спасибо, что пролили свет на эту проблему. – Karl
Внутренний дизайн Git действительно потрясающий. Его пользовательский интерфейс, с другой стороны ... :-) – torek
Да, нужно привыкнуть к пользовательскому интерфейсу. Но ваш ответ должен быть защищен, он так хорошо поставлен. Удивительно. – Karl