Verificando se há um índice sujo ou arquivos não rastreados com o Git


243

Como posso verificar se tenho alterações não confirmadas no meu repositório git:

  1. Alterações adicionadas ao índice, mas não confirmadas
  2. Arquivos não rastreados

de um script?

git-status parece sempre retornar zero com a versão 1.6.4.2 do git.


3
O status git retornará 1 se houver arquivos modificados em estágios. Mas, em geral, acho que as ferramentas git não são particularmente completas com o status de retorno. EG git diff retorna 0, havendo ou não diferenças.
intuited

11
@ intuited: Se você precisar diffindicar a presença ou ausência de diferenças em vez da execução bem-sucedida do comando, precisará usar --exit-codeou --quiet. Os comandos git geralmente são muito consistentes com o retorno de um código de saída zero ou diferente de zero para indicar o sucesso do comando.
perfil completo de CB Bailey

1
@ Charles Bailey: Ei, ótimo, eu perdi essa opção. Obrigado! Acho que nunca precisei muito disso, ou provavelmente teria vasculhado a página de manual. Que bom que você me corrigiu :)
intuited

1
robert - este é um tópico muito confuso para a comunidade (não ajuda o fato de a porcelana ser usada de maneira diferente em contextos diferentes). A resposta que você selecionou ignora uma resposta com votação 2,5 vezes mais alta que é mais robusta e segue o design do git. Você já viu a resposta de @ChrisJ?
Mike

1
@ Robert - Espero que você considere alterar sua resposta aceita. O que você selecionou depende de comandos mais frágeis de 'porcelana' do git; a resposta correta real (também com 2x os votos positivos) depende dos comandos corretos do git 'encanamento'. Essa resposta correta foi originalmente de Chris Johnsen. Trago isso à tona como hoje é a terceira vez que tive que encaminhar alguém para esta página, mas não posso apenas apontar a resposta, expliquei por que a resposta aceita está abaixo do ideal / está errada. Obrigado!
Mike1 /

Respostas:


173

Ótimo momento! Escrevi um post sobre exatamente isso alguns dias atrás, quando descobri como adicionar informações de status do git ao meu prompt.

Aqui está o que eu faço:

  1. Para status sujo:

    # Returns "*" if the current git branch is dirty.
    function evil_git_dirty {
      [[ $(git diff --shortstat 2> /dev/null | tail -n1) != "" ]] && echo "*"
    }
  2. Para arquivos não rastreados (observe o --porcelainsinalizador ao git statusqual você fornece uma boa saída analisável):

    # Returns the number of untracked files
    
    function evil_git_num_untracked_files {
      expr `git status --porcelain 2>/dev/null| grep "^??" | wc -l` 
    }

Embora git diff --shortstatseja mais conveniente, você também pode usar git status --porcelainpara obter arquivos sujos:

# Get number of files added to the index (but uncommitted)
expr $(git status --porcelain 2>/dev/null| grep "^M" | wc -l)

# Get number of files that are uncommitted and not added
expr $(git status --porcelain 2>/dev/null| grep "^ M" | wc -l)

# Get number of total uncommited files
expr $(git status --porcelain 2>/dev/null| egrep "^(M| M)" | wc -l)

Nota: Os 2>/dev/nullfiltros filtram as mensagens de erro para que você possa usar esses comandos em diretórios não-git. (Eles simplesmente retornam 0para a contagem de arquivos.)

Editar :

Aqui estão os posts:

Adicionando informações de status do Git ao seu prompt de terminal

Prompt de shell aprimorado para Git


8
Vale notar que a conclusão do git bash vem com uma função shell para fazer praticamente o que você está fazendo com o prompt - __git_ps1. Ele mostra os nomes das ramificações, incluindo tratamento especial se você estiver no processo de rebase, aplicação, mesclagem ou divisão. E você pode definir a variável de ambiente GIT_PS1_SHOWDIRTYSTATEpara obter um asterisco para alterações sem etapas e mais para alterações com etapas. (Acho que você também pode indicar arquivos não rastreados e fornecer alguma git-describesaída)
Cascabel

