Есть ли способ восстановить фиксацию, которая была случайно пропущена во время перебазирования?

Когда случается, что полезная фиксация случайно пропускается во время операции перебазирования, есть ли надежда, что Git сохранит ссылку на нее, которую можно будет применить повторно?

Это была неинтерактивная перебазировка с большим количеством бинарных файлов, в которой я слишком долго находился в настроении счастливого триггера, используя git rebase --skip, поэтому вообще не было сообщений об ошибках, просто паршивое отношение.

Это похоже на сценарий восстановления после сбоя жесткого диска, но вместо того, чтобы гоняться за фантомными инодами, должен быть способ отфильтровать потерянные объекты дерева внутри .git/objects и вернуть их живыми.


person milton    schedule 08.08.2014    source источник
comment
Что вы подразумеваете под фиксацией, которая была случайно пропущена во время перебазирования? Вы говорите, что оставили фиксацию вне списка редакторов во время интерактивной перебазировки или что перебазировка просто не применила фиксацию, как предполагалось? Какую команду вы использовали? Какие были сообщения об ошибках?   -  person    schedule 09.08.2014
comment
@Cupcake, это была неинтерактивная перебазировка с большим количеством двоичных файлов, в которой я слишком долго находился в настроении триггера счастья, используя git rebase --skip, поэтому сообщений об ошибках не было вообще, просто паршивое отношение.   -  person milton    schedule 09.08.2014


Ответы (2)


Когда вы запускаете git rebase (интерактивный или нет), git в основном выполняет серию cherry-pick операций, чтобы скопировать исходную цепочку коммитов в новую цепочку. Давайте используем o для исходных коммитов и нарисуем фрагмент графа коммитов для ветки branch, отходящей от ветки main:

        o1 - o2 - o3 - o4   <-- branch
      /
..- * - x                   <-- main

Теперь вы можете запустить git rebase, чтобы скопировать все старые коммиты o в новые коммиты n, но на основе x, вершины main, а не на основе *, старой базовой точки слияния. Чтобы сделать это еще более похожим на то, что произошло, давайте «случайно» опустим одно:

        o1 - o2 - o3 - o4   <-- ???
      /
..- * - x                   <-- main
          \
            n1 - n3 - n4    <-- branch

Метка ??? выше представляет ссылку git (имя ветки, имя тега или любую другую подходящую метку), которая указывает или указывала на коммит o4. Все ваши старые коммиты все еще там, если на них указывает имя. Если имени нет, они останутся, пока git gc не очистит их (но вы не хотите, чтобы это произошло, поэтому не запускайте git gc :-) ).

Таким образом, возникает важный вопрос: «Какое имя или имена мы можем (и git) использовать, чтобы найти o4?» Оказывается, их как минимум два:

  • один или несколько в «reflog» и
  • один пишется ORIG_HEAD.

ORIG_HEAD проще всего использовать, но это имя также используется другими командами (например, git merge), поэтому вам нужно проверить, правильно ли оно по-прежнему:

$ git log ORIG_HEAD

Если это дает вам правильную цепочку, дайте себе более постоянное имя, указывающее на коммит o4. Это может быть имя ветки (таким образом вы «воскресите» старую ветку под новым именем), или имя тега, или любое другое имя, но ветка и тег — самые простые:

$ git branch zombie ORIG_HEAD

(Вы не обязаны делать это, и когда вы освоитесь с git, вы можете пропустить этот шаг, но, вероятно, лучше сделать до тех пор.)


Что, если ORIG_HEAD был взломан (например, другой перебазировкой, слиянием или чем-то еще)? Ну, тогда есть рефлоги.

Существует один журнал ссылок для HEAD и по умолчанию другой журнал ссылок для каждого имени ветки. В этом случае следует использовать reflog для branch:

$ git reflog branch
$ git log -g branch

но вы можете просто использовать git reflog, чтобы показать вариант для HEAD (это более шумный, поэтому может быть лучше посмотреть на вариант только для branch):

$ git reflog
$ git log -g

Где-то во всем этом выводе вы сможете найти коммит o4. Вы можете найти много других коммитов, похожих на o4, поэтому git log -g может быть полезным, поскольку он позволит вам найти настоящий (или правильный) o4.

В любом случае, если вы в конечном итоге придумаете «относительное имя» в стиле журнала ссылок (например, branch@{1} или branch@{yesterday}), вы можете найти необработанный SHA-1 или использовать это относительное имя, чтобы еще раз воскресить зомби-версию branch:

