.gitignore Sintaxe: bin vs bin / vs. bin / * vs. bin / **

90

Qual é a diferença entre a adição bin, bin/, bin/*e bin/**no meu arquivo .gitignore? Tenho usado bin/, mas olhando outros arquivos .gitignore (no arquivo eclipse, a estrela dupla e a única são usadas juntas assim: o tmp/**/*que há com isso?), Vejo que os dois primeiros padrões também são amplamente usados. Alguém pode explicar as diferenças entre os três?

Chandsie
fonte
4
@unutbu: A resposta aceita para essa pergunta é aparentemente contestada. Um dos principais comentários afirma que a resposta é, na verdade, um mito completo.
chandsie
O comportamento é completamente especificado na página de manual e tenho certeza de que há uma pergunta / resposta por aqui (ou dez) que incluem todas essas informações.
Cascabel
3
Com relação a **: stackoverflow.com/questions/1470572/…
Cascabel

Respostas:

85

bincorresponde a qualquer arquivo ou diretório denominado 'bin'.

bin/corresponde a qualquer diretório denominado 'bin', o que na verdade significa todo o seu conteúdo, já que o Git não rastreia os diretórios sozinho.

bin/*corresponde a todos os arquivos e diretórios diretamente em qualquer bin/. Isso evita que o Git encontre automaticamente qualquer arquivo em seus subdiretórios, mas se, digamos, um bin/foosubdiretório for criado, esta regra não corresponderáfoo ao conteúdo de.

bin/** corresponde a todos os arquivos e diretórios em qualquer bin/ diretório e todos os seus subdiretórios.

A palavra "qualquer" é crítica aqui, uma vez que as regras não são relativas à raiz do repositório e se aplicam a qualquer lugar na árvore do sistema de arquivos. Você deve começar as regras com um /(ou!/ para não ignorar), o que significa a raiz do repositório, não a raiz do sistema, para corresponder apenas ao que foi pretendido.

AVISO: Você nunca deve usar regras como dir/*, /dir/**etc. sozinhas, a menos que também não ignore algo que existe dentro desse diretório . Omita o asterisco ou você pode perder permanentemente muitos dados de certas invocações de git gc,git stash e muito mais.

Eu realmente não sei o que devo tmp/**/*fazer. Inicialmente pensei que poderia ser usado para combinar arquivos nos subdiretórios do, tmp/mas não arquivos diretamente presentes nele tmp/. Mas um teste simples parece sugerir que isso ignora todos os arquivos em tmp/.

