Citando Linus Torvalds, quando perguntado quantos arquivos o Git pode manipular durante sua Tech Talk no Google em 2007 (43:09):
… O Git rastreia seu conteúdo. Ele nunca rastreia um único arquivo. Você não pode rastrear um arquivo no Git. O que você pode fazer é acompanhar um projeto que possui um único arquivo, mas se o seu projeto tiver um único arquivo, faça-o e você poderá fazê-lo, mas se acompanhar 10.000 arquivos, o Git nunca os verá como arquivos individuais. O Git pensa em tudo como conteúdo completo. Toda a história no Git é baseada na história de todo o projeto ...
(Transcrições aqui .)
No entanto, quando você mergulhar o livro Git , a primeira coisa que é dito é que um arquivo no Git pode ser tanto rastreado ou untracked . Além disso, parece-me que toda a experiência do Git é voltada para o versionamento de arquivos. Quando o uso git diff
ou a git status
saída são apresentados por arquivo. Ao usar, git add
você também escolhe por arquivo. Você pode até revisar o histórico com base em arquivos e é extremamente rápido.
Como essa declaração deve ser interpretada? Em termos de rastreamento de arquivos, como o Git é diferente de outros sistemas de controle de origem, como o CVS?
fonte
Respostas:
No CVS, o histórico foi rastreado por arquivo. Uma ramificação pode consistir em vários arquivos com suas próprias revisões, cada um com seu próprio número de versão. O CVS foi baseado no RCS ( Revision Control System ), que rastreia arquivos individuais de maneira semelhante.
Por outro lado, o Git tira instantâneos do estado de todo o projeto. Os arquivos não são rastreados e versionados independentemente; uma revisão no repositório refere-se a um estado de todo o projeto, não a um arquivo.
Quando o Git se refere ao rastreamento de um arquivo, significa simplesmente que ele deve ser incluído no histórico do projeto. A palestra de Linus não se referia ao rastreamento de arquivos no contexto do Git, mas estava contrastando o modelo CVS e RCS com o modelo baseado em instantâneo usado no Git.
fonte
$Id$
em um arquivo. O mesmo não funciona no git, porque o design é diferente.Eu concordo com brian m. resposta de carlson : Linus realmente faz distinção, pelo menos em parte, entre sistemas de controle de versão orientados a arquivos e comprometidos. Mas acho que há mais do que isso.
No meu livro , que está parado e pode nunca terminar, tentei criar uma taxonomia para os sistemas de controle de versão. Na minha taxonomia, o termo para o que estamos interessados aqui é a atomicidade do sistema de controle de versão. Veja o que está atualmente na página 22. Quando um VCS possui atomicidade no nível de arquivo, existe de fato um histórico para cada arquivo. O VCS deve lembrar o nome do arquivo e o que ocorreu em cada ponto.
Git não faz isso. O Git tem apenas um histórico de confirmações - a confirmação é sua unidade de atomicidade e a história é o conjunto de confirmações no repositório. O que uma confirmação lembra são os dados - uma árvore inteira cheia de nomes de arquivos e o conteúdo que acompanha cada um desses arquivos - além de alguns metadados: por exemplo, quem fez a confirmação, quando, por que e o ID de hash interno do Git do commit pai do commit. (É esse pai, e o gráfico de ciclismo direcionado formado pela leitura de todos os commits e seus pais, que é o histórico em um repositório.)
Observe que um VCS pode ser orientado a confirmação, mas ainda assim armazenar dados arquivo por arquivo. Esse é um detalhe de implementação, embora às vezes seja importante, e o Git também não faz isso. Em vez disso, cada confirmação registra uma árvore , com o objeto da árvore que codifica os nomes dos arquivos , modos (isto é, este arquivo é executável ou não?) E um ponteiro para o conteúdo real do arquivo . O conteúdo em si é armazenado independentemente, em um objeto de blob . Como um objeto de confirmação, um blob obtém um ID de hash exclusivo para seu conteúdo - mas, diferentemente de um commit, que pode aparecer apenas uma vez, o blob pode aparecer em muitos commits. Portanto, o conteúdo do arquivo subjacente no Git é armazenado diretamente como um blob e, indiretamente, em um objeto de árvore cujo hash ID é registrado (direta ou indiretamente) no objeto de confirmação.
Quando você pede ao Git para mostrar o histórico de um arquivo usando:
o que o Git está realmente fazendo é percorrer o histórico de consolidação , que é o único histórico que o Git possui, mas não mostra nenhum desses consertos, a menos que:
(mas algumas dessas condições podem ser modificadas por meio de
git log
opções adicionais , e é muito difícil descrever o efeito colateral chamado Simplificação do Histórico, que faz com que o Git omita alguns commits do histórico. O histórico do arquivo que você vê aqui não existe exatamente no repositório, em certo sentido: em vez disso, é apenas um subconjunto sintético do histórico real. Você obterá um "histórico de arquivos" diferente se usargit log
opções diferentes !fonte
A parte confusa está aqui:
O Git geralmente usa hashes de 160 bits no lugar de objetos em seu próprio repositório. Uma árvore de arquivos é basicamente uma lista de nomes e hashes associados ao conteúdo de cada um (mais alguns metadados).
Mas o hash de 160 bits identifica exclusivamente o conteúdo (dentro do universo do banco de dados git). Portanto, uma árvore com hashes como conteúdo inclui o conteúdo em seu estado.
Se você alterar o estado do conteúdo de um arquivo, seu hash será alterado. Mas se o hash for alterado, o hash associado ao conteúdo do nome do arquivo também será alterado. O que, por sua vez, altera o hash da "árvore de diretórios".
Quando um banco de dados git armazena uma árvore de diretórios, essa árvore de diretórios implica e inclui todo o conteúdo de todos os subdiretórios e todos os arquivos nele .
Ele é organizado em uma estrutura de árvore com ponteiros (imutáveis, reutilizáveis) para blobs ou outras árvores, mas logicamente é um instantâneo único de todo o conteúdo de toda a árvore. A representação no banco de dados git não é o conteúdo de dados simples, mas logicamente são todos os dados e nada mais.
Se você serializou a árvore em um sistema de arquivos, excluiu todas as pastas .git e disse ao git para adicionar a árvore novamente ao banco de dados, você acabaria adicionando nada ao banco de dados - o elemento já estaria lá.
Pode ajudar pensar nos hashes do git como um ponteiro de referência contado para dados imutáveis.
Se você criou um aplicativo em torno disso, um documento é um monte de páginas, com camadas, grupos e objetos.
Quando você deseja alterar um objeto, é necessário criar um grupo completamente novo para ele. Se você deseja alterar um grupo, é necessário criar uma nova camada, que precisa de uma nova página, que precisa de um novo documento.
Toda vez que você altera um único objeto, ele gera um novo documento. O documento antigo continua a existir. Os documentos novos e antigos compartilham a maior parte de seu conteúdo - eles têm as mesmas páginas (exceto 1). Essa página tem as mesmas camadas (exceto 1). Essa camada tem os mesmos grupos (exceto 1). Esse grupo tem os mesmos objetos (exceto 1).
E, do mesmo modo, quero dizer logicamente uma cópia, mas em termos de implementação é apenas mais um ponteiro de referência contado para o mesmo objeto imutável.
Um repositório Git é muito parecido com isso.
Isso significa que um dado conjunto de alterações git contém sua mensagem de confirmação (como um código hash), sua árvore de trabalho e suas alterações pai.
Essas alterações pai contêm as alterações pai, desde o início.
A parte do repositório git que contém a história é essa cadeia de mudanças. Essa cadeia de alterações a um nível acima da árvore "diretório" - de uma árvore "diretório", você não pode acessar exclusivamente um conjunto de alterações e a cadeia de alterações.
Para descobrir o que acontece com um arquivo, você começa com esse arquivo em um conjunto de alterações. Esse changeset tem um histórico. Freqüentemente nesse histórico, existe o mesmo arquivo nomeado, às vezes com o mesmo conteúdo. Se o conteúdo for o mesmo, não houve alteração no arquivo. Se for diferente, há uma mudança e é preciso trabalhar para descobrir exatamente o que.
Às vezes o arquivo se foi; mas a árvore "diretório" pode ter outro arquivo com o mesmo conteúdo (o mesmo código de hash), para que possamos acompanhá-lo dessa maneira (observe; é por isso que você deseja um commit para mover um arquivo separado de um commit para -editar). Ou o mesmo nome de arquivo e, após verificar o arquivo, é semelhante o suficiente.
Assim, o git pode juntar um "histórico de arquivos".
Mas esse histórico de arquivo vem da análise eficiente do "conjunto de alterações inteiro", não de um link de uma versão do arquivo para outra.
fonte
"git não rastreia arquivos" basicamente significa que as confirmações do git consistem em um instantâneo da árvore de arquivos que conecta um caminho na árvore a um "blob" e um gráfico de confirmação acompanhando o histórico das confirmações . Todo o resto é reconstruído on-the-fly por comandos como "git log" e "git blame". Essa reconstrução pode ser explicada por várias opções com que dificuldade deve parecer as alterações baseadas em arquivos. As heurísticas padrão podem determinar quando um blob muda de lugar na árvore de arquivos sem alterações ou quando um arquivo está associado a um blob diferente do que antes. Os mecanismos de compactação que o Git usa não se importam muito com os limites de blob / arquivo. Se o conteúdo já estiver em algum lugar, isso manterá o crescimento do repositório pequeno sem associar os vários blobs.
Agora esse é o repositório. O Git também possui uma árvore de trabalho e nessa árvore de trabalho há arquivos rastreados e não rastreados. Somente os arquivos rastreados são gravados no índice (área de armazenamento temporário? Cache?) E apenas o que é rastreado lá entra no repositório.
O índice é orientado a arquivos e existem alguns comandos orientados a arquivos para manipulá-lo. Mas o que acaba no repositório é apenas confirmado na forma de instantâneos da árvore de arquivos e os dados de blob associados e os ancestrais do commit.
Como o Git não rastreia históricos de arquivos e renomeia e sua eficiência não depende deles, às vezes você precisa tentar algumas vezes com opções diferentes até que o Git produza o histórico / diferenças / acusações de seu interesse para históricos não triviais.
Isso é diferente com sistemas como o Subversion, que registram e não reconstroem histórias. Se não estiver registrado, você não ouvirá sobre isso.
Na verdade, eu construí um instalador diferencial que comparou as árvores de lançamento, verificando-as no Git e produzindo um script duplicando seus efeitos. Como algumas vezes árvores inteiras foram movidas, isso produziu instaladores diferenciais muito menores do que substituir / excluir tudo o que teria produzido.
fonte
O Git não rastreia um arquivo diretamente, mas rastreia os instantâneos do repositório, e esses instantâneos consistem em arquivos.
Aqui está uma maneira de ver isso.
Em outros sistemas de controle de versão (SVN, Rational ClearCase), é possível clicar com o botão direito do mouse em um arquivo e obter seu histórico de alterações .
No Git, não há comando direto que faça isso. Veja esta pergunta . Você ficará surpreso com quantas respostas diferentes existem. Não existe uma resposta simples porque o Git não controla simplesmente um arquivo , não da maneira que o SVN ou o ClearCase o faz.
fonte
git log
ou algum programa construído sobre isso (ou algum apelido que faz a mesma coisa). Mas mesmo que houvesse muitas maneiras diferentes, como Joe diz, isso também é válido para mostrar o histórico das filiais. (tambémgit log -p <file>
é embutido e faz exatamente isso) #Rastrear "conteúdo", aliás, é o que levou a não rastrear diretórios vazios.
É por isso que, se você der o último arquivo de uma pasta, a própria pasta será excluída .
Esse nem sempre foi o caso, e apenas o Git 1.4 (maio de 2006) aplicou a política de "rastreamento de conteúdo" com o commit 443f833 :
Isso foi ecoado anos depois, em janeiro de 2011, com o commit 8fe533 , Git v1.7.4:
Enquanto isso, com o Git 1.4.3 (setembro de 2006), o Git começa a limitar o conteúdo não rastreado a pastas não vazias, com o commit 2074cb0 :
O rastreamento de conteúdo é o que permitiu ao git culpar, desde o início (Git 1.4.4, outubro de 2006, confirmar cee7f24 ) ter mais desempenho:
Isso (conteúdo de rastreamento) também é o que colocou o git add na API do Git, com o Git 1.5.0 (dezembro de 2006, confirmar 366bfcb )
Isso é o que tornou
git add --interactive
possível, com o mesmo Git 1.5.0 ( commit 5cde71d )É também por isso que, para remover recursivamente todo o conteúdo de um diretório, você precisa passar a
-r
opção, não apenas o nome do diretório como o<path>
(ainda Git 1.5.0, confirmar 9f95069 ).Ver o conteúdo do arquivo em vez do próprio arquivo é o que permite mesclar cenários como o descrito em commit 1de70db (Git v2.18.0-rc0, abr. 2018)
O commit 37b65ce , Git v2.21.0-rc0, dezembro de 2018, melhorou recentemente as resoluções de conflito em colisão.
E commit bbafc9c firther ilustra a importância de considerar o conteúdo do arquivo , melhorando a manipulação de conflitos de renomear / renomear (2to1):
fonte