Как создать правильный коммит слияния в git, который также раздавлен - PullRequest
0 голосов
/ 19 февраля 2020

Я пытаюсь решить проблему с моей git историей.

У меня есть две ветви, давайте назовем их a и b, a - это ветка-источник для моего репозитория. и b был разветвлен. Многие слитые запросы на получение слияния (смесь коммитов слияния и сдавленных коммитов) происходят с a, и b не перебазируется, и эти PR повторно не открываются против b. Чтобы обновить b, я (наивно) прибегаю к черчению, выбирая коммиты слияния из a, применимые к b.

Со временем, когда код проверен и утвержден в a, b теперь равен a, с пустым дифференциалом, но история показывает (как и ожидалось) расхождение в коммитах. (b рассматривается как много коммитов перед a в Github).

Я хотел бы устранить эту расходимость, если я выполняю коммиты слияния из одного в другой, я получаю длинную строку фиксирует без изменений (за исключением некоторых случаев на Github, где он запутывается), что может загрязнить историю любой ветви, если разрешено для них.

например, git merge a от b или git merge b от a показывает длинную строку коммитов в журнале. ( Редактировать: это с --no-ff).

Если я выполняю сква sh, я получаю хорошее сообщение со списком этих коммитов без изменений.

например, git merge a --squash из b или git merge b --squash из a требует git commit --allow-empty.

Но это не мешает ситуации "эта ветвь впереди / позади".

Так Мой вопрос заключается в том, как мне создать один коммит (может быть в одной или обеих ветках), чтобы предотвратить ситуацию «вперед / назад».

Редактировать: Это в основном для простоты отслеживания что случилось на Github, я знаю, как работает git, и знаю, как это сделать, уничтожая историю одной или других ветвей путем принудительного нажатия, но я пытаюсь сохранить обе так, чтобы их было легко исследовать.

Ответы [ 3 ]

0 голосов
/ 20 февраля 2020