Siddhartha Reddy
fonte
10
só para esclarecer, qual é a diferença entre bin/e bin/**?
chandsie
1
Suspeito que bin/irá ignorar o diretório bin, bin/**mas incluirá o diretório bin, mas nenhum de seu conteúdo
Robin Winslow
1
Isso parece inconsistente com a resposta de Siddhartha. O desenho da resposta bin/irá ignorar o próprio diretório (incluindo todos os subdiretórios e arquivos), enquanto bin/**irá ignorar todos os arquivos no diretório bin e seus subdiretórios, mas não o próprio diretório bin. Se isso é preciso ou não, não tenho certeza.
Christopher Berman
3
note que, se você deseja rastrear todos os arquivos no diretório bin / mas ignorar todos os arquivos em seus subdiretórios, você pode fazer (nas linhas subsequentes) bin/** \n !bin/*(já que não consigo ver como forçar uma
quebra de
9
Essa resposta está errada de muitas maneiras. Primeiro, o git não controla os diretórios, então uma entrada .gitignore só pode corresponder ao conteúdo do diretório, nunca a um diretório como tal. Em segundo lugar, bincorresponde a um arquivo nomeado bin e ao conteúdo da binpasta. Terceiro, bin/* corresponde a quaisquer arquivos em seus subdiretórios. Vocês ao menos testaram isso?
ThomasR
46

bine bin/diferem apenas porque o último corresponderá apenas a um diretório.

bin/**/*é o mesmo que bin/**(aparentemente desde 1.8.2, de acordo com a resposta de @VonC).

O complicado, que acabei de passar uma hora ou mais arrancando meu cabelo, é isso bin/e bin/**não são exatamente iguais! Já que o anterior ignora o diretório como um todo, e o último ignora cada um dos arquivos dentro dele, e o git em quase todos os casos não se preocupa com diretórios, normalmente não há diferença. No entanto, se você tentar !cancelar a ignorar um subcaminho, descobrirá que o git (ahem) o ignora se você ignorou o diretório pai! (novamente, em vez do conteúdo do diretório)

Isso é mais claro por exemplo, portanto, para um repositório recém-iniciado, configure assim:

$ cat .gitignore
ignored-file
or-dir
dir-only/
!dir-only/cant-reinclude
dir-contents/**
!dir-contents/can-reinclude

$ mkdir or-dir dir-only dir-contents

$ touch file ignored-file or-dir/ignored-file dir-only/cant-reinclude dir-contents/can-reinclude

Existem os seguintes arquivos não rastreados:

$ git ls-files --other
.gitignore
dir-contents/can-reinclude
dir-only/cant-reinclude
file
ignored-file
or-dir/ignored-file

Mas você pode ver que os seguintes arquivos não são ignorados:

$ git ls-files --other --exclude-standard
.gitignore
dir-contents/can-reinclude
file

E se você tentar adicionar, obterá:

$ git add dir-only/cant-reinclude
The following paths are ignored by one of your .gitignore files:
dir-only/cant-reinclude
Use -f if you really want to add them.
fatal: no files added

Eu considero esse comportamento um bug. (Está tudo ligado git version 1.8.4.msysgit.0)

Simon Buchan
fonte
1
+1, de fato. Você deve considerar preencher um relatório de bug real sobre isso porque o comportamento parece inesperado.
Chandsie
1
Exatamente meu caso de uso. Obrigado!
Sebastian Graf
3
O comportamento diferente de dir/e dir/**re. des-ignorar !acontece porque "Não é possível incluir novamente um arquivo se um diretório pai desse arquivo for excluído" [fonte ]. Confuso, mas feito por motivos de desempenho. Veja uma pergunta relacionada ao SO .
tanius
23

Observe que, estritamente falando, git não rastreia diretórios, apenas arquivos. Portanto, não é possível adicionar um diretório, apenas seu conteúdo .

No .gitignoreentanto, no contexto de git finge entender os diretórios pela única razão de

Não é possível incluir novamente um arquivo se um diretório pai desse arquivo for excluído.
https://git-scm.com/docs/gitignore#_pattern_format

O que isso significa para os padrões de exclusão? Vamos examiná-los em detalhes:

bin

Isso ignora

  • arquivos nomeados bin .
  • o conteúdo das pastas chamadas bin

Você pode colocar na lista de permissões binarquivos e pastas ignorados adicionando !entradas subsequentes , mas você não pode colocar na lista de permissões o conteúdo de pastas nomeadasbin

bin

!bin/file_in_bin # has no effect, since bin/ is blacklisted!
!bin/* # has no effect, since bin/ is blacklisted!
!file_in_bin # has no effect, since bin/ is blacklisted!

!bin # this works

bin/

O mesmo que acima, exceto que não corresponde a arquivos nomeados bin. Adicionar um final /diz ao git para corresponder apenas aos diretórios.

bin/*

Isso ignora

  • arquivos contidos em uma pasta chamadabin
  • conteúdo de subpastas diretas de pastas nomeadas bin
bin/*  # blacklists bin/file_in_bin and bin/subfolder/

!bin/subfolder/file_in_sub # has no effect, since bin/subfolder is blacklisted!
!bin # whitelists files named bin/bin, since bin/ itself is not blacklisted
!bin/ # has no effect, since bin/ itself is not blacklisted


!bin/file_in_bin # works since bin/ itself is not blacklisted
!file_in_bin # works too
!bin/subfolder # works (so implicitly whitelists bin/subfolder/file_in_sub)
!bin/subfolder/ # works just as well
!bin/* # works for file_in_bin and subfolder/

bin/**

Isso ignora

  • conteúdo de bin
  • conteúdo de subpastas (qualquer nível de aninhamento) dentro bin
bin/**  # blacklists bin/file_in_bin and
        # bin/subfolder/ and bin/subfolder/file_in_sub and
        # bin/subfolder/2/ and bin/subfolder/2/file_in_sub_2

!bin/subfolder/file_in_sub # has no effect, since bin/subfolder is blacklisted
!bin/subfolder/2/ # has no effect, since bin/subfolder is blacklisted
!bin/subfolder/2/file_in_sub_2 # has no effect, since bin/subfolder is blacklisted

!bin/subfolder # works only in combinations with other whitelist entries,
               # since all contents of subfolder are blacklisted (1)

!bin/file_in_bin # works since bin itself is not blacklisted
!bin/* # works for file_in_bin and subfolder; see (1)
ThomasR
fonte
9

Acabei de fazer um novo repo e tentei algumas coisas. Aqui estão meus resultados:

NOVOS RESULTADOS

git versão 2.10.1.windows.1

  1. Inicialize o repositório quase vazio. Apenas o arquivo README
  2. Preencher o bindiretório com várias camadas de profundidade
    • bin.txt
    • Test.txt
    • bin/a/b/bin.txt
    • bin/a/b/Test.txt
    • bin/a/bin/bin.txt
    • bin/a/bin/Test.txt
    • bin/a/bin.txt
    • bin/a/Test.txt
    • bin/bin.txt
    • bin/Test.txt
  3. Adicionar binao gitignore: Resultados
    • Tudo abaixo do bindiretório (e mais profundo) agora é ignorado
    • O nível raiz não é ignorado (/bin.txt e /Test.txt ainda são exibidos)
  4. Editar binpara bin/no gitignore: Resultados
    • Sem mudança
  5. Editar bin/parabin/*
    • Sem mudança
  6. Editar bin/*parabin/**
    • Sem mudança
  7. Editar bin/**parabin/**/
    • bin/bin.txte bin/Test.txtnão são mais ignorados
  8. Editar bin/**/parabin/**/*
    • bin/bin.txte bin/Test.txtvoltaram a ser ignorados

