Як перебрати всі гіти git за допомогою bash script


105

Як я можу повторити всі локальні гілки мого сховища за допомогою bash script. Мені потрібно повторити і перевірити, чи є різниця між гілкою та деякими віддаленими гілками. Вих

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

Мені потрібно зробити щось на кшталт наведеного вище, але проблема, з якою я стикаюся, - $ (git гілка) дає мені папки всередині папки сховища разом із гілками, що знаходяться у сховищі.

Це правильний спосіб вирішити це питання? Або є інший спосіб це зробити?

Дякую


Можливий дублікат для петлі над усіма
гітками

1
@pihentagy Це було написано перед пов'язаним питанням.
kodaman

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

Відповіді:


156

Не слід використовувати гіт git при написанні сценаріїв. Git забезпечує "сантехнічний" інтерфейс, який явно розроблений для використання в сценаріях (багато поточних та історичних реалізацій звичайних команд Git (додавання, оформлення замовлення, об'єднання тощо) використовують цей самий інтерфейс).

Команда сантехніка, яку ви хочете, - це git for-every-ref :

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

Примітка: Вам не потрібен remotes/префікс на пульті реф , якщо у вас немає інших рефов , що причина , origin/masterщоб відповідати кілька місць в пошук по імені вих шляху (див «Символічне ім'я реф ....» У ревізіях розділі мерзотника-Rev-синтаксичного аналізу Завдання (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

Ви можете передати цей вихід у ш .

Якщо вам не подобається ідея генерування коду оболонки, ви можете відмовитися від надійності * і зробити це:

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

* Імена Ref повинні бути захищені від розбиття слова оболонки (див. 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)додавання елементів).


39
Серйозно. Не існує простішого способу зробити це?
Джим Fell

4
Але що робити, якщо я хочу скористатися одним із варіантів фільтрації гітової гілки, як --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

50

Це тому, що git branchпозначає поточну гілку зірочкою, наприклад:

$ git branch
* master
  mybranch
$ 

так $(git branch)розширюється до eg * 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, побудований для цього

всі гіти 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}"

Джерело: Список усіх локальних гілок 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, не виконуючи додаткових маніпуляцій з текстом, полягає у тому, щоб вивід гілки git ніколи не розширювався як частина команди, що виконується оболонкою. Це запобігає розширенню зірочки при розширенні імені файлу (крок 8) розширення оболонки (див. Http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html )

Використовуйте Баш в той час як конструкція з командою читання , щоб подрібнити GIT вихідний гілки на лінію. "*" Буде читатися як буквальний символ. Використовуйте випадок справи, щоб відповідати цьому, звертаючи особливу увагу на відповідні шаблони.

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


4

Найпростіший варіант, який можна запам'ятати на мою думку:

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

Вихід:

bamboo
develop
master

Опція Grep's -o (- тільки відповідність) обмежує вихід лише відповідними частинами вводу.

Оскільки ані пробіл, ані * не є дійсними у назвах гілок 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

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


1

Продовжуючи відповідь @ finn (спасибі!), Наступне дозволить вам переглядати гілки, не створюючи втручається скрипт оболонки. Це досить надійно, поки у назві гілки немає нових рядків :)

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

Цикл "time" працює в підзаголовці, що зазвичай добре, якщо ви не встановлюєте змінні оболонки, до яких потрібно отримати доступ у поточній оболонці. У такому випадку ви використовуєте процедуру заміни, щоб повернути трубу:

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
абхі

Це працювало і для мене:for b in "$(git branch)"; do git branch -D $b; done
Абхі

1

Не ускладнювати

Простий спосіб отримання імені гілки в циклі за допомогою bash script.

#!/bin/bash

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

Вихід:

master
other

1

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

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

1
Це не працює для імен гілок, що розділяються на імена, як у гілках, у яких є коса риса. Це означає, що гілки, створені залежною роботою, які виглядають приблизно як "залежна робота / npm_and_yarn / typecript-3.9.5", замість цього з'являться як "typecript-3.9.5".
ecbrodie

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.