Пошук точки відгалуження за допомогою Git?


455

У мене сховище з майстром гілок та A та великою кількістю активностей злиття між ними. Як я можу знайти фіксацію у своєму сховищі, коли гілка A була створена на основі master?

Моє сховище в основному виглядає так:

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

Я шукаю редакцію A, яка не є тим, що git merge-base (--all)знаходить.


Відповіді:


499

Я шукав те саме, і знайшов це питання. Дякую за запитання!

Однак я виявив, що відповіді, які я бачу тут, здається, не дають відповіді, яку ви просили (або що я шукав) - вони, здається, дають Gзобов’язання замість цього A.

Отже, я створив таке дерево (букви, присвоєні в хронологічному порядку), щоб я міг перевірити речі:

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 з теми

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


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

git log --graph --oneline --all

(Редагувати / сторона-примітка: додавання --decorateтакож може бути цікавим; воно додає вказівки імен гілок, тегів тощо. Не додаючи це до командного рядка вище, оскільки вихідний результат не відображає його використання.)

який показує (припускаючи git config --global color.ui auto):

вихід журналу git --graph --oneline - всі

Або прямим текстом:

* a9546a2 злиття від теми назад до головного
| \  
| * 648ca35 злиття майстра з темою
| | \  
| * | 132ee2a Перший комітет з теми теми
* | | Команда e7c863d на майстер після того, як майстер був об'єднаний у тему
| | /  
| / |   
* | 37ad159 післягалузевий комітет на майстра
| /  
* 6aafd7f секундна фіксація на майстер перед розгалуженням
* 4112403 початковий комітет на майстра

в будь-якому випадку ми бачимо, що комбінація 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 вийде - синтаксис у ванілі не існує . (Дякую ще раз, @conny, за те, що я повідомив про це у коментарі до іншої відповіді на цій сторінці!)<()sh

Примітка: альтернативна версія вищевказаного:

Завдяки 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!


5
Завдяки lindes, опція оболонки чудово підходить для ситуацій, коли ви хочете знайти точку відгалуження довгої роботи гілки обслуговування. Коли ви шукаєте версію, яка могла б скласти тисячу комітетів у минулому, візуальні параметри насправді не збираються її скорочувати. * 8 ')
Марк Бут

4
У вашому третьому методі ви залежаєте від того, що контекст покаже перший незмінний рядок. Це не відбудеться в певних крайових випадках або якщо у вас виникають дещо інші вимоги (наприклад, мені потрібна лише одна історія - перший батько, і я використовую цей метод у сценарії, який іноді може використовувати той самий гілки з обох боків). Я вважав, що безпечніше використовувати режим diff'if-then-else' та видаляти змінені / видалені рядки з його результату, а не розраховувати на наявність достатньо великого контексту diff --old-line-format='' --new-line-format='' <(git rev-list …) <(git rev-list …)|head -1.
liori

3
git log -n1 --format=format:%H $(git log --reverse --format=format:%H master..topic | head -1)~буде добре працювати, я думаю
Тобіас Шульте

4
@ JakubNarębski: Чи є у вас спосіб git merge-base --fork-point ...надати команду B (зробити комісію 6aafd7f) для цього дерева? Коли я опублікував це, я зайнявся іншими речами, і це звучало добре, але я, нарешті, просто спробував це, і я не змусив його працювати ... Я або отримую щось більш недавнє, або просто безшумний збій (без помилки повідомлення, але статус виходу 1), намагаючись аргументи , як git co master; git merge-base --fork-point topic, git co topic; git merge-base --fork-point master, git merge-base --fork-point topic master(або для перевірки) і т.д. чи є що - то , що я роблю неправильно або НЕ вистачає?
lindes

25
@ JakubNarębski @lindes --fork-pointзаснований на рефлогу, тому він працюватиме лише в тому випадку, якщо ви внесли зміни на місцевому рівні. Вже тоді записи про відкладення могли бути закінчені. Це корисно, але зовсім не надійно.
Даніель Любаров

