Git - если я удалю коммит из родительской ветви, он также исчезнет из дочерней ветви, которая была создана, когда коммит находился в родительской ветви? - PullRequest
0 голосов
/ 28 апреля 2020

Я добавил функцию в родительскую ветку. Это было совершено и выдвинуто к происхождению. С тех пор было решено пока не реализовывать эту функцию. Поэтому я хочу удалить коммит, но сохранить работу на будущее. Как мне это сделать?

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

Но если я удалю коммит из родительской ветви, он также исчезнет из дочерней ветви?

Ответы [ 2 ]

1 голос
/ 29 апреля 2020

Вы в буквальном смысле не можете удалить коммит - не напрямую, во всяком случае. Так что я полагаю, что ответ «нет». Но вы можете забыть коммит. Это усложняет ответ.

В ветвях также нет родительских / дочерних отношений. коммиты имеют отношения родитель / потомок.

Способ, которым это работает, на самом деле очень прост:

  • Каждый коммит Git имеет уникальный идентификатор ha sh , Этот ha sh ID - большая уродливая строка букв и цифр, которую вы, например, увидите в выводе git log - фактически является истинным именем коммита. Вот как Git находит коммит.
  • Каждый Git коммит хранит две вещи: его основные данные, которые представляют собой снимок всех ваших файлов, и некоторые метаданные: информацию о коммите, например, кто сделал это, когда и почему (ваше лог-сообщение). В этих метаданных Git включает ha sh ID коммита parent commit.

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

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

Здесь H - это последний коммит. Он содержит в качестве части своих метаданных идентификатор ha sh предыдущего коммита G. Таким образом, Git может извлечь G из хранилища, используя часть метаданных H. Конечно, G содержит идентификатор ha sh предыдущего коммита F и т. Д.

Итак, теперь мы добавим еще одну точку маркера:

  • A имя ветки содержит идентификатор

    последнего в цепочке га sh.

То есть, если нам немного лень рисовать внутренние стрелки, которые указывают назад, мы можем нарисовать так:

...--F--G--H   <-- master
*1049* name master содержит га sh ID коммита H, поэтому master указывает на H. Метаданные commit H содержат га sh ID commit G, поэтому H указывает на G. G указывает на F, что указывает на спину еще раньше.

Если у вас более одного имени ветви, оба имени ветви могут оба указывать на одинаковую фиксацию:

...--F--G--H   <-- master, br1

или они могут указывать на различные коммиты:

...--F--G--H   <-- master
            \
             I   <-- br1

Здесь действительно важны коммиты . Имена ветвей просто позволяют нам - и Git - находят коммиты.

Когда вы используете git checkout или git switch, чтобы выбрать ветку по имени, Git:

  • извлекает файлы фиксированного за все время коммита в рабочую область, чтобы вы могли просматривать их и работать с ними
  • присоединяет специальное имя HEAD к имени ветви, так что Git может найти как текущую ветку (имя HEAD прикреплено), так и текущий commit (фиксация, на которую указывает имя).

Мы можем нарисовать это так:

...--F--G--H   <-- master (HEAD)
            \
             I   <-- br1

или:

...--F--G--H   <-- master
            \
             I   <-- br1 (HEAD)

На одном чертеже текущая ветвь равна master, а текущая фиксация равна H; в другом текущая ветвь равна br1, а текущая фиксация - I.

Чтобы сделать новый коммит, Git:

  • записывает снимок (используя Git index , который мы не будем здесь рассматривать);
  • устанавливает метаданные нового коммита, используя:
    • your имя и адрес электронной почты, от вашего git config user.name и т. д .;
    • «сейчас» в качестве отметки даты и времени;
    • ваше сообщение журнала;
    • the текущий коммит га sh ID в качестве родителя нового коммита; и
  • последний, теперь, когда существует новый коммит, записывает идентификатор ha sh нового коммита в имя текущей ветви.

Так что если мы при коммите I через имя br1, и мы делаем новый коммит J, мы получаем:

...--F--G--H   <-- master
            \
             I--J   <-- br1 (HEAD)

Все, что вы можете сделать таким образом, это добавить коммитов .

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

git branch brexit br1~1

, что дает:

...--F--G--H   <-- master
            \
             I   <-- brexit
              \
               J   <-- br1 (HEAD)

Мы также можем удаляйте имена веток в любое время, например, git branch -d brexit. Это не влияет на сами коммиты:

...--F--G--H   <-- master
            \
             I
              \
               J   <-- br1 (HEAD)

Просто имя , которое напрямую находит коммит I, теперь исчезло. Мы все еще можем найти коммит I: мы просто начинаем с J и работаем в обратном направлении. (И мы можем уменьшить чертеж, чтобы использовать меньше линий, если захотим.)

Имена ветвей, по сути, являются просто метками, которые указывают на один конкретный c коммит, каждый. Вы можете иметь много имен для коммита или коммита без имен вообще. Эти имена ветвей имеют одно специальное свойство : вы можете прикрепить к ним HEAD и делать новые коммиты, а имена веток перемещаются вместе с вами, когда вы делаете новые коммиты.

" Отмена "коммита: git revert добавляет коммит

Если вы используете git revert для возврата коммита, Git просто добавляет новый коммит, который отменяет эффект предыдущего коммита. Например, если вы запустите:

