Как я могу найти git commit ha sh, из которого я спрятал? - PullRequest
2 голосов
/ 13 июля 2020

Я понимаю, что мой вопрос очень похож на Как перечислить родительскую фиксацию sta sh в `git sta sh list` и Get git sta sh родительский коммит , но у них было так много запутанных, разрозненных ответов, что я задаю свой вопрос.

Предположим, что если я запустил git stash list, я увижу stash@{0}: On featureX: someMessageHere

Как я могу выявить ha sh фиксации, с которой я работал, когда сделал эту sh (которую, я думаю, можно было бы считать родительской фиксацией)?

Я видел так много разных ответов, и я не понимаю, что они делают, чем они отличаются, и какой из них является ответом на мой вопрос:

  • git show stash@{1}^
  • git log -1 commitish^
  • git log -1 stash@{0}^
  • git log -g --no-walk --parents refs/stash
  • git for-each-ref --format='%(refname:short)' --points-at $(git rev-parse refs/stash~1) refs/heads
git log -g --format="%gd %H" refs/stash |
while read name hash; do
    printf "%s %s " $name $(git rev-parse --short $name^)
    git log -1 --format=%s $hash
done

Для дополнительного контекста это причина, по которой я спрашиваю .

Ответы [ 2 ]

4 голосов
/ 13 июля 2020

Коммит, который вы ищете: stash@{0}^:

git show stash@{0}^
git log -1 --oneline stash@{0}^
git rev-parse stash@{0}^
1 голос
/ 13 июля 2020

LeGE C ответ правильный. Чтобы (я надеюсь) помочь вам понять эту часть:

Я не понимаю, что они делают, чем они отличаются, и какой из них является ответом на мой вопрос: [список различные команды]

... давайте быстро (ну ... может быть, не так быстро) рассмотрим, как Git работает внутри в этом отношении.

Во-первых, центральная вещь в Git - это commit . В Git есть повторяющаяся тема: вы делаете фиксацию (git commit), вы находите фиксацию (разными способами), вы показываете фиксацию (git show или иногда git log), вы проверяете фиксацию (git checkout или git switch), и вы просматриваете или просматриваете коммиты (снова git log). Даже git stash работает, делая коммиты.

Есть три важных особенности коммитов:

  • Каждый имеет уникальный ID. Это его га sh ID , который выглядит, например, как 4a0fcf9f760c9774be77f51e1e88a7499b53d2e2. Многие команды Git сокращают их - иногда вы можете go всего лишь первые четыре символа, например 4a0f, если это не двусмысленно, но в большом репозитории вам обычно понадобится 7 или больше символов (а в репозитории Linux теперь их 12). 1

  • Каждый из них хранит полный снимок файлов. Мы не будем здесь подробно описывать go.

  • И каждый из них хранит некоторые метаданные: информацию, например, кто совершил фиксацию, когда и почему (сообщение журнала). Одна часть этих метаданных предназначена для самого Git и дает ha sh ID родительского коммита коммита - коммита, который происходит непосредственно перед самой фиксацией.

Большинство коммитов имеют только одного родителя. У некоторых их два или более, и в этом случае интересен родительский элемент первый . По крайней мере, у одного коммита - самого первого, который когда-либо был сделан в репозитории, - обязательно есть родительский элемент no , потому что перед первым коммитом не существует фиксации. Обычно есть только один из этих root коммитов; у всех остальных есть история.

1 Эти вещи выглядят случайными, но на самом деле вовсе не случайны. По мере того, как вы добавляете все больше и больше объектов в репозиторий Git, каждый из которых получает один из этих уникальных идентификаторов для этого объекта, становится все более вероятным, что вам потребуется использовать более полное имя, чтобы отличить их друг от друга. . Это похоже на вечеринку: имя Брюс может быть уникальным, если на нем всего десять человек, но когда вы наберете 10 000 человек, вам, вероятно, понадобится как минимум последний инициал.

Есть четыре вида Git объектов в репозитории, но в основном мы имеем дело с объектами фиксации и игнорируем остальные.

Родительский след - это то, как Git работает

Этот родитель - или первый родитель, для коммитов слияния - работает Git следующим образом: назад . Обычно мы запускаем Git с последней фиксации, или, точнее, последней фиксации в некоторой ветке. Затем Git делает что-то с этим последним коммитом, например: показывает дату автора, его имя, адрес электронной почты и сообщение журнала. Затем Git использует родителя этого коммита, чтобы вернуться на один коммит. Он показывает нам предыдущий коммит. Затем Git переходит к родительскому родительскому объекту - прародителю исходного коммита - и показывает нам этот коммит, а затем возвращается снова.