131

Ви можете шукати git merge-base:

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


10
Зверніть увагу також на --allваріант " git merge-base"
Якуб Нарбскі

10
Це не відповідає на початкове запитання, але більшість людей задають набагато простіше питання, на яке це відповідь :)
FelipeC

6
він сказав, що не хоче результату злиття бази
Том Таннер

9
@TomTanner: Я просто переглянув історію запитань і оригінальне запитання було відредаговано, щоб включити цю примітку приблизно через git merge-baseп’ять годин після публікації моєї відповіді (ймовірно, у відповідь на мою відповідь). Тим не менш, я залишу цю відповідь такою, оскільки вона все ще може бути корисною для когось іншого, хто знайде це питання за допомогою пошуку.
Грег Хьюгілл

Я вважаю, що ви можете використовувати результати merge-base --all, а потім просіяти список повернених комісій за допомогою merge-base --is-предка, щоб знайти найстаршого спільного предка зі списку, але це не найпростіший спосіб .
Matt_G

42

Я використовував 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

Зауважте: це, здається, дає перше зобов’язання на гілці, а не загальному предкові. (тобто він дає Gзамість A, за графіком в оригінальному запитанні.) У мене є відповідь A, яку я опублікую зараз.
lindes

@lindes: Це дає загального предка в кожному випадку, коли я його спробував. Чи є у вас приклад, коли цього немає?
mipadi

Так. У моїй відповіді (яка має посилання на репо, ви можете клонувати;git checkout topic а потім запустити це topicзамість branch-a), вона перераховує 648ca357b946939654da12aaf2dc072763f3caeeта 37ad15952db5c065679d7fc31838369712f0b338- і те, 37ad159і інше, 648ca35є в родовій поточних гілках (остання є поточною ГОЛОВОЮ topic), але немає сенсу перед тим, як відбулося розгалуження. Ви отримуєте щось інше?
lindes

@lindes: мені не вдалося клонувати ваше репо (можливо, проблема з дозволом?).
mipadi

На жаль, вибачте! Дякую, що повідомили. Я забув запустити git update-server-info. Слід піти зараз. :)
lindes

31

Якщо вам подобаються ладні команди,

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

Ось пояснення.

Наступна команда дає вам список усіх комітетів у майстрі, що відбулися після створення імені гілки

git rev-list - головний батько ^ master_name master 

Оскільки ви дбаєте лише про найдавніші з цих комісій, вам потрібен останній рядок виводу:

git rev-list ^ branch_name - головний батько-батьків | хвіст -n1

Батько самого раннього комітету, який не є родоначальником "ім'я гілки", за визначенням, в «branch_name» , і в «господаря» , так як це предок що - то в «господаря» . Таким чином, у вас є найраніші зобов'язання, що є в обох відділеннях.

Команда

git rev-list commit ^^!

це лише спосіб показати посилання на батьківські комісії. Ви можете використовувати

git log -1 фіксувати ^

чи що завгодно.

PS: Я не згоден з аргументом, що порядок предків не має значення. Це залежить від того, що ви хочете. Наприклад, у цьому випадку

_C1___C2_______ майстер
  \ \ _XXXXX_ гілка A (X позначає довільні перехрестя між головним та A)
   \ _____ / відділення В

має сенс виводити С2 як "розгалуження". Це коли розробник розгалужується від "майстра". Коли він розгалужувався, гілка "Б" навіть не була об'єднана у його гілку! Ось що дає рішення у цій публікації.

Якщо ви хочете - це остання фіксація C такою, що всі шляхи від початку походження до останнього комітету на гілці "A" проходять через C, то ви хочете ігнорувати порядок походження. Це суто топологічно і дає вам уявлення про те, коли у вас є дві версії коду одночасно. Ось тоді ви підете з підходами на основі злиття, і це поверне C1 у моєму прикладі.


