Как мне найти следующий коммит в git? (ребенок / дети реф.)

236

ref^относится к коммиту до ref, как насчет получения коммита после ref ?

Например, если я git checkout 12345как проверить следующий коммит?

Спасибо.

PS Да, git - это дерево структуры указателя узла DAG. Как мне найти коммит после этого?

Schwern
источник
4
Дубликат: stackoverflow.com/questions/1761825/…
hcs42
2
Смотрите также " git children-of"!
VonC

Ответы:

185

Чтобы вывести список всех коммитов, начиная с текущего, потом его потомка и т. Д. - в основном это стандартный git log, но в другом направлении, используйте что-то вроде

git log --reverse --ancestry-path 894e8b4e93d8f3^..master

где 894e8b4e93d8f3 - первый коммит, который вы хотите показать.

Тим Хант
источник
3
Для конкретного случая в первоначальном вопросе, просто заменить HEAD^на 894e8b4e93d8f3^.
Сёрен Лёвборг,
2
Может быть, добавить --oneline - лучший краткий результат.
Firo
1
Мне нужно использовать ..., ^..молча не получается
TankorSmash
1
Начало fatal: unrecognized argument: --ancestry-pathв git версии 1.7.1
user151841
6
Это будет работать, только если masterнаходится на пути предка текущего коммита. Посмотрите второй фрагмент кода моего ответа для решения, которое будет работать во всех случаях.
Том Хейл,
37

Создатель для Hudson (сейчас Jenkins) Kohsuke Kawaguchi только что опубликовал (ноябрь 2013):
kohsuke / git-children-of :

Получив коммит, найдите непосредственных потомков этого коммита.

#!/bin/bash -e
# given a commit, find immediate children of that commit.
for arg in "$@"; do
  for commit in $(git rev-parse $arg^0); do
    for child in $(git log --format='%H %P' --all | grep -F " $commit" | cut -f1 -d' '); do
      git describe $child
    done
  done
done

Как показано в этой теме , в VCS, основанной на истории, представленной DAG (направленный ациклический граф) , нет ни «одного родителя», ни «одного потомка».

        C1 -> C2 -> C3
      /               \
A -> B                  E -> F
      \               /
        D1 -> D2 ----/

Упорядочение коммитов осуществляется с помощью «топо-порядка» или «даты-порядка» (см. Книгу GitPro )

Но начиная с Git1.6.0 , вы можете перечислить потомков коммита.

git rev-list --children
git log --children

Примечание: для родительских коммитов ^возникает та же проблема с суффиксом к параметру ревизии, означающим первого родителя этого объекта коммита. ^<n>означает <n>родителя (то rev^ есть эквивалентно rev^1).

Если вы находитесь на ветке fooи выпускаете " git merge bar", то fooбудете первым родителем.
Т.е.: первый родитель - это ветвь, в которой вы были, когда вы сливались, а второй - коммит в ветке, в которую вы вошли.

VonC
источник
6
git rev-list --childrenконечно, похоже на то, что я хочу, но это не DWIM. Кажется, чтобы перечислить всех родителей и их детей. Я полагаю, что могу перечислить их все и разобрать их ... блеф, но это что-то.
Шверн
@Schwern: правда, git rev-list --childrenне для перечисления только детей, но для перечисления родителей со своими детьми ... вам всегда нужно разобрать.
VonC
Я пробовал этот код на коммите с двумя детьми: $ git children0of 9dd5932 fatal: No annotated tags can describe '71d7b5dd89d241072a0a078ff2c7dfec05d52e1f'. However, there were unannotated tags: try --tags. какой вывод вы получаете?
Том Хейл,
@ TomHale Я не могу проверить это прямо сейчас, но задаю новый вопрос (с версией ОС и Git), так что каждый может проверить.
VonC
1
Единственная проблема в git-children-ofтом, что он использует git description, который пытается отформатировать SHA как удобочитаемый для человека, что может привести к ошибке @ TomHale, и дает такие результаты v1.0.4-14-g2414721, которые вводят в заблуждение, если вы ожидаете SHA. Замена на простой echoделает это отличным инструментом, спасибо!
Николай
18

что я нашел

git rev-list --ancestry-path commit1..commit2

где я установил commit1текущий коммит и commit2текущий заголовок. Это возвращает мне список всех коммитов, которые строят путь между commit1и commit2.

Последняя строка вывода является дочерним элементом commit1 (на пути к commit2).

white_gecko
источник
3
Так что просто добавьте, | tail -1чтобы получить ребенка.
Джесси Глик
8