Когда нет никаких слияний, это формирует красивый простой обратная цепочка коммитов. Если мы оставим одну заглавную букву вместо ha sh ID каждого коммита, мы можем нарисовать это так:

... <-F <-G <-H

Здесь H - последний коммит в цепь. У нас (каким-то образом) есть Git найти этот коммит и показать его. Затем Git находит идентификатор G ha sh, хранящийся в метаданных для H. Git использует это для поиска фиксации G, которую он нам показывает. Затем Git находит F ha sh ID внутри G и т. Д.

(Обратите внимание, что мы говорим, что указывает обратно на их более ранний - родительский - признает, поэтому мы нарисовали эти стрелки, направленные назад. Иногда важно понимать, что Git может легко go назад , но ему сложно двигаться вперед . Зафиксировать G указывает на предыдущий F, но не на более поздний H. Однако в большинстве случаев нам это не нужно, и эти стрелки трудно хорошо нарисовать, поэтому в большинстве случаев я не беспокоюсь.)

Вот что git log делает, например. Но как найти фиксацию H? Что ж, самый простой способ - сказать это git log master. К приведенному выше рисунку мы можем добавить еще один указатель: у нас есть имя master, указывающее на фиксацию H, например:

...--F--G--H   <-- master

Если мы git checkout master и сделайте новый коммит, Git добавит новый коммит, так что его родительский элемент будет H:

...--F--G--H   <-- master
            \
             I

, но затем немедленно обновите имя master , чтобы он указывал на фиксацию I now:

...--F--G--H--I   <-- master

Последняя часть означает, что git log использует name для поиска last совершить. Если мы дадим ему имя ветки, это будет то имя, которое она использует. Если мы не даем ему никакого имени, git log использует специальное имя HEAD. Но мы также можем дать ему что-то, что не имя ветки, а именно stash.

Что нужно знать о sta sh коммиты

Когда git stash save (старый способ создания sta sh) или git stash push (новый способ создания sta sh) совершает свои коммиты, он устанавливает их так, чтобы специальное имя stash относилось к один из этих коммитов, и , который имеет в качестве своего первого родителя - мы поговорим больше о первых родителях через мгновение - коммит, который был (и остается) текущим правым при запуске git stash.

То есть, если мы их нарисуем, мы получим:

...--G--H   <-- master
        |\
        i-w   <-- stash

Я не буду go объяснять, почему я называю их i и w здесь, но в документации git stash они также называются I и W (прописные буквы вместо строчных - я предпочитаю использовать прописные буквы для более обычных коммитов, а не для этих sh).

Здесь важно то, что первый родитель коммита w - это коммит H, то есть фиксация, в которой вы находитесь во время выполнения. git stash push или то, что вы использовали для создания w.

Существует целый ряд сложных способов присвоения имен коммитам

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

Если мы возьмем какое-нибудь допустимое имя, например HEAD или master или stash, и добавим каретку / шляпу ^ или тильда ~ до конца, это директива для внутреннего средства поиска ревизий Git: Начиная с уже названного коммита, найдите его родителя (-ей) . Затем суффикс ^ выбирает первого родителя фиксации, так что stash^ означает первый родительский элемент фиксации, найденный по имени stash.

Суффикс тильды также выбирает первородников. Поначалу это кажется избыточным: develop^ и develop~ оба выбирают первого родителя фиксации, выбранного по имени branch. Но мы можем добавить после них числа , и тогда они станут другими. Ключ к пониманию этого лежит в схемах, которые мы нарисовали выше. Предположим, у нас есть:

          I--J
         /    \
...--G--H      M   <-- develop
         \    /
          K--L   <-- feature

Здесь фиксация M - это фиксация слияния , поэтому у нее есть два родителя. Допустим, что первый родительский элемент M - это J, а второй родительский элемент M - L - это то, что мы получили бы, если бы сделали commit M, выполнив git checkout develop когда develop с именем commit J, затем запустил git merge feature, чтобы получить M.

Синтаксис develop^2 означает найти второго родителя commit M, т.е. найти фиксацию L. Это имя той же фиксации, которую мы получили бы, используя имя feature - так что мы могли бы просто сделать это, пока мы еще не удалили имя feature. Но суть в том, что M^2 или develop^2 находит фиксацию L, потому что это означает найти второго родителя .