3
Це, безумовно, найчистіша відповідь, давайте проголосуємо на вершині. Пропонована редакція: git rev-list commit^^!можна спростити якgit rev-parse commit^
Рассел Девіс

Це має бути відповідь!
тринадцяте

2
Ця відповідь хороший, я просто замінити git rev-list --first-parent ^branch_name masterз , git rev-list --first-parent branch_name ^masterтому що якщо головний гілка 0 фіксацій попереду іншої гілки (швидко FORWARDABLE до нього), висновок не буде створено. З моїм рішенням, вихід не створюється, якщо master суворо попереду (тобто гілка повністю об'єднана), що я хочу.
Michael Schmeißer

3
Це не спрацює, якщо я щось зовсім не пропускаю. У прикладних гілках є злиття в обох напрямках. Здається, ви намагалися це врахувати, але я вважаю, що це призведе до невдачі вашої відповіді. git rev-list --first-parent ^topic masterповерне вас до першого фіксації лише після останнього злиття з masterу topic(якщо таке взагалі існує).
Джеррі

1
@jerry Ви праві, ця відповідь - сміття; наприклад, у випадку, якщо заднім числом щойно відбувся (об'єднав головний у тему), а у майстра немає нових комісій після цього, перша команда git rev-list - команда first-parent взагалі нічого не дає.
TamaMcGlinn

19

Зважаючи на те, що стільки відповідей у ​​цій темі не дають відповіді на запитання, яке було задано, ось короткий підсумок результатів кожного рішення, а також сценарій, який я використовував для реплікації сховища, поданого у питанні.

Журнал

Створюючи сховище із заданою структурою, ми отримуємо журнал 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 'тільки дає вам самі останні спочатку divergance .

Це означає, що для мого робочого процесу я думаю, що мені доведеться дотримуватися позначення точки відгалуження довгих працюючих гілок, оскільки я не можу гарантувати, що їх можна буде надійно знайти пізніше.

Це дійсно все зводиться до gitвідсутності того, що hgназиває гілки . Блогер Jhw називає ці лінії та сім’ї у своїй статті Чому мені подобається більше, ніж Git, Mercurial та його подальшій статті Більше про Mercurial vs. 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

Дякую Чарльзу Бейлі за те, що він показав мені більш компактний спосіб скриптування прикладу сховища.


2
Рішення по Карлу легко виправити: diff -u <(git rev-list branch_A) <(git rev-list master) | tail -2 | head -1. Дякуємо за надані вказівки щодо створення репо :)
FelipeC

Я думаю, ти маєш на увазі "Очищений варіант рішення, наданий Карлом, повертає X". Оригінал добре працював, це було просто потворно :-)
Карл

Ні, ваш оригінал не працює добре. Зрозуміло, варіація працює навіть найгірше. Але додавання параметра - замовлення на замовлення змушує вашу версію працювати :)
FelipeC

@felipec - Дивіться мій остаточний коментар до відповіді Чарльза Бейлі . На жаль, наш чат (і, отже, всі старі коментарі) тепер видалено. Я спробую оновити свою відповідь, коли знайду час.
Марк Бут

Цікаво. Я вважаю, що за замовчуванням була топологічна. Дурний мені :-)
Карл

10

Загалом, це неможливо. В історії галузей гілка і злиття перед відгалуженням названої гілки і проміжна гілка двох названих гілок виглядає однаково.

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


3
Дякую Чарльзу, ти переконав мене, якщо я хочу знати, в якій точці гілка спочатку розходилася , мені доведеться її позначити. Я дійсно хочу , щоб gitбуло еквівалент hg«и імені філій, це зробило б управління довгоживучими гілками технічного обслуговування , так набагато простіше.
Марк Бут

1
"В git, гілки - це лише поточні назви підказок розділів історії. Вони насправді не мають сильної ідентичності". Це страшно сказати і переконало мене, що мені потрібно краще зрозуміти гілки Git - спасибі (+ 1)
dumbledad

В історії галузей гілка і злиття перед відгалуженням названої гілки і проміжна гілка двох названих гілок виглядає однаково. Так. +1.
користувач1071847

