Как выполнить итерацию через все ветки git, используя bash script

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

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

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

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

Спасибо

Ответ 1

Вы не должны использовать ветвь git при написании скриптов. git предоставляет "сантехнический" интерфейс, который явно предназначен для использования в сценариях (многие текущие и исторические реализации обычных команд git (add, checkout, merge и т.д.) используют этот же интерфейс).

Команда сантехника, которую вы хотите, - git для-каждого-ref:

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

Примечание: вам не нужен префикс remotes/ на удаленной ссылке ref, если у вас нет других ссылок, которые вызывают origin/master для соответствия нескольким местам в пути поиска имени ссылки (см. "Символьное имя ссылки..." в раздел "Указание изменений" в разделе git -rev-parse (1)). Если вы пытаетесь явно избегать двусмысленности, перейдите с полным именем ref: 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

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

Ответ 2

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

$ git branch
* master
  mybranch
$ 

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

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

$(git branch | cut -c 3-)

Ответ 3

mapfile 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 без звездочки

Ответ 4

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

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;

Ответ 5

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

Ответ 6

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

Используйте конструкцию bash в то время как с командой чтения, чтобы разорвать выход ветвей 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 и в подстановке параметров должны быть экранированы с помощью обратных косых черт, чтобы оболочка не интерпретировала их как символы соответствия шаблону. Пробелы также экранируются (чтобы предотвратить токенизацию), потому что вы буквально сопоставляете "*".

Ответ 7

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

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

Вывод:

bamboo
develop
master

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

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

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

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

Ответ 8

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

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

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

Ответ 9

Расширяясь от ответа @finn (спасибо!), следующее позволит вам перебирать ветки без создания промежуточной оболочки script. Он достаточно прочен, если в названии ветки нет новых строк:)

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 )

Ответ 10

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

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

Ответ 11

Будь проще

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

#!/bin/bash

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

Выход:

master
other

Ответ 12

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

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

Ответ 13

for branch in "$(git for-each-ref --format='%(refname:short)' refs/heads)"; do
    ...
done

Здесь используются команды git plumbing, предназначенные для сценариев. Это также просто и стандартно.

Ссылка: завершение Git Bash