Как перебрать все ветки git с помощью скрипта bash


105

Как я могу перебрать все локальные ветки в моем репозитории с помощью сценария bash. Мне нужно выполнить итерацию и проверить, есть ли разница между веткой и некоторыми удаленными ветвями. Ex

for branch in $(git branch); 
do
    git log --oneline $branch ^remotes/origin/master;
done

Мне нужно сделать что-то вроде приведенного выше, но проблема, с которой я столкнулся, заключается в том, что $ (git branch) дает мне папки внутри папки репозитория вместе с ветвями, присутствующими в репозитории.

Это правильный способ решить эту проблему? Или есть другой способ сделать это?

Спасибо



1
@pihentagy Это было написано до связанного вопроса.
kodaman 01

Например: -for b in "$(git branch)"; do git branch -D $b; done
Абхи

Ответы:


157

При написании скриптов не следует использовать ветку git . Git предоставляет «сантехнический» интерфейс который явно предназначен для использования в сценариях (многие текущие и исторические реализации обычных команд Git (добавление, проверка, слияние и т. Д.) Используют этот же интерфейс).

Вам нужна команда сантехники git for-each-ref :

git for-each-ref --shell \
  --format='git log --oneline %(refname) ^origin/master' \
  refs/heads/

Примечание. remotes/Префикс удаленной ссылки не нужен, если только у вас нет других ссылок, которые приводят origin/masterк совпадению нескольких мест в пути поиска имени ссылки (см. «Символьное имя ссылки…» в разделе «Указание редакций» в git-rev-parse (1) ). Если вы пытаетесь Явно избежать двусмысленности, то идти с полным именем исх: refs/remotes/origin/master.

Вы получите такой результат:

git log --oneline 'refs/heads/master' ^origin/master
git log --oneline 'refs/heads/other' ^origin/master
git log --oneline 'refs/heads/pu' ^origin/master

Вы можете направить этот вывод в sh .

Если вам не нравится идея генерации кода оболочки, вы можете отказаться от некоторой надежности * и сделать следующее:

for branch in $(git for-each-ref --format='%(refname)' refs/heads/); do
    git log --oneline "$branch" ^origin/master
done

* Имена ссылок должны быть защищены от разделения слов оболочки (см. Git-check-ref-format (1) ). Лично я бы придерживался предыдущей версии (сгенерированный код оболочки); Я более уверен, что с этим не может случиться ничего неуместного.

Поскольку вы указали bash, и он поддерживает массивы, вы можете поддерживать безопасность и по-прежнему избегать создания внутренностей вашего цикла:

branches=()
eval "$(git for-each-ref --shell --format='branches+=(%(refname))' refs/heads/)"
for branch in "${branches[@]}"; do
    # …
done

Вы можете сделать что-то подобное, $@если вы не используете оболочку, поддерживающую массивы ( set --для инициализации и set -- "$@" %(refname)добавления элементов).


40
Шутки в сторону. Нет более простого способа сделать это?
Джим Фелл

4
Но что, если я хочу использовать один из вариантов фильтрации ветки git, например --merged, нужно ли мне дублировать логику в ветке git? Должен быть лучший способ сделать это.
Thayne

3
Упрощенная версия:git for-each-ref refs/heads | cut -d/ -f3-
wid

4
@wid: Или просто,git for-each-ref refs/heads --format='%(refname)'
Джон Гитцен

5
@Thayne: это старый вопрос, но ребята из Git наконец-то решили проблему: for-each-refтеперь поддерживаются все селекторы веток, например--merged и, git branchи git tagтеперь они фактически реализованы в терминах самих git for-each-refсебя, по крайней мере, для существующих списков случаев. (Создание новых веток и тегов не является и не должно быть частью for-each-ref.)
torek

49

Это потому, git branchчто текущая ветвь помечается звездочкой, например:

$ git branch
* master
  mybranch
$ 

так $(git branch)расширяется до, например * master mybranch, а затем* расширяется до списка файлов в текущем каталоге.

