Если вы когда-либо не удаляли коммитов или не удаляли некоторые имена ветвей, или удаляли и повторно клонировали ваш репозиторий, это на самом деле довольно просто для типичной установки.Частично это связано с:
... 10 декабря ...
, что на данный момент всего десять дней назад.Все становится сложнее через 30 или 90 дней, из-за истечения срока регистрации.(Предполагается, что конфигурация по умолчанию, в которой вы не указали Git использовать другой период хранения.)
Фон
Здесь следует помнить, что то, что Git хранит, не изменяет , а не файлы , а фиксирует .(Каждый коммит хранит файлы, так что Git косвенно сохраняет файлы, но уровень, на котором вещи видны, является коммитом.) Каждый коммит уникально идентифицируется некоторым хеш-идентификатором, который представляет собой большую уродливую строку, такую как 5d826e972970a784bd7a7bdf587512510097b8c7
.Обычно вы также добавляете только новые коммиты в репозиторий Git.Это верно даже для операций, которые кажутся для удаления коммитов, таких как git commit --amend
или git rebase
.Оригинальные коммиты с их неизменными хэш-идентификаторами, которые являются такими же постоянными, как и сами коммиты, все еще находятся в вашем хранилище.
Каждое из ваших имен ветвей просто действует как указатель.То есть каждый из них хранит один хэш-идентификатор.Один хэш-идентификатор, сохраненный в master
, может быть 5d826e972970a784bd7a7bdf587512510097b8c7
сегодня.Завтра, после того, как вы сделаете новый коммит, это будет что-то другое, но в master
.
будет только один идентификатор хэша. Когда вы делаете новый commit, Git:
Упаковывает полный снимок (из вашего индекса, также называемого промежуточной областью или кэшем).Это делает объект дерева Git (не то, о чем вам обычно нужно заботиться), который получает собственный хэш-идентификатор.
Собирает ваше сообщение журнала, ваше имя и адрес электронной почты.адрес и текущая дата / время и тд.К этому Git добавляет хэш дерева из шага 1, строку parent
, записывающую идентификатор хеша текущего коммита из имени ветви, и любые другие подходящие метаданные, которые Git хочет включить.Из всех этих данных Git создает объект коммита , который получает свой собственный новый уникальный идентификатор хеш-функции.
(Часть причины, по которой метка времени состоит в том, чтобы убедиться, что хэшИдентификатор уникален. В противном случае, если вы создадите новый снимок, который соответствует старому, с тем же родительским и другими метаданными, вы получите хеш-идентификатор old коммит!одна ветвь. Это на самом деле не фатально - вы можете заставить это условие возникать с помощью хитрости и сценариев, например, для выполнения более одного коммита в секунду, и это действительно работает - но это глубоко удивительно и может нарушить некоторые рабочие процессы.)
Записывает идентификатор хеша из шага 2 в текущее имя ветви.Вуаля, ветка теперь указывает на новый коммит.Новый коммит указывает на предыдущий коммит.
Итак, перед выполнением этого нового коммита, если имя master
указывает на какой-то коммит H
(Здесь H обозначает действительный идентификатор хеша), чей родитель G
, с родительским G
F
и т. Д., У вас есть:
... <-F <-G <-H <--master
Впоследствии, если мы имеемI
означает новый коммит, картинка:
... <-F <-G <-H <-I <--master
Reflogs
Всякий раз, когда Git обновляет ссылку, такую как имя ветки, например master
, Git записывает предыдущее значение этого имени ветки в журнал.Этот журнал называется reflog для справки.На каждой записи журнала также есть отметка даты / времени, так что вы можете запустить git reflog master
, чтобы она вылилась:
$ git reflog master
5d826e9729 master@{0}: merge refs/remotes/origin/master: Fast-forward
965798d1f2 master@{1}: merge refs/remotes/origin/master: Fast-forward
8a0ba68f6d master@{2}: merge refs/remotes/origin/master: Fast-forward
...
(Это мой Git-репозиторий для Git, где не оченьИнтересные вещи случаются на master
: в основном, я просто перематываю это все время.)
ThВсе записи пронумерованы в соответствии с их недавностью в журнале: @{0}
- текущее значение, @{1}
- предыдущее, @{2}
- это то, что было раньше, когда @{1}
было @{0}
и так далее.Значение по умолчанию для git reflog
состоит в том, чтобы распечатывать их нумерованными, как это, но с --date=relative
он печатает отметки времени вместо:
5d826e9729 master@{11 days ago}: merge refs/remotes/origin/master: Fast-forward
965798d1f2 master@{12 days ago}: merge refs/remotes/origin/master: Fast-forward
и т. Д.
Вы также можете использовать --date=local
и многие другие форматы.Чтобы увидеть их все, прочитайте документацию git log
(git reflog
на самом деле git log -g
).Попробуйте это с --date=local
сейчас.
Ваша работа
Итак, представьте, что у нас есть что-то простое:
...--F--G--H--I <--master
(я перестал рисоватьвнутренние стрелки, потому что это слишком сложно / раздражает, как вы увидите через мгновение. Просто помните, они обязательно указывают назад: вы не можете сделать старый коммит, который ссылается на новый коммит, который еще несуществует, потому что вы не знаете хеш-идентификатор нового коммита до тех пор, пока вы не сделаете после . Тогда будет слишком поздно, потому что ничего в любом коммите—или на самом деле, в любом внутреннем объекте Git - когда-либо можно изменить.)
Для того, чтобы откатить master
назад, как это было вчера, когда он указывал на H
вместо I
, вам просто нужно сказать Git: установить имя master
для указания на коммит H
сейчас. Результат:
I
/
...--F--G--H <-- master
Commit I
не ушел .Фактически, это просто добавило новую запись в reflog, так что master@{1}
запоминает хэш-идентификатор master
на момент времени, указанный в I
.(Эта новая запись reflog имеет метку даты и времени «сейчас».)
В конце концов - по крайней мере, через 30 дней по умолчанию и 90 дней для большинства типичных случаев - Git просканирует этот журнал на наличиеmaster
и удалите все записи, которые слишком стары.Но, по крайней мере, через 30 дней, master@{<something>}
будет помнить хеш-идентификатор commit I
.
Все, что нам нужно сделать, это выяснить, что все reflogs говорят о всех ветвях . Для каждой ветки, существовавшей десять дней назад, какой коммит указывал эту ветвь?
Вы могли бы сделать это, перечислив все имена веток:
$ git branch
<list of names spills out, one of them probably marked `*` as current branch>
Затем для каждого имени вы можете запустить git reflog --date=local <em>name</em>
и найти запись, которая соответствует 10 декабря.Это может быть датировано до 10-го числа, как в этом случае:
$ git reflog --date=local master
5d826e9729 master@{Sun Dec 9 11:03:46 2018}: merge refs/remotes/origin/master: Fast-forward
965798d1f2 master@{Sat Dec 8 07:53:19 2018}: merge refs/remotes/origin/master: Fast-forward
Это означает, что 5d826e9729
- это значение master
, хранящееся 9-го числа, с тех пор ничего более нового, поэтому оно также должно бытьзначение master
хранится 10-го числа.
Вы можете повторить это для каждого имени ветви и выяснить, какой коммит вы хотите, чтобы каждое из этих имен идентифицировало.Если ветвь не существовала до 10-го числа, вы можете удалить ветвь полностью;Имейте в виду, что если вы это сделаете, Git также удалит свой reflog, поэтому возвращаться к нему некуда.
Однако есть более простой способ.Синтаксис reflog позволяет вам написать:
master@{10.days.ago}
или:
master@{10.dec.2018}
, чтобы сам Git понял это!В общем, любая команда Git, которая принимает хеш-идентификатор, также принимает это.Например, git rev-parse
превращает имя в соответствующий хэш-идентификатор:
$ git rev-parse master
5d826e972970a784bd7a7bdf587512510097b8c7
, но:
$ git rev-parse master@{8.dec.2018}
965798d1f2992a4bdadb81eba195a7d465b6454a
Я использовал 8 dec
здесь, так как в моем reflog нет ничего интересногопозже этого.Обратите внимание, что получилось 96579...
, то есть master@{1}
, что равно от восьмого.
Будьте внимательны: если у вас более одного значение в выбранный вами день, Git выберет один из них.Фактически, 8.dec.2018
означает полночь в этот день, и вы можете добавить больше спецификаторов времени, если хотите 0900 или 1432 или что-то еще.Также остерегайтесь проблем с часовыми поясами - убедитесь, что вы выбрали правильную запись reflog!
Автоматизация этого
Вместо того, чтобы вручную вызывать git branch
, копировать имена, а затем вручную делать каждое,вы можете начать с этого:
git for-each-ref --format='%(refname:short)' refs/heads |
while read branch; do echo git branch -f $branch $branch@{10.dec}; done
, который выводит команды, которые он будет выполнять, если бы он echo
был удален.
Если tэй, выглядишь хорошо, и ты действительно уверен, что это все правильно, теперь ты можешь извлечь echo
, чтобы заставить команды действительно выполняться.
(Предполагается, что интерпретатор командной строки совместим с sh / bash.)