Найти точку ветвления с помощью Git? - PullRequest
404 голосов
/ 06 октября 2009

У меня есть репозиторий с мастером веток и А и большим количеством операций слияния между ними. Как мне найти коммит в моем репозитории, когда ветка A была создана на основе master?

Мой репозиторий в основном выглядит так:

-- X -- A -- B -- C -- D -- F  (master) 
          \     /   \     /
           \   /     \   /
             G -- H -- I -- J  (branch A)

Я ищу ревизию A, которую git merge-base (--all) не находит.

Ответы [ 21 ]

456 голосов
/ 14 февраля 2011

Я искал то же самое и нашел этот вопрос. Спасибо, что спросили!

Однако я обнаружил, что ответы, которые я вижу здесь, не кажутся вполне ответами, которые вы просили (или которые я искал) - они, кажется, дают коммит G вместо A commit.

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

A - B - D - F - G   <- "master" branch (at G)
     \   \     /
      C - E --'     <- "topic" branch (still at E)

Это выглядит немного иначе, чем у вас, потому что я хотел убедиться, что получил (ссылаясь на этот график, а не ваш) B, но не A (и не D или E). Вот буквы, прикрепленные к префиксам SHA и сообщениям о коммитах (мой репо можно клонировать с здесь , если это кому-нибудь интересно):

G: a9546a2 merge from topic back to master
F: e7c863d commit on master after master was merged to topic
E: 648ca35 merging master onto topic
D: 37ad159 post-branch commit on master
C: 132ee2a first commit on topic branch
B: 6aafd7f second commit on master before branching
A: 4112403 initial commit on master

Итак, цель : найти B . Вот три способа, которые я нашел после небольшого переделывания:


1. визуально, с гитком:

Вы должны визуально увидеть такое дерево (если смотреть с мастера):

gitk screen capture from master

или здесь (если смотреть в тему):

gitk screen capture from topic

в обоих случаях я выбрал коммит, равный B в моем графике. После того, как вы щелкнете по нему, его полный SHA будет представлен в поле ввода текста чуть ниже графика.


2. визуально, но с терминала:

git log --graph --oneline --all

(Редактировать / примечание: добавление --decorate также может быть интересным; оно добавляет указание имен веток, тегов и т. Д. Не добавляя это в командную строку выше, так как вывод ниже не отражает его использование.)

, который показывает (при условии git config --global color.ui auto):

output of git log --graph --oneline --all

Или прямым текстом:

*   a9546a2 merge from topic back to master
|\  
| *   648ca35 merging master onto topic
| |\  
| * | 132ee2a first commit on topic branch
* | | e7c863d commit on master after master was merged to topic
| |/  
|/|   
* | 37ad159 post-branch commit on master
|/  
* 6aafd7f second commit on master before branching
* 4112403 initial commit on master

в любом случае мы видим коммит 6aafd7f как самую низкую общую точку, то есть B на моем графике или A на вашем.


3. С магией раковины:

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

diff -u <(git rev-list --first-parent topic) \
             <(git rev-list --first-parent master) | \
     sed -ne 's/^ //p' | head -1
6aafd7ff98017c816033df18395c5c1e7829960d

Который вы также можете вставить в ~ / .gitconfig как (примечание: конечный тире важен; спасибо Брайан за то, что вы обратили на это внимание) :

[alias]
    oldest-ancestor = !zsh -c 'diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne \"s/^ //p\" | head -1' -

Что можно сделать с помощью следующей (свернутой в кавычки) командной строки:

git config --global alias.oldest-ancestor '!zsh -c '\''diff -u <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | sed -ne "s/^ //p" | head -1'\'' -'

Примечание: zsh могло бы с таким же успехом быть bash, но sh будет не работать - синтаксис <() не существует в vanilla sh. (Еще раз спасибо, @conny, за то, что сообщили мне об этом в комментарии к другому ответу на этой странице!)