git revert <hash-of-I>

, пока мы находимся в вышеуказанном состоянии, мы получим:

...--F--G--H   <-- master
            \
             I--J--K   <-- br1 (HEAD)

где commit K - это просто коммит, который отменяет что бы мы ни делали в коммите I.

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

«Отмена коммита»: мы можем забыть коммиты с git reset

Что если бы у нас была команда, которая могла бы достать и вырвать метку из некоторого существующего коммита и вставить его в другой совершить? Предположим, например, что мы решили сделать так, чтобы имя br1 снова указывало на J. Вот как это может выглядеть:

...--F--G--H   <-- master
            \
             I--J   <-- br1 (HEAD)
                 \
                  K   ???

Как и прежде, нам пришлось переместить коммит K, чтобы нарисовать это. Это все еще там, но есть действительно важный вопрос: Как мы можем найти коммит K?

Ответ: мы не можем! Это потеряно. Если бы мы где-то сохранили его идентификатор sh - например, если бы записали его на клочке бумаги - мы могли бы получить его таким образом. Но если мы забыли идентификатор ha sh, найти его действительно сложно.

Существует команда, которая делает именно это: git reset. (На самом деле, есть несколько команд, которые могут перемещать имена ветвей, но мы будем придерживаться здесь git reset.) Если мы запустим:

git reset --hard <hash-of-J>

Git вернет имя на один шаг назад, в направление внутренних стрелок go, но противоположно нормальному направлению роста ветки. Git сохранит коммит K ID ha sh где-нибудь, на случай, если мы захотим его вернуть, и сохраняем его некоторое время, но сохраненный идентификатор ha sh не отображается в обычном режиме каждый день Git команд, и вы не увидите коммит K. Это кажется ушедшим.

В сторону: Вы можете задаться вопросом, почему это git reset --hard. Это связано с индексом, который мы не рассматриваем, и вашим рабочим деревом. Команда git reset сложная! Он имеет несколько режимов, некоторые из которых делают больше, чем другие. Часть --hard сообщает Git: сбросить мое рабочее дерево тоже . Ваше дерево работ на самом деле не является частью Git хранилища. Файлы, которые вы видите здесь и редактируете для работы с ними, ваши и Git, как правило, не перезаписывают их и не удаляют, если вы не скажете это сделать; но git reset --hard означает перезаписать мои файлы .

Поскольку эти файлы не в хранилище, Git не может вернуть их после перезаписи. Так что будьте очень осторожны с git reset --hard. Если вы сделали git add и git commit, commit содержит снимков файлов. Эти снимки имеют значение в Git и предназначены только для чтения и сохраняются навсегда или, по крайней мере, до тех пор, пока вы сможете найти коммит. Так что эти копии в безопасности. Это копии вашего рабочего дерева, которые являются только временными и могут быть легко стерты.

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

Ключ достижимость

Реальный ключ здесь, к тому, является ли коммит остается навсегда или в конечном итоге удаляется, это то, что называется достижимость . Подробнее об этом см. Думайте как (а) Git, но в двух словах рассмотрим следующее состояние:

...--G--H   <-- master
         \
          I--J   <-- br1 (HEAD)
              \
               K--L   <-- br2

Многие люди называют br2 а " дочерняя ветвь "br1, но Git так не считает. Git думает, вместо этого, br2 указывает на L, и поэтому все коммиты до * 1271 включительно достижимы с br2. Git начнется с L и будет работать в обратном направлении: K, J, I, H, G и т. Д.

Между тем, начиная с br1 все коммиты до J достижимы: Git начинается с J, затем возвращается к I и так далее. Начиная с master все коммиты до H достижимы. Обратите внимание, что коммиты до H находятся на всех трех ветвях , а коммиты до J - на двух.

Если мы теперь используем git reset --hard, чтобы переместить br1 назад на одну шаг, мы получаем:

...--G--H   <-- master
         \
          I   <-- br1 (HEAD)
           \
            J--K--L   <-- br2

То есть, коммит J все еще существует и все еще доступен - но теперь он только на br1.

Заключение

В этом большая разница между возвратом и сбросом: git revert добавляет новый коммит , тогда как git reset позволяет вам перемещать имя ветки вокруг (а с --hard ударяет ваш файлы рабочего дерева). С revert оригинальные коммиты определенно все еще существуют; с помощью reset, сможете ли вы найти какие-либо коммиты, которые вы сбрасывали, зависит от того, какие другие имена все еще существуют, чтобы найти коммиты, и от связей между коммитами.

Как michid ответил Вы, вероятно, не хотите использовать здесь git reset, потому что вы уже сделали git push. Это взяло новый коммит, который вы сделали с git commit, и передал его в некоторый другой Git репозиторий. Они сохранят ваш коммит: Гиты не «любят» забывать коммиты. Заставить их забыть о своем коммите труднее, и, как правило, это не очень хорошая идея, хотя насколько сложно зависит от того, насколько широко этот коммит получил за это время.

С revert добавляет коммит, а Gits жадные и будут добавлять новые коммиты так быстро, как они их видят; легко получить другие Git к add коммит в стиле "отменить" .

1 голос
/ 28 апреля 2020

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

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