Git checkout для фиксации 2 коммитов до хэша

Мне нужно отменить изменение, внесенное в файл, во многих ветках, во многих репозиториях. Я знаю, что могу использовать хэш-имя файла git checkout, а затем нажать это изменение.

Проблема в том, что я знаю только две фиксации перед фактической фиксацией, к которой я хочу вернуться.

Как я могу спросить git, что такое хеш двух коммитов до этого, и использовать его для правильного возврата?


person Khadijah Celestine    schedule 07.12.2016    source источник
comment
Если история изменений A-B-C-D-E (где E — самая новая), и вы хотите вернуться к C, вы говорите, что знаете A или знаете E?   -  person John Zwinck    schedule 07.12.2016
comment
git-коммиты знают своих родителей, но, поскольку у них может быть любое количество дочерних элементов (веток), ярлык для x коммитов впереди не имеет смысла. Итак, если вы ищете 2 коммита назад, ответ hash~2, но если для 2 коммитов вперед, то нет простого пути   -  person Dunno    schedule 07.12.2016
comment
Итак, я знаю о хэше git, где это либо E, либо до E. Он может быть последним, а может и не быть. Допустим, я знаю E. Мне нужно сказать git checkout C.   -  person Khadijah Celestine    schedule 07.12.2016
comment
Также см. здесь   -  person Honey    schedule 13.02.2018


Ответы (3)


Мне непонятно, нужно ли вам смотреть назад или вперед от какой-то конкретной фиксации.

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

Вот несколько примеров графиков. Круглые узлы o представляют коммиты. * — это коммит, идентификатор которого у вас есть, и я дал «потенциально интересным» коммитам однобуквенные имена:

...--A--o--*--o--B--...--o   <-- branchname
     ^           ^
   2 before    2 after

Этот график довольно прост. Коммит A идет за два до коммита *, так что если у вас есть хеш * — скажем, это 1234567 — вы можете написать 1234567~2. ~2 означает «вернуться к двум первородителям» (см. ниже). Найти коммит B сложнее (см. ниже).

Но у нас может быть такой беспорядок:

                o--D--o   <-- branch1
               /
              /  E--o     <-- branch2
      A--o   /  /    \
          \ /  /      \
...--B--o--*--o--F-...-o   <-- branch3
            \         /
             o--C----o

Здесь коммиты A и B находятся оба на два шага раньше *, а все коммиты C, D, E и F — на два шага после.

В Git намного проще найти коммиты, которые находятся «до», потому что все внутренние стрелки Git указывают «назад». Имена веток, которые все справа и, следовательно, указывают на последний коммит в ветке — на самом деле так работают ветки в Git: имя указывает на кончик коммит, а крайний коммит указывает назад (влево на этих графиках) на более ранние коммиты — просто запустите Git с точки зрения поиска коммитов.

Получив коммит, Git следует по стрелке(ам) назад к родителю(ям) этого коммита. Здесь * — это коммит слияния, объединяющий строки A--o и B--o.

Порядок двух родителей * не показан на самом графике, но допустим, что линия A на самом деле находится через второго родителя. Предположим, что * снова равно 1234567. Тогда 1234567~2 фиксируется B (а не A), потому что ~ следует только за первыми родителями. Чтобы найти A, вы можете написать 1234567^2 (которое называет верхний o перед *), а затем добавить ^1 или ~1, чтобы вернуть еще одного родителя к A.

Другими словами, два коммита теперь 1234567~2 и 1234567^2~1. (Есть и другие способы записать это.) Но проще всего запустить git log --graph --oneline 1234567 и понаблюдать за графиком — Git нарисует его вертикально, но вы увидите две линии, выходящие из *, что позволит вам найти и A, и B.

Если есть более двух способов получить более двух коммитов, которые находятся «на два шага назад», вы увидите это на графике, как и для многих коммитов, которые «на два шага вперед». Но... найти коммиты, которые "на два шага вперед" заметно сложнее.

Опять же, Git начинает с самого нового (самого правого на наших рисунках) tip фиксации и работает в обратном направлении. Я нарисовал три вещи, которые в конечном итоге сливаются в кончик branch3,1, но обратите внимание, что фиксация D, которая также на два шага вперед, может быть найдена только путем поиска назад от branch1 .

Нет "поиска вперед"

У Git нет возможности «искать вперед» или «шаг вперед» от коммита. Все операции, которые двигаются вперед (есть одна!) на самом деле работают, двигаясь назад. По сути, мы начинаем со всех возможных отправных точек, т. е. всех названий веток/подсказок.2 Затем у нас есть список Git все< /em>, который возвращает к нужному нам коммиту, печатая хэш-идентификаторы этих коммитов тогда и только тогда, когда они являются потомками3 коммита, который мы рассматриваем.

