Поиск точки ветвления с помощью Git?

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

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

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

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

Ответ 1

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

Однако, я обнаружил, что ответы, которые я вижу здесь, похоже, не дают ответа, который вы просили (или того, что я искал) - они, похоже, дают G commit вместо 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:

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

gitk screen capture from master

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

gitk screen capture from topic

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


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

git log --graph --oneline --all

который показывает (при условии 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 как (обратите внимание: трейлинг-черта важна, спасибо Brian за внимание к этому):

[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 из микса и делает это "более безопасным" (т.е. возвращает результат (а именно, самый последний фиксат), даже если вы сравниваете master для master):

Как строка .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, для альтернативной формы.


Итак, это то, что я [и 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`.."'

Счастливый git -ing!

Ответ 2

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

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

Ответ 3

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

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

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

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

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

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

$ git diverges branch-a master

Ответ 4

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

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

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

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

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 в моем примере.

Ответ 5

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

Журнал

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

$ 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 без промежуточных коммитов, то решение lindes дает вам самое последнее первое расхождение.

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

Это действительно все сводится к git отсутствию того, что hg вызывает названные ветки. Блоггер jhw называет эти линии по сравнению с семьями в своей статье Почему мне нравится Mercurial More than Git и его последующая статья Подробнее о Mercurial vs. git (с графиками!). Я бы порекомендовал людям прочитать их, чтобы узнать, почему некоторые конвертеры Mercurial не имеют названий ветвей в 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

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

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

script

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

Спасибо Charles Bailey за то, что он показал мне более компактный путь к script примеру репозитория.

Ответ 6

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

В 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"

Ответ 7

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

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`^

Ответ 9

Конечно, я что-то пропустил, но 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

И он решает все мои обычные случаи. Конечно, пограничные не охвачены, но... ciao: -)

Ответ 10

Здесь улучшена версия предыдущего ответа предыдущего ответа. Он полагается на сообщения фиксации из объединений, чтобы найти, где была создана ветвь.

Он работает со всеми репозиториями, упомянутыми здесь, и я даже обратился к некоторым сложным, которые порождены в списке рассылки. Я также написал тесты для этого.

find_merge ()
{
    local selection extra
    test "$2" && extra=" into $2"
    git rev-list --min-parents=2 --grep="Merge branch '$1'$extra" --topo-order ${3:---all} | tail -1
}

branch_point ()
{
    local first_merge second_merge merge
    first_merge=$(find_merge $1 "" "$1 $2")
    second_merge=$(find_merge $2 $1 $first_merge)
    merge=${second_merge:-$first_merge}

    if [ "$merge" ]; then
        git merge-base $merge^1 $merge^2
    else
        git merge-base $1 $2
    fi
}

Ответ 11

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

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

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

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

Ответ 12

Чтобы найти фиксацию из точки ветвления, вы можете использовать это.

git log --ancestry-path master..topicbranch

Ответ 13

Следующая команда покажет SHA1 Commit A

git merge-base --fork-point A

Ответ 14

Кажется, я получаю некоторую радость от

git rev-list branch...master

Последняя строка, которую вы получаете, - это первая фиксация на ветке, так что это вопрос получения родительского элемента. Так

git rev-list -1 `git rev-list branch...master | tail -1`^

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

Коррекция: это не работает, если вы находитесь на главной ветке, но я делаю это в script, так что меньше проблемы

Ответ 15

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

Это не так просто вычислить с помощью обычных команд оболочки git, так как git rev-list - наш самый мощный инструмент - не позволяет нам ограничивать путь, по которому достигается фиксация. Самое близкое, что у нас есть git rev-list --boundary, что может дать нам набор всех коммитов, которые "заблокировали наш путь". (Примечание: git rev-list --ancestry-path интересен, но я не могу сделать это полезным здесь.)

Вот script: https://gist.github.com/abortz/d464c88923c520b79e3d. Это относительно просто, но из-за цикла он достаточно сложный, чтобы гарантировать суть.

Обратите внимание, что большинство других предложенных здесь решений не могут работать во всех ситуациях по простой причине: git rev-list --first-parent не является надежным в линеаризации истории, потому что могут быть слияния с любым упорядочением.

git rev-list --topo-order, с другой стороны, очень полезно - для хождения совершается в топографическом порядке, но выполнение различий является хрупким: существует множество возможных топографических порядков для данного графика, так что вы зависите от определенной стабильности заказов. Тем не менее, strongk7 решение, вероятно, работает чертовски большую часть времени. Однако это медленнее, чем мое в результате того, что нужно пройти всю историю репо... дважды.: -)

Ответ 16

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

Git не сохраняет историю ref (которая включает ветки). Он сохраняет только текущую позицию для каждой ветки (головы). Это означает, что с течением времени вы можете потерять историю веток в git. Всякий раз, когда вы, например, ветки, сразу теряете, какая ветка была оригинальной. Вся ветка делает это:

git checkout branch1    # refs/branch1 -> commit1
git checkout -b branch2 # branch2 -> commit1

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

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

Это то, что человек ожидает концептуально:

After branch:
       C1 (B1)
      /
    -
      \
       C1 (B2)
After first commit:
       C1 (B1)
      /
    - 
      \
       C1 - C2 (B2)

Это то, что вы на самом деле получаете:

After branch:
    - C1 (B1) (B2)
After first commit (human):
    - C1 (B1)
        \
         C2 (B2)
After first commit (real):
    - C1 (B1) - C2 (B2)

Вы бы предположили, что B1 является исходной ветвью, но он может на самом деле быть мертвой ветвью (кто-то сделал check -b, но никогда не делал этого). Это не до тех пор, пока вы не свяжетесь с тем, что получите легитимную структуру ветвей внутри git:

Either:
      / - C2 (B1)
    -- C1
      \ - C3 (B2)
Or:
      / - C3 (B1)
    -- C1
      \ - C2 (B2)

Вы всегда знаете, что C1 пришел до C2 и C3, но вы никогда не надежно знаете, появился ли C2 до того, как C3 или C3 появились до C2 (потому что вы можете установить время на своей рабочей станции на что угодно, например). B1 и B2 также вводят в заблуждение, поскольку вы не можете знать, какая ветка была первой. Во многих случаях вы можете сделать очень хорошую и обычно точную догадки. Это немного похоже на гоночный трек. Все вещи, в общем, равные с машинами, тогда вы можете предположить, что автомобиль, который приходит на колени, запустил колено. У нас также есть соглашения, которые очень надежны, например, мастер почти всегда будет представлять собой самые длинные живые ветки, хотя, к сожалению, я видел случаи, когда даже это не так.

Приведенный здесь пример - пример сохранения истории:

Human:
    - X - A - B - C - D - F (B1)
           \     / \     /
            G - H ----- I - J (B2)
Real:
            B ----- C - D - F (B1)
           /       / \     /
    - X - A       /   \   /
           \     /     \ /
            G - H ----- I - J (B2)

Настоящий также вводит в заблуждение, потому что мы, как люди, читаем его слева направо, корнем к листу (ref). git не делает этого. Где мы делаем (A- > B) в наших головах git делает (A < -B или B- > A). Он читает его от ref до root. Refs может быть где угодно, но имеет тенденцию быть листьями, по крайней мере для активных ветвей. Ссылка указывает на фиксацию и фиксации, которые содержат только их родителя/с, а не их детей. Когда фиксация является фиксацией слияния, она будет иметь более одного родителя. Первым родителем всегда является первоначальная фиксация, которая была объединена. Другие родители всегда являются коммитами, которые были объединены в первоначальную фиксацию.

Paths:
    F->(D->(C->(B->(A->X)),(H->(G->(A->X))))),(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))
    J->(I->(H->(G->(A->X))),(C->(B->(A->X)),(H->(G->(A->X)))))

