Git: проверьте, является ли ветка строго потомком другой ветки

Я видел несколько вопросов о том, «как я могу проверить, была ли ветка X перебазирована на Y», но я не нашел ни одного, который имеет тот особый вкус, который мне нужен.

Я хотел бы проверить, является ли X «простой ветвью, происходящей от Y». Строго говоря, разрешайте только этот шаблон между X и Y, а не шаблон между Z и Y:

Git: проверьте, является ли ветка строго потомком другой ветки

Другими словами, если каждый коммит в ветке X имеет Y в качестве предка (или равен Y, или является одним из предков Y) — не только то, имеет ли кончик XY в качестве предка.

Это желательно, чтобы помочь людям достичь шаблона слияния на основе перебазирования, где X — это функциональная ветвь, а Y — основная ветвь разработки. (Если кто-то действительно знает, что делает, и ему действительно нужна нестрогая ветвь ff, мы можем легко предоставить специальное разрешение и разрешить слияние. Но мы хотим, чтобы они, по крайней мере, осознавали, что делают это, чего постоянно не происходит.)

Обратите внимание, что это не то же самое, что «можно объединить с --ff-only». git merge --ff-only с радостью обновит Y, чтобы указать на X, если Y является потомком X, даже если некоторые предки X не являются ни потомками, ни предками Y.

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

Приложение:

Как указал @TTT, еще один распространенный сценарий, который может быть обнаружен, — это кто-то испортил перебазирование, выполнив pull перед своим push вместо push --force-with-lease. Я создал следующий рисунок для нашей внутренней документации «как использовать Git», было бы здорово автоматизировать эту проверку:

Git: проверьте, является ли ветка строго потомком другой ветки

Ради интереса, зачем вам это нужно?

tymtam 07.02.2023 01:18

В статье написано почему, нет?

John Kugelman 07.02.2023 02:49

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

TTT 07.02.2023 15:59

Обратите внимание, что ответ LeGEC в конце затрагивает проблему коммитов слияния.

TTT 07.02.2023 16:18

Спасибо @TTT - если бы я выполнял свое «циклическое» решение, я бы, вероятно, прервал его, если бы нашел что-то с несколькими родителями (коммит слияния), но на практике это не кажется большой проблемой. Ветки, как правило, либо полностью чистые/перебазированные, не объединяемые/устаревшие, либо результат работы кого-то, использующего стратегию «неоднократно объединять основную ветку с функциональной веткой».

Ken Williams 07.02.2023 18:25

@tymtam идея состоит в том, что это помогает автоматически проверять политику, которую мы хотим использовать в нашем репозитории, а именно то, что люди перебазируются перед слиянием, чтобы сохранить историю в чистоте. Для нас большая часть времени, когда люди этого не делают, происходит потому, что они не осознают, что делают это, а не потому, что у них есть философские разногласия по этому поводу.

Ken Williams 07.02.2023 18:28

@KenWilliams В одном из моих репозиториев, где мы форсируем коммит rebase+merge, у нас есть закрытая проверка, которая гарантирует, что они не вносят никаких новых коммитов слияния, и если они делают это намеренно, то им нужно запросить обход завершить пиар. Я бы добавил еще одну возможность в ваш список - по моему опыту, наиболее распространенный сценарий, по-видимому, заключается в том, что кто-то пытается выполнить обычную отправку своей перебазированной ветки вместо принудительной отправки, а Git говорит им, что они должны сначала вытащить, и они ошибочно делают что предлагает Git. ;)

TTT 07.02.2023 18:35

@ТТТ абсолютно точно! Добавляю картинку, которую я поместил в нашу внутреннюю документацию «Как использовать Git». " = "

Ken Williams 08.02.2023 00:16

FWIW: если вы хотите, чтобы все работали с рабочим процессом перебазирования, вы можете установить для всех git config pull.rebase true в их локальном клоне. По крайней мере, случайный git pull не создаст новые коммиты слияния.

LeGEC 08.02.2023 06:21

@LeGEC В сценарии, когда вы должны принудительно нажать, pull.rebase=true все равно перебазирует ваши новые коммиты на старые. Для рабочего процесса перебазирования я предпочитаю конфигурацию pull.ff=only, так что часть слияния извлечения завершится ошибкой, если ее нельзя перемотать вперед. (Я рекомендую эту конфигурацию и сообщаю моей команде, что в случае, когда вы действительно хотите либо перебазировать, либо слить в вышестоящую ветку, вы можете сделать это явно.)

TTT 08.02.2023 16:39

@TTT: согласен на 100%. На самом деле я вообще перестал использовать git pull и использую git fetch, за которым следует действие, которое я хочу.

LeGEC 08.02.2023 18:09
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
4
11
134
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я думаю, вы ищете что-то вроде (в Bash)

[[ $(git merge-base X Y) = $(git rev-parse Y) ]] && echo yes || echo no