Команда, которая печатает все эти идентификаторы, называется git rev-list (на самом деле это всего лишь git log,4 указано печатать только хэш-идентификаторы коммитов, а не регистрировать коммиты). флаг, который говорит «дайте мне потомков» — это --ancestry-path, и его нужно объединить с еще несколькими фрагментами информации. В частности, мы должны указать Git, какой коммит является «интересным», и мы делаем это с префиксом ^ (на этот раз без суффикса):

git rev-list --ancestry-path ^1234567 --branches

^1234567 сообщает Git, какая фиксация останавливает обход графа и ограничивает набор печатаемых ревизий теми, которые являются потомками 1234567. --branches сообщает Git, как обычно, с чего начать поиск: со всех подсказок веток. (Мы также можем добавить --tags или использовать --all, чтобы указать все ссылки: все ветки, все теги, тайник, если он есть, и все, что может существовать. Однако --branches или --branches --tags, вероятно, что вы хотите здесь.)

Теперь проблема с git rev-list заключается в том, что он просто выдает все идентификаторы ревизий. Сюда входят не только коммиты C, D, E и F, но и все «неинтересные» коммиты o, которые идут после коммита *. Для этого существует множество возможных решений, но, пожалуй, самое простое — вернуться к использованию git log --graph --oneline: вместо того, чтобы печатать только (полный) хеш, печатать (сокращенные) хэши и сообщения фиксации, а также рисовать график. Теперь вы можете просмотреть график, чтобы найти коммиты, которые «на два впереди».

(Конечно, поскольку git log и git rev-list в основном являются одной и той же командой, вы также можете запустить git rev-list --graph. Нет необходимости в --oneline, так как вывод в любом случае представляет собой один идентификатор фиксации на строку. Но git rev-list — это команда подключения. , предназначенный для использования в сценариях и не очень удобный для пользователя, в то время как git log предназначен для взаимодействия с пользователями и пытается быть более полезным.)


1Это слияние трех родителей называется "слияние осьминога" и на практике встречается редко. Чаще всего у вас было бы два слияния, одно из которых переводит branch2 в branch3, а другое — раньше или позже — переводит теперь безымянную строку коммитов в нижней строке в branch3.

2Мы также можем начать с тегов. Например, рассмотрим этот фрагмент графика:

...--o--*--o--o   <-- branch
         \
          o--o    <-- tag: v0.98alpha

Здесь версия 0.98alpha была сочтена "плохой", а два коммита, доступных из тега, больше не находятся ни в одной из веток ни в одной. Но если выяснится, что в одном из этих двух коммитов есть какие-то ценные данные или код, вы все равно можете найти их, начав с тега и, при необходимости, двигаясь назад к предыдущему коммиту. Здесь коммит на два шага назад, отмеченный *, также находится на ветке branch, так что вы обнаружите, что даже не начиная с тега v0.98alpha.

3В этих направленных графах, которые использует Git, предок — это любой коммит, которого можно достичь, начиная с какого-то коммита и работая в обратном направлении: сам коммит, любой из его родителей, любой родителей родителей — любого из бабушек и дедушек — и так далее. Это то же значение, что и «предки» для вас самих, за исключением того, что в Git вы считаетесь своим собственным предком.

Поскольку стрелки Git указывают назад, найти предков несложно.

Термин «потомки» работает так же: вы являетесь своим собственным потомком, но ваши дети, внуки и правнуки также являются вашими потомками, как и следовало ожидать. На самом деле Git не может найти этих внуков напрямую, но, учитывая пару коммитов, можно очень легко проверить, является ли коммит Да потомок коммита X": мы просто переворачиваем тест. Чтобы Y был потомком X, X должен быть предком Y. Поскольку Git может ответить на вопрос «является предком», мы обращаем тест и получаем ответ.

4Есть несколько существенных различий между git log и git rev-list, так что это не совсем одна и та же команда. Но они созданы из одного и того же исходного кода и могут делать одно и то же, в зависимости от параметров. Они просто устанавливают разные параметры по умолчанию: log печатает удобочитаемые вещи, использует пейджер, использует цветной вывод и так далее; в то время как rev-list печатает машиночитаемые вещи и не использует пейджер. Флаги, необходимые для того, чтобы сделать одно действие похожим на другое, также не всегда очевидны.

person torek    schedule 07.12.2016
comment
очень подробно. Благодарю. Мне нужно было зафиксировать A. ~ 2 сработало для меня! - person Khadijah Celestine; 08.12.2016

Используйте git log, чтобы увидеть изменения, внесенные в файл:

git log myfile

Когда вы выяснили хэш коммита, к которому хотите вернуться, используйте

git checkout thehash myfile
person blue112    schedule 07.12.2016

Вы можете использовать git rev-list --parents -n 2 <commithash>, чтобы перейти к коммиту 2 до вашего хэша.

person FunkyWolfe    schedule 04.12.2019