Чистое дерево веток - PullRequest
       3

Чистое дерево веток

0 голосов
/ 03 февраля 2019

У меня есть дерево коммитов, которое стало довольно сложно разобрать и дезорганизовать.Поэтому я чувствую, что мне нужно это почистить.Было бы здорово, если бы вы могли помочь мне исправить это.

Я работал над проектом A, который в какой-то момент был расширен двумя ветвями B и C. После нескольких коммитов в каждой ветви я вижу следующее, когдаЯ вхожу в каждую из веток.Я суммировал журналы, дайте мне знать, если он не читается.

master:
commit: B2 (HEAD -> master, origin/master, origin/HEAD)
commit: B1
commit: A3
...

branch_B:
commit: B2 (HEAD -> branch_B)
commit: C3 
commit: C2 (branch_C)
commit: C1
commit: B1
commit: A3
...

branch_C:
commit: C2 (HEAD -> branch_C)
commit: C1
commit: B1
commit: A3
...

РЕДАКТИРОВАТЬ: В частности, я хочу удалить B1 и B2 из мастера, удалить B1 из Branch_C, удалить C1, C2 и C3 изbranch_B и переместите C3 обратно в branch_C.

1 Ответ

0 голосов
/ 03 февраля 2019

Git создан и, следовательно, в некотором смысле «хочет», добавляет новые коммиты , но никогда не удаляет старые .Можно удалить коммиты, но:

  • Будьте уверены, что вы действительно хотите это сделать!
  • Имейте в виду, что коммиты похожи на определенные заболевания: если у вас был коммит Xв вашем хранилище и обмененных флюидах имел взаимодействия с другим хранилищем Git, клоном того же источника, или вашим клоном, или вы клоном их, они, вероятно, имеютсовершить X тоже сейчас.В следующий раз, когда вы подключите свой Git к их Git, вы, скорее всего, вернете коммит X обратно. Чтобы сделать коммит действительно пропавшим, вы должны вылечить удалить проблему из все затронутые / зараженные Git-репозитории.Поскольку, как правило, вы только контролируете свой собственный Git-репозиторий, это означает, что вы должны заставить всех остальных также исправить их репозиториев.

С этимкстати, вот как вы это делаете, используя git cherry-pick и git reset.Есть несколько способов сделать это, но давайте рассмотрим две эти команды здесь.

Git - это коммиты;имена вторичные

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

Каждый коммит сохраняет, пока существует сам коммит, полный снимок всех файлов.Ну, у него есть все файлы, которые есть на снимке, но это все равно, что сказать, что все синие карандаши синие: это глупо.Дело в том, что это снимок файлов.Здесь не написано «изменить README таким образом», что потребует возврата назад и выяснения того, как README выглядел раньше.Это просто говорит у нас есть README и выглядит так .Если у снимка нет файла, Git, возможно, удалит файл (хотя эта часть становится немного сложнее, потому что Git позволяет вам иметь «неотслеживаемые файлы»).В любом случае файлы в моментальном снимке замораживаются навсегда или, по крайней мере, до тех пор, пока существует фиксация.

Но каждый снимок также содержит некоторые метаданные, , такие как ваше имя (есливы сделали коммит), когда вы сделали это, почему вы сделали это - ваше сообщение журнала - и, что крайне важно для наших целей, хэш-идентификатор предыдущего коммита.Эти метаданные, как и файлы, замораживаются навсегда или до тех пор, пока существует фиксация.Обратите внимание, что когда Git показывает вам коммит, Git показывает (некоторые из) метаданные, а затем показывает разницу между файлами этого коммита и файлами родительского коммита.Это может быть сделано из-за хеш-идентификатора родительского или предыдущего коммита, сохраненного как часть этого коммита.

Для нас это означает, что мы можем извлекать строки коммитов, направленных назад, с каждым коммитомназывая его родителя:

A <-B <-C

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

Чтобы последний коммит в ветви хеш-идентификатор былфункция ветви names , например master:

A--B--C--D--E   <-- master

