Как перебрать все ветки git, используя скрипт bash - PullRequest
90 голосов
/ 02 октября 2010

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

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

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

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

Спасибо

Ответы [ 11 ]

142 голосов
/ 03 октября 2010

Вы не должны использовать git branch при написании скриптов. 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) для добавления элементов).

44 голосов
/ 02 октября 2010

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

$ git branch
* master
  mybranch
$ 

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

Я не вижу очевидной возможности не печатать звездочку в первую очередь; но вы можете отрубить его:

$(git branch | cut -c 3-)
8 голосов
/ 13 ноября 2018

Встроенный bash, mapfile, создан для этого

всех веток 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 )

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

#!/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}"

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

5 голосов
/ 21 марта 2016

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

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 голосов
/ 30 января 2016

Принятый ответ является правильным и действительно должен быть использован подход, но решение проблемы в bash - отличное упражнение в понимании работы оболочек. Хитрость в том, чтобы сделать это с помощью bash без выполнения дополнительных манипуляций с текстом, состоит в том, чтобы гарантировать, что вывод ветви git никогда не будет расширен как часть команды, которая должна быть выполнена оболочкой. Это предотвращает расширение звездочки в расширении имени файла (шаг 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 голосов
/ 20 июля 2012

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

3 голосов
/ 30 января 2018

То, что я закончил, применил к вашему вопросу (и вдохновлен ccpizza упоминанием tr):

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

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

3 голосов
/ 01 августа 2017

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

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

Выход:

bamboo
develop
master

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

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

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

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

1 голос
/ 24 октября 2017

Расширяя ответ @ 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 )
0 голосов
/ 10 апреля 2019

Не усложняйте

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

#!/bin/bash

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

Вывод:

master
other
...