Между тем, синтаксис develop~2 означает find первый родитель первого родителя коммита M, то есть найти коммит I. Это потому, что 2 в этом случае - это количество раз, когда отступает . Таким образом, мы делаем шаг назад один раз, по первой родительской строке от M до J, затем снова по первой (и единственной) родительской строке от J до I.

Когда число после ^ или ~ равно 1, или его нет вообще, оба делают одно и то же: ^1 означает найти первого родителя (который сначала отступает на один - родительская ссылка), а ~1 означает отступить на одну первую родительскую ссылку .

Теперь давайте рассмотрим ваш список маркеров

  • git show stash@{1}^

Мы рассмотрим @{1} чуть позже. А пока представьте, что это только что сказано stash^. Имя stash найдет фиксацию, а ^ найдет своего первого родителя. Тогда git show будет показать эту фиксацию. Команда git show делает это следующим образом:

  • распечатывает ha sh ID
  • печатает отформатированную версию сообщения журнала с указанием автора и т. Д. (Вы можете изменить это с параметром --pretty=format:...)
  • с отображением различий:
    • Git извлекает снимок родительского коммита во временную область (в памяти)
    • затем Git также получает снимок этого коммита
    • , а затем Git сравнивает два снимка и сообщает вам о файлах, которые разные , не говоря ничего об одинаковых файлах

Последняя часть делает вид, будто сама фиксация содержит разницу, но это не так. Разница была вычислена для вас, когда git show придумал это сделать.

  • git log -1 commitish^

И снова ^ суффикс возвращает Git go родителю коммита. Затем git log -1 показывает сообщение журнала, но не разницу, одной фиксации - первая часть того, что показывает git show, - но с -1 останавливается после показа этой одной фиксации.

  • git log -1 stash@{0}^

Это похоже, только теперь у нас stash@{0}^ вместо commitish^. Суффикс ^ применяется к спецификатору stash@{0}, к которому мы еще вернемся.

  • git log -g --no-walk --parents refs/stash

Этот совсем другой. Параметр --no-walk избыточен с -g и не имеет значения, потому что -g берет верх. Однако опция --parents имеет значение. Чтобы правильно говорить о -g, нам нужно перейти к разделу, в котором мы рассматриваем часть @{<em>number</em>}. Оставим две последние команды на потом, а теперь перейдем к рефлогам.

Reflogs

В Git каждая ссылка - каждое имя вроде master или develop или, действительно, stash - также может вести отдельный журнал «предыдущих» значений. Для обычных имен веток эти журналы просто запоминают, на что указывают имена веток. Таким образом, каждая запись журнала запоминает один ha sh ID: старый значение имени ветки.

Например, когда вы делаете новую фиксацию, Git автоматически продвигает имя ветки чтобы указать на новую фиксацию. Но имя использовало для указания на родителя коммита, поэтому журнал теперь содержит идентификатор родительского ha sh. Если вы используете git reset, чтобы повторно установить ветку, это также поместит в журнал предварительный сброс ha sh ID. Таким образом, журнал просто накапливает каждый ha sh ID по мере вашей работы.

Есть еще одна важная вещь, которую нужно знать здесь: суффикс @{<em>number</em>} выбирает номер -ю запись журнала. Число ноль означает текущее значение имени . Таким образом, master@{0} - это всего лишь длинный путь к master, но master@{1} - это старое значение master, а master@{2} - это значение, которое было старым значением, но теперь даже старше, после того, как вы что-то обновили master.

Git обычно удаляет старые записи журнала через некоторое время - через 90 дней по умолчанию для большинства записей и 30 дней по умолчанию для некоторых. Но stash особенный, и его записи в журнале обычно никогда не удаляются в зависимости от возраста. Поскольку stash не является именем ветки , он не управляется командами ветвления. Вместо этого он управляется командой git stash с ее операциями push, pop и drop.

Здесь git stash использует stash reflog для отслеживания ранее сохраненных тайников . Когда вы используете git stash push, Git перенумеровывает предыдущие записи журнала, так что было stash@{0} стало stash@{1}, то, что было stash@{1}, стало stash@{2} и так далее. На самом деле это то же самое, что и любая обычная запись в журнале рефлогов ветвления (за исключением вечной части). Но разница в том, что когда вы используете git stash pop или git stash drop, Git выбрасывает старую запись stash@{0}, так что то, что было stash@{1}, теперь stash@{0}, что было stash@{2} теперь stash@{1} и т. Д.

