Como iterar por todas as ramificações do git usando o script bash
Pergunta
Como posso iterar todas as ramificações locais do meu repositório usando o script bash.Preciso iterar e verificar se há alguma diferença entre a ramificação e algumas ramificações remotas.Ex
for branch in $(git branch);
do
git log --oneline $branch ^remotes/origin/master;
done
Preciso fazer algo como o indicado acima, mas o problema que estou enfrentando é que $(git branch) me fornece as pastas dentro da pasta do repositório junto com as ramificações presentes no repositório.
Esta é a maneira correta de resolver esse problema?Ou existe outra maneira de fazer isso?
Obrigado
Solução
Você não deve usar ramo git Ao escrever scripts. Git fornece a Interface “encanamento” Isso é projetado explicitamente para uso no script (muitas implementações atuais e históricas dos comandos GIT normais (add, checkout, mescla, etc.) usam essa mesma interface).
O comando de encanamento que você deseja é Git for-Eaça-Ref:
git for-each-ref --shell \
--format='git log --oneline %(refname) ^origin/master' \
refs/heads/
Nota: você não precisa do remotes/
prefixo no referência remoto, a menos que você tenha outros árbitros que causam origin/master
Para combinar com vários lugares no caminho da pesquisa de nome da ref (consulte “um nome de referência simbólica.…” Em A seção de revisões especificantes do git-rev-parse (1)). Se você está tentando evitar explicar a ambiguidade, vá com o nome completo da referência: refs/remotes/origin/master
.
Você obterá saída assim:
git log --oneline 'refs/heads/master' ^origin/master
git log --oneline 'refs/heads/other' ^origin/master
git log --oneline 'refs/heads/pu' ^origin/master
Você pode colocar esta saída para sh.
Se você não gosta da ideia de gerar o código do shell, você pode desistir de um pouco de robustez* E faça isso:
for branch in $(git for-each-ref --format='%(refname)' refs/heads/); do
git log --oneline "$branch" ^origin/master
done
* Os nomes de referência devem estar a salvo da divisão da palavra da concha (veja Git-check-ref-format (1)). Pessoalmente, eu continuaria com a versão anterior (código de shell gerado); Estou mais confiante de que nada inapropriado pode acontecer com isso.
Já que você especificou Bash E ele suporta matrizes, você pode manter a segurança e ainda evitar gerar as tripas do seu loop:
branches=()
eval "$(git for-each-ref --shell --format='branches+=(%(refname))' refs/heads/)"
for branch in "${branches[@]}"; do
# …
done
Você poderia fazer algo semelhante com $@
Se você não estiver usando um shell que suporta matrizes (set --
Para inicializar e set -- "$@" %(refname)
para adicionar elementos).
Outras dicas
Isto é porque git branch
marca o ramo atual com um asterisco, por exemplo:
$ git branch
* master
mybranch
$
assim $(git branch)
expande -se para por exemplo * master mybranch
, e então o *
expande -se para a lista de arquivos no diretório atual.
Não vejo uma opção óbvia para não imprimir o asterisco em primeiro lugar; Mas você pode cortar:
$(git branch | cut -c 3-)
A festa embutida, mapfile
, é construído para isso
Todos os ramos git: git branch --all --format='%(refname:short)'
Todos os ramos Git local: git branch --format='%(refname:short)'
Todos os ramos git remoto: git branch --remotes --format='%(refname:short)'
itera através de todos os ramos git: mapfile -t -C my_callback -c 1 < <( get_branches )
exemplo:
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 )
Para a situação específica do 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}"
Eu itero como ele, por exemplo:
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;
eu sugeriria$(git branch|grep -o "[0-9A-Za-z]\+")
Se suas filiais locais forem nomeadas apenas por letras de dígitos, AZ e/ou AZ
A resposta aceita está correta e realmente deve ser a abordagem usada, mas resolver o problema em Bash é um ótimo exercício para entender como as conchas funcionam. O truque para fazer isso usando o Bash sem executar manipulação de texto adicional é garantir que a saída do ramo Git nunca seja expandida como parte de um comando a ser executado pelo shell. Isso impede que o asterisco seja de expansão na expansão do nome do arquivo (etapa 8) da expansão da concha (ver http://tldp.org/ldp/bash-beginners-guide/html/sect_03_04.html)
Use a festa enquanto Construa com um comando de leitura para picar a saída do ramo Git em linhas. O '*' será lido como um caráter literal. Use uma declaração de caso para combiná -la, prestando atenção especial aos padrões correspondentes.
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
Os asteriscos em ambos caso Construa e na substituição de parâmetros precisam ser escapados com barras de barriga para impedir que o shell os interprete como caracteres de correspondência de padrões. Os espaços também são escapados (para evitar tokenização) porque você está literalmente correspondendo '*'.
Opção mais fácil de lembrar na minha opinião:
git branch | grep "[^* ]+" -Eo
Resultado:
bamboo
develop
master
A opção -O -o de Grep (-com correspondência) restringe a saída apenas às partes correspondentes da entrada.
Como nem o espaço nem * são válidos em nomes de ramificação Git, isso retorna a lista de ramificações sem os caracteres extras.
Editar: se você estiver em Estado de 'cabeça destacada', você precisará filtrar a entrada atual:
git branch --list | grep -v "HEAD detached" | grep "[^* ]+" -oE
O que acabei fazendo, aplicado à sua pergunta (e inspirado por ccpizza mencionando tr
):
git branch | tr -d ' *' | while IFS='' read -r line; do git log --oneline "$line" ^remotes/origin/master; done
(Eu uso muito os loops. Enquanto para coisas específicas, você definitivamente gostaria de usar um nome de variável pontiagudo ["ramo", por exemplo], na maioria das vezes, estou preocupado apenas em fazer algo com cada um linha de entrada. Usar 'linha' aqui em vez de 'ramo' é um aceno para a reutilização/memória/eficiência muscular.)
Estendendo-se a partir da resposta de @finn (obrigado!), o seguinte permitirá que você itere nas ramificações sem criar um script de shell intermediário.É robusto o suficiente, desde que não haja novas linhas no nome do branch :)
git for-each-ref --format='%(refname)' refs/heads | while read x ; do echo === $x === ; done
O loop while é executado em um subshell, o que geralmente é bom, a menos que você esteja definindo variáveis de shell que deseja acessar no shell atual.Nesse caso, você usa a substituição de processo para reverter o canal:
while read x ; do echo === $x === ; done < <( git for-each-ref --format='%(refname)' refs/heads )
Se você está neste estado:
git branch -a
* master
remotes/origin/HEAD -> origin/master
remotes/origin/branch1
remotes/origin/branch2
remotes/origin/branch3
remotes/origin/master
E você executa este código:
git branch -a | grep remotes/origin/*
for BRANCH in `git branch -a | grep remotes/origin/*` ;
do
A="$(cut -d'/' -f3 <<<"$BRANCH")"
echo $A
done
Você receberá este resultado:
branch1
branch2
branch3
master
Mantenha simples
A maneira simples de obter o nome da filial em loop usando o script bash.
#!/bin/bash
for branch in $(git for-each-ref --format='%(refname)' refs/heads/); do
echo "${branch/'refs/heads/'/''}"
done
Resultado:
master
other
Resposta de googliana, mas sem usar por
git for-each-ref --format='%(refname:lstrip=-1)' refs/heads/
for branch in "$(git for-each-ref --format='%(refname:short)' refs/heads)"; do
...
done
Isso usa Encanamento Git Comandos, projetados para scripts. Também é simples e padrão.
Referência: Conclusão da festa do Git