8
Um aviso: git diff --shortstatdará um falso negativo se as alterações já estiverem no índice.
Marko Topolnik 21/03

4
git status --porcelainé preferível, porque git diff --shortstatnão captura arquivos vazios recém-criados. Você pode experimentá-lo em qualquer árvore de trabalho limpa:touch foo && git diff --shortstat
Campadrenalin

7
NÃO - porcelana significa que a produção é para humanos e quebras facilmente !! veja a resposta de @ChrisJohnsen, que usa corretamente as opções estáveis ​​e compatíveis com scripts.
Mike12 /

7
@mike na página de manual do git-status sobre a opção porcelain: "Forneça a saída em um formato fácil de analisar para scripts. É semelhante à saída curta, mas permanecerá estável nas versões do Git e independentemente da configuração do usuário. "
itsadok

399

A chave para criar scripts com segurança no Git é usar os comandos 'encanamento'.

Os desenvolvedores tomam cuidado ao alterar os comandos de encanamento para garantir que eles forneçam interfaces muito estáveis ​​(ou seja, uma determinada combinação de estado do repositório, stdin, opções de linha de comando, argumentos etc.) produzirá a mesma saída em todas as versões do Git em que o comando / opção existe). Novas variações de saída nos comandos de encanamento podem ser introduzidas por meio de novas opções, mas isso não pode causar problemas para programas que já foram gravados em versões mais antigas (eles não usariam as novas opções, pois não existiam (ou pelo menos eram não utilizado) no momento em que o script foi escrito).

Infelizmente, os comandos Git 'cotidianos' são os comandos 'porcelana'; portanto, a maioria dos usuários do Git pode não estar familiarizada com os comandos de encanamento. A distinção entre porcelana e comando de encanamento é feita na página principal do git (consulte as subseções intituladas Comandos de alto nível (porcelana) e comandos de baixo nível (encanamento) .


Para descobrir mudanças não confirmadas, você provavelmente precisará git diff-index(comparar o índice (e talvez rastrear bits da árvore de trabalho) com outras árvores (por exemplo HEAD)), talvez git diff-files(comparar a árvore de trabalho com o índice) e possivelmente git ls-files(listar arquivos; por exemplo, lista não rastreada) , arquivos não ignorados).

(Observe que nos comandos abaixo, HEAD -- é usado em vez de HEADporque, caso contrário, o comando falhará se houver um arquivo nomeado HEAD.)

Para verificar se um repositório organizou alterações (ainda não confirmadas), use isto:

git diff-index --quiet --cached HEAD --
  • Se sair com 0 , não houve diferenças ( 1significa que houve diferenças).

Para verificar se uma árvore de trabalho possui alterações que podem ser testadas:

git diff-files --quiet
  • O código de saída é o mesmo que para git diff-index ( 0== sem diferenças; 1== diferenças).

Para verificar se a combinação do índice e dos arquivos rastreados na árvore de trabalho tem alterações em relação a HEAD :

git diff-index --quiet HEAD --
  • É como uma combinação dos dois anteriores. Uma diferença principal é que ele ainda relatará "sem diferenças" se você tiver uma mudança faseada que "desfeita" na árvore de trabalho (voltada para o conteúdo que está dentro HEAD). Nessa mesma situação, os dois comandos separados retornariam relatórios de "diferenças presentes".

Você também mencionou arquivos não rastreados. Você pode significar “não rastreado e não registrado” ou apenas “não rastreado” (incluindo arquivos ignorados). De qualquer jeito,git ls-files é a ferramenta para o trabalho:

Para "não rastreado" (incluirá arquivos ignorados, se houver):

git ls-files --others

Para "não rastreado e não registrado":

git ls-files --exclude-standard --others

Meu primeiro pensamento é apenas verificar se esses comandos têm saída:

