Como determino programaticamente se há alterações não confirmadas?

226

Em um Makefile, eu gostaria de executar determinadas ações se houver alterações não confirmadas (na árvore de trabalho ou no índice). Qual é a maneira mais limpa e eficiente de fazer isso? Um comando que sai com um valor de retorno zero em um caso e diferente de zero no outro seria adequado para meus propósitos.

Posso correr git statuse canalizar a saída grep, mas sinto que deve haver uma maneira melhor.

Daniel Stutzbach
fonte

Respostas:

289

ATUALIZAÇÃO : o OP Daniel Stutzbach ressalta nos comentários que esse comando simples git diff-indexfuncionou para ele:

git update-index --refresh 
git diff-index --quiet HEAD --

( nornagon menciona nos comentários que, se houver arquivos que foram tocados, mas cujo conteúdo é o mesmo do índice, você precisará executar git update-index --refreshantes git diff-index, caso contrário diff-index, informará incorretamente que a árvore está suja)

Você pode ver " Como verificar se um comando foi bem-sucedido? " Se você o estiver usando em um script bash:

git diff-index --quiet HEAD -- || echo "untracked"; // do something about it

Nota: como comentado por Anthony Sottile

git diff-index HEAD ...falhará em uma ramificação que não possui confirmações (como um repositório recém-inicializado).
Uma solução alternativa que encontrei égit diff-index $(git write-tree) ...

E haridsvressalta nos comentários que git diff-filesem um novo arquivo não o detecta como um diff.
A abordagem mais segura parece ser executada git addprimeiro nas especificações do arquivo e depois usada git diff-indexpara verificar se alguma coisa foi adicionada ao índice antes da execução git commit.

git add ${file_args} && \
git diff-index --cached --quiet HEAD || git commit -m '${commit_msg}'

E 6502 relata nos comentários:

Um problema que encontrei é que git diff-indexdirá que existem diferenças quando, na verdade, não há nenhuma, exceto os registros de data e hora dos arquivos.
A execução git diffresolve o problema (surpreendentemente, git diffna verdade, altera o conteúdo da sandbox, ou seja, aqui .git/index)

Esses problemas de carimbo de data e hora também podem ocorrer se o git estiver sendo executado na janela de encaixe .


Resposta original:

"Programaticamente" significa nunca confiar em comandos de porcelana .
Sempre confie nos comandos de encanamento .

Consulte também " Verificando se há um índice sujo ou arquivos não rastreados com o Git " para obter alternativas (como git status --porcelain)

Você pode se inspirar na nova " require_clean_work_treefunção " que está escrita enquanto falamos ;) (início de outubro de 2010)

require_clean_work_tree () {
    # Update the index
    git update-index -q --ignore-submodules --refresh
    err=0

    # Disallow unstaged changes in the working tree
    if ! git diff-files --quiet --ignore-submodules --
    then
        echo >&2 "cannot $1: you have unstaged changes."
        git diff-files --name-status -r --ignore-submodules -- >&2
        err=1
    fi

    # Disallow uncommitted changes in the index
    if ! git diff-index --cached --quiet HEAD --ignore-submodules --
    then
        echo >&2 "cannot $1: your index contains uncommitted changes."
        git diff-index --cached --name-status -r --ignore-submodules HEAD -- >&2
        err=1
    fi

    if [ $err = 1 ]
    then
        echo >&2 "Please commit or stash them."
        exit 1
    fi
}
VonC
fonte
12
O princípio "encanamento versus porcelana para script" é uma lição que Jakub Narębski mencionou várias vezes para mim: " Como listar todo o log do projeto atual no git? ", " Git: changelog dia a dia ", ...
VonC
18
Após clicar em alguns dos links que você sugere, eu encontrei o que eu estava procurando: git diff-index --quiet HEAD.
Daniel Stutzbach 7/10/10
11
@DanielStutzbach: Isso pode falhar se você tiver um arquivo chamado HEADno diretório de trabalho. Melhor uso git diff-index --quiet HEAD --.
David Ongaro
7
E ainda o manual em git status --helpestados: --porcelain Forneça a saída em um formato fácil de analisar para scripts. Isso é semelhante à saída curta, mas permanecerá estável nas versões do Git e independentemente da configuração do usuário. Veja abaixo para detalhes.
Ed Randall
7
@VonC que realmente não faz sentido. Dessa forma, você pode girar tudo no sentido inverso. --porcelain lhe dá a impressão de que vai quebrar em breve. Caso contrário, deve ser chamado de encanamento, não de porcelana. O uso de --porcelain faz com que seu script não se quebre, o que o torna NÃO um script de porcelana ;-). Se você queria que seu script fosse quebrado, não deveria usar --porcelain !!. Portanto, isso é completamente incompreensível e afasta todo mundo.
Xennex81
104

Embora as outras soluções sejam muito completas, se você quiser algo realmente rápido e sujo, tente algo como isto:

[[ -z $(git status -s) ]]

Apenas verifica se há alguma saída no resumo do status.