Я знаю, что Вы имеете ввиду. Расстраивает наличие обильного синтаксиса для перехода к предыдущим коммитам, но нет перехода к следующим. В сложной истории проблема «что будет следующим коммитом» становится довольно сложной, но затем в сложном слиянии возникает такая же сложность и с «предыдущими» коммитами. В простом случае, внутри одной ветви с линейной историей (даже только локально для некоторого ограниченного числа коммитов) было бы неплохо и имеет смысл идти вперед и назад.

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

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

Выберите коммит вперед, где вы находитесь. Вероятно, это может быть глава филиала. Если вы находитесь на ветке ~ 10, то "git checkout branch ~ 9", затем "git checkout branch ~ 8", чтобы получить следующую после этого, затем "git checkout branch ~ 7" и так далее.

Уменьшение числа должно быть очень простым в сценарии, если вам это нужно. Намного проще, чем разбирать git rev-list.

vontrapp
источник
Хотя это полезно, он не находит следующий коммит. Это идет к текущему коммиту.
Шверн
1
Ну, тогда, я полагаю, вы могли бы сделать: " BRANCH=master; git co $BRANCH~$[ $(git rev-list HEAD..$BRANCH | wc -l) - 1 ]" Вы должны идти к ветви, никак не обойти это.
vontrapp
8

Два практических ответа:

Один ребенок

Основываясь на ответе @ Майкла , я взломал childпсевдоним в моем .gitconfig.

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

# Get the child commit of the current commit.
# Use $1 instead of 'HEAD' if given. Use $2 instead of curent branch if given.
child = "!bash -c 'git log --format=%H --reverse --ancestry-path ${1:-HEAD}..${2:\"$(git rev-parse --abbrev-ref HEAD)\"} | head -1' -"

По умолчанию он задает дочерний элемент HEAD (если не указан другой аргумент commit-ish), следуя шагу предка до кончика текущей ветви (если только в качестве второго аргумента не указан другой commit-ish).

Используйте %hвместо, %Hесли вы хотите короткую форму хеша.

Несколько детей

С отсоединенной ГОЛОВКОЙ (нет ветки) или чтобы получить всех детей независимо от ветки:

# For the current (or specified) commit-ish, get the all children, print the first child 
children = "!bash -c 'c=${1:-HEAD}; set -- $(git rev-list --all --not \"$c\"^@ --children | grep $(git rev-parse \"$c\") ); shift; echo $1' -"

Измените на, $1чтобы $*распечатать всех детей.

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

Том Хейл
источник
7

Не существует уникального «следующего коммита». Поскольку история в Git - это DAG, а не строка, многие коммиты могут иметь общего родителя (ветви), а коммиты могут иметь более одного родителя (слияния).

Если вы имеете в виду конкретную ветвь, вы можете посмотреть в ее журнале и посмотреть, в каком коммите указана текущая ветка в качестве ее родителя.

Фил Миллер
источник
37
По этой логике также нет «предыдущего коммита», но есть много синтаксиса для получения родительского (ых).
Шверн
7
@Schwern: «Предыдущего коммита» тоже нет; <rev>^является «родительским коммитом» («первый родительский» для коммитов слияния).
Якуб Наребски
6

Я пробовал много разных решений, и ни одно из них не помогло мне. Пришлось придумать свое.

найти следующий коммит

function n() {
    git log --reverse --pretty=%H master | grep -A 1 $(git rev-parse HEAD) | tail -n1 | xargs git checkout
}

найти предыдущий коммит

function p() {
    git checkout HEAD^1
}
MK
источник
6

В том случае, если вы не имеете в виду конкретный «целевой» коммит, но вместо этого хотите увидеть дочерние коммиты, которые могут быть в любой ветви, вы можете использовать эту команду:

git rev-list --children --all | grep ^${COMMIT}

Если вы хотите увидеть всех детей и внуков , вы должны использовать rev-list --childrenрекурсивно, например так:

git rev-list --children --all | \
egrep ^\($(git rev-list --children --all | \
           grep ^${COMMIT} | \
           sed 's/ /|/g')\)

(Версия, которая дает только внукам, будет использовать более сложную sedи / или cut.)

Наконец, вы можете передать это в log --graphкоманду, чтобы увидеть древовидную структуру, вот так:

git log --graph --oneline --decorate \
\^${COMMIT}^@ \
$(git rev-list --children --all | \
  egrep ^\($(git rev-list --children --all | \
             grep ^${COMMIT} | \
             sed 's/ /|/g')\))

Примечание : все приведенные выше команды предполагают, что вы установили переменную оболочки ${COMMIT}для некоторой ссылки (branch, tag, sha1) коммита, чьи дочерние элементы вам интересны.