прибегают к сбору вишни […] Со временем, когда код проверен и утвержден в a, b теперь равно a, с пустым diff, но история показывает, (как и ожидалось Расхождение в коммитах. Я хотел бы устранить это расхождение, если я выполняю коммиты слияния из одного в другой, я получаю длинную строку коммитов […], которая загрязнит историю

У вас либо разные коммиты в истории, или те же самые. У вас не может быть обоих способов.

Но вы легко не можете перечислить объединенные коммиты. git log --first-parent показывает вам только коммиты вашего основного предка, без перечисления каких-либо подробностей объединенной истории. Это, вероятно, то, что вы хотите. Полезным трюком является сложение длинной строки коммитов в один коммит с суммарным слиянием, который затем можно перечислить отдельно с помощью --first-parent. Выслеживайте --no-ff слияния, это то, как вы записываете сводные слияния, помещая историю слияния в ветвь второго родителя, а не просто перемотайте весь список вперед, сохраняя его как часть основной строки.

0 голосов
/ 26 февраля 2020

Мое решение состояло в том, чтобы заново создать ветвь.

Создание новой ветки a и выбор вишни слияния с b, но с использованием:

git cherry-pick -m 1 --allow-empty --keep-redundant-commits <sha>

Наряду с разрешением любых конфликтов в пользу HEAD и использованием git commit --allow-empty.

я получил чистую версию истории, о которой я заботился в b (это были даты объединение различных PR), который был основан на a. Это позволяет корректно объединяться с b в a, чтобы поддерживать его в актуальном состоянии, и наоборот.

0 голосов
/ 20 февраля 2020

Единственный реальный ответ здесь заключается в том, что вы этого не делаете.

То, что имеет Git - то, что оно хранит - это график коммитов. Каждый коммит имеет свой уникальный идентификатор ha sh, который никогда не будет использоваться никаким другим коммитом в этом или любом другом репозитории. 1 Каждый коммит также указывает (в обратном направлении) на свой родительский коммит (ы); это то, что формирует график.

То есть, учитывая линейную цепочку коммитов, используя заглавные буквы, чтобы заменить их настоящие большие уродливые идентификаторы ha sh, мы можем нарисовать их так:

... <-F <-G <-H ...

Коммит, чей ha sh равен H, содержит идентификатор * ha sh commit G, так что H указывает на G. Коммит, чье значение ha sh равно G, содержит идентификатор кома ha sh F: G указывает на F. Это продолжается вплоть до первоначального коммита, который больше не указывает назад. (Обычно существует только один такой коммит, root, хотя Git допускает несколько root коммитов.)

Имя ветви, подобное master или develop или что-то просто указывает на один конкретный c коммит. Этот коммит является последним коммитом в этой ветке. В таких ситуациях:

          I--J   <-- branch1
         /
...--G--H
         \
          K--L   <-- branch2

нам нужны два имени ветви, чтобы найти все коммиты, потому что Git работает, когда имя ветки указывает на последний коммит, который мы будем использовать назовите "часть ветви". Commit J - последний коммит branch1. J указывает на I, что указывает на H. Между тем имя branch2 указывает на коммит L, который указывает на K, который указывает на H. Обратите внимание, что коммит H находится на в обеих ветвях (как и коммиты G и ранее).

Если H имеет имя, указывающее на него:

          I--J   <-- branch1
         /
...--G--H   <-- master
         \
          K--L   <-- branch2

все в порядке: commit H i last commit ветви master, а H on все три ветви .

Commits is называется достижимым с некоторой точки, если, следуя внутренним стрелкам, направленным назад, мы можем перейти от этой точки (какой бы она ни была) к коммиту, который мы называем достижимым. Так что H достижимо из всех трех ветвей, но I и J достижимо только из branch1.

Когда Git говорит, что одно имя ветви «опережает» другое, это потому что есть коммиты, которые достигаются только с этой одной ветки. Следовательно, branch1 на 2 коммита опережает master, а два коммита опережают branch2. master вовсе не опережает ни одно из других имен, но branch2 на 2 коммита опережает как master, так и branch1.

Обычная операция git merge объединяет изменения и создает новый коммит и новые коммиты возвращаются к обоим более ранним коммитам:

          I--J
         /    \
...--G--H      M   <-- branch1 (HEAD)
         \    /
          K--L   <-- branch2

Множество стрелок назад, выходящих из M, указывающих на J и L означает, что branch1 теперь на три коммита впереди branch2: с branch1 мы можем достичь коммитов M, J и I, а также L и K и, конечно, H и ранее. С branch2 мы можем достичь L и K и H и ранее, но мы не можем go переместиться с H на I.

Используя git merge --squash, Git сделает новый коммит с тем же содержимым , что и при слиянии M, но вместо двух стрелок назад вы получите только одну:

          I--J
         /    \
...--G--H      M   <-- branch1 (HEAD)
         \
          K--L   <-- branch2

Это больше невозможно чтобы перейти от M к L, так что branch2 остается на два коммита "впереди" branch1.

Обратите внимание, однако, что коммит M имеет что-то хорошее от K и L в нем. Так что вы можете просто полностью удалить имя branch2:

...--G--H--I--J--M   <-- branch1 (HEAD)
         \
          K--L   [abandoned]

Если нет имени , по которому можно найти коммит L , вы никогда не увидите это снова. Так как commit L был методом, который вы использовали для поиска commit K, вы никогда не увидите его снова. Они некоторое время остаются в вашем хранилище Git - сколько именно времени трудно предсказать, поскольку это зависит как от любых скрытых (только для reflog) имен, которые все еще находят коммит L, так и от того, как скоро команда техобслуживания git gc пытается найти неиспользованные коммиты и действительно удалить их. Но удаление имени branch2 удаляет reflog branch2, поэтому единственный reflog, который, вероятно, все еще помнит необработанный ha sh ID L, - это для HEAD.

(Если у вас есть другая ветвь и / или имена тегов, которые запоминают коммиты L и / или K, они все равно будут найдены таким образом и никогда не будут собраны сборщиком мусора git gc. *

Обычно после git merge --squash правильнее всего удалить имя другой ветви и полностью забыть, что эти коммиты когда-либо существовали.


1 Технически, любой Git хранилища, которые никогда не "встречаются", могут безвредно использовать идентификаторы друг друга. На практике они просто не в любом случае. Вероятность того, что один объект Git будет иметь тот же идентификатор ha sh, что и некоторый объект Git, составляет один на 2 160 . Из-за парадокса Дня Рождения этот шанс возрастает довольно быстро по мере увеличения количества объектов, но он все равно порядка 1 в 10 -17 , когда у вас меньше, чем триллионы объекты. См. Также Git га sh дубликаты .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...