Я не вижу очевидного варианта, чтобы вообще не печатать звездочку; но можно было отрубить:

$(git branch | cut -c 3-)

4
Если вы заключите в двойные кавычки, вы можете запретить bash расширять звездочку, хотя вы все равно захотите удалить ее из вывода. Более надежным способом удаления звездочки из любой точки будет $(git branch | sed -e s/\\*//g).
Ник

2
хорошо, мне очень нравится твое 3-решение.
Андрей-Никулае Петре

1
Чуть более простая версия sed:$(git branch | sed 's/^..//')
jdg

5
немного более простая версия tr:$(git branch | tr -d " *")
ccpizza

13

mapfileВстроенная команда bash создана для этого.

все ветки git: git branch --all --format='%(refname:short)'

все локальные ветки git: git branch --format='%(refname:short)'

все удаленные ветки git: git branch --remotes --format='%(refname:short)'

перебрать все ветки git: mapfile -t -C my_callback -c 1 < <( get_branches )

пример:

my_callback () {
  INDEX=${1}
  BRANCH=${2}
  echo "${INDEX} ${BRANCH}"
}
get_branches () {
  git branch --all --format='%(refname:short)'
}
# mapfile -t -C my_callback -c 1 BRANCHES < <( get_branches ) # if you want the branches that were sent to mapfile in a new array as well
# echo "${BRANCHES[@]}"
mapfile -t -C my_callback -c 1 < <( get_branches )

для конкретной ситуации OP:

#!/usr/bin/env bash


_map () {
  ARRAY=${1?}
  CALLBACK=${2?}
  mapfile -t -C "${CALLBACK}" -c 1 <<< "${ARRAY[@]}"
}


get_history_differences () {
  REF1=${1?}
  REF2=${2?}
  shift
  shift
  git log --oneline "${REF1}" ^"${REF2}" "${@}"
}


has_different_history () {
  REF1=${1?}
  REF2=${2?}
  HIST_DIFF=$( get_history_differences "${REF1}" "${REF2}" )
  return $( test -n "${HIST_DIFF}" )
}


print_different_branches () {
  read -r -a ARGS <<< "${@}"
  LOCAL=${ARGS[-1]?}
  for REMOTE in "${SOME_REMOTE_BRANCHES[@]}"; do
    if has_different_history "${LOCAL}" "${REMOTE}"; then
      # { echo; echo; get_history_differences "${LOCAL}" "${REMOTE}" --color=always; } # show differences
      echo local branch "${LOCAL}" is different than remote branch "${REMOTE}";
    fi
  done
}


get_local_branches () {
  git branch --format='%(refname:short)'
}


get_different_branches () {
  _map "$( get_local_branches )" print_different_branches
}


# read -r -a SOME_REMOTE_BRANCHES <<< "${@}" # use this instead for command line input
declare -a SOME_REMOTE_BRANCHES
SOME_REMOTE_BRANCHES=( origin/master remotes/origin/another-branch another-remote/another-interesting-branch )
DIFFERENT_BRANCHES=$( get_different_branches )

echo "${DIFFERENT_BRANCHES}"

источник: перечислить все локальные ветки git без звездочки


5

Я повторяю как это например:

for BRANCH in `git branch --list|sed 's/\*//g'`;
  do 
    git checkout $BRANCH
    git fetch
    git branch --set-upstream-to=origin/$BRANCH $BRANCH
  done
git checkout master;

4

Я бы посоветовал, $(git branch|grep -o "[0-9A-Za-z]\+")если ваши местные отделения будут называться только цифрами, буквами az и / или AZ.


4

Принятый ответ верен и действительно должен быть подходом, но решение проблемы в bash - отличное упражнение в понимании того, как работают оболочки. Уловка, позволяющая сделать это с помощью bash без дополнительных манипуляций с текстом, состоит в том, чтобы гарантировать, что вывод git branch никогда не будет расширен как часть команды, выполняемой оболочкой. Это предотвращает расширение звездочки при расширении имени файла (шаг 8) расширения оболочки (см. Http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html )

Используйте конструкцию bash while с командой чтения, чтобы разбить вывод ветки git на строки. '*' Будет читаться как буквальный символ. Используйте оператор case, чтобы сопоставить его, уделяя особое внимание шаблонам сопоставления.

git branch | while read line ; do                                                                                                        
    case $line in
        \*\ *) branch=${line#\*\ } ;;  # match the current branch
        *) branch=$line ;;             # match all the other branches
    esac
    git log --oneline $branch ^remotes/origin/master