5

Як щодо чогось подібного

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

Це працює. Це дійсно громіздко, але це єдине, що я виявив, що насправді робить роботу.
GaryO

1
Git псевдонім еквівалент: diverges = !bash -c 'git rev-parse $(diff <(git log --pretty=oneline ${1}) <(git log --pretty=oneline ${2}) | tail -1 | cut -c 3-42)^'(без тимчасових файлів)
conny

@conny: О, уау - я ніколи не бачив синтаксис <(foo) ... це неймовірно корисно, дякую! (Працює і в zsh, FYI.)
lindes

це, здається, дає мені перше зобов’язання у галузі, а не спільного предка. (тобто він дає Gзамість A, за графіком в оригінальному запитанні.) Я думаю, що знайшов відповідь, який я опублікую зараз.
lindes

Замість "git log --pretty = oneline" ви можете просто зробити "git rev-list", тоді ви також можете пропустити розріз, і, тим більше, це дає батьківській фіксації точки розбіжності, тому просто хвіст -2 | голова 1. Отже:diff -u <(git rev-list branch_A) <(git rev-list master) | tail -2 | head -1
FelipeC

5

напевно, мені щось не вистачає, але ІМО, всі проблеми, що були вище, викликані тим, що ми завжди намагаємось знайти точку відгалуження, що повертається в історію, і це спричиняє всілякі проблеми через наявні поєднання комбінацій.

Натомість я дотримувався іншого підходу, який базується на тому, що обидві гілки мають багато історії, саме вся історія перед розгалуженням на 100% однакова, тому замість того, щоб повертатися назад, моя пропозиція про те, щоб рухатися вперед (з 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 :-)


diff <(git rev-list "$ {1: -master}" - перший батько) <(git rev-list "$ {2: -HEAD}" - перший батько) -U1 | хвіст -1
Олександр Птах

я виявив, що це швидше (2-100x):comm --nocheck-order -1 -2 <(git rev-list --reverse --topo-order topic) <(git rev-list --reverse --topo-order master) | head -1
Андрій Некулау,


4

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

Тому я написав пару патчів, які додають поняття tailгілки. Кожного разу, коли створюється гілка, створюється також вказівник на початкову точку, tailпосилання. Ця посилання оновлюється щоразу, коли філія буде перезавантажена.

Щоб дізнатися точку відгалуження гілки Devel, все, що вам потрібно зробити, - це використовувати devel@{tail}.

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


1
Можливо, це єдине стабільне рішення. Ви бачили, чи може це потрапити в git? Я не бачив запиту на витяг.
Олександр Климечек

@AlexanderKlimetschek я не надсилав патчі, і не думаю, що це було б прийнято. Однак я спробував інший метод: гачок "оновлення-гілка", який робить щось дуже схоже. Таким чином за замовчуванням Git нічого не зробить, але ви можете дозволити гачку оновити хвостову гілку. У вас не було б devel @ {tail}, але було б не так вже й погано використовувати хвости / devel.
FelipeC

3

Ось покращена версія попередньої відповіді моєї попередньої відповіді . Він спирається на повідомлення фіксації злиття, щоб знайти місце, в якому вперше створено відділення.

Він працює на всіх згаданих тут сховищах, і я навіть звертався до деяких хитрих, які з’явились у списку розсилки . Я теж писав для цього тести .

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
}

3

Наступна команда розкриє SHA1 комітету A

git merge-base --fork-point A


Це звичайно, якщо між батьками та дочірніми гілками є проміжні злиття між собою.
nawfal

2

Я, здається, отримує деяку радість

git rev-list branch...master

Останній рядок, який ви отримаєте, - це перший фіксатор на гілці, тож це питання про те, щоб отримати його батьків. Тому

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

Здається, працює для мене і не потребує розмінників тощо (що корисно, оскільки у нас немає такої версії diff)