Мы - и Git - начинаем с конца, используя имя master для получения идентификатора хеша (здесьE).Затем мы работаем в обратном направлении, следуя этим неизменным внутренним стрелкам.

имя ветви стрелки - идентификаторы хеша, хранящиеся под именами - могут меняться, как мыПосмотрим.

Добавление коммитов в ветку

Чтобы добавитьНовый коммит в текущей ветке позволяет Git сохранить снимок файлов, добавить наше имя, адрес электронной почты и наше сообщение журнала, а также сохранить хэш-идентификатор current commit.Git записывает все это в новый коммит, который получает новый хэш-идентификатор:

A--B--C--D--E   <-- master
             \
              F

Теперь Git просто обновляет имя , чтобы записать новый последний коммит :

A--B--C--D--E
             \
              F   <-- master

, который мы затем можем выправить:

A--B--C--D--E--F   <-- master

Обратите внимание, что это фиксирует и их отношения друг к другу - внутренняя, обратная.стрелки - это важно здесь.Имена имеют значение, но только потому, что как мы находим коммиты.Сами коммиты образуют Направленный ациклический граф или DAG.Имена позволяют нам получить в DAG.Ничто в самой DAG не может измениться, но имена могут двигаться, и мы можем добавить новые коммиты.

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

Добавление дополнительных ветвей кgraph

Предположим, у нас есть шесть коммитов:

A--B--C--D--E--F   <-- master

и мы хотим создать новую ветку.Мы используем git branch или git checkout для создания ветви, поэтому теперь у нас есть:

A--B--C--D--E--F   <-- BranchA, master

Оба имени указывают на одинаковый коммит, F.Все шесть коммитов теперь находятся в обеих ветвях .

Если мы добавим новый коммит, очевидно, мы получим:

A--B--C--D--E--F
                \
                 G

так же, как мы получили F ранее.Но какое имя должно измениться?Чтобы ответить на этот вопрос, Git прикрепляет имя HEAD к одной из веток:

A--B--C--D--E--F   <-- BranchA (HEAD), master

Это говорит Git, какое имя изменить:

A--B--C--D--E--F   <-- master
                \
                 G   <-- BranchA (HEAD)

Вложение HEAD остаетсякогда имя двигается.Нам нужно знать о привязанности, когда мы хотим знать: На какой ветке мы находимся?На какую ветку повлияет наша команда, если она повлияет на текущую ветку? Если мы просто посмотрим на то, что находится в репозитории, мы можем оставить это в стороне.

Итак, давайте покончим с этим, давайтенарисуйте существующий график более полно

У вас есть серия коммитов, заканчивающаяся той, которую вы называете A3 выше, после чего все становится немного более утомительным.Мне нравятся названия одной буквы, но я буду использовать ваши здесь:

...--A3

Теперь вы говорите, что ваш master достигает B2, которому предшествует B1, которому предшествует A3, поэтомупосле этого должно быть еще два коммита:

...--A3--B1--B2   <-- master

Между тем ваш Branch_B начинается с B2, которому предшествует C3, но это буквально невозможно:

...--A3--B1--B2   <-- master
           \
            C3--B2   <-- Branch_B

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

...--A3--B1--B2a   <-- master
           \
            C3--B2   <-- Branch_B

Ваш Branch_C начинается - ну, заканчивается? - с C2, которому предшествуют C1, затем B1, затем A3:

            C1--C2   <-- Branch_C
           /
...--A3--B1--B2a   <-- master
           \
            C3--B2   <-- Branch_B

Вы можете подтвердить это с помощью git log --decorate --oneline --graph --decorate master Branch_B Branch_C (или git log --all --decorate --oneline --graph, Получить справкуОт собаки ).Это рисует вертикально ориентированные графы, которые не так красивы или очевидны для меня, но все же очень полезны.

Как получить то, что вы хотите: требуется изменить то, что вы хотите, немного

Теперь, вот что вы говорите, что хотели бы:

        C1--C2--C3   <-- Branch_C
       /
...--A3   <-- master
       \
        B1--B2   <-- Branch_B

