Найдите родительскую ветвь ветки Git - PullRequest
346 голосов
/ 01 июля 2010

Допустим, у меня есть следующий локальный репозиторий с деревом коммитов, подобным этому:

master --> a
            \
             \
      develop c --> d
               \
                \
         feature f --> g --> h

master - это мой , это код последней стабильной версии , develop - это мой , это код следующей версии , а feature - готовится новая функция для develop.

То, что я хочу иметь в своем удаленном репо с использованием хуков, - это отклонение запросов к feature, если только коммит f не является прямым потомком develop HEAD. то есть дерево коммитов выглядит так, потому что функция была git rebase на d.

master --> a
            \
             \
      develop c --> d
                     \
                      \
               feature f --> g --> h

Так можно ли:

  • Укажите родительскую ветвь feature?
  • Укажите коммит в родительской ветви, потомком которого является f?

Оттуда я бы проверил, что такое HEAD родительской ветви, и посмотрел, соответствует ли f предшественник родительской ветви HEAD, чтобы определить, нужно ли перебазировать эту функцию.

Ответы [ 19 ]

314 голосов
/ 02 июля 2010

Предполагая, что удаленный репозиторий имеет копию ветви develop (ваше первоначальное описание описывает ее в локальном репозитории, но, похоже, она также существует в удаленном), вы должны быть в состоянии достичь то, что я думаю, вы хотите, но подход немного отличается от того, что вы предполагали.

История Git основана на 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. Тем не менее, родословная не изменилась, изменились только ярлыки.

Если, однако, мы спрашиваем «является ли коммит 6 потомком коммита 3?» (При условии, что 3 и 6 - полные имена коммитов SHA-1), тогда ответом будет «да». ”, Присутствуют метки bar и quux или нет.

Таким образом, вы можете задавать вопросы типа «является ли проталкиваемый коммит потомком текущей вершины ветви development ?», Но вы не можете с уверенностью спросить «какова родительская ветвь проталкиваемого коммита» ?».

Наиболее достоверный вопрос, который, кажется, приближается к тому, что вы хотите:

Для всех предков выдвинутого коммита (исключая текущий наконечник development и его предков), у которых текущий наконечник разрабатывает в качестве родителя:

  • существует хотя бы один такой коммит?
  • это все такие коммиты с одним родителем?

Что может быть реализовано как:

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 ( они включают слияния, но не сливаются с вершиной development ).

Чтобы также отклонить L и P, вы можете изменить вопрос и задать

Для всех предков выдвинутого коммита (исключая текущий совет development и его предков):

  • есть ли коммиты с двумя родителями?
  • если нет, то по крайней мере у одного такого коммита текущая подсказка разрабатывает его (только) родителя?
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
197 голосов
/ 25 июля 2013

Перефразирование

Еще один способ сформулировать вопрос: «Какой ближайший коммит находится в ветке, отличной от текущей, и какая это ветка?»

Решение

Вы можете найти это с небольшим количеством волшебства командной строки

git show-branch -a \
| grep '\*' \
| grep -v `git rev-parse --abbrev-ref HEAD` \
| head -n1 \
| sed 's/.*\[\(.*\)\].*/\1/' \
| sed 's/[\^~].*//'

Вот как это работает:

  1. Отображение текстовой истории всех коммитов, включая удаленные ветви.
  2. Предки текущего коммита отмечены звездочкой. Отфильтруйте все остальное.
  3. Игнорировать все коммиты в текущей ветке.
  4. Первым результатом будет ближайшая ветвь предка. Игнорировать другие результаты.
  5. Имена ветвей отображаются [в скобках]. Игнорируйте все, что находится за пределами скобок и скобок.
  6. Иногда имя ветки будет содержать ~ # или ^ #, чтобы указать, сколько коммитов находится между ссылочным коммитом и подсказкой ветки. Нам все равно. Игнорировать их.

И Результат

Запуск вышеуказанного кода на

 A---B---D <-master
      \
       \
        C---E---I <-develop
             \
              \
               F---G---H <-topic

Даст вам develop, если вы запустите его из H, и master, если вы запустите его из I.

Код доступен в виде сущности

83 голосов
/ 24 апреля 2017

Вы также можете попробовать:

git log --graph --decorate
58 голосов
/ 26 августа 2018

git parent

Вы можете просто запустить команду

git parent

для поиска родителя ветви, если вы добавите ответ @ Joe Chrysler в качестве git alias . Это упростит использование.

Откройте файл 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

Вот и все!

50 голосов
/ 02 июля 2010

У меня есть решение вашей общей проблемы (определите, происходит ли feature от кончика develop), но оно не работает с использованием описанного вами метода.

Вы можете использовать git branch --contains для перечисления всех ветвей, спущенных с кончика develop, затем используйте grep, чтобы убедиться, что feature среди них.

git branch --contains develop | grep "^ *feature$"

Если он входит в их число, он выводит " feature" на стандартный вывод и имеет код возврата 0. В противном случае он ничего не печатает и имеет код возврата 1.

35 голосов
/ 02 марта 2017

Это нормально работает для меня.

git show-branch | grep '*' | grep -v "$(git rev-parse --abbrev-ref HEAD)" | head -n1 | sed 's/.*\[\(.*\)\].*/\1/' | sed 's/[\^~].*//'

Вежливые ответы от @droidbot и @ Jistanidiot

11 голосов
/ 09 апреля 2016

Поскольку ни один из приведенных выше ответов не работал в нашем хранилище, я хочу поделиться своим собственным способом, используя последние слияния в git log:

#!/bin/bash
git log --oneline --merges "$@" | grep into | sed 's/.* into //g' | uniq --count | head -n 10

Поместите его в скрипт с именем git-last-merges, который также принимает имя ветви в качестве аргумента (вместо текущей ветви), а также другие git log аргументы

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

EDIT: Если вы часто используете git rebase в дочерних ветвях (а слияния выполняются быстро, поэтому не слишком много коммитов при слиянии), этот ответ не будет работать хорошо, поэтому я написал скрипт для подсчета коммитов вперед (обычный и слияния ) и за коммитами (не должно быть никаких слияний в родительской ветке) во всех ветках по сравнению с текущей веткой. Просто запустите этот скрипт и дайте мне знать, работает у вас или нет

#!/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 голосов
/ 02 июля 2010

Помните, что, как описано в "Git: Найти ветвь, из которой произошел коммит" , вы не можете легко определить ветку, в которой был сделан этот коммит (ветки можно переименовывать, перемещать, удалять ...), даже если git branch --contains <commit> является началом.

  • Вы можете вернуться от фиксации к фиксации, пока git branch --contains <commit> не выведет список feature и список develop,
  • сравните этот коммит SHA1 с /refs/heads/develop

Если два идентификатора коммитов совпадают, вы можете пойти (это означает, что ветвь feature имеет свое происхождение в HEADdevelop).

5 голосов
/ 28 октября 2014

Магия командной строки 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.

4 голосов
/ 19 марта 2019

Решение

Решение на основе 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) → develop
  • Для коммитов S, T, U (ветвь feature/c) → develop
  • Для коммитовP, Q, R (ветвь 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 оказалось ненадежным для меня в следующих ситуациях:

  • отсоединенная ГОЛОВА -в том числе с отстраненной головкойs заменяет grep '\*' \ на `grep '!'\ - и это только начало всех неприятностей
  • выполнение сценарий на master и develop приводит к develop и `` соответственно
  • ветви на ветви master (ветви hotfix/) заканчиваются на develop в качестве родителя, так как их ближайший родительский элемент master был отмечен ! вместо* по причине.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...