Виправлення: Це не працює, якщо ви перебуваєте на головній гілці, але я це роблю за сценарієм, тому це менше проблеми


2

Щоб знайти коміти з точки розгалуження, ви можете використовувати це.

git log --ancestry-path master..topicbranch

Ця команда не працює для мене на даному прикладі. Будь ласка, що б ви вказали як параметри для діапазону комісій?
Jesper Rønn-Jensen

2

Іноді це фактично неможливо (за деякими винятками, де вам може пощастити мати додаткові дані), і рішення тут не спрацьовують.

Git не зберігає історію поновлення (що включає гілки). Він зберігає лише поточну позицію для кожної гілки (голови). Це означає, що ви можете з часом втратити частину філій історії в 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 є оригінальною гілкою, але це може просто бути мертвою гілкою (хтось робив замовлення -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). Гіт цього не робить. Де ми робимо (A-> B) в наших головах git робить (A <-B або B-> A). Він читає це від ref до root. Рефлекси можуть бути де завгодно, але, як правило, листя, принаймні для активних гілок. Заява вказує на зобов’язання і зобов’язання містять лише подібні до своїх батьків / батьків, а не до своїх дітей. Коли фіксація є об'єднанням об'єднання, у неї буде більше одного з батьків. Перший з батьків - це завжди оригінальний документ, в який було об'єднано. Інші батьки - це завжди коміти, які були об'єднані в первісний документ.

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 може взяти з кожного посилання (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

Якщо ви скидаєте необмежену команду git, ви побачите нульове або більше батьківських полів. Якщо є нуль, це означає, що немає батьків, а фіксація - корінь (ви можете мати декілька коренів). Якщо він є, це означає, що злиття не було, і це не кореневий запис. Якщо їх більше, це означає, що вчинення є результатом злиття, і всі батьки після першого є об'єднаннями.

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)

Коли обидва ударять А, їх ланцюг буде однаковим, до цього ланцюг буде зовсім іншим. Перший вчинок, що має ще два спільні дії, є спільним предком і від того вони розійшлися. тут може виникнути деяка плутанина між термінами зобов’язати, відділити і відіслати. Насправді ви можете об'єднати комісію. Це те, що насправді робить злиття. Посилання просто вказує на фіксацію, а гілка - це не що інше, як посилання на папку .git / refs / heads, розташування папки - це те, що визначає, що ref - це гілка, а не щось інше, наприклад тег.

Там, де ви втрачаєте історію, це злиття буде робити одну з двох речей залежно від обставин.

Поміркуйте:

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

У цьому випадку злиття в будь-якому напрямку створить нову команду з першим батьківським, оскільки комісія, на яку вказує поточна перевірена гілка, а другий з батьків - як фіксація на кінчику гілки, яку ви об'єднали у вашу поточну гілку. Він повинен створити новий комітет, оскільки обидві гілки мають зміни, оскільки їх спільний пращур повинен поєднуватися.

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

На даний момент D (B1) тепер має обидва набори змін з обох гілок (себе і B2). Однак друга гілка не має змін від B1. Якщо ви об'єднаєте зміни з B1 в B2, щоб вони були синхронізовані, то, можливо, ви очікуєте щось таке, що виглядає так (ви можете змусити git merge зробити це так, як це зроблено з --no-ff):

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

Ви отримаєте це, навіть якщо B1 має додаткові комітети. Поки в B2 не буде змін, дві гілки будуть об'єднані. Це робить швидкий перемотка вперед, що схоже на ребазування (також бази їдять або історію лінеаризації), за винятком на відміну від повторної бази, оскільки лише одна гілка має набір змін, їй не потрібно застосовувати набір змін з однієї гілки поверх іншої від іншої.

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