git merge-base X Y находит лучшего общего предка и выводит его полное значение sha1. Что касается лучшего, doc говорит, что

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

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

git rev-parse Y печатает значение sha1 головы Y. Если лучший общий предок совпадает с головой Y, он соответствует этому every commit in branch X has Y as an ancestor (or is equal to Y or is one of Y's ancestors). Другими словами, набор коммитов Y является подмножеством набора коммитов X.

Но на практике Y в удаленном репозитории может обновляться другими, а локальный Y может обновляться непреднамеренно. Тест скажет «нет», даже если X действительно является потомком Y в течение периода после создания X и до обновления Y.

Это очень хорошо! Я шел по другой линии, чтобы посмотреть, совпадает ли последний элемент, указанный в git rev-list Z...Y~1, с git rev-parse Y (что действительно работает в этом случае). Что-то вроде [[ $(git rev-list Z...Y~1 | tail -1) = $(git rev-parse Y) ]]

matt 07.02.2023 03:36

чтобы проверить родословную, есть опция --is-ancestor для git merge-base : git merge-base --is-ancestor A D (A будет предком, D будет потомком). Нет вывода на стандартный вывод, вы должны проверить возвращаемое значение (или использовать if ..., или ... && something, или ... || something)

LeGEC 07.02.2023 06:40

Спасибо - однако, похоже, это сообщает yes как для Y->Z, так и для Y->X в моем примере последовательности коммитов, и я хочу, чтобы это было no для Y->Z. merge-base для всех X Y, Y X, Z Y и Y Z сообщают тот же хэш, что и git rev-parse Y.

Ken Williams 08.02.2023 00:35

Что касается раздела But in practice... - на самом деле это должно быть хорошо, потому что наши критерии запроса на слияние уже будут отклонять слияние, которое становится недействительным из-за состояния гонки.

Ken Williams 08.02.2023 01:16
Ответ принят как подходящий

Один из способов проверить, являются ли все коммиты в Y..X потомками Y, — это проверить границу этого диапазона с помощью git log --boundary Y..X или git rev-list --boundary Y..X.

Начиная с этой истории:

$ git log --graph --oneline --all
* 036a9f9 (HEAD -> X) create d.txt
* cadd199 create c.txt
| * 0680934 (Z) Merge commit '22a23fe' into Z
|/| 
| * 22a23fe create b.txt
* | 8dec744 (Y) create a.txt
|/  
* 878ac8b first commit

Ты получишь :

$ git log --oneline --boundary Y..X
036a9f9 (HEAD -> X) create d.txt
cadd199 create c.txt
- 8dec744 (Y) create a.txt   # <- one single boundary commit, pointing at Y

$ git log --oneline --boundary Y..Z
0680934 (Z) Merge commit '22a23fe' into Z
22a23fe create b.txt
- 8dec744 (Y) create a.txt   # <- two commits on the boundary
- 878ac8b first commit       # <-

Скриптовый способ проверить, находитесь ли вы в этой ситуации:

# 'git rev-list' prints full hashes, boundary commits are prefixed with '-'
boundary=$(git rev-list --boundary Y..X | grep -e '^-')

want=$(git rev-parse Y)
want = "-$want"

# the boundary should consist of "-<hash of Y>" only:
if [ "$boundary" = "$want" ]; then
   echo "all commits in X are descendants of Y"
fi

Вышеупомянутое просто проверяет, что все коммиты идут после Y. Вы также можете столкнуться со следующей ситуацией:

* 036a9f9 (HEAD -> X) create d.txt
* 0680934 Merge 'origin/X' into X # <- someone created a merge commit in between
|\
| * cadd199 create c.txt
* | 22a23fe create b.txt
|/
* 8dec744 (Y) create a.txt
* 878ac8b first commit

и это также может помешать рабочему процессу перебазирования.

Если вы также хотите исключить это, используйте опцию --mergesgit log или git rev-list :

# git rev-list also has a --count option, which will output the count
# rather than the complete list of commits
merges=$(git rev-list --count --merges Y..X)
if [ "$merges" -eq 0 ]; then
   echo "all good, no merges between Y and X"
fi

Документация для --boundary не дает хорошего объяснения того, что такое «граничная фиксация».

Я бы сказал у этого ТАКОГО ответа есть достойное определение:

Граничная фиксация — это фиксация, которая ограничивает диапазон ревизий, но не не принадлежат этому диапазону. Например, диапазон ревизий HEAD~3..HEAD состоит из 3 коммитов (HEAD~2, HEAD~1 и HEAD), а коммит HEAD~3 служит для него граничным коммитом.

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

Спасибо, похоже, это помогает! Это успешно различает X и Z в приведенной выше настройке. Я создам задание CI/CD, которое это проверит.

Ken Williams 08.02.2023 01:17

обновлена ​​часть «слияния», чтобы сделать ее более понятной для будущих читателей (и похоже, что это соответствует вашим потребностям)

LeGEC 08.02.2023 06:25

Другие вопросы по теме