Nepthar
fonte
7
funciona para mim. use -n para o inverso (você tem alterações), por exemplo, `if [[-n $ (status do git -s)]]; então ... fi`
aaron
Isso funciona, mas você pode dizer o que a [[ ... ]]sintaxe está realmente fazendo? Eu nunca vi nada assim antes.
GMA
2
@EM o código de retorno git statusé realmente ignorado neste teste. Ele olha apenas para a saída. Confira esta página relacionada festa para mais informações sobre [, [[e como testar obras em bash.
Nepthar
2
Esta é a resposta quase certo, mas para o script é melhor uso --porcelaindo parâmetro como mostrado aqui
Mariusz Pawelski
2
Você pode querer usar git status -s -uallpara incluir arquivos não rastreados.
Barfuin
59

git diff --exit-coderetornará diferente de zero se houver alguma alteração; git diff --quieté o mesmo sem saída. Como você deseja verificar a árvore de trabalho e o índice, use

git diff --quiet && git diff --cached --quiet

Ou

git diff --quiet HEAD

Qualquer um deles dirá se há alterações não confirmadas, preparadas ou não.

Josh Lee
fonte
6
Aqueles não são equivalentes. O comando git diff --quite HEADúnico informa apenas se a árvore de trabalho está limpa, não se o índice está limpo. Por exemplo, se filefoi alterado entre HEAD ~ e HEAD, depois git reset HEAD~ -- fileserá finalizado 0, mesmo havendo alterações faseadas presentes no índice (wt == HEAD, mas índice! = HEAD).
Chris Johnsen
2
Aviso, isso não capturará arquivos removidos da área de preparação com o git rm, AFAICS.
RMN de
24
Novos arquivos (não rastreados) não são detectados por git diff --quiet && git diff --cached --quiet.
LegsDrivenCat
17

Expandindo a resposta de @ Nepthar:

if [[ -z $(git status -s) ]]
then
  echo "tree is clean"
else
  echo "tree is dirty, please commit changes before running this"
  exit
fi
Travis Reeder
fonte
1
Isso é bom; Eu uso ele para autocommit arquivos individuais por meio de testes $(git status -s "$file")e, em seguida, na elsecláusulagit add "$file"; git commit -m "your autocommit process" "$file"
toddkaufmann
Se você fizer isso git status -suma git status --porcelain ; git clean -ndvez, diretórios lixo será tona também aqui, que são invisíveis para git status.
ecmanaut
4

Como apontado em outra resposta, o simples comando é suficiente:

git diff-index --quiet HEAD --

Se você omitir os dois últimos traços, o comando falhará se você tiver um arquivo chamado HEAD.

Exemplo:

#!/bin/bash
set -e
echo -n "Checking if there are uncommited changes... "
trap 'echo -e "\033[0;31mFAILED\033[0m"' ERR
git diff-index --quiet HEAD --
trap - ERR
echo -e "\033[0;32mAll set!\033[0m"

# continue as planned...

Palavra de cautela: este comando ignora arquivos não rastreados.

sanmai
fonte
2
Como apontado nos comentários para essa resposta, isso não detecta arquivos adicionados recentemente
minexew 22/01
Não, ele detecta novos arquivos adicionados ao índice. Apenas verificado.
precisa saber é
Veja a pergunta. Arquivos não rastreados não são alterações . git adde git cleanpara o resgate
sanmai
4

Eu criei alguns aliases úteis do git para listar arquivos em estágios e em etapas:

git config --global alias.unstaged 'diff --name-only'
git config --global alias.staged 'diff --name-only --cached'

Então você pode facilmente fazer coisas como:

[[ -n "$(git unstaged)" ]] && echo unstaged files || echo NO unstaged files
[[ -n "$(git staged)" ]] && echo staged files || echo NO staged files

Você pode torná-lo mais legível criando um script em algum lugar PATHchamado git-has:

#!/bin/bash
[[ $(git "$@" | wc -c) -ne 0 ]]

Agora os exemplos acima podem ser simplificados para:

git has unstaged && echo unstaged files || echo NO unstaged files
git has staged && echo staged files || echo NO staged files

Para completar, aqui estão aliases semelhantes para arquivos não rastreados e ignorados:

git config --global alias.untracked 'ls-files --exclude-standard --others'
git config --global alias.ignored 'ls-files --exclude-standard --others --ignored'
stk
fonte
2

Com python e o pacote GitPython:

import git
git.Repo(path).is_dirty(untracked_files=True)

Retorna True se o repositório não estiver limpo

Pablo
fonte
Isso evitou alguns dos problemas de "registro de data e hora" mencionados em outros comentários
Jason
1
Observe que o GitPython também está usando a CLI do git. Se você definir LOGLEVEL=DEBUG, verá todos os comandos do Popen usando-o para executargit diff
Jason
-3

Aqui está a melhor e mais limpa maneira.

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
}
codyc4321
fonte
4
Não, isso não é o melhor. git statusé um comando de "porcelana". Não use comandos de porcelana nos scripts, pois eles podem mudar entre as versões do git. Em vez disso, use os comandos 'encanamento'.
Spuder
3
Eu acho que se você o atualizou para uso git status --porcelain(que é para esse fim - um formato estável que você pode analisar em um script), possivelmente também com -z (nulo-separado em vez de nova linha?), Você poderia fazer algo útil com essa ideia . @ codyc4321 consulte stackoverflow.com/questions/6976473/… para obter detalhes
msouth