Якщо ви припините роботу над B1, то в основному все добре для збереження історії в довгостроковій перспективі. Тільки B1 (який може бути головним) зазвичай просувається, тому розташування В2 в історії В2 успішно являє собою точку, що вона була об'єднана в B1. Це те, що git очікує від вас, щоб відгалужувати B від A, тоді ви можете об'єднати A в B скільки завгодно, як накопичуються зміни, однак при злитті B назад в A не очікується, що ви будете працювати над B і далі . Якщо ви продовжуєте працювати над своєю філією після швидкого переходу вперед, об'єднання її назад у гілку, над якою ви працювали, то кожен раз стираючи попередню історію Б. Ви дійсно створюєте нову гілку кожного разу після швидкого переходу вперед до джерела, а потім зобов’язання до гілки.

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

1 - 3 і 5 - 8 - це структурні гілки, які відображаються, якщо ви слідкуєте за історією або 4, або 9. Ні в якому разі не можна дізнатися, до якої з цих безіменних і невіднесених структурних гілок належать названі і посилаються гілки як кінець будови. З цього креслення можна припустити, що 0 до 4 належить до B1, а 4 до 9 належить до B2, але крім 4 і 9 не можна було знати, до якої гілки належить, я просто намалював це таким чином, що дає ілюзія того. 0 може належати до B2, а 5 може належати B1. У цьому випадку існує 16 різних можливостей, до яких названа галузь, до якої могла б належати кожна структура.

Існує низка стратегій git, які допомагають вирішити цю проблему. Ви можете змусити git merge ніколи не переходити вперед і завжди створювати гілку злиття. Жахливим способом збереження історії філій є теги та / або гілки (теги дійсно рекомендуються) згідно з певним домовленістю. Я дійсно не рекомендував би пустую пусту комітку у гілці, в яку ви зливаєтесь. Дуже поширеною умовою є не об’єднуватись у галузь інтеграції, поки ви не захочете справді закрити свою філію. Це практика, якої люди повинні намагатися дотримуватися, оскільки в іншому випадку ви працюєте навколо того, щоб у вас були гілки. Однак у реальному світі ідеал не завжди є практичним, тому що робити правильну справу не під силу для кожної ситуації. Якщо те, що ти


"Git не зберігає історію рефлексу" Це робить, але не за замовчуванням і не протягом тривалого часу. Дивіться man git-reflogта частину про дати: "master@{one.week.ago} означає", де майстер вказував тиждень тому в цьому локальному сховищі "". Або обговорення <refname>@{<date>}в man gitrevisions. І core.reflogExpireв man git-config.
Патрік Мевзек

2

Не зовсім вирішення питання, але я вважав, що варто відзначити підхід, який я використовую, коли у мене є довгоживуча галузь:

Одночасно я створюю гілку, я також створюю тег з такою ж назвою, але із -initсуфіксом, наприклад, feature-branchта feature-branch-init.

(Це наче химерно, що на таке складне питання відповісти!)


1
Враховуючи абсолютну глузливу дурість проектування поняття "філія" без будь-якого поняття, коли і де вона була створена ... плюс величезні ускладнення інших запропонованих рішень - люди, які намагаються перетворити розумний цей шлях - занадто розумний річ, я думаю, що я віддаю перевагу вашому рішенню. Тільки це обтяжує когось необхідністю ЗАБУДИТИ це робити кожного разу, коли ви створюєте філію - користувачі, які git роблять дуже часто. Крім того - я десь читав, що "теги мають штраф" бути важким ". Все-таки я думаю, що саме так я думаю, що зроблю.
Motti Shneor

1

Простий спосіб просто полегшити бачення точки розгалуження git log --graph- це використовувати варіант --first-parent.

Наприклад, візьміть репо з прийнятої відповіді :

$ git log --all --oneline --decorate --graph

*   a9546a2 (HEAD -> master, origin/master, origin/HEAD) merge from topic back to master
|\  
| *   648ca35 (origin/topic) 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

Тепер додайте --first-parent:

$ git log --all --oneline --decorate --graph --first-parent

* a9546a2 (HEAD -> master, origin/master, origin/HEAD) merge from topic back to master
| * 648ca35 (origin/topic) 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

Це полегшує!

