Можно ли изменить родительский коммит без изменения файлов в текущем коммите? - PullRequest
0 голосов
/ 27 июня 2018

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

Можно ли выполнить перебазирование без изменения хеша дерева окончательного коммита.

Например, у нас есть 3 коммита, A, B и C: Это коммит A

# test_file.py
# this is commit A
does_stuff()

Это новый коммит B

# test_file.py
# this is commit B
does_stuff()

Это новый коммит C

# test_file.py
# this is commit C
does_stuff()

где оригинальное дерево выглядело как

... -- C -- A

Но мы хотим «разбить» A на два коммита

... -- C -- B -- A

Когда мы создаем B с помощью интерактивной перебазировки, комментарий A также будет обновляться и отображает # this is commit B (при условии, что есть несколько других изменений, отличных от этого файла). Тем не менее, мы хотим оставить комментарий как есть. Примечание: A будет иметь другой общий хеш из-за наличия другого родителя, но его хэш дерева должен оставаться идентичным предыдущему.

1 Ответ

0 голосов
/ 28 июня 2018

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

Когда мы создаем B с помощью интерактивной перебазировки, комментарий A также будет обновляться, чтобы показать # this is commit B (при условии, что есть несколько других изменений, отличных от этого файла).

В конкретном примере, который вы показали, не должно быть: вы должны получить конфликт слияния.

Для других дел, конечно, вы правы.

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

Помните, вы начали с:

... -- C -- A

который я бы нарисовал как:

...--C--A   <-- branchname (HEAD)

указывает, что некоторая существующая ветка с именем branchname указывает на фиксацию C, а HEAD присоединяется к A.

Затем вы запустили git rebase -i <hash-of-C> или подобное. Это дает вам список того, что нужно сделать, и вы решаете «редактировать» A. Git сейчас:

  • отсоединяет ГОЛОВУ от цели перебазировки:

           A   <-- branchname
          /
    ...--C   <-- HEAD
    
  • Копирует коммит A (в этом случае используется точное копирование / перемотка вперед, так что он сам использует A; вы можете отключить его, если хотите, с помощью --no-ff, хотя в конец не имеет значения):

           A   <-- HEAD, branchname
          /
    ...--C
    

    или

           A   <-- branchname
          /
    ...--C--A'   <-- HEAD
    

    (используя --no-ff для принудительного копирования).

На этом этапе вы должны внести некоторые изменения и запустить git add и git commit --amend, чтобы отодвинуть текущий коммит в сторону и заставить HEAD указать на новый коммит B, родитель которого - C. Допустим, вы не использовали --no-ff; результат тогда:

       A   <-- branchname
      /
...--C--B   <-- HEAD

(Если вы использовали --no-ff, есть еще A', висящий без имени; он будет собирать мусор через месяц. Затем нам нужно будет вызвать следующую копию A", чтобы сообщить их отдельно, поэтому давайте предположим, что вы не использовали --no-ff.)

Теперь вы хотите получить файлы из коммита A и сообщение о коммите из коммита A и сделать новый коммит. Поскольку branchname по-прежнему указывает на исходный коммит A, просто сделайте это:

$ git checkout branchname -- .     # assumes you're at the top level of your repo
$ git commit -C branchname         # or -c if you want to edit it again

Теперь у вас есть:

       A   <-- branchname
      /
...--C--B--A'  <-- HEAD

На этом этапе вы заканчиваете ребаз с помощью git rebase --continue. Поскольку не осталось коммитов для копирования - вы завершили копирование последнего коммита, A, что касается перебазирования, - это последний шаг перебазирования, который заключается в удалении имени ветви из исходной цепочки коммитов и переместите его так, чтобы он указывал на тот же коммит, что и HEAD, при повторном присоединении HEAD:

       A   <-- ORIG_HEAD
      /
...--C--B--A'  <-- branchname (HEAD)

В качестве побочного эффекта, rebase устанавливает ORIG_HEAD, чтобы запомнить, где branchname имел обыкновение указывать, поэтому легко убедиться, что все работает правильно, и вы оказались в желаемом состоянии:

git diff ORIG_HEAD HEAD

и если это не так, вы можете git reset --hard ORIG_HEAD, в результате чего:

       A   <-- branchname (HEAD)
      /
...--C--B--A'  <-- ORIG_HEAD

Обратите внимание, что другие команды, включая git reset, устанавливают ORIG_HEAD (именно поэтому они поменялись местами здесь). В конце концов, один из этих двух коммитов будет полностью отменен, за исключением записей reflog, и когда срок их действия истечет, недостижимые коммиты действительно исчезнут. Срок действия таких коммитов по умолчанию - 30 дней.

...