Я видел несколько вопросов о том, «как я могу проверить, была ли ветка X
перебазирована на Y
», но я не нашел ни одного, который имеет тот особый вкус, который мне нужен.
Я хотел бы проверить, является ли X
«простой ветвью, происходящей от Y
». Строго говоря, разрешайте только этот шаблон между X
и Y
, а не шаблон между Z
и Y
:
Другими словами, если каждый коммит в ветке X
имеет Y
в качестве предка (или равен Y
, или является одним из предков Y
) — не только то, имеет ли кончик X
Y
в качестве предка.
Это желательно, чтобы помочь людям достичь шаблона слияния на основе перебазирования, где 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», было бы здорово автоматизировать эту проверку:
В статье написано почему, нет?
Я просто хочу отметить, что правила, которые вы изложили, позволяют X вносить новые коммиты слияния, если обе стороны могут достичь Y. Это может противоречить вашему желанию «достичь шаблона слияния на основе перебазирования».
Обратите внимание, что ответ LeGEC в конце затрагивает проблему коммитов слияния.
Спасибо @TTT - если бы я выполнял свое «циклическое» решение, я бы, вероятно, прервал его, если бы нашел что-то с несколькими родителями (коммит слияния), но на практике это не кажется большой проблемой. Ветки, как правило, либо полностью чистые/перебазированные, не объединяемые/устаревшие, либо результат работы кого-то, использующего стратегию «неоднократно объединять основную ветку с функциональной веткой».
@tymtam идея состоит в том, что это помогает автоматически проверять политику, которую мы хотим использовать в нашем репозитории, а именно то, что люди перебазируются перед слиянием, чтобы сохранить историю в чистоте. Для нас большая часть времени, когда люди этого не делают, происходит потому, что они не осознают, что делают это, а не потому, что у них есть философские разногласия по этому поводу.
@KenWilliams В одном из моих репозиториев, где мы форсируем коммит rebase+merge, у нас есть закрытая проверка, которая гарантирует, что они не вносят никаких новых коммитов слияния, и если они делают это намеренно, то им нужно запросить обход завершить пиар. Я бы добавил еще одну возможность в ваш список - по моему опыту, наиболее распространенный сценарий, по-видимому, заключается в том, что кто-то пытается выполнить обычную отправку своей перебазированной ветки вместо принудительной отправки, а Git говорит им, что они должны сначала вытащить, и они ошибочно делают что предлагает Git. ;)
@ТТТ абсолютно точно! Добавляю картинку, которую я поместил в нашу внутреннюю документацию «Как использовать Git». " = "
FWIW: если вы хотите, чтобы все работали с рабочим процессом перебазирования, вы можете установить для всех git config pull.rebase true
в их локальном клоне. По крайней мере, случайный git pull
не создаст новые коммиты слияния.
@LeGEC В сценарии, когда вы должны принудительно нажать, pull.rebase=true
все равно перебазирует ваши новые коммиты на старые. Для рабочего процесса перебазирования я предпочитаю конфигурацию pull.ff=only
, так что часть слияния извлечения завершится ошибкой, если ее нельзя перемотать вперед. (Я рекомендую эту конфигурацию и сообщаю моей команде, что в случае, когда вы действительно хотите либо перебазировать, либо слить в вышестоящую ветку, вы можете сделать это явно.)
@TTT: согласен на 100%. На самом деле я вообще перестал использовать git pull
и использую git fetch
, за которым следует действие, которое я хочу.
Я думаю, вы ищете что-то вроде (в 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) ]]
чтобы проверить родословную, есть опция --is-ancestor
для git merge-base
: git merge-base --is-ancestor A D
(A
будет предком, D
будет потомком). Нет вывода на стандартный вывод, вы должны проверить возвращаемое значение (или использовать if ...
, или ... && something
, или ... || something
)
Спасибо - однако, похоже, это сообщает yes
как для Y->Z
, так и для Y->X
в моем примере последовательности коммитов, и я хочу, чтобы это было no
для Y->Z
. merge-base
для всех X Y
, Y X
, Z Y
и Y Z
сообщают тот же хэш, что и git rev-parse Y
.
Что касается раздела But in practice...
- на самом деле это должно быть хорошо, потому что наши критерии запроса на слияние уже будут отклонять слияние, которое становится недействительным из-за состояния гонки.
Один из способов проверить, являются ли все коммиты в 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
и это также может помешать рабочему процессу перебазирования.
Если вы также хотите исключить это, используйте опцию --merges
git 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, которое это проверит.
обновлена часть «слияния», чтобы сделать ее более понятной для будущих читателей (и похоже, что это соответствует вашим потребностям)
Ради интереса, зачем вам это нужно?