Найти коммит слияния, который включает конкретный коммит

184

Представьте себе следующую историю:

       c---e---g--- feature
      /         \
-a---b---d---f---h--- master

Как я могу найти, когда коммит "c" был объединен с мастером (то есть, найти коммит коммит "h")?

Гийом Морен
источник

Ответы:

158

Ваш пример показывает, что ветка feature все еще доступна.

В этом случае hпоследний результат:

git log master ^feature --ancestry-path

Если ветка featureбольше недоступна, вы можете показать коммиты слияния в строке истории между cи master:

git log <SHA-1_for_c>..master --ancestry-path --merges

Это, однако, также покажет все слияния, которые произошли после h, между eи так gдалее feature.


Сравнение результата следующих команд:

git rev-list <SHA-1_for_c>..master --ancestry-path

git rev-list <SHA-1_for_c>..master --first-parent

даст вам SHA-1 в hкачестве последнего общего ряда.

Если он у вас есть, вы можете использовать comm -1 -2эти результаты. Если вы используете msysgit, вы можете использовать следующий код perl для сравнения:

perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/'  file1 file2

(Perl-код от http://www.cyberciti.biz/faq/command-to-display-lines-common-in-files/ , который взят у «кого-то из группы новостей comp.unix.shell»).

Посмотрите процесс замены, если вы хотите сделать его однострочным.

Готье
источник
4
Даже если у вас есть comm, вы не можете использовать его, потому что вывод команды "git rev-list" не отсортирован лексикографически. Конечно, вы можете отсортировать вывод каждой команды перед поиском общих строк, но тогда желаемый коммит не обязательно будет последним. Поэтому я думаю, что что-то вроде команды perl (хотя и неясно) необходимо.
Mhagger
12
Я только что написал скрипт git-when-merged, который реализует это предложение (с довольно многими другими функциями). См github.com/mhagger/git-when-merged
mhagger
2
Предположим, что в какой-то момент masterбыл объединен с feature, затем сразу же featureобъединен в masterкачестве ускоренной перемотки вперед (наконечник featureзаменяет master). Будет ли это причиной, --first-parentчтобы вернуть неправильный родитель?
Кельвин
4
Я пытался, comm -1 -2но это не сработало. commработает только на отсортированных строках. (Perl однострочный работает, хотя я не мог его прочитать.)
Domon
2
Это немного поздно, но добавление так, чтобы люди могли найти это полезным, поскольку git изначально не обеспечивает этого. В некоторых угловых случаях я думаю, что этот ответ не обрабатывается (см. Мой ответ ниже stackoverflow.com/a/43716029/58678, который обрабатывает большинство угловых случаев). Вот угловые случаи: git find-merge h master(ничего не возвращает, но должен вернуть h), git find-merge d master(возвращает f, но должен вернуть d), git find-merge c feature(возвращает e, но должен вернуть g).
HIPPY
133

Добавьте это к вашему ~/.gitconfig:

[alias]
    find-merge = "!sh -c 'commit=$0 && branch=${1:-HEAD} && (git rev-list $commit..$branch --ancestry-path | cat -n; git rev-list $commit..$branch --first-parent | cat -n) | sort -k2 -s | uniq -f1 -d | sort -n | tail -1 | cut -f2'"
    show-merge = "!sh -c 'merge=$(git find-merge $0 $1) && [ -n \"$merge\" ] && git show $merge'"

Затем вы можете использовать псевдонимы следующим образом:

# current branch
git find-merge <SHA-1>
# specify master
git find-merge <SHA-1> master

Чтобы увидеть сообщение коммита слияния и другие детали, используйте git show-merge те же аргументы.

(Основано на ответе Готье . Спасибо Розену Матеву и Джавабретту за исправление проблемы с sort.)

robinst
источник
6
Прекрасный! Это лучшее решение.
Н Джонс
1
Остерегайтесь того, что sort -k2 | uniq -f1 -d | sort -n | tail -1 | cut -f2не найдете правильно последний общий ряд. Вот пример, где это терпит неудачу.
Розен Матев
1
@ RosenMatev Это находит 16db9fef5c581ab0c56137d04ef08ef1bf82b0b7здесь, когда я запускаю его на вашей пасте, это не ожидается? На какой ОС вы работаете?
Робинст
1
@robinst, ваш правильно. У меня есть "(GNU coreutils) 8.4" и для меня это находит29c40c3a3b33196d4e79793bd8e503a03753bad1
Розен Матев
2
@powlo: Ничего, это просто одна команда вместо двух для удобства.
Робинст
28

git-get-merge найдет и покажет коммит слияния, который вы ищете:

pip install git-get-merge
git get-merge <SHA-1>

Команда следует за дочерними элементами данного коммита, пока не будет найдено слияние с другой ветвью (предположительно master).

Цзянь
источник
22

То есть, чтобы подвести итог сообщения Готье:

perl -ne 'print if ($seen{$_} .= @ARGV) =~ /10$/' <(git rev-list --ancestry-path <SHA-1_for_c>..master) <(git rev-list --first-parent <SHA-1_for_c>..master) | tail -n 1

РЕДАКТИРОВАТЬ: потому что это использует подстановку процесса " <()", это не POSIX-совместимый, и он может не работать с вашей оболочкой. Это работает с bashили zshхотя.

Totor
источник
4
Я не часто копирую / вставляю код из интернета, но когда я это делаю, он работает отлично! Спасибо Тотор за то, что позволил мне не думать.
Бен
2
@ TheodoreR.Smith, к сожалению, синтаксис <()не совместим с POSIX. Вы должны использовать bash, zshили оболочка поддерживает замену процесса . Я отредактировал свой ответ соответственно.
Тотор
Не работал для меня Я думаю, что моя функциональная ветвь сначала была объединена с другой ветвью, прежде чем она была объединена с мастером. Это может объяснить, почему ваши команды приводят к «ветви слияния 'release_branch'", а не к "ветви слияния" feature_branch "".
Тим Кейперс
14

Мне нужно было сделать это и как-то найти git-when-merged(что на самом деле ссылается на этот ТАК вопрос, но Майкл Хаггерти никогда не добавлял сюда ссылку на свой очень хороший скрипт на Python). Так что теперь у меня есть.

Алекс Дюпуй
источник
Я действительно пришел к этому вопросу со своей страницы.
Флавий
12

Опираясь на отличный ответ Готье, нам не нужно использовать commдля сравнения списков. Так как мы ищем последний результат, в --ancestry-pathкотором он также находится --first-parent, мы можем просто использовать grep для последнего в выходных данных первого:

git rev-list <SHA>..master --ancestry-path | grep -f <(git rev-list <SHA>..master --first-parent) | tail -1

Или для чего-то быстрого и многократно используемого, вот функция, чтобы войти в .bashrc:

function git-find-merge() {
  git rev-list $1..master --ancestry-path | grep -f <(git rev-list $1..master --first-parent) | tail -1
}
evilstreak
источник
Это сработало для меня. commне работал, когда входы не были отсортированы.
HIPPY
4

Для толпы Ruby есть git-откуда . Очень просто.

$ gem install git-whence
$ git whence 1234567
234557 Merge pull request #203 from branch/pathway
стали
источник
4

Я использую ниже bash скрипт, который я размещаю по пути ~/bin/git-find-merge. Он основан на ответ Готье и ответ evilstreak в с несколькими настройками для обработки угловых случаев. commбросает, когда входы не отсортированы. grep -fработает отлично.

Угловые чехлы:

  • Если commit - это коммит слияния по первому родительскому пути ветви, тогда верните commit.
  • Если commit является коммитом без слияния по перво-родительскому пути ветви, тогда верните ветку. Это либо слияние, либо коммит только на ветке, и нет хорошего способа определить правильный коммит.
  • Если commit и branch одинаковы, верните commit.

~/bin/git-find-merge сценарий:

#!/bin/bash

commit=$1
if [ -z $commit ]; then
    echo 1>&2 "fatal: commit is required"
    exit 1
fi
commit=$(git rev-parse $commit)
branch=${2-@}

# if branch points to commit (both are same), then return commit
if [ $commit == $(git rev-parse $branch) ]; then
    git log -1 $commit
    exit
fi

# if commit is a merge commit on first-parent path of branch,
# then return commit
# if commit is a NON-merge commit on first-parent path of branch,
# then return branch as it's either a ff merge or commit is only on branch
# and there is not a good way to figure out the right commit
if [[ $(git log --first-parent --pretty='%P' $commit..$branch | \
    cut -d' ' -f1 | \
    grep $commit | wc -l) -eq 1 ]]; then
    if [ $(git show -s --format="%P" $commit | wc -w) -gt 1 ]; then
        # if commit is a merge commit
        git log -1 $commit
    else
        # if commit is a NON-merge commit
        echo 1>&2 ""
        echo 1>&2 "error: returning the branch commit (ff merge or commit only on branch)"
        echo 1>&2 ""
        git log -1 $branch
    fi
    exit
fi

# 1st common commit from bottom of first-parent and ancestry-path
merge=$(grep -f \
    <(git rev-list --first-parent  $commit..$branch) \
    <(git rev-list --ancestry-path $commit..$branch) \
        | tail -1)
if [ ! -z $merge ]; then
    git log -1 $merge
    exit
fi

# merge commit not found
echo 1>&2 "fatal: no merge commit found"
exit 1

Что позволяет мне сделать это:

(master)
$ git find-merge <commit>    # to find when commit merged to current branch
$ git find-merge <branch>    # to find when branch merged to current branch
$ git find-merge <commit> pu # to find when commit merged to pu branch

Этот скрипт также доступен на моем github .

хиппи
источник
2
git log --topo-order

Затем найдите первое слияние перед фиксацией.

Римский
источник
1

Моя рубиновая версия идеи @ robinst работает в два раза быстрее (что важно при поиске очень старого коммита).

find-commit.rb

commit = ARGV[0]
master = ARGV[1] || 'origin/master'

unless commit
  puts "Usage: find-commit.rb commit [master-branch]"
  puts "Will show commit that merged <commit> into <master-branch>"
  exit 1
end

parents = `git rev-list #{commit}..#{master} --reverse --first-parent --merges`.split("\n")
ancestry = `git rev-list #{commit}..#{master} --reverse --ancestry-path --merges`.split("\n")
merge = (parents & ancestry)[0]

if merge
  system "git show #{merge}"
else
  puts "#{master} doesn't include #{commit}"
  exit 2
end

Вы можете просто использовать это так:

ruby find-commit.rb SHA master
Каплан Илья
источник
0

Вы можете попробовать что-то вроде этого. Идея состоит в том, чтобы перебрать все коммиты слияния и посмотреть, достижим ли коммит "c" из одного из них:

$ git log --merges --format='%h' master | while read mergecommit; do
  if git log --format='%h' $mergecommit|grep -q $c; then
    echo $mergecommit;
    break
  fi
done
holygeek
источник
Это так же, как:git rev-list <SHA-1_for_c>..master --ancestry-path --merges
Евгений Коньков
0

Мне пришлось делать это несколько раз (спасибо всем, кто ответил на этот вопрос!), И в итоге я написал скрипт (используя метод Готье), который я мог бы добавить в свою небольшую коллекцию утилит git. Вы можете найти его здесь: https://github.com/mwoehlke/git-utils/blob/master/bin/git-merge-point .

Мэтью
источник