Изменения теряются при перебазировании и случайном переключении на другую ветку - PullRequest
0 голосов
/ 08 мая 2018

В ветке foo Я начал перебазирование следующим образом: git rebase --interactive HEAD~1, где я хотел добавить изменения для последнего коммита в файле A.

Я сделал свои изменения, git add их, а затем git commit --amend их. (Обратите внимание, что я еще не ввел команду git rebase --continue)

Затем я переключился на ветку bar через git checkout bar; ничего не сделал там и переключился обратно на foo через git checkout foo. При проверке файла A я обнаружил, что все изменения, которые я сделал во время ребазинга, пропали, хотя git status говорит:

Last command done (1 command done):
   e deadbee Nice commit message

Можно ли вернуть эти изменения обратно?

Ответы [ 2 ]

0 голосов
/ 08 мая 2018

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

Ответ Лемуэля Набонга содержит ключ (но неверный): вы должны перепроверитьиз соответствующего коммита detached-HEAD, который вы можете найти, используя git reflog.Не делайте этого с git reset, делайте это с git checkout <em>hash</em> или git checkout HEAD@{<em>number</em>}, после нахождения правильного коммита в reflog.После этого вы сможете продолжить перебазирование.

Длинное описание

Что detached HEAD означает, что специальный файл .git/HEAD (который всегда существует) больше не существуетсодержит название ветви .Обычно HEAD, или .git/HEAD, содержит строку типа ref: refs/heads/master, указывающую, что текущая ветвь имеет имя master.Затем текущая ветвь определяет текущий коммит.

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

Изображение, я думаю, делает это намного яснее.Если мы начнем с небольшого репозитория, содержащего всего три коммита, мы можем нарисовать их так, используя одиночные заглавные буквы, чтобы заменить эти ужасные строки хеш-идентификаторов, такие как ccdcbd54c4475c2238b310f7113ab3075b5abc9c.Мы будем называть наш первый коммит A, наш второй B и наш третий C:

A <-B <-C   <--master

Commit C, наш последний коммит, его хеш-идентификатор хранится под именемmaster.Мы говорим, что имя master указывает на C.Коммит C сам сохраняет хэш-идентификатор коммита B в качестве родителя , поэтому мы говорим, что C указывает на B.Коммит B хранит хэш-идентификатор A по очереди, поэтому B указывает на A.Коммит A - это самый первый коммит из когда-либо сделанных, поэтому у него вообще нет родителя.Git называет это root commit , и именно там действие останавливается, если мы запустим, например, git log, потому что нет более ранних коммитов, на которые можно посмотреть.

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

git checkout master   # if needed
... do things to modify files ...
git add file1 file2 ...
git commit

Шаг фиксации упаковывает последний снимок (из index aka). промежуточная область , где git add скопировал их, но мы оставим это для другой темы), затем записывает новый коммит D, чьим родителем является текущий коммит C:

A <-B <-C   <--master
         \
          D

Наконец, выписав новый коммит, git commit записывает хэш-идентификатор нового коммита - каким бы он ни был;это нелегко предсказать - на имя master, так что master теперь указывает на D:

A <-B <-C
         \
          D   <--master

и фиксация выполнена.

Способ, которым Git знает которое имя ветки для обновления, если у вас более одного имени ветки, - это присоединение HEAD к нему.Предположим, что вместо фиксации D на master мы делаем это:

git checkout master
git checkout -b develop    # create new develop branch

Теперь чертеж выглядит следующим образом (я опускаю внутренние стрелки, мы знаем, что они всегда указывают назад и получаюттрудно рисовать):

A--B--C   <-- master, develop (HEAD)

Мы делаем свою работу, git add и git commit, и, поскольку HEAD присоединен к develop, а не master, Git пишет новый коммит *Хэш-идентификатор 1104 * в develop вместо master, давая:

A--B--C   <-- master
       \
        D   <-- develop (HEAD)

A detached HEAD просто означает, что вместо HEAD, прикрепленного к какому-либо имени ветвиHEAD указывает непосредственно на некоторый коммит.Если бы мы отсоединились HEAD сейчас и имели точку для фиксации D, мы могли бы нарисовать это как:

A--B--C   <-- master
       \
        D   <-- develop, HEAD

Если мы сейчас сделаем new commit E, мыВы получите это:

A--B--C   <-- master
       \
        D   <-- develop
         \
          E   <-- HEAD

Еслитеперь мы говорим git checkout master, вот что происходит:

A--B--C   <-- master (HEAD)
       \
        D   <-- develop
         \
          E   <-- ???

Способ вернуться туда, где мы были, - это найти какое-то имя для коммита E (помните, его настоящее имя - какой-то большой уродливый идентификатор хеша).

И rebase, и git commit --amend работают, делая new commits. Особая вещь, которую делает --amend, состоит в том, чтобы сделать новый коммит с его родителем, являющимся parent текущего коммита. Если мы начнем с:

A--B--C   <-- master
       \
        D   <-- develop (HEAD)

и запустите git commit --amend, Git делает новый коммит E, чьим родителем является родитель D C, а не D. Затем Git пишет это в соответствующее имя - в данном случае develop, давая:

        E   <-- develop (HEAD)
       /
A--B--C   <-- master
       \
        D   <-- ??? [abandoned?]

Это где reflogs приходят

У каждого имени ветви есть журнал регистрации, в котором записываются идентификаторы коммитов, которые использовало имя ветви, на которые указывает . То есть, если master указывал на A за один раз (что должно быть), тогда в reflog для master включается хэш-идентификатор для коммита A. Этот reflog также включает хеш-код для коммита B. Как только master больше не указывает прямо на C, рефлог master будет содержать также хеш-идентификатор C и т. Д.

Существует также рефлог для самого HEAD, в котором записываются хэш-идентификаторы, на которые HEAD указал, прямо (отсоединен) или косвенно (будучи прикрепленным к имени ветки). Так что git reflog HEAD показывает вам те записи reflog, которые позволяют вам найти фактический идентификатор хеша для коммита, который вы ищете.

Один недостаток записей reflog состоит в том, что срок их действия истекает: через 30-90 дней, Git предполагает, что вам уже все равно. Этот недостаток здесь не применим, так как коммит, который вы ищете, свежий. Другим (другим?) Недостатком является то, что коммиты, найденные в reflog, как правило, выглядят одинаково, и их может быть очень много, поэтому может быть трудно найти их в шуме. Помогает также отметить, что они сохранены в порядке: запись @{1} - это старое значение, которое было несколько минут назад, запись @{2} - та, что была до этого, и так далее. Так что, если вы только недавно перешли, тот, который вы хотите, будет в числе первых.

0 голосов
/ 08 мая 2018
git reflog
git checkout HEAD@{X}

где X - индексный коммит до"o3820h HEAD @ {Y}: извлечение: переход от foo к бару"

...