Как найти ближайшего родителя ветки 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"