Примечание: альтернативная версия выше:

Благодаря liori для , указывающим , что вышеупомянутое может упасть при сравнении идентичных ветвей, и появлению альтернативной формы diff, которая удаляет форму sed из смеси, и делает это «безопаснее» (т. е. возвращает результат (а именно, самый последний коммит) даже при сравнении мастера с мастером):

В виде строки .git-config:

[alias]
    oldest-ancestor = !zsh -c 'diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1' -

Из скорлупы:

git config --global alias.oldest-ancestor '!zsh -c '\''diff --old-line-format='' --new-line-format='' <(git rev-list --first-parent "${1:-master}") <(git rev-list --first-parent "${2:-HEAD}") | head -1'\'' -'

Итак, в моем тестовом дереве (которое некоторое время было недоступно, извините, оно вернулось), теперь оно работает как для мастера, так и для темы (давая коммиты G и B соответственно). Еще раз спасибо, Лиори, за альтернативную форму.


Итак, вот что я [и liori] придумали. Кажется, это работает для меня. Он также допускает дополнительную пару псевдонимов, которые могут оказаться полезными:

git config --global alias.branchdiff '!sh -c "git diff `git oldest-ancestor`.."'
git config --global alias.branchlog '!sh -c "git log `git oldest-ancestor`.."'

Удачи!

119 голосов
/ 06 октября 2009

Возможно, вы ищете git merge-base:

git merge-base находит наилучшего общего предка (ей) между двумя коммитами для использования в трехстороннем слиянии. Один общий предок лучше , чем другой общий предок, если последний является предком первого. Общим предком, который не имеет лучшего общего предка, является лучший общий предок , то есть база слияния . Обратите внимание, что для пары коммитов может быть несколько баз слияния.

37 голосов
/ 03 марта 2010

Я использовал git rev-list для такого рода вещей. Например, (обратите внимание на 3 точки)

$ git rev-list --boundary branch-a...master | grep "^-" | cut -c2-

выплюнет точку ветвления. Теперь это не идеально; так как вы пару раз слили мастер в ветвь А, это разделит пару возможных точек ветвления (в основном, исходную точку ветвления, а затем каждую точку, в которой вы слили мастер в ветку А) , Однако это должно как минимум сузить возможности.

Я добавил эту команду к своим псевдонимам в ~/.gitconfig как:

[alias]
    diverges = !sh -c 'git rev-list --boundary $1...$2 | grep "^-" | cut -c2-'

так что я могу назвать это как:

$ git diverges branch-a master
28 голосов
/ 29 августа 2012

Если вам нравятся краткие команды,

git rev-list $(git rev-list --first-parent ^branch_name master | tail -n1)^^! 

Вот объяснение.

Следующая команда выдает список всех коммитов в мастере, которые произошли после создания имени_в ветви

git rev-list --first-parent ^branch_name master 

Поскольку вы заботитесь только о самых ранних из этих коммитов, вам нужна последняя строка вывода:

git rev-list ^branch_name --first-parent master | tail -n1

Родитель самого раннего коммита, который не является предком "branch_name", по определению в"branch_name" и находится в "master", так как это предок чего-то в "master" Таким образом, у вас есть самый ранний коммит в обеих ветвях.

Команда

git rev-list commit^^!

- это просто способ показать ссылку на родительский коммит. Вы можете использовать

git log -1 commit^

или что-то еще.

PS: Я не согласен с аргументом, что порядок предков не имеет значения. Это зависит от того, что вы хотите. Например, в этом случае

_C1___C2_______ master
  \    \_XXXXX_ branch A (the Xs denote arbitrary cross-overs between master and A)
   \_____/ branch B

имеет смысл выводить C2 как "ветвящийся" коммит. Это когда разработчик разветвился от "мастера". Когда он разветвился, ветвь «Б» даже не слилась в его ветке! Вот что дает решение в этом посте.