test -z "$(git ls-files --others)"
  • Se sair com 0 , não haverá arquivos não rastreados. Se sair com 1, haverá arquivos não rastreados.

Há uma pequena chance de que isso traduza saídas anormais de git ls-filesrelatórios "sem arquivos não rastreados" (ambos resultam em saídas diferentes de zero do comando acima). Uma versão um pouco mais robusta pode ser assim:

u="$(git ls-files --others)" && test -z "$u"
  • A ideia é a mesma do comando anterior, mas permite que erros inesperados git ls-filessejam propagados. Nesse caso, uma saída diferente de zero pode significar "existem arquivos não rastreados" ou um erro. Se você deseja que os resultados de "erro" combinem com o resultado "sem arquivos não rastreados", use test -n "$u"(onde sair de0 significa "alguns arquivos não rastreados" e diferente de zero significa erro ou "nenhum arquivo não rastreado").

Outra idéia é usar --error-unmatchpara causar uma saída diferente de zero quando não houver arquivos não rastreados. Isso também corre o risco de confundir “nenhum arquivo não rastreado” (saída 1) com “ocorreu um erro” (saída diferente de zero, mas provavelmente 128). Mas a verificação de códigos de saída 0vs. 1vs. diferentes de zero é provavelmente bastante robusta:

git ls-files --others --error-unmatch . >/dev/null 2>&1; ec=$?
if test "$ec" = 0; then
    echo some untracked files
elif test "$ec" = 1; then
    echo no untracked files
else
    echo error from ls-files
fi

Qualquer um dos git ls-filesexemplos acima pode ser --exclude-standardusado se você quiser considerar apenas arquivos não acompanhados e não rastreados.


5
Eu gostaria de salientar que git ls-files --othersfornece arquivos não rastreados locais , enquanto a git status --porcelainresposta aceita fornece todos os arquivos não rastreados que estão no repositório git. Não sou qual deles o pôster original queria, mas a diferença entre os dois é interessante.
Eric O Lebigot

1
@phunehehe: Você precisa fornecer um pathspec --error-unmatch. Tente (por exemplo) git ls-files --other --error-unmatch --exclude-standard .(observe o período à direita, refere-se ao cwd; execute isso no diretório de nível superior da árvore de trabalho).
precisa

8
@phs: Talvez você precise fazer git update-index -q --refreshantes diff-indexde evitar alguns "falsos positivos" causados ​​por informações incorretas sobre o stat (2).
precisa

1
Fui mordido pela git update-indexnecessidade! Isso é importante se algo estiver tocando os arquivos sem fazer modificações.
Nakedible 10/09/14

2
@RobertSiemer Por "local", eu quis dizer os arquivos no diretório atual , que podem estar abaixo do repositório principal do git. A --porcelainsolução lista todos os arquivos não rastreados encontrados em todo o repositório git (menos arquivos ignorados pelo git), mesmo se você estiver dentro de um de seus subdiretórios.
Eric O Lebigot

139

Supondo que você esteja no git 1.7.0 ou posterior ...

Depois de ler todas as respostas desta página e algumas experiências, acho que o método que atinge a combinação certa de correção e brevidade é:

test -n "$(git status --porcelain)"

Embora o git permita muitas nuances entre o que é rastreado, ignorado, não rastreado, mas não ignorado, e assim por diante, acredito que o caso de uso típico é para automatizar scripts de compilação, onde você deseja interromper tudo se o checkout não estiver limpo.

Nesse caso, faz sentido simular o que o programador faria: digite git statuse observe a saída. Mas não queremos confiar em palavras específicas que aparecem, então usamos o --porcelainmodo introduzido na 1.7.0; Quando ativado, um diretório limpo resulta em nenhuma saída.

Então usamos test -npara ver se houve alguma saída ou não.

Este comando retornará 1 se o diretório de trabalho estiver limpo e 0 se houver alterações a serem confirmadas. Você pode alterar -npara para -zse desejar o contrário. Isso é útil para encadear isso a um comando em um script. Por exemplo:

test -z "$(git status --porcelain)" || red-alert "UNCLEAN UNCLEAN"

Isso diz efetivamente "ou não há alterações a serem feitas ou dispara um alarme"; essa linha única pode ser preferível a uma instrução if, dependendo do script que você está escrevendo.


1
Para mim, todos os outros comandos estavam dando resultados diferentes no mesmo representante entre Linux e Windows. Este comando me deu a mesma saída em ambos.
Adarsha

6
Obrigado por responder à pergunta e não divagar sem parar, nunca fornecendo uma resposta clara.
NateS

Para um script de implantação manual, combinar isso com test -n "$(git diff origin/$branch)", para ajudar a prevenir commits locais de ser permitido em uma implantação
Erik Aronesty


8

Deu uma olhada em algumas dessas respostas ... (e tive vários problemas no * nix e windows, que era um requisito que eu tinha) ... encontrou o seguinte funcionou bem ...

git diff --no-ext-diff --quiet --exit-code

Para verificar o código de saída em * nix

echo $?   
#returns 1 if the repo has changes (0 if clean)

Para verificar o código de saída na janela $

echo %errorlevel% 
#returns 1 if the repos has changes (0 if clean) 

Originado de https://github.com/sindresorhus/pure/issues/115 Obrigado a @paulirish nessa publicação por compartilhar


4

Por que não encapsular ' git statuscom um script que:

  • analisará a saída desse comando
  • retornará o código de erro apropriado com base no que você precisa

Dessa forma, você pode usar esse status 'aprimorado' em seu script.


Como 0xfe menciona em sua excelente resposta , git status --porcelainé fundamental em qualquer solução baseada em script

--porcelain

Forneça a saída em um formato estável e fácil de analisar para scripts.
Atualmente, isso é idêntico --short output, mas é garantido que não será alterado no futuro, tornando-o seguro para scripts.


Porque sou preguiçosa, provavelmente. Eu pensei que havia um built-in para isso, pois parece um caso de uso bastante encontrado.
Robert Munteanu

Publiquei uma solução com base em sua sugestão, embora não esteja extremamente satisfeito com ela.
Robert Munteanu

4

Uma possibilidade DIY, atualizada para seguir a sugestão do 0xfe

#!/bin/sh
exit $(git status --porcelain | wc -l) 

Como observado por Chris Johnsen , isso funciona apenas no Git 1.7.0 ou mais recente.


6
O problema é que você não pode esperar com segurança a string 'diretório de trabalho limpo' em versões futuras. A bandeira --porcelain foi concebido para análise, de modo a melhor solução seria: exit $ (git status de --porcelain | wc -l)
0xfe

@ 0xfe - Você sabe quando a --porcelainbandeira foi adicionada? Não funciona com 1.6.4.2.
Robert Munteanu

@ Robert: tente git status --shortentão.
VonC 17/04/10

3
git status --porcelaine git status --shortforam ambos introduzidos na 1.7.0. --porcelainFoi introduzido especificamente para permitir git status --shortvariar seu formato no futuro. Portanto, git status --shortsofreria o mesmo problema que git status(a saída pode mudar a qualquer momento, pois não é um comando 'encanamento').
31810 Chris Johnsen

@ Chris, obrigado pela informação de fundo. Atualizei a resposta para refletir a melhor maneira de fazer isso no Git 1.7.0.
Robert Munteanu

2

Muitas vezes, eu precisava de uma maneira simples de falhar em uma compilação se, no final da execução, houver arquivos rastreados modificados ou arquivos não rastreados que não sejam ignorados.

Isso é muito importante para evitar casos em que as construções produzem sobras.

Até agora, o melhor comando que acabei usando se parece com:

 test -z "$(git status --porcelain | tee /dev/fd/2)" || \
     {{ echo "ERROR: git unclean at the end, failing build." && return 1 }}