Зверніть увагу, якщо репо має багато гілок, ви хочете вказати 2 гілки, які ви порівнюєте, а не використовувати --all:

$ git log --decorate --oneline --graph --first-parent master origin/topic

0

Проблема, як видається, полягає в тому, щоб знайти самий останній однорізний зріз між обома гілками на одній стороні та найдавнішим загальним предком з іншого (можливо, початковий прийом репо). Це відповідає моїй інтуїції того, що таке "відгалуження".

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

Ось сценарій: https://gist.github.com/abortz/d464c88923c520b79e3d . Це порівняно просто, але через петлю досить складно, щоб гарантувати суть.

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

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


0

Нижче наведено реалізацію git-еквівалента журналу svn - stop-on-copy і також можна використовувати для пошуку походження філії.

Підхід

  1. Отримайте голову для всіх гілок
  2. збирати mergeBase для цільової гілки один одного
  3. git.log та повторити
  4. Спершу зупиніть комісію, яка з’являється у списку mergeBase

Як і всі річки протікають до моря, і всі гілки проходять до майстра, і тому ми знаходимо базу злиття між начебто не пов'язаними гілками. Коли ми йдемо назад від голови гілки через предків, ми можемо зупинитися на першій потенційній базі злиття, оскільки теоретично вона повинна бути точкою початку цієї гілки.

Примітки

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

подробиці: https://stackoverflow.com/a/35353202/9950


-1

Ви можете використовувати таку команду, щоб повернути найстаріший фіксатор у branch_a, який недоступний від master:

git rev-list branch_a ^master | tail -1

Можливо , з додатковою перевіркою розсудливості , що батько , що зробити це на самому справі можна дістатися від майстра ...


1
Це не працює. Якщо гілка_a один раз об'єднується у головний, а потім продовжується, доручення цього злиття вважатимуться частиною головного, тому вони не відображатимуться у ^ master.
FelipeC

-2

Ви можете ознайомитись із рефлогом гілки А, щоб виявити, з якого комітету вона створена, а також повну історію, на яку вказує цією гілкою. Відходи є в .git/logs.


2
Я не думаю, що це взагалі працює, тому що рефлог можна підрізати. І я не думаю, що (?) Відхилення також не підштовхуються, тож це спрацювало б лише в ситуації, що склалася з одним репо.
GaryO

-2

Я вважаю, що я знайшов спосіб вирішення всіх згаданих тут куточків:

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' у головний", це говорить вам про те, що всі зобов'язання від другого батьківського (команд ^ 2) надходили з 'branch_A' і були об'єднані до першого батьківський (фіксувати ^ 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'.

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


4
Посилання на повідомлення про злиття є більш крихким, ніж гіпотеза про батьківський порядок. Це не просто гіпотетична ситуація; Я часто використовую, git merge -mщоб сказати, в що я об'єднався, а не назву потенційно ефемерної гілки (наприклад, "об'єднати зміни основного рядка в функціональний рефактор xyz"). Припустимо, я був менш корисним зі -mсвоїм прикладом? Проблема просто не вирішується в повній її загальності, тому що я можу скласти ту саму історію з однією або двома тимчасовими гілками, і немає можливості сказати різницю.
CB Bailey

1
@CharlesBailey Тож у тебе тоді проблема. Не слід видаляти ці рядки з повідомлення про фіксацію, ви повинні додати решту повідомлення нижче початкового. На сьогоднішній день "git merge" автоматично відкриває редактор, щоб ви додавали все, що завгодно, а для старих версій git ви можете робити "git merge --edit". У будь-якому випадку, ви можете використовувати гачок фіксації, щоб додати "Здійснено на відгалуження" foo "до кожної комісії, якщо це те, що ви дійсно хочете. Однак це рішення працює для більшості людей .
FelipeC

2
Не працювали для мене. Гілка_А була роздвоєна з майстра, який вже мав багато злиття. Ця логіка не дала мені точного хеша фіксації, де створено відділення_A.
Венкат Котра
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.