Мэтт МакГенри
источник
2
это отвечает на вопрос, который я не знал, как сформулировать для google и stackoverflow, но пытался задать. спасибо за активное признание необходимости
Томми Ноултон
для меня ${COMMIT}не важно, но вы могли бы использовать $(git rev-parse HEAD) вместо этого
Radon8472
извините, добавил заметку о ${COMMIT}.
Мэтт МакГенри
5

(Это началось как ответ на дубликат вопроса. Я сделал небольшое редактирование, чтобы очистить его.)

Все внутренние стрелки Git односторонние и направлены назад. Поэтому нет короткого удобного синтаксиса для продвижения вперед: это просто невозможно.

Это является возможным «двигаться против стрел», но способ сделать это удивительно , если вы еще не видели его раньше, и затем очевидно после этого. Допустим, у нас есть:

A <-B <-C <-D <-E   <-- last
        ^
        |
         \--------- middle

Используя, middle~2следует за стрелками дважды от Cспины к A. Так как же мы переходим от Cк D? Ответ: мы начинаем E, используя имя lastи работать в обратном направлении , пока не дойдем до middle, записей точек , которые мы посещаем по пути . Затем мы просто движемся так далеко, как мы хотим, в направлении last: двигаться на один шаг Dили на два E.

Это особенно важно, когда у нас есть филиалы:

          D--E   <-- feature1
         /
...--B--C   <-- master
         \
          F--G   <-- feature2

Какой коммит будет на один шаг после C? Там нет правильного ответа, пока вы не добавите к вопросу: в направлении признака ___ (заполните пробел).

Чтобы перечислить коммиты между C(исключая C) себя и, скажем G, мы используем:

git rev-list --topo-order --ancestry-path master..feature2

Это --topo-orderгарантирует, что даже при наличии сложных ветвлений и слияний коммиты получаются в топологически отсортированном порядке. Это требуется только в том случае, если цепь не является линейной. В --ancestry-pathозначает ограничение , что , когда мы работаем в обратном направлении от feature2, мы только список коммитов , которые имеют обязательство в Cкачестве одного из своих предков. То есть, если график - или его часть в любом случае - на самом деле выглядит так:

A--B--C   <-- master
 \     \
  \     F--G--J   <-- feature2
   \         /
    H-------I   <-- feature3

простой запрос формы feature2..masterперечисляет коммиты J, Gи I, и, Fи Hв некотором порядке. С --ancestry-pathнами выбиваем Hи I: они не потомки C, только из A. С помощью --topo-orderмы удостоверимся, что фактический порядок перечисления Jтогда G, потом F.

Команда git rev-listвыдает эти хэш-идентификаторы на свой стандартный вывод, по одному на строку. Чтобы сделать шаг вперед в направлении feature2, то нам просто нужна последняя строка.

Можно (и заманчиво, и полезно) добавить, --reverseчтобы git rev-listпечатать коммиты в обратном порядке после их генерации. Это работает, но если вы используете его в конвейере, как это:

git rev-list --topo-order --ancestry-path --reverse <id1>...<id2> | head -1

чтобы просто получить «следующий коммит в направлении id2», и есть очень длинный список коммитов, git rev-listкоманда может получить прерванный канал, когда она пытается записать, headкоторый прекратил чтение своего ввода и завершился. Поскольку ошибки в сломанной трубе обычно игнорируются оболочкой, это в основном работает. Просто убедитесь, что они игнорируются при вашем использовании.

Также заманчиво добавить -n 1в git rev-listкоманду вместе с --reverse. Не делай этого! Это делает git rev-listостановку после перехода на один шаг назад , а затем переворачивает список посещений (с одной записью). Так что это просто производит <id2>каждый раз.

Важное примечание

Обратите внимание, что с фрагментами графа «алмаз» или «бензольное кольцо»:

       I--J
      /    \
...--H      M--...  <-- last
      \    /
       K--L

перемещая один совершает «вперед» от в Hсторону lastполучит Вас либо I или K . С этим ничего не поделаешь: оба коммита на шаг впереди! Если вы затем начнете с полученного коммита и сделаете еще один шаг, то теперь вы привержены тому пути, на котором начали.

Лекарство от этого состоит в том, чтобы не двигаться по одному шагу за раз и не зацикливаться на зависимых от пути цепях. Вместо этого, если вы планируете посетить всю цепочку пути предков, прежде чем делать что-либо еще , составьте полный список всех коммитов в цепочке:

git rev-list --topo-order --reverse --ancestry-path A..B > /tmp/list-of-commits

