Как найти ближайшего родителя ветки Git?
Скажем, у меня есть следующий локальный репозиторий с деревом фиксации следующим образом:
master --> a
\
\
develop c --> d
\
\
feature f --> g --> h
master
- это мой последний стабильный код выпуска, develop
- это мой следующий код выпуска, а feature
- новая функция, подготовленная для develop
.
То, что я хочу иметь в своем удаленном репо с помощью перехватчиков, заключается в том, чтобы отбрасывать на feature
отказ, если commit f
является прямым потомком develop
HEAD. т.е. дерево фиксации выглядит так, потому что функция git rebase
на d
.
master --> a
\
\
develop c --> d
\
\
feature f --> g --> h
Так можно:
- Определите родительскую ветвь
feature
?
- Определите фиксацию в родительской ветке, которая
f
является потомком?
Оттуда я бы проверил, что такое HEAD родительской ветки, и посмотрите, соответствует ли предшественник f
родительскому ветку HEAD, чтобы определить, нужно ли переустанавливать эту функцию.
Ответы
Ответ 1
Предполагая, что удаленный репозиторий имеет копию ветки разработки (ваше первоначальное описание описывает ее в локальном репозитории, но похоже, что она также существует в удаленном), вы должны быть в состоянии достичь того, что я думаю, что вы хотите, но подход немного отличается от того, что вы предполагали.
История Gits основана на DAG коммитов. Ветви (и вообще "ссылки") - это просто временные метки, которые указывают на конкретные коммиты в постоянно растущем коммите DAG. Таким образом, отношения между ветвями могут меняться со временем, но отношения между фиксациями не изменяются.
---o---1 foo
\
2---3---o bar
\
4
\
5---6 baz
Похоже, что baz
основана на (старой версии) bar
? Но что если мы удалим bar
?
---o---1 foo
\
2---3
\
4
\
5---6 baz
Теперь похоже, что baz
основана на foo
. Но происхождение baz
не изменилось, мы просто удалили метку (и получившийся висячий коммит). А что если мы добавим новый ярлык на 4
?
---o---1 foo
\
2---3
\
4 quux
\
5---6 baz
Теперь, похоже, baz
основан на quux
. Тем не менее, родословная не изменилась, изменились только ярлыки.
Если, однако, мы спрашиваем "является ли commit 6
потомком commit 3
?" (при условии, что 3
и 6
являются полными именами коммитов SHA-1), тогда ответом будет "да", независимо от того, quux
метки bar
и quux
или нет.
Таким образом, вы можете задать такие вопросы, как "является ли проталкиваемый коммит потомком текущего кончика ветки разработки?", Но вы не можете с уверенностью спросить "какова родительская ветвь проталкиваемого коммита?".
Наиболее надежный вопрос, который, кажется, приближается к тому, что вы хотите:
Для всех выдвинутых предков коммитов (за исключением текущего наконечника развертки и его предков), которые имеют текущий наконечник развертки в качестве родителя:
- существует хотя бы один такой коммит?
- все ли такие коммиты с одним родителем?
Который может быть реализован как:
pushedrev=...
basename=develop
if ! baserev="$(git rev-parse --verify refs/heads/"$basename" 2>/dev/null)"; then
echo "'$basename' is missing, call for help!"
exit 1
fi
parents_of_children_of_base="$(
git rev-list --pretty=tformat:%P "$pushedrev" --not "$baserev" |
grep -F "$baserev"
)"
case ",$parents_of_children_of_base" in
,) echo "must descend from tip of '$basename'"
exit 1 ;;
,*\ *) echo "must not merge tip of '$basename' (rebase instead)"
exit 1 ;;
,*) exit 0 ;;
esac
Это покроет то, что вы хотите ограничить, но, возможно, не все.
Для справки, вот расширенный пример истории:
A master
\
\ o-----J
\ / \
\ | o---K---L
\ |/
C--------------D develop
\ |\
F---G---H | F'--G'--H'
| |\
| | o---o---o---N
\ \ \ \
\ \ o---o---P
\ \
R---S
Приведенный выше код может быть использован для отклонения H
и S
при принятии H'
, J
, K
или N
, но он также будет принимать L
и P
(они включают в себя слияния, но они не объединяют кончик разработки).
Чтобы также отклонить L
и P
, вы можете изменить вопрос и задать
Для всех заданных коммитов предков (исключая текущий наконечник Develop и его предков):
- есть ли коммиты с двумя родителями?
- если нет, имеет ли хотя бы один такой коммит текущий совет развития своего (единственного) родителя?
pushedrev=...
basename=develop
if ! baserev="$(git rev-parse --verify refs/heads/"$basename" 2>/dev/null)"; then
echo "'$basename' is missing, call for help!"
exit 1
fi
parents_of_commits_beyond_base="$(
git rev-list --pretty=tformat:%P "$pushedrev" --not "$baserev" |
grep -v '^commit '
)"
case "$parents_of_commits_beyond_base" in
*\ *) echo "must not push merge commits (rebase instead)"
exit 1 ;;
*"$baserev"*) exit 0 ;;
*) echo "must descend from tip of '$basename'"
exit 1 ;;
esac
Ответ 2
Рефразал
Другой способ сформулировать вопрос: "Что такое ближайшая фиксация, которая находится на ветке, отличной от текущей ветки, и какая ветка это?"
Решение
Вы можете найти его с небольшой манерой командной строки
git show-branch -a \
| grep '\*' \
| grep -v `git rev-parse --abbrev-ref HEAD` \
| head -n1 \
| sed 's/.*\[\(.*\)\].*/\1/' \
| sed 's/[\^~].*//'
Здесь как это работает:
- Отобразить текстовую историю всех коммитов, включая удаленные ветки.
- Предки текущего фиксации обозначаются звездочкой. Отфильтруйте все остальное.
- Игнорировать все коммиты в текущей ветке.
- Первым результатом будет ближайшая ветвь предка. Игнорируйте другие результаты.
- Названия веток отображаются [в скобках]. Игнорируйте все за пределами скобок и скобки.
- Иногда имя ветки будет содержать ~ # или ^ #, чтобы указать, сколько коммитов находится между указанным фиксатором и концом ветки. Нам все равно. Игнорируйте их.
И результат
Запуск вышеуказанного кода на
A---B---D <-master
\
\
C---E---I <-develop
\
\
F---G---H <-topic
Дает вам develop
, если вы запустите его из H и master
, если вы запустите его из I.
Код доступен как сущность
Ответ 3
Вы также можете попробовать:
git log --graph --decorate
Ответ 4
мерзавец родитель
Вы можете просто запустить команду
git parent
чтобы найти родителя ветки, если вы добавите ответ @Joe Chrysler в качестве псевдонима git. Это упростит использование.
Откройте файл gitconfig, расположенный в "~/.gitconfig"
, используя любой текстовый редактор. (Для Linux). А для Windows путь ".gitconfig" обычно находится по адресу c:\users\your-user\.gitconfig
vim ~/.gitconfig
Добавьте в файл следующую команду псевдонимов:
[alias]
parent = "!git show-branch | grep '*' | grep -v \"$(git rev-parse --abbrev-ref HEAD)\" | head -n1 | sed 's/.*\\[\\(.*\\)\\].*/\\1/' | sed 's/[\\^~].*//' #"
Сохраните и выйдите из редактора.
Запустите команду git parent
Это!
Ответ 5
У меня есть решение вашей общей проблемы (определите, если feature
сходит с кончика develop
), но он не работает, используя описанный вами метод.
Вы можете использовать git branch --contains
, чтобы перечислить все ветки, исходящие из кончика develop
, затем используйте grep
, чтобы убедиться, что среди них есть feature
.
git branch --contains develop | grep "^ *feature$"
Если он среди них, он будет печатать " feature"
на стандартный вывод и иметь код возврата 0. В противном случае он ничего не будет печатать и имеет код возврата 1.
Ответ 6
Это отлично работает для меня.
git show-branch | grep '*' | grep -v "$(git rev-parse --abbrev-ref HEAD)" | head -n1 | sed 's/.*\[\(.*\)\].*/\1/' | sed 's/[\^~].*//'
Предоставленные ответы: @droidbot и @Jistanidiot
Ответ 7
Поскольку ни один из вышеперечисленных ответов не работал в нашем репозитории, я хочу поделиться своим собственным путем, используя последние слияния в git log
:
#!/bin/bash
git log --oneline --merges "[email protected]" | grep into | sed 's/.* into //g' | uniq --count | head -n 10
Поместите его в script с именем git-last-merges
, который также принимает имя ветки в качестве аргумента (вместо текущей ветки), а также другие аргументы git log
Из вывода мы можем вручную определить родительскую ветку (ветки) на основе собственных соглашений ветвления и количества слияний из каждой ветки.
EDIT:
Если вы часто используете git rebase
в дочерних ветвях (и слияния часто пересылаются часто, поэтому не так много коммитов слияния), этот ответ не будет работать, поэтому я написал script, чтобы пересчитывать заранее (обычный и слияние), а также за фиксации (не должно быть никаких за слиянием в родительском ветки) во всех ветвях по сравнению с текущей ветвью. Просто запустите этот script и сообщите мне, работает ли для вас или нет.
#!/bin/bash
HEAD="`git rev-parse --abbrev-ref HEAD`"
echo "Comparing to $HEAD"
printf "%12s %12s %10s %s\n" "Behind" "BehindMerge" "Ahead" "Branch"
git branch | grep -v '^*' | sed 's/^\* //g' | while read branch ; do
ahead_merge_count=`git log --oneline --merges $branch ^$HEAD | wc -l`
if [[ $ahead_merge_count != 0 ]] ; then
continue
fi
ahead_count=`git log --oneline --no-merges $branch ^$HEAD | wc -l`
behind_count=`git log --oneline --no-merges ^$branch $HEAD | wc -l`
behind_merge_count=`git log --oneline --merges ^$branch $HEAD | wc -l`
behind="-$behind_count"
behind_merge="-M$behind_merge_count"
ahead="+$ahead_count"
printf "%12s %12s %10s %s\n" "$behind" "$behind_merge" "$ahead" "$branch"
done | sort -n
Ответ 8
Помните, что, как описано в Git: для определения того, из какой ветки произошел коммит, из, вы не можете легко определить ветвь, в которой была выполнена эта фиксация ( ветки могут быть переименованы, перемещены, удалены...), хотя git branch --contains <commit>
является началом.
- Вы можете вернуться с фиксации до фиксации до тех пор, пока
git branch --contains <commit>
не будет отображать ветвь ветки feature
и список develop
,
- сравните, что передайте SHA1 в
/refs/heads/develop
Если оба коммиты совпадают с идентификатором, вам хорошо идти (это означало бы, что ветвь feature
имеет свое начало в HEAD develop
).
Ответ 9
Магия командной строки JoeChrysler может быть упрощена. Здесь логика написана:
git show-branch -a |
ack '\*' | # we want only lines that contain an asterisk
ack -v "$current_branch" | # but also don't contain the current branch
head -n1 | # and only the first such line
sed 's/.*\[\(.*\)\].*/\1/' | # really, just the part of the line between []
sed 's/[\^~].*//' # and with any relative refs (^, ~n) removed
Мы можем выполнить то же самое, что и все пять из этих отдельных фильтров команд в относительно простой команде awk
:
git show-branch -a | awk -F'[]^~[]' '/\*/ && !/'"$current_branch"'/ {print $2;exit}'
Это разрушается следующим образом:
-F'[]^~[]'
разделите строку на поля с символами ]
, ^
, ~
и [
.
/\*/
Найти строки, содержащие звездочку
&& !/'"$current_branch"'/
... но не имя текущей ветки
{ print $2;
Когда вы найдете такую строку, напечатайте ее второе поле (т.е. часть между первым и вторым вхождениями символов разделителя полей). Для простых имен ветвей это будет именно то, что между скобками; для ссылок с относительными переходами это будет просто имя без модификатора. Поэтому наш набор разделителей полей обрабатывает намерение обеих команд sed
.
exit }
Затем выйдите немедленно. Это означает, что он обрабатывает только первую совпадающую линию, поэтому нам не нужно передавать вывод через head -n 1
.
Ответ 10
Решение
Решение, основанное на git show-branch
мне не совсем помогло (см. Ниже), поэтому я объединил его с решением, основанным на git show-branch
git log
и в итоге получилось так:
git log --decorate --simplify-by-decoration --oneline \ # selects only commits with a branch or tag
| grep -v "(HEAD" \ # removes current head (and branch)
| head -n1 \ # selects only the closest decoration
| sed 's/.* (\(.*\)) .*/\1/' \ # filters out everything but decorations
| sed 's/\(.*\), .*/\1/' \ # picks only the first decoration
| sed 's/origin\///' # strips "origin/" from the decoration
Ограничения и предостережения
- HEAD может быть отсоединен (многие инструменты CI делают это для обеспечения правильной фиксации в данной ветки), но исходная ветвь и локальная ветвь должны быть как на уровне, так и "выше" текущего HEAD.
- В пути не должно быть никаких тегов (я полагаю; я не проверял скрипт на коммитах с тегом между дочерней и родительской ветвями)
- сценарий основан на том факте, что "HEAD" всегда указывается в качестве первого украшения командой
log
- запустить скрипт на
master
и develop
результаты (в основном) в <SHA> Initial commit
Результаты, достижения
A---B---D---E---F <-origin/master, master
\ \
\ \
\ G---H---I <- origin/hotfix, hotfix
\
\
J---K---L <-origin/develop, develop
\
\
M---N---O <-origin/feature/a, feature/a
\ \
\ \
\ P---Q---R <-origin/feature/b, feature/b
\
\
S---T---U <-origin/feature/c, feature/c
Несмотря на существование локальной ветки (например, присутствует только origin/topic
поскольку коммит O
был извлечен непосредственно его SHA), сценарий должен вывести следующее:
- Для коммитов
G
, H
, I
(hotfix
ветки) → master
- Для коммитов
M
, N
, O
(feature/a
ветвления feature/a
) → develop
- Для коммитов
S
, T
, U
(feature/c
ветвления feature/c
) → develop
- Для коммитов
P
, Q
, R
(feature/b
ветвления feature/b
) → feature/a
- Для коммитов
J
, K
, L
(develop
ветки) → <sha> Initial commit
* - Для коммитов
B
, D
, E
, F
(master
ветки) → <sha> Initial commit
* - или master
если коммиты develop
были на вершине главного HEAD (~ мастер мог бы быстро развиваться)
Почему у меня не работает шоу-ветка
Решение на основе git show-branch
оказалось ненадежным для меня в следующих ситуациях:
- отсоединенная ГОЛОВА - включая случай с отсоединенной головкой означает замену
grep '\*' \
на 'grep'! ' \- и это только начало всех неприятностей - запустив скрипт на
master
и develop
результаты в develop
и '' соответственно - ветки на
master
ветки (hotfix/
веток) в конечном итоге с develop
как родитель, так как их ближайший master
филиал родитель был отмечен !
вместо *
по причине.
Ответ 11
Вот реализация PowerShell решения Mark Reed:
git show-branch -a | where-object { $_.Contains('*') -eq $true} | Where-object {$_.Contains($branchName) -ne $true } | select -first 1 | % {$_ -replace('.*\[(.*)\].*','$1')} | % { $_ -replace('[\^~].*','') }
Ответ 12
Кросс-платформенная реализация с Ant
<exec executable="git" outputproperty="currentBranch">
<arg value="rev-parse" />
<arg value="--abbrev-ref" />
<arg value="HEAD" />
</exec>
<exec executable="git" outputproperty="showBranchOutput">
<arg value="show-branch" />
<arg value="-a" />
</exec>
<loadresource property="baseBranch">
<propertyresource name="showBranchOutput"/>
<filterchain>
<linecontains>
<contains value="*"/>
</linecontains>
<linecontains negate="true">
<contains value="${currentBranch}"/>
</linecontains>
<headfilter lines="1"/>
<tokenfilter>
<replaceregex pattern=".*\[(.*)\].*" replace="\1"/>
<replaceregex pattern="[\^~].*" replace=""/>
</tokenfilter>
</filterchain>
</loadresource>
<echo message="${currentBranch} ${baseBranch}" />
Ответ 13
Я не говорю, что это хороший способ решить эту проблему, однако, похоже, это работает для меня.
git branch --contains $(cat.git/ORIG_HEAD)
Проблема заключается в том, что отслеживание файла заглядывает во внутреннюю работу git, так что это не обязательно является прямой (или обратной) совместимостью.
Ответ 14
@Mark Reed: вы должны добавить, что строка фиксации должна содержать не только звездочку, но и начинаться со звездочки! В противном случае сообщения, содержащие звездочку, также включаются в согласованные строки. Поэтому это должно быть:
git show-branch -a | awk -F'[]^~[]' '/^\*/ && !/'"$current_branch"'/ {print $2;exit}'
или длинная версия:
git show-branch -a |
awk '^\*' | # we want only lines that contain an asterisk
awk -v "$current_branch" | # but also don't contain the current branch
head -n1 | # and only the first such line
sed 's/.*\[\(.*\)\].*/\1/' | # really, just the part of the line between []
sed 's/[\^~].*//' # and with any relative refs (^, ~n) removed`
Ответ 15
vbc=$(git rev-parse --abbrev-ref HEAD)
vbc_col=$(( $(git show-branch | grep '^[^\[]*\*' | head -1 | cut -d* -f1 | wc -c) - 1 ))
swimming_lane_start_row=$(( $(git show-branch | grep -n "^[\-]*$" | cut -d: -f1) + 1 ))
git show-branch | tail -n +$swimming_lane_start_row | grep -v "^[^\[]*\[$vbc" | grep "^.\{$vbc_col\}[^ ]" | head -n1 | sed 's/.*\[\(.*\)\].*/\1/' | sed 's/[\^~].*//'
Достигает тех же целей, что и Марк Рид, но использует гораздо более безопасный подход, который не мешает в ряде сценариев:
- Последняя фиксация родительской ветки - это слияние, в результате чего в столбце отображается
-
не *
- Фиксированное сообщение содержит название ветки
- Фиксированное сообщение содержит
*
Ответ 16
Любой, кто хочет сделать это в наши дни - приложение Atlassian SourceTree показывает вам отличное визуальное представление о том, как ваши ветки относятся друг к другу, то есть, где они начались и где они в настоящее время сидят в порядке фиксации (например, HEAD или 4 фиксации позади, и др.).
Ответ 17
Если вы используете Source Tree, посмотрите на детали фиксации > Родители > , тогда вы увидите подчеркнутые строки фиксации (ссылки)
Ответ 18
Альтернатива: git rev-list master | grep "$(git rev-list HEAD)" | head -1
git rev-list master | grep "$(git rev-list HEAD)" | head -1
Получите последний коммит, который будет моей веткой и master
(или любой веткой, которую вы хотите указать)
Ответ 19
Это не сработало для меня, когда я сделал что-то наподобие develop > release-v1.0.0 > feature-foo
, все вернулось к разработке, заметьте, что произошел ребаз, не уверенный, что это усугубляет мою проблему...
Следующий код дал мне правильный коммит
git log --decorate \
| grep 'commit' \
| grep 'origin/' \
| head -n 2 \
| tail -n 1 \
| awk '{ print $2 }' \
| tr -d "\n"