Pode parecer um pouco complexo e eu apreciaria se alguém encontrar uma variante em curto que isso mantenha o comportamento desejado:

  • nenhum código de saída e de saída de sucesso, se tudo estiver em ordem
  • código de saída 1 se falhar
  • mensagem de erro no stderr explicando por que falha
  • Para exibir a lista de arquivos que causam a falha, stderr novamente.

2

A resposta eduard-wirch foi bastante completa, mas como eu queria verificar as duas ao mesmo tempo, aqui está minha variante final.

        set -eu

        u="$(git ls-files --others)"
        if ! git diff-index --name-only --quiet HEAD -- || [ -z "${u:-}" ]; then
            dirty="-dirty"
        fi

Ao não executar usando set -e ou equivalente, podemos fazer um u="$(git ls-files --others)" || exit 1 (ou retornar se isso funcionar para uma função usada)

Portanto, untracked_files, é definido apenas se o comando for bem-sucedido.

após o qual, podemos verificar as duas propriedades e definir uma variável (ou qualquer outra coisa).


1

Essa é uma variação mais amigável ao shell para descobrir se existe algum arquivo não rastreado no repositório:

# Works in bash and zsh
if [[ "$(git status --porcelain 2>/dev/null)" = *\?\?* ]]; then
  echo untracked files
fi

Isso não bifurca um segundo processo, grepe não precisa ser verificado se você está em um repositório git ou não. O que é útil para solicitações de shell etc.


1

Você também pode fazer

git describe --dirty

. Anexará a palavra "-dirty" no final se detectar uma árvore de trabalho suja. De acordo com git-describe(1):

   --dirty[=<mark>]
       Describe the working tree. It means describe HEAD and appends <mark> (-dirty by default) if
       the working tree is dirty.

. Advertência: arquivos não rastreados não são considerados "sujos" porque, como afirma a página de manual, eles se preocupam apenas com a árvore de trabalho.


0

Pode haver uma melhor combinação de respostas de esta discussão .. mas isso funciona para mim ... para a sua .gitconfig's [alias]seção ...

          # git untracked && echo "There are untracked files!"
untracked = ! git status --porcelain 2>/dev/null | grep -q "^??"
          # git unclean && echo "There are uncommited changes!"
  unclean = ! ! git diff --quiet --ignore-submodules HEAD > /dev/null 2>&1
          # git dirty && echo "There are uncommitted changes OR untracked files!"
    dirty = ! git untracked || git unclean

0

O teste automático mais simples que eu uso para detectar estado sujo = quaisquer alterações, incluindo arquivos não rastreados :

git add --all
git diff-index --exit-code HEAD

NOTA:

  • Sem add --all diff-index não percebe arquivos não rastreados.
  • Normalmente, eu corro git resetdepois de testar o código de erro para desestabilizar tudo de volta.

A questão é especificamente "de um script" ... não é uma boa ideia alterar o índice apenas para testar o estado sujo.
ScottJ

@ ScottJ, quando resolve o problema, nem todos são necessariamente rigorosos quanto à possibilidade de o índice ser modificado ou não. Considere o caso do trabalho automatizado, que trata das fontes de correção automática com o número da versão e faça uma marcação - tudo o que você precisa para garantir é que absolutamente nenhuma outra modificação local fique no caminho (independentemente de elas estarem no índice ou não). Até agora, este foi um teste confiável para quaisquer alterações, incluindo arquivos não rastreados .
Uvsmtid 02/03

-3

Aqui está a melhor e mais limpa maneira. A resposta selecionada não funcionou para mim por algum motivo, não captou as alterações organizadas que eram novos arquivos que não foram confirmados.

function git_dirty {
    text=$(git status)
    changed_text="Changes to be committed"
    untracked_files="Untracked files"

    dirty=false

    if [[ ${text} = *"$changed_text"* ]];then
        dirty=true
    fi

    if [[ ${text} = *"$untracked_files"* ]];then
        dirty=true
    fi

    echo $dirty
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.