Если вам нужен последний коммит C такой, что все пути от источника до последнего коммита в ветви «A» проходят через C, то вы хотите игнорировать порядок предков. Это чисто топология и дает вам представление о том, когда у вас есть две версии кода, работающие одновременно. Именно тогда вы применили подходы, основанные на слиянии, и в моем примере он вернет C1.

19 голосов
/ 02 апреля 2012

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

Бревно

Создавая репозиторий с заданной структурой, мы получаем git log:

$ git --no-pager log --graph --oneline --all --decorate
* b80b645 (HEAD, branch_A) J - Work in branch_A branch
| *   3bd4054 (master) F - Merge branch_A into branch master
| |\  
| |/  
|/|   
* |   a06711b I - Merge master into branch_A
|\ \  
* | | bcad6a3 H - Work in branch_A
| | * b46632a D - Work in branch master
| |/  
| *   413851d C - Merge branch_A into branch master
| |\  
| |/  
|/|   
* | 6e343aa G - Work in branch_A
| * 89655bb B - Work in branch master
|/  
* 74c6405 (tag: branch_A_tag) A - Work in branch master
* 7a1c939 X - Work in branch master

Мое единственное добавление - это тег, который явно указывает точку, в которой мы создали ветвь, и, следовательно, фиксацию, которую мы хотим найти.

Решение, которое работает

Единственное решение, которое работает, это решение, предоставленное lindes , правильно возвращает A:

$ diff -u <(git rev-list --first-parent branch_A) \
          <(git rev-list --first-parent master) | \
      sed -ne 's/^ //p' | head -1
74c6405d17e319bd0c07c690ed876d65d89618d5

Как указывает Чарльз Бэйли , это решение очень хрупкое.

Если вы branch_A в master и затем слили master в branch_A без промежуточных коммитов, тогда решение Линдеса даст вам самое последнее первое расхождение .

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

На самом деле все сводится к отсутствию git того, что hg называет именованными ветвями . Блоггер jhw называет эти происхождений против семейств в своей статье Почему мне нравится Mercurial больше, чем Git и в его последующей статье More On Mercurial против Git (с графиками!) . Я бы порекомендовал людям прочитать их, чтобы понять, почему некоторые ртутные новообращенные не имеют именованных ветвей в git.

Решения, которые не работают

Решение, предоставленное mipadi , возвращает два ответа: I и C:

$ git rev-list --boundary branch_A...master | grep ^- | cut -c2-
a06711b55cf7275e8c3c843748daaa0aa75aef54
413851dfecab2718a3692a4bba13b50b81e36afc

Решение предоставлено Грег Хьюгилл возврат I

$ git merge-base master branch_A
a06711b55cf7275e8c3c843748daaa0aa75aef54
$ git merge-base --all master branch_A
a06711b55cf7275e8c3c843748daaa0aa75aef54

Решение, предоставленное Карл возвращает X:

$ diff -u <(git log --pretty=oneline branch_A) \
          <(git log --pretty=oneline master) | \
       tail -1 | cut -c 2-42
7a1c939ec325515acfccb79040b2e4e1c3e7bbe5

Сценарий

mkdir $1
cd $1
git init
git commit --allow-empty -m "X - Work in branch master"
git commit --allow-empty -m "A - Work in branch master"
git branch branch_A
git tag branch_A_tag     -m "Tag branch point of branch_A"
git commit --allow-empty -m "B - Work in branch master"
git checkout branch_A
git commit --allow-empty -m "G - Work in branch_A"
git checkout master
git merge branch_A       -m "C - Merge branch_A into branch master"
git checkout branch_A
git commit --allow-empty -m "H - Work in branch_A"
git merge master         -m "I - Merge master into branch_A"
git checkout master
git commit --allow-empty -m "D - Work in branch master"
git merge branch_A       -m "F - Merge branch_A into branch master"
git checkout branch_A
git commit --allow-empty -m "J - Work in branch_A branch"

