
Concordo, é bom e útil quando o código-fonte do projeto é bonito e consistente. Isso facilita sua compreensão e apoio. Mostramos e mostramos como implementar a formatação do código fonte usando o formato
clang ,
git e
sh .
Problemas de formatação e como resolvê-los
A maioria dos projetos possui certas regras para o design do código. Como garantir que todos os participantes os executem? Programas especiais vêm em socorro - formato clang, estilo, falta de credibilidade - , mas eles têm suas desvantagens.
O principal problema dos formatadores é que eles alteram arquivos inteiros, não apenas as linhas alteradas. Contaremos como lidamos com isso, usando o ClangFormat como parte de um dos projetos para o desenvolvimento de firmware para eletrônicos, onde o C ++ era a linguagem principal. Várias pessoas trabalharam na equipe, por isso era importante fornecer um estilo de código uniforme. Nossa solução pode ser adequada não apenas para programadores em C ++, mas também para quem escreve código em C, Objective-C, JavaScript, Java, Protobuf.
Para formatação, usamos o clang-format-diff-6.0 . No começo, eles começaram a equipe
git diff -U0 - sem cor | clang-format-diff-6.0 -i -p1 , mas houve problemas com ele:
- O programa determinou os tipos de arquivo apenas por extensão. Por exemplo, arquivos com a extensão ts, que tínhamos no formato xml, percebidos como JavaScript e travavam ao formatar. Então, por algum motivo, ela tentou consertar os arquivos profissionais dos projetos Qt, provavelmente como o Protobuf.
- O programa teve que ser iniciado manualmente antes de adicionar arquivos ao índice git. Foi fácil esquecer isso.
Solução
O resultado foi o seguinte sh-script, executado como um
pré-commit - hook para git:
#!/bin/sh CLANG_FORMAT="clang-format-diff-6.0 -p1 -v -sort-includes -style=Chromium -iregex '.*\.(cxx|cpp|hpp|h)$' " GIT_DIFF="git diff -U0 --no-color " GIT_APPLY="git apply -v -p0 - " FORMATTER_DIFF=$(eval ${GIT_DIFF} --staged | eval ${CLANG_FORMAT}) echo "\n------Format code hook is called-------" if [ -z "${FORMATTER_DIFF}" ]; then echo "Nothing to be formatted" else echo "${FORMATTER_DIFF}" echo "${FORMATTER_DIFF}" | eval ${GIT_APPLY} --cached echo " ---Format of staged area completed. Begin format unstaged files---" eval ${GIT_DIFF} | eval ${CLANG_FORMAT} | eval ${GIT_APPLY} fi echo "------Format code hook is completed----\n" exit 0
O que o script faz:
GIT_DIFF = "git diff -U0 --no-color" - altera o código que será inserido no
clang-format-diff-6.0.- -U0 : geralmente o git diff exibe o chamado "contexto": algumas linhas de código inalteradas em torno daquelas que foram alteradas. Mas o clang-format-diff-6.0 também os formata! Portanto, o contexto neste caso não é necessário.
CLANG_FORMAT = "clang-format-diff-6.0 -p1 -v -sort-includes -style = Chromium -iregex '. * \. (Cxx | cpp | hpp | h) $'" - um comando para formatar diff recebido via padrão entrada.
- clang-format-diff-6.0 - um script do pacote clang-format-6.0 . Existem outras versões, mas todos os testes foram apenas nessa.
- -p1, tirado de exemplos na documentação , fornece compatibilidade com a saída git diff .
- -style = Chromium - predefinição de estilo de formato de código pronta. Outros valores possíveis: LLVM, Google, Mozilla, WebKit .
- -sort-includes - opção para classificar alfabeticamente #include diretivas (opcional).
- -iregex '. * \. (cxx | cpp | hpp | h) $' é uma expressão regular que filtra nomes de arquivos por extensão. Somente as extensões que precisam ser formatadas são listadas aqui. Isso salvará o programa de quedas e falhas inesperadas. Provavelmente, a lista precisará ser complementada em novos projetos. Além do C ++, você pode formatar C / Objective-C / JavaScript / Java / Protobuf . Embora não tenhamos testado esses tipos de arquivos.
GIT_APPLY = "git apply -v -p0 -" - aplica o patch emitido pelo comando anterior ao código.
- -p0 : por padrão, o git apply ignora o primeiro componente no caminho do arquivo, isso não é compatível com o formato produzido pelo clang-format-diff-6.0 . Este salto está desativado aqui.
FORMATTER_DIFF = $ (eval $ {GIT_DIFF} - encenado | eval $ {CLANG_FORMAT}) - as alterações do formatador para o índice.
eco "$ {FORMATTER_DIFF}" | eval $ {GIT_APPLY} --cached formata o código fonte no índice (após o
git add ). Infelizmente, não há gancho que funcione antes de adicionar arquivos ao índice. Portanto, a formatação é dividida em duas partes: formatar o que está no índice e separadamente o que não é adicionado ao índice.
avaliação $ {GIT_DIFF} | avaliação $ {CLANG_FORMAT} | eval $ {GIT_APPLY} - a formatação do código não está no índice (ela inicia apenas quando algo foi formatado no índice). Formata, em geral, todas as alterações atuais no projeto (sob controle de versão), e não apenas na etapa anterior. Esta é uma decisão controversa, à primeira vista. Mas acabou sendo conveniente, porque mais cedo ou mais tarde, outras alterações também precisam ser formatadas. Você pode substituir
"| eval $ {GIT_APPLY}" pela opção
-i , que forçará
$ {CLANG_FORMAT} a alterar os arquivos.
Demonstração de trabalho
- Instale o clang-format-6.0
- cd / tmp && mkdir temp_project && cd temp_project
- git init
- Adicione controle de versão e confirme qualquer arquivo C ++ com o nome wrong.cpp . De preferência> 50 linhas de código não formatado.
- Crie o script .git / hooks / pre-commit mostrado acima.
- Atribua o direito de executar o script (para git): chmod + x .git / hooks / pre-commit .
- Execute o script .git / hooks / pre-commit manualmente, ele deve ser executado com a mensagem "Nada a ser formatado" , sem erros de interpretador.
- Crie file.cpp com o conteúdo int main () {for (int i = 0; i <100; ++ i) {std :: cout << "Primeiro caso" << std :: endl; std :: cout << "Segundo caso" << std :: endl; std :: cout << "Terceiro caso" << std :: endl; }} com uma linha ou com outra formatação incorreta. No final da linha!
- git add file.cpp && git commit -m "file.cpp" devem ser mensagens de um script como "Patch file.cpp aplicado sem erros" .
- O git log -p -1 deve mostrar a adição de um arquivo formatado.
- Se file.cpp entrou no commit realmente formatado, você pode testar a formatação apenas no diff. Altere o par de linhas wrong.cpp para que o formatador responda a elas. Por exemplo, adicione recuo inadequado no seu código junto com outras alterações. git commit -a -m "Somente formato" deve preencher as alterações formatadas, mas não afeta outras partes do arquivo.
Desvantagens e problemas
O git diff --staged (que está aqui
$ {GIT_DIFF} --staged )
difere apenas os arquivos que foram adicionados ao índice. E o
clang-format-diff-6.0 acessa as versões completas dos arquivos fora dele. Portanto, se você alterar um arquivo, faça o
git add e, em seguida, altere o mesmo arquivo, o
clang-format-diff-6.0 gerará um patch para formatar o código (no índice) com base em um arquivo diferente. Portanto, é melhor não editar o arquivo após o
git add e antes de confirmar.
Aqui está um exemplo de tal erro:
- Adicione std :: endl extra a file.cpp , "Segundo caso" . (std :: cout << "Segundo caso" << std :: endl << std :: endl;) e algumas guias de recuo extra antes da linha.
- git add file.cpp
- Limpe a linha (no mesmo arquivo) com "Primeiro caso" para que apenas a quebra de linha permaneça em seu lugar (!).
- git commit -m "Erro do formatador ao confirmar" .
O script deve relatar
"erro: ao pesquisar:" , ou seja,
O git apply não encontrou o contexto do patch emitido pelo
clang-format-diff-6.0 . Se você não entender qual é o problema, simplesmente não altere os arquivos após o
git os adicionar e antes do
git commit . Se você precisar alterar, poderá confirmar (sem push) e, em seguida,
git commit --amend com novas alterações.
A limitação mais séria é a necessidade de interromper a linha no final de cada arquivo. Esse é um recurso antigo do git; portanto, a maioria dos editores de código suporta a inserção automática dessa tradução no final do arquivo. Sem isso, o script falhará ao confirmar um novo arquivo, mas não causará nenhum dano.
Muito raramente, o clang-format-diff-6.0 formata o código de maneira inadequada. Nesse caso, você pode adicionar alguns elementos inúteis ao código, como um ponto e vírgula. Ou, envolva o código problemático com comentários, / * clang-format desativado * / e / * clang-format ativado * / .
O clang-format-diff-6.0 também pode produzir um patch inadequado. Isso acaba com o git apply e não o aceita, e o código da parte de confirmação permanece não formatado. O motivo está dentro do clang-format-diff . Não há tempo para entender todos os erros do programa. Nesse caso, você pode observar o patch de formatação usando o comando git diff -U0 --no-color HEAD ^ | clang-format-diff-6.0 -p1 -v -sort-includes -style = Chromium -iregex '. * \. (cxx | cpp | hpp | h) $' . A solução mais fácil é adicionar a opção -i ao comando anterior. Nesse caso, o utilitário não emitirá um patch, mas formatará o código. Se isso não ajudar, tente a formatação de arquivos individuais, clang-format-6.0 -i -sort-includes -style = Chromium file.cpp . A seguir, git add file.cpp e git commit --amend .
Supõe-se que, quanto mais próxima sua configuração do formato .clang estiver de uma das predefinições, menos erros ocorrerão. (Aqui é substituído pela opção -style = Chromium ).
Depuração
Se você quiser ver quais alterações o script fará nas edições atuais (não no índice), use
git diff -U0 --no-color | clang-format-diff-6.0 -p1 -v -sort-includes -style = Chromium -iregex '. * \. (cxx | cpp | hpp | h) $' Você também pode verificar como o script funcionará nas confirmações mais recentes, por exemplo , às trinta:
git filter-branch -f --tree-filter "$ {PWD} /. git / hooks / pre-commit" --prune-empty HEAD ~ 30..HEAD . Este comando deve ter formatado as confirmações anteriores, mas na verdade apenas as alterações de identificação. Portanto, vale a pena realizar esses experimentos em uma cópia separada do projeto! Depois que ela se torna inutilizável.
Conclusão
Subjetivamente, essa decisão é muito mais boa do que prejudicial. Mas você precisa testar o comportamento do
clang-format-diff de diferentes versões no código do seu projeto, com uma configuração para o seu estilo de código.
Infelizmente, não fizemos o mesmo git-hook para Windows. Sugira nos comentários como fazê-lo lá. E se você precisar de um artigo para iniciar rapidamente o
formato clang , recomendamos que você analise a
descrição ClangFormat .