done

Звездочки в bash конструкции case так и в подстановке параметров необходимо экранировать обратной косой чертой, чтобы оболочка не интерпретировала их как символы сопоставления с образцом. Пробелы также экранированы (для предотвращения токенизации), потому что вы буквально соответствуете '*'.


4

Самый простой вариант для запоминания, на мой взгляд:

git branch | grep "[^* ]+" -Eo

Вывод:

bamboo
develop
master

Опция Grep -o (--only-matching) ограничивает вывод только соответствующими частями ввода.

Поскольку ни пробел, ни * недопустимы в именах веток Git, это возвращает список ветвей без дополнительных символов.

Изменить: если вы находитесь в состоянии «отдельная голова» , вам необходимо отфильтровать текущую запись:

git branch --list | grep -v "HEAD detached" | grep "[^* ]+" -oE


3

Что я в итоге сделал, применительно к вашему вопросу (и вдохновленный упоминанием ccpizza tr):

git branch | tr -d ' *' | while IFS='' read -r line; do git log --oneline "$line" ^remotes/origin/master; done

(Я часто использую циклы while. Хотя для определенных вещей вы определенно захотите использовать указанное имя переменной [, например, "ветвь"], большую часть времени я занимаюсь только тем, чтобы что-то делать с каждой строкой ввода. 'линия' здесь вместо 'ветки' указывает на возможность повторного использования / мышечную память / эффективность.)


1

Продолжая ответ @ finn (спасибо!), Следующее позволит вам перебирать ветки без создания промежуточного сценария оболочки. Это достаточно надежно, если в имени ветки нет новых строк :)

git for-each-ref --format='%(refname)' refs/heads  | while read x ; do echo === $x === ; done

Цикл while выполняется в подоболочке, что обычно нормально, если вы не устанавливаете переменные оболочки, к которым хотите получить доступ в текущей оболочке. В этом случае вы используете подстановку процесса, чтобы перевернуть канал:

while read x ; do echo === $x === ; done < <( git for-each-ref --format='%(refname)' refs/heads )

1

Если вы в этом состоянии:

git branch -a

* master

  remotes/origin/HEAD -> origin/master

  remotes/origin/branch1

  remotes/origin/branch2

  remotes/origin/branch3

  remotes/origin/master

И вы запускаете этот код:

git branch -a | grep remotes/origin/*

for BRANCH in `git branch -a | grep remotes/origin/*` ;

do
    A="$(cut -d'/' -f3 <<<"$BRANCH")"
    echo $A

done        

Вы получите такой результат:

branch1

branch2

branch3

master

Мне это кажется менее сложным решением +1
Abhi

Это сработало и для меня:for b in "$(git branch)"; do git branch -D $b; done
Абхи

1

Будь проще

Простой способ получить имя ветки в цикле с помощью сценария bash.

#!/bin/bash

for branch in $(git for-each-ref --format='%(refname)' refs/heads/); do
    echo "${branch/'refs/heads/'/''}" 
done

Вывод:

master
other

1

Ответ Googlian, но без использования for

git for-each-ref --format='%(refname:lstrip=-1)' refs/heads/

1
Это не работает для имен веток с именами, например для ветвей, в которых есть косая черта. Это означает, что ветки, созданные зависимым компьютером, которые выглядят примерно как «independentabot / npm_and_yarn / typescript-3.9.5», будут отображаться как «typescript-3.9.5».
ecbrodie,

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.