RESULTADOS ANTIGOS

versão git: 2.7.0.windows.1

  1. Inicialize o repositório quase vazio. Apenas o arquivo README
  2. Preencher o bindiretório com várias camadas de profundidade
    • bin/a/b/Test.txt
    • bin/a/bin/Test.txt
    • bin/a/Test.txt
    • bin/Test.txt
  3. Adicionar binao gitignore: Resultados
    • Tudo abaixo do bindiretório (e mais profundo) agora é ignorado
  4. Editar binparabin/no gitignore: Resultados
    • Tudo abaixo do bindiretório (e mais profundo) ainda é ignorado (sem alteração)
  5. Editar bin/parabin/*
    • Tudo abaixo do bindiretório (e mais profundo) ainda é ignorado (sem alteração)
  6. Editar bin/*parabin/**
    • Tudo abaixo do bindiretório (e mais profundo) ainda é ignorado (sem alteração)
  7. Editar bin/**parabin/**/
    • bin/Test.txt não é mais ignorado
  8. Editar bin/**/parabin/**/*
    • Tudo abaixo do bindiretório (e mais profundo) é ignorado novamente
Joe Phillips
fonte
1
Este é um bom teste, acho que renomear text.txt para bin.txt mostraria melhor algumas diferenças importantes. Se você fizesse isso, eu votaria a favor desta resposta.
Matt Johnson
@MattJohnson True ... infelizmente, estou em uma versão mais recente do git no momento
Joe Phillips
@MattJohnson Eu finalmente resolvi isso
Joe Phillips
8

Observe que o ' **', quando combinado com um subdiretório ( **/bar), deve ter mudado de seu comportamento padrão, uma vez que a nota de lançamento para git1.8.2 agora menciona:

Os padrões em arquivos .gitignoree .gitattributespodem ter **/, como um padrão que corresponde a 0 ou mais níveis de subdiretório.

Por exemplo, " foo/**/bar" corresponde a " bar" no foopróprio " " ou em um subdiretório de " foo".


A regra a lembrar (e que ajuda a entender a diferença de intenção por trás dessa sintaxe) é:

Não é possível incluir novamente um arquivo se um diretório pai desse arquivo for excluído.


Normalmente, se você deseja excluir arquivos de uma subpasta de uma pasta ignorar f, você faria:

f/**
!f/**/
!f/a/sub/folder/someFile.txt

Isso é:

  • Se a primeira regra fosse f/, a pasta f/seria ignorada e as regras abaixo relacionadas fnão importariam.
  • f/**obter o mesmo que f/, mas ignorar todos os subelementos (arquivos e subpastas).
    Isso dá-lhe a oportunidade de whitelist (excluir da gitignore) as subpastas: !f/**/.
  • Uma vez que todas as fsubpastas não são ignoradas, você pode adicionar uma regra para excluir um arquivo ( !f/a/sub/folder/someFile.txt)
VonC
fonte
Como isso responde à pergunta?
ThomasR
0

Há outra diferença entre bin/*e bin/.

bin/corresponde foo/bin/test.txt(como esperado), mas bin/*não, o que parece estranho, mas está documentado: https://git-scm.com/docs/gitignore

"Documentation / *. Html" corresponde a "Documentation / git.html", mas não a "Documentation / ppc / ppc.html" ou "tools / perf / Documentation / perf.html".

A razão para isso parece ser estas regras:

  • Se o padrão terminar com uma barra, ele será removido para o propósito da seguinte descrição ...

  • Se o padrão não contém uma barra /, o Git o trata como um padrão shell glob e verifica se há uma correspondência com o nome do caminho relativo à localização do arquivo .gitignore ...

  • Caso contrário, o Git trata o padrão como um shell glob adequado para consumo por fnmatch (3) com a sinalização FNM_PATHNAME…

Portanto, se o padrão terminar com uma barra, a barra será removida e tratada como um padrão shell glob, caso em que bincorresponde foo/bin/test.txt. Se terminar com /*, a barra não é removida e é passada para fnmatch, que não corresponde aos subdiretórios.

No entanto, o mesmo não é verdadeiro para foo/bin/e foo/bin/*, porque mesmo depois de remover a barra final de foo/bin/, ele ainda contém uma barra, então é tratado como um padrão fnmatch, não um glob. Ou seja, não vai combinarbar/foo/bin/test.txt

user1431317
fonte