$ git branch zombie branch@{yesterday}

or:

$ git branch zombie feedd0gf00d

или что-то еще.


Все, что это делает, это дает вам имя, zombie, где на рисунке графика было три вопросительных знака. Вам все еще нужно использовать это, чтобы найти удаленный коммит, в данном случае коммит o2. Вы можете найти его с помощью необработанного SHA-1 (прочитав git log) и перебазировать и извлечь его, или выбрать его, чтобы добавить копию в n4, или что-то еще.

Если все, что вы хотите сделать, это установить branch обратно, чтобы зафиксировать o4, вы даже можете полностью отказаться от ветки зомби и просто выполнить git reset --hard, находясь на ветке branch:

$ git checkout branch           # if needed
$ git reset --hard feedd0gf00d

or:

$ git reset --hard ORIG_HEAD

Обратите внимание, что после reset --hard указывается просто любой идентификатор фиксации. --hard заставляет reset стереть ваше рабочее дерево и заменить его целевым коммитом, в то время как само действие reset сообщает git: "сделайте текущую ветку точкой на идентификатор коммита, который я собираюсь вам дать, независимо от того, какая ветка- подсказка - зафиксируйте его имена прямо сейчас».

Другими словами, после того, как ваш git rebase завершится и вы обнаружите, что пропустили o2 при создании цепочки n1 - n3 - n4, если вы сразу git reset --hard ORIG_HEAD, git изменит это:1

        o1 - o2 - o3 - o4   <-- ORIG_HEAD
      /
..- * - x                   <-- main
          \
            n1 - n3 - n4    <-- HEAD=branch

к этому:

        o1 - o2 - o3 - o4   <-- ORIG_HEAD, HEAD=branch
      /
..- * - x                   <-- main
          \
            n1 - n3 - n4    [abandoned]

Конечно, цепочка [abandoned] из n коммитов все еще находится в репозитории: в журналах ссылок есть имя, указывающее на n4!

(Записи reflog в конечном счете истекают — по умолчанию через 30–90 дней, в зависимости от деталей, которые еще не представляют интереса, — и когда срок их действия истечет, и не будет имени, по которому можно найти n4 или o4 или что-то еще, < em>затем git gc очистит и удалит их.)


1Обратите внимание, что я добавил к этому графику обозначение HEAD=, чтобы указать, на какой ветви вы находитесь. Этот материал HEAD= на самом деле является довольно хорошим приближением к тому, как git отслеживает, на какой ветке вы находитесь. В каталоге .git есть файл с именем HEAD, и этот файл просто содержит название текущей ветки!2 Если вы напишете в файле новое имя, git изменит представление о том, какую ветку вы используете. снова (ничего не меняя). Именно это и делает git reset --soft: записывает новое имя в HEAD. (Использование --mixed добавляет немного больше действий: git reset затем обновляет индекс/промежуточную область; а использование --hard добавляет еще больше: git reset затем стирает содержимое рабочего каталога, заменяя его тем, что вы поместили в файл HEAD. )

2В режиме "detached HEAD" файл содержит необработанный SHA-1 текущего коммита вместо имени текущей ветки . В этом, собственно, и заключается реальная разница между нахождением «на ветке» и режимом «отсоединенной HEAD». Когда git хочет узнать текущий коммит , он просматривает файл HEAD. Если у него есть необработанный SHA-1, это ответ. Если у него есть имя ветки, git считывает имя ветки, чтобы получить необработанный SHA-1. Это единственные две разрешенные настройки — ничего другого в файле HEAD быть не должно.

person torek    schedule 09.08.2014

git reflog работает на вас? Я думаю, что он все еще должен быть в сборщике мусора, если вы не запустили git gc

person AgileDan    schedule 08.08.2014
comment
Нет сборщика мусора, в котором хранятся оборванные коммиты до тех пор, пока они не будут удалены сборщиком мусора. - person ; 09.08.2014
comment
@perlsufi, может быть, краткое описание того, как использовать эту команду для отслеживания и восстановления пропущенной фиксации. У меня не так много идей о том, как это может быть полезно в этой процедуре, и эти purge слова на странице руководства меня пугают. - person milton; 09.08.2014
comment
После reflog получите нужный SHA1, а затем git слейте эту фиксацию. - person AgileDan; 09.08.2014