Затем, посетите каждый коммит в этом списке, по одному, и вы получите всю цепочку. --topo-orderБудет убедиться , что вы попали I-и- Jв таком порядке, и K-и- Lв таком порядке (хотя нет никакого простого способа предсказать , будете ли вы сделать пару IJ до или после пары KL).

Торек
источник
« Там нет правильного ответа, пока вы не добавите к вопросу: в направлении признака ___ » Это очень хороший момент. Спасибо за Ваш ответ.
Шверн
4

У меня есть этот псевдоним в ~/.gitconfig

first-child = "!f() { git log  --reverse --ancestry-path --pretty=%H $1..${2:-HEAD} | head -1; }; f"
weakish
источник
что такое f()? И это должно быть, head -1чтобы быть первым ребенком, иначе это просто сообщит ГОЛОВУ.
Xerus
@Xerus Потому что он использует некоторый «сложный» синтаксис оболочки, и git не распознает его, если его не оборачивать f(). Да, head -1это смелое предположение.
слабый
Ницца! Настроил мой, чтобы: nextref = "!f() { git log --reverse --ancestry-path --pretty=%H $1..HEAD | head -${2:-1} | tail -1; }; f"чтобы вы могли выбрать, насколько далеко вперед, по желанию
З. Хулла
2

Мне удалось найти следующего ребенка следующим образом:

git log --reverse --children -n1 HEAD (where 'n' is the number of children to show)
DevRQ
источник
1
для меня это показывает не первый ребенок, это показывает текущий коммит
Radon8472
2

Если все дочерние коммиты находятся в некоторой ветке, вы можете использовать gitk --all commit^.., где «коммит» - это что-то, идентифицирующее коммит. Например, если обозначение SHA-1 для фиксации - c6661c5, введитеgitk --all c6661c5^..

Вам, вероятно, потребуется ввести полный SHA-1 в ячейку gitk "SHA1 ID:". Вам понадобится полный SHA-1, который для этого примера можно получить черезgit rev-parse c6661c5

В качестве альтернативы, git rev-list --all --children | grep '^c6661c5883bb53d400ce160a5897610ecedbdc9d'будет производиться строка, содержащая все дочерние элементы этого коммита, предположительно независимо от того, участвует ли ветвь.

user339589
источник
0

Каждый коммит хранит указатель на своего родителя (родителей, в случае коммита (стандартного) коммита).

Таким образом, нет способа указать на дочерний коммит (если он есть) от родителя.

Лакшман Прасад
источник
Коммит не может хранить указатели на своих потомков, так как дополнительные дочерние коммиты (точки ветвления) могут быть добавлены позже.
Якуб Наребски
@ Якуб, я не очень понимаю. Они не могут быть добавлены позже?
Шверн
1
Якуб: Это именно то, что я сказал. «Каждый коммит хранит указатели только на своего родителя».
Лакшман Прасад
@Schwern: коммиты в git являются неизменяемыми (что имеет хорошее следствие ответственности), поэтому указатели на детей не могут быть «добавлены позже». Одна из причин заключается в том, что идентификатор commit (используемый, например, в «родительских» ссылках) зависит от содержимого comit; это единственное решение в распределенной системе, без центральной нумерации. Также «потомки» коммитов зависят от веток, которые у вас есть, и это, в свою очередь, может отличаться от репозитория к репозиторию (и коммиты одинаковы в каждом репозитории).
Якуб Наребски
4
@becomingGuru Я за это проголосовал. Это может быть правдой, но это не отвечает на мой вопрос. Вопрос в том, как найти следующий коммит в git? Это не "git commit хранит указатель на своих потомков?"
Шверн
0

Этот пост ( http://www.jayway.com/2015/03/30/using-git-commits-to-drive-a-live-coding-session/#comment-282667 ) показывает аккуратный способ, если делать это, если Вы можете создать четко определенный тег в конце стека фиксации. По сути, git config --global alias.next '!git checkout `git rev-list HEAD..demo-end | tail -1`' где «demo-end» является последним тегом.

Алекс Дреско
источник
0

Существующие ответы предполагают, что у вас есть ветка, содержащая коммит, который вы ищете.

В моем случае, коммит, который я искал, не был git rev-list --allвключен, так как ни одна ветка не содержала его.

Я закончил просматривать gitk --reflogвручную.

Если вы не можете найти свой коммит даже в reflog, попробуйте либо:

  • git fsck --full перечислить висячие (т.е. не в какой-либо ветке) коммиты, или
  • git fsck --lost-found сделать ссылки, указывающие на висячие коммиты, чтобы применить методы в других ответах.
snipsnipsnip
источник