Вы не можете получить это. Мы уже говорили, что нигде нет силы изменить что-либо в любом существующем коммите,и, глядя на то, что мы имеем сейчас, родителем коммита B2 является, например, коммит C3.

Но вы можете получить что-то, что, вероятно, так же хорошо, как , а именно:Вы можете сделать копию из B2.На самом деле, вы, вероятно, уже имеете - B2a и B2 - вероятные копии друг друга.

Сне беспокоясь о точном механизме копирования, давайте посмотрим, что произойдет, если мы создадим B2b, который является копией B2, но имеет B1 в качестве родителя:

            C1--C2   <-- Branch_C
           /
...--A3--B1--B2a   <-- master
         | \
         |  C3--B2   <-- Branch_B
          \
           B2b   <-- new-branch-b

Далее, давайте скопируемC1 в новый C1a, который исходит от A3:

          C1a   <-- new-branch-C
         /
        /   C1--C2   <-- Branch_C
       /   /
...--A3--B1--B2a   <-- master
         | \
         |  C3--B2   <-- Branch_B
          \
           B2b   <-- new-branch-b

Тогда нам просто нужно скопировать C2 и C3, один за другим:

          C1a--C2a--C3a   <-- new-branch-C
         /
        /   C1--C2   <-- Branch_C
       /   /
...--A3--B1--B2a   <-- master
         | \
         |  C3--B2   <-- Branch_B
          \
           B2b   <-- new-branch-b

Почти последний, нам нужно переместить старых имен, Branch_B и Branch_C, чтобы точка совершения B2b и C3a соответственно:

          C1a--C2a--C3a   <-- new-branch-C, Branch_C
         /
        /   C1--C2   [abandoned]
       /   /
...--A3--B1--B2a   <-- master
         | \
         |  C3--B2   [abandoned]
          \
           B2b   <-- new-branch-b, Branch_B

Затем нам нужно переместить имя master назад на два шага, чтобы оно указывало на A3 вместо B2a, оставляя B2a целикомТрудно рисовать, пока мы не перестанем рисовать брошенные коммиты .Они все еще будут в вашем хранилище некоторое время (по крайней мере, 30 дней по умолчанию), но скрыты, так что вы больше не сможете их видеть, что дает нам:

          C1a--C2a--C3a   <-- new-branch-C, Branch_C
         /
        /__________
       /           \
...--A3--B1         -- master
         |
         |
          \
           B2b   <-- new-branch-b, Branch_B

Теперь мы можем отброситьnew-branch-[bc] имен и очистить расположение чертежа:

        C1a--C2a--C3a   <-- Branch_C
       /
...--A3   <-- master
       \
        B1--B2b   <-- Branch_B

За исключением здесь суффиксов, которые означают, что это разные идентификаторы хеша , это именно то, что вы хотели!

Получение отсюда туда: добавление новых имен

Сначала вам просто нужно добавить новые имена, указывающие на нужные коммиты:

git branch new-branch-b <hash of B1>
git branch new-branch-c <hash of A3>

Хеш-идентификаторымы выбираем здесь коммиты, которые будут продолжать быть на вновь построенных ветвях.Для Branch_B это B1, который мы можем оставить на месте, но для Branch_C это коммит A3, потому что мы должны скопировать C1 в C1a.

Получение отздесь туда: копирование коммитов

Теперь пришло время скопировать коммиты.Давайте скопируем B2 или B2a.Вы можете использовать все, что захотите, при условии, что они вносят одинаковые изменения и имеют одинаковые сообщения фиксации, потому что команда копирования git cherry-pick, и способ ее работы очень похож на то, что мы говорили ранее о показе фиксации:

[Git] показывает разницу между файлами этого коммита и файлами родительского коммита

Вместо , показывающего разницу,git cherry-pick находит разницу, затем применяет ее к любому подтвержденному нами коммиту, вносит одинаковых изменений и фиксирует результат, используя то же сообщение журнала в качестве исходного коммита тоже.

Так что нам просто нужно:

git checkout new-branch-b
git cherry-pick <hash-of-B2a or whatever>

, что позволяет нам зайти так далеко, когда мы рисуем график и упускаем много:

