Возможно ли иметь как чистую линейную историю, как результат git rebase с последующим git merge, так и запись (в коммите) изменений, внесенных действием rebase?
Не совсем, нет.
То, что есть в Git - это коммиты. Это означает, что в большинстве случаев все Git являются коммитами. Я сейчас произнесу слово , в основном .
Каждый коммит имеет свой уникальный хэш-идентификатор. Этот хэш-идентификатор действует как истинное имя коммита;все другие методы поиска фиксации, как правило, заключаются в поиске одного истинного хеш-идентификатора и последующем использовании его для поиска фиксации.
Имя ветви, например master
или dev
, или удаленное отслеживаниеимя типа origin/master
или origin/dev
, содержит хэш-идентификатор одного коммита.
Каждый коммит содержит хэш-идентификатор некоторого числа непосредственных предшествующих коммитов: родителей этого коммита. Большинство коммитов содержат один родительский хэш-идентификатор. Коммиты слияния удерживают два или более, что делает их коммитами слияния. По крайней мере, один коммит - самый первый, который кто-либо сделал в этом хранилище - обязательно не имеет родителей. Этот коммит (обычно "the") root commit. Git использует родительский хеш tip коммита ветки, чтобы найти предыдущий коммит в ветке, затем продолжает обратно, по одному коммиту за раз, чтобы добраться до корня: в этом процессе пройденные коммиты, - это коммиты, которые находятся в ветви.
Несколько имен веток могут указывать на несколько коммитов-подсказок, которые в конечном итоге достигают одного и того же корня. Набор начальных точек, а также все внутренние соединения с помощью родительских хэшей составляют граф фиксации.
Каждый коммит хранит полный и полный снимок всех файлов, которые составляют этот коммит. (Внутренние элементы здесь на самом деле не имеют отношения к вашей проблеме; факт, что коммит хранит, косвенно, только один снимок.) Он также хранит метаданные для этого коммита: кто его сделал (автор и коммиттер, каждый из которых состоит изимя + электронная почта + дата и время), родительские хэш-идентификаторы и сообщение журнала. Вот и все! Это все, что есть. Git находит изменения , сравнивая смежные коммиты. Что бы ни отличалось в двух снимках, это должно быть то, что изменилось .
Когда вы используете git rebase
, вы действительно говорите Git: копировать некоторые коммиты. оригинальная цепочка коммитов может выглядеть так:
I--J--K <-- feature
/
...--G--H--L <-- master
Ветвь feature
состоит из коммита K
(коммит-подсказка, найденная по имени feature
, в которой хранится фактический хэш-идентификатор, который мы находимсявызов K
здесь), перед которым J
предшествует I
, перед которым H
, и так далее. Ветвь master
состоит из коммита L
, затем H
, затем G
и т. Д., Таким же образом.
Такая структура потребует истинного слияния для повторного объединения feature
в master
, поэтому ваша группа использует rebase. Rebase означает: Найти коммиты, уникальные для feature
: те, которые не могут быть найдены, начиная с кончика master
и работая в обратном направлении. Затем скопируйте большинство или все из них, создав новые копии, превратив каждый коммит в изменения, и применив эти изменения к новой начальной точке. В этом случае Git должен скопировать коммиты I
, J
,и K
, чтобы новые копии появлялись после L
.
Каждый шаг копирования по сути совпадает с git cherry-pick
, а некоторые команды git rebase
действительно буквально используют git cherry-pick
для выполнения копирования,Вы можете думать о каждой копии как о вишне. Мы будем обозначать скопированные коммиты простой меткой, например, I'
:
I--J--K <-- feature
/
...--G--H--L <-- master
\
I'-J'-K' <-- [new feature, being built]
Теперь, когда перебазирование завершено, Git просто вытаскивает имя feature
из старого совета и делает егоуказать на новый совет:
I--J--K [abandoned]
/
...--G--H--L <-- master
\
I'-J'-K' <-- feature
WhЕсли вы посмотрите на этот репозиторий позже, не зная, какими были исходные три хеш-идентификатора, вы не сможете найти исходные три. Вы можете даже не знать, что оригинальные три коммита когда-либо существовали. Если тот, кто делал копирование, не передавал оригиналов , а только когда-либо передавал копий , они будут всем, что вы когда-либо увидите. И, поскольку оригиналы больше не доступны для поиска, Git в конечном итоге удалит их полностью («соберите мусор» с помощью git gc
). С таким же успехом они могли бы никогда не существовать. Только если вы сохраните где-нибудь хэш-идентификаторы, чтобы сам Git не собирал оригиналы GC, вы сможете их увидеть и увидеть. А так как Git хранит только снимков , вам нужно оригиналов, чтобы увидеть что произошло между оригиналами и их копиями.
Обращаясь к "в основном "выше" и другие вафельные биты
Git имеет:
Теги (аннотированные теги): они хранят данные. Данные тегов, которые вы можете хранить здесь, являются произвольными и зависят от вас. Если бы вы могли заставить своих пользователей помещать важные данные в аннотированный тег, а также прикреплять и вставлять этот тег, это сохранит его. Но для этого необходимо, чтобы они поместили важные данные куда-то.
Примечания: git notes
позволяет пользователям записывать произвольные дополнительные данные и свободно связывать эти данные с фиксацией (по хэш-идентификатору фиксации). Если бы вы могли заставить своих пользователей помещать важные данные в заметки и прикреплять эти заметки к скопированным коммитам, это сохранит их.
Фиксация сообщений. Мы уже упоминали об этом, но они позволяют пользователям хранить произвольные данные. Если бы вы могли заставить своих пользователей помещать важные данные в сообщения коммита перебазированных коммитов, это сохраняло бы их.
Но все эти методы требуют, чтобы ваши пользователи генерировали важные данные, например, путем различий оригинальных коммитов с копированными, сделанными перебазированием. Вы могли бы дать им инструмент для этого, но если они достаточно небрежны, чтобы вносить ошибки в ребазинге, они могут быть достаточно небрежными, чтобы не использовать инструмент - и написать хороший инструмент, который мог бы сделать это некоторым удобным способом, довольно сложно.
Я уже упоминал выше, что ребаз копирует "большинство или все" коммиты. Rebase пропускает:
- все коммиты слияния (по умолчанию) и
- любой коммит, который Git считает уже существующим в восходящем потоке. 1
По крайней мере, он делает это по умолчанию: -p
, а новые опции --rebase-merges
позволяют «копировать» слияния (действительно, заново выполняя слияние).
1 Git делает это, сравнивая git patch-id
, что приводит к симметричной разности перебазируемой ветви и переброски вверх по течению. Технически это означает, что Git использует эквивалент git rev-list --left-right HEAD...upstream
(обратите внимание на трехточечный синтаксис) для генерации списков коммитов. Затем он запускает эквивалент git patch-id
для всех из них. Затем он сравнивает левый и правый идентификаторы патчей коммитов. Коммиты со стороны HEAD
, то есть слева, которые соответствуют коммиту в правой / восходящей стороне, выбиваются. Идея здесь состоит в том, чтобы исключить вишневый коммит, который уже был вишнево выбран в восходящем потоке.
Заключение
То, к чему это все сводится, это:
- Не позволяйте пользователям, которые регулярно неправильно разрешают конфликты, перебазировать ветки.
Некоторые несчастные случаи произойдут независимо от того, что вы делаете: все в конце концов делают «ошибки», и эти новыеразрешение конфликта, несомненно, будет иметь больше проблем, чем старые руки. Так что не будьте слишком жесткими с этим правилом ... но, в конце концов, если вы не можете доверять кому-то, чтобы он не разрушил оригинальные коммиты при использовании rebase, чтобы превратить их в новые и улучшенные замены, которые заставят всех забыть оригиналы, не заставляйте их делать это вообще.
Git вполне способен справиться с запутанной историей:
I--J--K
/ \
...--G--H--L------M--Q--R <-- master
\ /
N--O--P
может быть более запутанным для людей, но Git будет справляться с этим.