Git найти все несвязанные коммиты в группе, сгруппированные по ветвям, которые они были созданы в
Мне нужно создать некоторый обзор кода из несвязанных ветвей.
Примечание: при поиске решений не переходите на проблему контекста локального ветки, так как это будет выполняться на сервере, будет только удаленный источник, я всегда буду запускать git fetch origin перед другими командами, и когда мы будем говорить о ветвях, мы будем ссылаться на origin/branch-name.
Side-note 2: Я хорошо знаю, как git работает как в фарфоре, так и в сантехнике.
Если настройка будет простой, и каждая ветвь, которая возникла из мастера, будет продолжаться по своему собственному пути, мы могли бы просто запустить:
git rev-list origin/branch-name --not origin/master --no-merges
для каждой несвязанной ветки и добавить полученные коммиты к каждому обзору на каждую ветвь.
Проблема возникает при слиянии 2-3 веток и продолжении работы над некоторыми из них. Как я уже сказал, для каждого ветки я хочу создать программные обзоры программ, и я не хочу включать коммит в несколько обзоров.
В основном проблемы сводятся к поиску исходной ветки для каждой фиксации.
Или, проще говоря, найти все несвязанные коммиты, сгруппированные по ветке, которые, скорее всего, были созданы.
Давайте сосредоточимся на простом примере:
-
* b4 - branch2 head
* | a4 - branch1 head
| * b3
* | merge branch2 into branch1
* |\ | m3 - master head
| * \| a3
| | |
| | * b2
| * | merge master into branch1
* /| | m2
|/ | * merge branch1 into branch2
| * /| a2
| |/ |
| | * b1
| | /
| |/
| /|
|/ |
| * a1
* / m1
|/
|
* start
-
и я хочу получить:
- branch1: a1, a2, a3, a4
- branch2: b1, b2, b3, b4
Лучшей идеей решения, которую я нашел до сих пор, является запуск:
git show-branch --topo-order --topics origin/master origin/branch1 origin/branch2
и проанализируйте результат:
* [master] m3
! [branch1] a4
! [branch2] b4
---
+ [branch2] b4
+ [branch2^] b3
+ [branch1] a4
++ [branch2~2] b2
-- [branch2~3] Merge branch 'branch1' into branch2
++ [branch2~4] b1
+ [branch1~2] a3
+ [branch1~4] a2
++ [branch1~5] a1
*++ [branch2~5] m1
Выходная интерпретация выглядит так:
- Первые n строк анализируются n ветвей
- одна строка с помощью
- одна строка для каждой фиксации с плюсом (или минусом в случае коммитов) для n-го символа отступа, если это commit находится на n-й ветке.
- последняя строка - это база слияния для всех проанализированных ветвей.
Для точки 3. разрешение имени фиксации начинается с имени ветки и из того, что я вижу, эта ветвь соответствует ветвям, которые были созданы, возможно, путем продвижения пути, достигаемого первым родителем.
Поскольку меня не интересуют слияния, я проигнорирую их.
Затем я проанализирую каждую ветвь-путь-фиксацию, чтобы получить хэш с rev-parse.
У кого-нибудь есть лучшая программная идея для решения этой проблемы?
Ответы
Ответ 1
Репозиторий может быть клонирован с помощью --mirror
, который создает голый репозиторий, который может использоваться как зеркало исходного репозитория и может быть обновлен с помощью git remote update --prune
, после чего все теги должны быть удалены для этой функции.
Я реализую его так:
1. получить список ветвей, не объединенных в master
git branch --no-merged master
2. для каждой ветки получают список ревизий на этой ветке, а не в основной ветке
git rev-list branch1 --not master --no-merges
Если список пуст, удалите ветку из списка ветвей
3. для каждой ревизии определите исходную ветвь с помощью
git name-rev --name-only revisionHash1
и соответствие регулярному выражению для ^([^\~\^]*)([\~\^].*)?$
. Первый шаблон - это имя ветки, второе - относительный путь к ветке.
Если найденное имя ветки не равно исходной ветке, удалите ревизию из списка.
В конце я получил список ветвей и для каждого из них список коммитов.
После нескольких исследований bash это можно сделать в одной строке:
git rev-list --all --not master --no-merges | xargs -L1 git name-rev | grep -oE '[0-9a-f]{40}\s[^\~\^]*'
Результатом является вывод в форме
hash branch
который можно читать, анализировать, упорядочивать, группировать или что угодно.
Ответ 2
Если я понимаю ваше проблемное пространство, подумайте, что вы можете использовать -sha1-name
git show-branch --topo-order --topics --sha1-name origin/master origin/branch1 origin/branch2
чтобы указать, что вас интересует, затем выполните коммиты через git -what-branch
git-what-branch: узнайте, в какую ветку включена фиксация, или как она попала в именованную ветку. Это Perl script из Сет Робертсон
и отформатируйте отчет в соответствии с вашими потребностями?
Ответ 3
Нет правильного ответа на этот вопрос, потому что он не указан.
Git История - это просто ориентированный ациклический граф (DAG), и вообще невозможно определить семантические отношения между двумя произвольными узлами в DAG, если узлы не будут достаточно помечены. Если вы не можете гарантировать, что сообщения фиксации в вашем графе примеров соответствуют надежному шаблону, обрабатываемому машиной, коммиты не имеют достаточной маркировки и не могут автоматически идентифицировать комманды, которые вас интересуют, без дополнительного контекста (например, гарантирует, что ваши разработчики следуют некоторые лучшие практики).
Вот пример того, что я имею в виду. Вы говорите, что commit a1
связан с branch1
, но это невозможно определить с уверенностью, просто посмотрев на узлы вашего примерного графика. Возможно, когда-то ваш пример истории хранилища выглядел следующим образом:
* merge branch1 into branch2 - branch2 head
|\
_|/
/ * b1
| |
| |
_|_/
/ |
| * a1
* / m1
|/
|
* start - master head
Обратите внимание, что branch1
еще не существует в приведенном выше графике. Вышеприведенный график мог возникнуть из следующей последовательности событий:
-
branch2
создается в start
в общем репозитории
- пользователь # 1 создает
a1
в своей локальной ветке branch2
- тем временем пользователь # 2 создает
m1
и b1
в своей локальной ветке branch2
- пользователь # 1 подталкивает свою локальную ветвь
branch2
в общий репозиторий, в результате чего репозиторий branch2
в общем репозитории указывает на a1
- пользователь # 2 пытается вывести свою локальную ветвь
branch2
в общий репозиторий, но это не выполняется с ошибкой без перемотки вперед (branch2
в настоящее время указывает на a1
и не может быть быстрым, переадресован на b1
)
- пользователь # 2 запускает
git pull
, слияние a1
в b1
- пользователь # 2 запускает
git commit --amend -m "merge branch1 into branch2"
по какой-то необъяснимой причине
- пользователь # 2 нажимает, а история общего хранилища заканчивается похожим на приведенную выше группу DAG
Спустя некоторое время пользователь # 1 создает branch1
off a1
и создает a2
, в то время как пользователь # 2 быстро пересылает m1
в master
, что приводит к следующей истории фиксации:
* merge a1 into b1 - branch2 head
* |\ a2 - branch1 head
| _|/
|/ * b1
| |
| |
_|_/
/ |
| * a1
* / m1 - master head
|/
|
* start
Учитывая, что эта последовательность событий технически возможна (хотя и маловероятна), как может сказать человек, не говорящий о Git, который обязывает "принадлежать" к какой ветки?
Объяснение слияния с сообщениями
Если вы можете гарантировать, что пользователи не будут изменять сообщения слияния (они всегда принимают значение Git по умолчанию), и что Git никогда и никогда не будет изменять формат сообщения об объединении с объявлением по умолчанию, сообщение может использоваться как ключ, который a1
начинался с branch1
. Вам нужно будет написать script для синтаксического анализа сообщений фиксации: нет простых Git однострочных файлов для этого.
Если слияния всегда преднамеренно
В качестве альтернативы, если ваши разработчики руководствуются лучшими практиками (каждое слияние является намеренным и предназначено для вхождения в другую ветку, в результате чего создается репозиторий без тех глупых коммитов, созданных git pull
), и вас не интересуют коммиты из завершенной дочерней ветки, тогда коммиты, которые вас интересуют, находятся на пути первого родителя. Если вы знаете, какая ветвь является родительским элементом ветки, которую вы анализируете, вы можете сделать следующее:
git rev-list --first-parent --no-merges parent-branch-ref..branch-ref
Эта команда перечисляет идентификаторы SHA1 для коммитов, достижимых из branch-ref
, за исключением достижимых достижений от parent-branch-ref
и коммитов, которые были объединены из дочерних ветвей.
В приведенном выше примере графика, предполагая, что родительский порядок определяется вашими аннотациями, а не порядком строк, входящих в фиксацию слияния, git rev-list --first-parent --no-merges master..branch1
будет печатать идентификаторы SHA1 для совершения a4, a3, a2 и a1 ( в этом порядке, используйте --reverse
, если вы хотите выполнить противоположный порядок), а git rev-list --first-parent --no-merges master..branch2
будет печатать идентификаторы SHA1 для коммитов b4, b3, b2 и b1 (опять же в этом порядке).
Если ветки имеют четкие отношения родителя/ребенка
Если ваши разработчики не придерживаются лучших практик, и ваши ветки усеяны этими глупыми слияниями, созданными git pull
(или эквивалентной операцией), но у вас есть четкие отношения родительских/дочерних ветвей, а затем запись script для выполнения для вас может работать следующий алгоритм:
-
Найти все коммиты, доступные из ветки интереса, за исключением всех коммитов из ее родительской ветки, родительской родительской ветки, родительской родительской ветки и т.д., и сохранить результаты. Например:
git rev-list master..branch1 >commit-list
-
Сделайте то же самое для всех дочерних, внуков и т.д. ветвей интересующей отрасли. Например, если считать, что branch2
считается дочерним элементом branch1
:
git rev-list ^master ^branch1 branch2 >commits-to-filter-out
-
Отфильтруйте результаты шага # 2 из результатов шага # 1. Например:
grep -Fv -f commits-to-filter-out commit-list
Проблема с этим подходом заключается в том, что после того, как дочерняя ветвь будет объединена с родителем, эти коммиты считаются частью родителя, даже если развитие дочерней ветки продолжается. Хотя это имеет смысл семантически, это не дает результат, который вы говорите, вы хотите.
Некоторые рекомендации
Вот несколько примеров, которые помогут решить эту проблему в будущем. Большинство, если не все из них могут быть реализованы с помощью умного использования перехватчиков в общем хранилище.
- Только одна задача для каждой ветки. Несколько задач запрещены.
- НИКОГДА разрешить разработку продолжать дочернюю ветвь после ее объединения с родителем. Слияние подразумевает, что задача выполнена, конец истории. Ответы на ожидаемые вопросы:
- В: Что делать, если я обнаружил ошибку в дочерней ветке? A: Начните новую ветку родителя. НЕ продолжайте разработку дочерней ветки.
- В: Что делать, если новая функция еще не выполнена? A: Тогда почему вы объединили ветку? Возможно, вы объединили полную подзадачу; если это так, остальные подзадачи должны перейти в свои ветки от родительской ветки. НЕ продолжайте разработку дочерней ветки.
- Запретить использование
git pull
- дочерняя ветвь не должна объединяться в ее родительскую, если все дочерние ветки ее не объединены.
- Если ветка не имеет дочерних ветвей, рассмотрите чтобы перегрузить ее в родительскую ветвь до слияния с
--no-ff
. Если у него есть дочерние ветки, вы все равно можете переустанавливать, но, пожалуйста, сохраните слияния дочерних ветвей --no-ff
(это сложнее, чем должно быть).
- Объединить родительскую ветвь в дочернюю ветвь, чтобы упростить решение конфликтов слияния.
- Избегайте слияния ветки бабушки и дедушки непосредственно в ее ветку внука. Сначала сливайтесь с ребенком, затем слейте ребенка в внука.
Если все ваши разработчики следуют этим правилам, просто:
git rev-list --first-parent --no-merges parent-branch..child-branch
- все, что вам нужно, чтобы увидеть фиксации, которые были сделаны на этой ветке, минус фиксации, сделанные на дочерних ветвях.
Ответ 4
Я бы предложил сделать так, как вы это описали. Но я бы работал над выходом git log --format="%H:%P:%s" ^origin/master origin/branch1 origin/branch2
, чтобы вы могли лучше дрессировать.
- Создайте правильную древовидную структуру из вывода, помечая родителей и детей.
- Начните ходить с головы (получите их SHA от
git rev-parse
). Отметьте каждую фиксацию именами головы, из которой вы пришли, и ее расстоянием.
- Для шагов с не-первым родителем (другая часть слияния) я бы добавил 100 к расстоянию.
- Если вы соглашаетесь с фиксацией слияния, проверьте, что он говорит о том, какая ветка была объединена с ней. Используйте эту информацию, следуя двум родительским ссылкам: если проанализированное имя ветки, которое вы собираетесь, не соответствует вашей текущей HEAD, добавьте 10000 к расстоянию.
- Для обоих родителей: теперь вы знаете их имя. Добавьте всех своих детей, чтобы они были первичными родителями к dict:
commit -> known-name
.
- Возьмите свой диктот известных названий и начните ходить по дереву (к детям, а не к родителям). Substract 10000 с расстояния от объединенной ветки. Выполняя эту прогулку, чтобы не перейти к фиксации, вы не первыми родителями и не остановитесь, как только вы нажмете точку ветвления (коммит, у которого два ребенка). Также остановитесь, если вы нажмете один из ваших ответвлений.
Теперь для каждого из ваших коммитов у вас будет список значений расстояния (которые могут быть отрицательными) для ваших ветвей. Для каждой фиксации ветвь с наименьшим расстоянием является той, которую, скорее всего, создала фиксация.
Если у вас есть время, вам может понадобиться пройти всю историю, а затем вычесть историю мастера - это может дать несколько лучшие результаты, если ваши ветки были объединены в мастер раньше.
Не могу сопротивляться: сделал python script, который делает то, что я описал. Но с одним изменением: с каждым нормальным шагом расстояние не увеличивается, но уменьшается. Это приводит к тому, что ветки, которые жили дольше после точки слияния, являются предпочтительными, что мне лично больше нравится. Вот он: https://gist.github.com/Chronial/5275577
Использование: просто запустите git-annotate-log.py ^origin/master origin/branch1 origin/branch2
проверьте качество результатов (выведет дерево журналов git с аннотациями).