Вы в буквальном смысле не можете удалить коммит - не напрямую, во всяком случае. Так что я полагаю, что ответ «нет». Но вы можете забыть коммит. Это усложняет ответ.
В ветвях также нет родительских / дочерних отношений. коммиты имеют отношения родитель / потомок.
Способ, которым это работает, на самом деле очень прост:
- Каждый коммит 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
и т. Д.
Итак, теперь мы добавим еще одну точку маркера:
То есть, если нам немного лень рисовать внутренние стрелки, которые указывают назад, мы можем нарисовать так:
...--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 коммит в стиле "отменить" .