Это не очень эффективное представление, а выражение всех путей git может принимать из каждого ref (B1 и B2).

Git внутреннее хранилище больше похоже на это (не то, что A в качестве родителя появляется дважды):

    F->D,I | D->C | C->B,H | B->A | A->X | J->I | I->H,C | H->G | G->A

Если вы выгрузите raw git commit, вы увидите нулевые или более родительские поля. Если есть ноль, это означает, что нет родителя, а фиксация является корнем (на самом деле вы можете иметь несколько корней). Если это одно, это означает, что не было слияния, и это не корневая фиксация. Если есть более одного, это означает, что коммит является результатом слияния, и все родители после первого совершают слияние.

Paths simplified:
    F->(D->C),I | J->I | I->H,C | C->(B->A),H | H->(G->A) | A->X
Paths first parents only:
    F->(D->(C->(B->(A->X)))) | F->D->C->B->A->X
    J->(I->(H->(G->(A->X))) | J->I->H->G->A->X
Or:
    F->D->C | J->I | I->H | C->B->A | H->G->A | A->X
Paths first parents only simplified:
    F->D->C->B->A | J->I->->G->A | A->X
Topological:
    - X - A - B - C - D - F (B1)
           \
            G - H - I - J (B2)

Когда оба ударяют А, их цепочка будет одинаковой, до этого их цепочка будет совсем другой. Первыми совершают еще две коммиты, которые являются общим предком и откуда они расходятся. может возникнуть некоторая путаница между терминами commit, branch и ref. Фактически вы можете объединить фиксацию. Это то, что действительно делает слияние. Ссылка просто указывает на фиксацию, а ветка - не более чем ссылка в каталоге .git/refs/heads, расположение папки - это то, что определяет, что ссылка является веткой, а не чем-то еще, например тегом.

Если вы теряете историю, это то, что слияние будет делать одну из двух вещей в зависимости от обстоятельств.

Рассмотрим:

      / - B (B1)
    - A
      \ - C (B2)

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

      / - B - D (B1)
    - A      /
      \ --- C (B2)

В этот момент D (B1) теперь имеет оба набора изменений от обеих ветвей (само и B2). Однако вторая ветвь не имеет изменений от B1. Если вы объедините изменения из B1 в B2 так, чтобы они были синхронизированы, тогда вы можете ожидать чего-то похожего на это (вы можете заставить слияние git сделать это, как это, но с --no-ff):

Expected:
      / - B - D (B1)
    - A      / \
      \ --- C - E (B2)
Reality:
      / - B - D (B1) (B2)
    - A      /
      \ --- C

Вы получите это, даже если B1 имеет дополнительные фиксации. Пока в B2 нет изменений, которые B1 не имеет, две ветки будут объединены. Он выполняет быструю перемотку, которая похожа на переустановку (ребазы также едят или линеаризуют историю), за исключением, в отличие от перестановки, поскольку только одна ветвь имеет изменение, она не должна применять набор изменений от одной ветки поверх нее.

From:
      / - B - D - E (B1)
    - A      /
      \ --- C (B2)
To:
      / - B - D - E (B1) (B2)
    - A      /
      \ --- C

Если вы прекратите работу над B1, тогда все будет в порядке с сохранением истории в долгосрочной перспективе. Только B1 (который может быть мастером) будет обычно развиваться, поэтому местоположение B2 в истории B2 успешно представляет точку, в которой оно было объединено в B1. Это то, что ожидает git, чтобы отделить B от A, тогда вы можете объединить A в B столько, сколько хотите, поскольку изменения накапливаются, однако при слиянии B с A в не ожидается, что вы будете работать на B и далее. Если вы продолжаете работать в своем филиале после быстрой смены его обратно в филиал, над которым вы работали, тогда вы каждый раз удаляете предыдущую историю B. Вы действительно создаете новую ветвь каждый раз после быстрой переадресации на источник, а затем фиксируете ее. В конце концов, когда вы выполняете быстрое переадресацию, это множество ветвей/объединений, которые вы можете видеть в истории и структуре, но не имея возможности определить, что такое имя этой ветки, или если то, что выглядит как две отдельные ветки, действительно является одной и той же ветвью.

         0   1   2   3   4 (B1)
        /-\ /-\ /-\ /-\ /
    ----   -   -   -   -
        \-/ \-/ \-/ \-/ \
         5   6   7   8   9 (B2)

От 1 до 3 и от 5 до 8 являются структурными ветвями, которые появляются, если вы следуете истории для 4 или 9. В git нет способа узнать, какие из этих неназванных и неопубликованных структурных ветвей принадлежат к названию и ссылается на ветки как на конец структуры. Вы можете предположить из этого чертежа, что от 0 до 4 принадлежит B1 и от 4 до 9 принадлежит B2, но кроме 4 и 9 не может знать, какая ветвь принадлежит к какой ветке, я просто нарисовал ее таким образом, чтобы иллюзия этого. 0 может принадлежать B2 и 5 может принадлежать B1. Есть 16 различных возможностей, в этом случае именованная ветвь, к которой может принадлежать каждая из структурных ветвей. Это предполагает, что ни одна из этих структурных ветвей не была получена из удаленной ветки или в результате слияния ветки в себя при выводе из мастера (одно имя ветки на двух репозиториях имеет две ветки, отдельный репозиторий похож на ветвление всех ветвей).

Существует несколько стратегий git, которые работают вокруг этого. Вы можете принудительно слить git, чтобы никогда не ускоряться вперед и всегда создавать ветвь слияния. Ужасный способ сохранить историю ветвей - это теги и/или ветки (рекомендуется использовать теги) в соответствии с выбранным вами соглашением. Я действительно не рекомендовал бы фиктивную пустую фиксацию в ветке, в которую вы сливаетесь. Очень распространенная конвенция заключается в том, чтобы не сливаться в интеграционную ветвь, пока вы не захотите действительно закрыть свою ветку. Это практика, которую люди должны пытаться придерживаться, так как в противном случае вы работаете над тем, чтобы иметь ветки. Однако в реальном мире идеальным является не всегда практическое значение, так как правильная вещь не является жизнеспособной для каждой ситуации. Если то, что вы делаете на ветке, изолировано, что может работать, но в противном случае вы можете оказаться в ситуации, когда несколько разработчиков работают над тем, что им нужно, чтобы быстро разделить их изменения (в идеале вы действительно можете работать на одной ветке, но не все ситуации подходят, и как правило, два человека, работающие на ветке, - это то, чего вы хотите избежать).

Ответ 17

Далее реализуется git эквивалент svn log --stop-on-copy и также может использоваться для поиска происхождения ветки.

Подход

  • Получить голову для всех ветвей
  • собирать mergeBase для ветки для каждой ветки.
  • git.log и итерация
  • Остановить сначала фиксацию, которая появляется в списке mergeBase

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

Примечания

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

подробнее: fooobar.com/questions/2552/...

Ответ 18

Вы можете использовать следующую команду, чтобы вернуть старую фиксацию в branch_a, которая недоступна для мастера:

git rev-list branch_a ^master | tail -1

Возможно, с дополнительной проверкой правильности того, что родительский элемент этой фиксации действительно доступен из мастера...

Ответ 19

Вы можете проверить reflog ветки A, чтобы найти, с какого фиксатора оно было создано, а также полную историю, на которую указывает эта ветвь. Reflogs находятся в .git/logs.

Ответ 20

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

branch=branch_A
merge=$(git rev-list --min-parents=2 --grep="Merge.*$branch" --all | tail -1)
git merge-base $merge^1 $merge^2

Чарльз Бейли совершенно прав, что решения, основанные на порядке предков, имеют лишь ограниченную ценность; в конце дня вам потребуется какая-то запись о том, что "это совершение произошло из ветки X", но такая запись уже существует; по умолчанию "git merge" будет использовать сообщение фиксации, такое как "Объединение ветки" branch_A "в master", это говорит вам, что все коммиты от второго родителя (commit ^ 2) поступают из "branch_A" и объединены с первый родитель (commit ^ 1), который является "мастером".

Вооружившись этой информацией, вы можете найти первое слияние "branch_A" (т.е. когда "branch_A" действительно появилось) и найти базу слияния, которая будет точкой разветвления:)

Я пробовал с репозиториями Марка Бута и Чарльза Бейли, и решение работает; как он мог? Единственный способ, с помощью которого это не сработает, - это вручную изменить сообщение фиксации по умолчанию для слияний, чтобы информация о ветке была действительно потеряна.

Для удобства:

[alias]
    branch-point = !sh -c 'merge=$(git rev-list --min-parents=2 --grep="Merge.*$1" --all | tail -1) && git merge-base $merge^1 $merge^2'

Тогда вы можете сделать "git branch-point branch_A".

Наслаждайтесь;)