Итак, теперь мы можем правильно адресовать исходный stash@{1}^ из первого git show:

git show stash@{1}^

Операция stash@{1} означает найти sta sh commit, который на один уровень глубже в sta sh stack . Затем суффикс ^ выбирает своего первого родителя.

Поскольку stash@{1} - это w фиксация sta sh на один уровень в стеке sta sh, stash@{1}^ является его родительским совершить. Это фиксация, от которой зависает этот sh

1414 * Мы также можем, наконец, обратиться к этому:
  • git log -g --parents refs/stash

(я убрал бессмысленное --no-walk.)

  • Опция -g указывает git log на просмотр рефлогов, а не на выполнение их обычное дело - найти фиксацию и затем вернуться назад по истории. Единственный рефлог, который он рассмотрит, - это запись refs/stash - что является полным написанием stash.

  • Параметр --parents указывает git log показывать не только каждый commit ha sh ID, но также и все его родительские коммиты ha sh ID.

  • Итак, мы увидим каждый w коммит в стеке sta sh, вместе с обоими его родителей. Первым родителем будет коммит, от которого зависает пара i-w, а вторым родителем будет коммит i.

Последние две команды

  • git for-each-ref --format='%(refname:short)' --points-at $(git rev-parse refs/stash~1) refs/heads

Команда git for-each-ref - это внутренняя рабочая лошадка, которая на самом деле не предназначена для конечных пользователей, которая реализует как git branch --list, так и git tag --list, а также несколько других. Поскольку эта команда предназначена для написания команд, ориентированных на пользователя, а не для непосредственного использования пользователями, она имеет множество опций:

  • Опция --format сообщает ей, как создавать свои вывод. Здесь мы выбираем распечатать краткую форму имени (которое будет именем ветки из-за более поздней опции).

  • Параметр --points-at говорит ему не печатать name , если имя не называет конкретный коммит. Коммит, о котором мы говорим, это результат другой команды Git, git rev-parse.

  • Параметр refs/heads сообщает git for-each-ref, какие ссылки использовать. Пространство refs/heads содержит все имена ваших веток. Итак, это говорит об этом: Глядя только на имена моих веток, найди те, которые имеют конкретное имя c commit; затем для любого найденного имени выведите краткую версию имени этой ветки.

Коммит, который мы выбираем для поиска, - это ha sh ID фиксации refs/stash~1. Здесь используется суффикс ~, чтобы получить первого родителя фиксации, идентифицированного именем refs/stash. Это полностью прописанная форма stash, поэтому мы просим Git использовать refs/stash для поиска w фиксации, а затем использовать ~, чтобы найти его родительский элемент, например, совершить H. Затем у нас есть Git поиск по всем именам веток, чтобы увидеть, фиксирует ли какое-либо из этих имен H. Если имя refs/heads/master - ветка master - идентифицирует фиксацию H, команда выведет имя master.

Наконец:

git log -g --format="%gd %H" refs/stash |
while read name hash; do
    printf "%s %s " $name $(git rev-parse --short $name^)
    git log -1 --format=%s $hash
done

Здесь снова используются git log -g и refs/stash, чтобы просмотреть записи журнала ссылок для refs/stash. Директивы %gd %H для --format сообщают Git , как печатать каждую такую ​​запись: %gd печатает stash@{0} или stash@{1} или stash@{2} или что угодно, и %H выводит идентификатор ha sh, связанный с этой записью reflog.

Вывод этой команды попадает в оболочку l oop. Это l oop читает имя - часть stash@{<em>number</em>} - и идентификатор ha sh. Затем команда printf печатает:

  • имя;
  • пробел;
  • сокращенную версию ha sh, найденную git rev-parse, когда учитывая только что прочитанный идентификатор ha sh, плюс суффикс ^, т.е. сокращенную версию ha sh родительского коммита sta sh;
  • и еще один пробел, но пока нет новой строки.

Наконец, это запускает git log -1 --format=%s $hash, который печатает строку темы самого sta sh.

Таким образом, это также напечатает нужную вам информацию , хотя - из-за --short в команде git rev-parse - с использованием сокращенной формы для каждого из ваших sh соответствующих родительских коммитов вашего sta sh ID.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...