...--A3
       \
        B1--B2b   <-- new-branch-b

Затем нам нужно построить новую ветвь C таким же образом:

git checkout new-branch-b
git cherry-pick <hash-of-C1>
git cherry-pick <hash-of-C2>
git cherry-pick <hash-of-C3>

Результат, опять-таки оставляющий множество графических рисунков, является желаемым:

        C1a--C2a--C3a   <-- Branch_C
       /
...--A3

Последний шаг - master идентифицировать коммит A3 иr что нам просто нужно git checkout master, а затем git reset --hard:

git checkout master
git reset --hard <hash-of-A3>

(Примечание: если вы делаете это с помощью хеш-идентификаторов, хорошей идеей будет вырезать и вставить их, и / илисохраняйте их в файлах, так как далеко слишком легко получить опечатки здесь.Существуют приемы использования относительных имен, но я не собираюсь включать их в этот ответ.)

Команда git reset влияет на любое имя ветви, к которому присоединено HEAD, а команда git cherry-pick создаетновые коммиты с любым именем ветви HEAD, к которому прикреплено.Вот почему нам пришлось git checkout каждое из этих имен.

На данный момент у нас есть новые имена ветвей, и master указывает на A3, но мы не обновили два других имени ветвей,Как и раньше, мы можем использовать git checkout и git reset --hard здесь:

git checkout BranchB
git reset --hard new-branch-b
git checkout BranchC
git reset --hard new-branch-c

На этот раз нам не нужны хеш-идентификаторы, потому что для таких команд, как git cherry-pick и git reset, имя ветви означает коммит, идентификатор которого хранится в имени этой ветви .

Как только мы закончим все это, мы можем просто удалить имена new-branch-b и new-branch-c:

git branch -D new-branch-b
git branch -D new-branch-c

-D - это принудительное удаление, которое заставляет Git делать это, даже если Git считает, что это небезопасно.(Идея Гита о том, когда это безопасно, а когда нет, хорошая попытка, но не очень удачная.)

У Cherry-pick могут быть конфликты слияния

Это не особенно вероятно для вашего случая, но важно знать на будущее.Каждый git cherry-pick на самом деле является своего рода слиянием.Git собирается «объединить» изменения, сделанные в самом коммите, вычисленные путем сравнения родительского коммита с коммитом, так же как git show сравнивает их - в текущий коммит, найдя изменения текущего коммитасравнивая родительский коммит вишневого коммита с текущим (HEAD) коммитом.

Если вы немного запутались, не волнуйтесь: предыдущий абзац определенно трудно прочитать.Это действительно лучше всего показано на иллюстрации:

       o--o--...--P--C--o--...--o   <-- other-branch
      /
...--o
      \
       o--o--H   <-- your-branch (HEAD)

Вы запускаете git cherry-pick <hash of C>.Git:

  • Diffs P против C: вот что они изменили.
  • Diffs P против H: это то, что вы изменили, вроде
  • Объединяет эти два набора изменений, применяя объединенные изменения к файлам из P (т. Е. Повторяя «то, что вы изменили», просто чтобы вернуться к тому, что в H, но затем добавляя"что они изменили", чтобы получить от H к результату).
  • Если объединение работает, делает новый коммит C'.В противном случае останавливается и оставляет беспорядок.

Когда это работает без усилий с вашей стороны, возникает эффект, что все, что изменилось с P на C, те же самые изменения теперь в новом коммитеC', который git cherry-pick сделал копией коммита C:

       o--o--...--P--C--o--...--o   <-- other-branch
      /
...--o
      \
       o--o--H--C'  <-- your-branch (HEAD)

Когда все идет не так, Git останавливается с конфликтом слияния, так же, как и вgit merge когда что-то идет не так.В этот момент ваша задача - завершить «объединение» - в данном случае «вишневый пик» - и затем запустить git commit или git cherry-pick --continue, чтобы завершить работу.Вы можете использовать все те же инструменты, что и во время git merge, чтобы завершить работу, так что, если хотите, для git merge, используйте тот же метод.

...