Я сомневаюсь, что версия git имеет большое значение для этого, но:

$ git --version
git version 1.7.1

Спасибо Чарльзу Бэйли за то, что он показал мне более компактный способ написания сценария для репозитория примеров.

10 голосов
/ 06 октября 2009

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

В git ветки - это просто текущие названия подсказок разделов истории. У них нет сильной идентичности.

Обычно это не является большой проблемой, так как база слияния (см. Ответ Грега Хьюгилла) двух коммитов, как правило, гораздо более полезна, давая самый последний коммит, которым поделились две ветви.

Решение, основанное на порядке родителей коммитов, очевидно, не будет работать в ситуациях, когда ветвь полностью интегрирована в какой-то момент в истории ветки.

git commit --allow-empty -m root # actual branch commit
git checkout -b branch_A
git commit --allow-empty -m  "branch_A commit"
git checkout master
git commit --allow-empty -m "More work on master"
git merge -m "Merge branch_A into master" branch_A # identified as branch point
git checkout branch_A
git merge --ff-only master
git commit --allow-empty -m "More work on branch_A"
git checkout master
git commit --allow-empty -m "More work on master"

Эта методика также не работает, если объединение интеграции было выполнено с перевернутыми родителями (например, временная ветвь использовалась для выполнения тестового объединения в мастер, а затем быстро пересылалась в ветвь функций для дальнейшего развития). *

git commit --allow-empty -m root # actual branch point
git checkout -b branch_A
git commit --allow-empty -m  "branch_A commit"
git checkout master
git commit --allow-empty -m "More work on master"
git merge -m "Merge branch_A into master" branch_A # identified as branch point
git checkout branch_A
git commit --allow-empty -m "More work on branch_A"

git checkout -b tmp-branch master
git merge -m "Merge branch_A into tmp-branch (master copy)" branch_A
git checkout branch_A
git merge --ff-only tmp-branch
git branch -d tmp-branch

git checkout master
git commit --allow-empty -m "More work on master"
5 голосов
/ 05 ноября 2009

Как насчет чего-то вроде

git log --pretty=oneline master > 1
git log --pretty=oneline branch_A > 2

git rev-parse `diff 1 2 | tail -1 | cut -c 3-42`^
5 голосов
/ 01 июня 2012

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

Вместо этого я следовал другому подходу, основанному на том факте, что обе ветви имеют большую историю, точно вся история до ветвления на 100% одинакова, поэтому вместо того, чтобы вернуться назад, мое предложение о продвижении вперед (с 1-го коммита), ища 1-ую разницу в обеих ветках. Точка ветвления будет просто родителем первой найденной разницы.

На практике:

#!/bin/bash
diff <( git rev-list "${1:-master}" --reverse --topo-order ) \
     <( git rev-list "${2:-HEAD}" --reverse --topo-order) \
--unified=1 | sed -ne 's/^ //p' | head -1

И это решает все мои обычные дела. Конечно, есть пограничные, которые не покрыты, но ... Чао: -)

4 голосов
/ 29 июля 2012

Мне недавно тоже нужно было решить эту проблему, и в итоге я написал для этого скрипт на Ruby: https://github.com/vaneyckt/git-find-branching-point

3 голосов
/ 01 октября 2013

После долгих исследований и дискуссий становится ясно, что нет волшебной палочки, которая бы работала во всех ситуациях, по крайней мере, в текущей версии Git.

Вот почему я написал пару патчей, которые добавляют концепцию tail ветви. Каждый раз, когда создается ветвь, создается указатель на исходную точку, tail ref. Эта ссылка обновляется каждый раз, когда ветка перебазируется.

Чтобы найти точку ветвления ветки devel, все, что вам нужно сделать, это использовать devel@{tail}, вот и все.

https://github.com/felipec/git/commits/fc/tail

...