Как вернуть весь git-репозиторий, включая все ветки, к предыдущей дате? - PullRequest
0 голосов
/ 21 декабря 2018

Я испортил свой репозиторий github.Я отправил вещи в GitHub (не только локально), чего бы не хотел.Хранилище имеет 20 «шагов», в которых Step1 сливается в Step2, который сливается в Step3 и так далее, вплоть до Step20 Это очень плохо запутано.

Я хочу, чтобы мой *Хранилище 1007 * весь будет возвращено к тому, что было 10 декабря, для всех ветвей во всем хранилище.

Я видел несколько способов сделать это для данной ветви, и я думаю, что могу сделать это двадцать раз, верно?

Однако я надеюсь, что есть способсделать это, не проверяя все двадцать ветвей и не откладывая каждую обратно.

1 Ответ

0 голосов
/ 21 декабря 2018

Если вы когда-либо не удаляли коммитов или не удаляли некоторые имена ветвей, или удаляли и повторно клонировали ваш репозиторий, это на самом деле довольно просто для типичной установки.Частично это связано с:

... 10 декабря ...

, что на данный момент всего десять дней назад.Все становится сложнее через 30 или 90 дней, из-за истечения срока регистрации.(Предполагается, что конфигурация по умолчанию, в которой вы не указали Git использовать другой период хранения.)

Фон

Здесь следует помнить, что то, что Git хранит, не изменяет , а не файлы , а фиксирует .(Каждый коммит хранит файлы, так что Git косвенно сохраняет файлы, но уровень, на котором вещи видны, является коммитом.) Каждый коммит уникально идентифицируется некоторым хеш-идентификатором, который представляет собой большую уродливую строку, такую ​​как 5d826e972970a784bd7a7bdf587512510097b8c7.Обычно вы также добавляете только новые коммиты в репозиторий Git.Это верно даже для операций, которые кажутся для удаления коммитов, таких как git commit --amend или git rebase.Оригинальные коммиты с их неизменными хэш-идентификаторами, которые являются такими же постоянными, как и сами коммиты, все еще находятся в вашем хранилище.

Каждое из ваших имен ветвей просто действует как указатель.То есть каждый из них хранит один хэш-идентификатор.Один хэш-идентификатор, сохраненный в master, может быть 5d826e972970a784bd7a7bdf587512510097b8c7 сегодня.Завтра, после того, как вы сделаете новый коммит, это будет что-то другое, но в master.

будет только один идентификатор хэша. Когда вы делаете новый commit, Git:

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

  2. Собирает ваше сообщение журнала, ваше имя и адрес электронной почты.адрес и текущая дата / время и тд.К этому Git добавляет хэш дерева из шага 1, строку parent, записывающую идентификатор хеша текущего коммита из имени ветви, и любые другие подходящие метаданные, которые Git хочет включить.Из всех этих данных Git создает объект коммита , который получает свой собственный новый уникальный идентификатор хеш-функции.

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

  3. Записывает идентификатор хеша из шага 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.)

...