O essencial de GIT

As limitações das ferramentas anteriores deram lugar a uma abordagem distribuída, onde o Git se destaca, proporcionando eficiência, rastreabilidade e uma colaboração harmoniosa. O post 'O essencial de GIT' explorou a compreensão dos comandos essenciais, conceitos como delta e hash, juntamente com a flexibilidade oferecida pelos branches, e como isso fortalece os desenvolvedores na construção e evolução de projetos. É verdade que não existe bala de prata, mas até o momento ninguém, na minha visão, superou o GIT.

Antigamente, em ambientes mais simples, ferramentas como SVN e SourceSafe dominavam, mas suas limitações eram evidentes. Surgia, então, a história do Git, um divisor de águas no universo do controle de versão. Assim, vamos ver um pouco do que chamei de O essencial de GIT.

Então, desde suas origens no Linux, passando pelo uso de tarballs e patches até o problema com o BitKeeper, a busca por um sistema distribuído e confiável levou Linus Torvalds a criar o GIT. Desse modo o artigo explora a evolução dele, seus conceitos fundamentais, como delta, commit e hash, até sua estrutura interna baseada no Merkle DAG.

História do GIT

Antigamente, não havia um controle efetivo das versões de códigos, muitas vezes desenvolvidos individualmente e orientados ao banco dados. Nessa época existiam ferramentas como SVN (Subversion) e SourceSafe (sendo esse último sofrível!)

No início o Linux utilizava tarballs e patches antes mesmo de existir o Git. Embora tenham experimentado o BitKeeper, essa opção teve que ser descartada por conta de mudanças no modelo de licenciamento, deixando o Linux sem um sistema de controle de versão. Linus Torvalds buscava um sistema verdadeiramente distribuído, pois as soluções existentes na época corriam o risco de corromper o código: algo inaceitável.

O Git foi criado para resolver vários problemas. Imagine ter que lembrar um código escrito há duas semanas, ou o risco de excluí-lo acidentalmente. E se quiser comparar o código de uma versão de produção com o desenvolvimento que evoluiu por meses? Em ambientes colaborativos, como garantir que um membro da equipe não comprometa o trabalho do outro? Esses desafios foram fundamentais para justificar a necessidade do Git.

Além disso, as ferramentas de controle de versão da época mantinham cópias integrais do código para suas versões, o que era problemático, especialmente considerando as restrições de espaço em disco e processamento da época. O Git revolucionou ao adotar uma abordagem mais eficiente, otimizando o armazenamento e proporcionando uma solução distribuída para o controle de versão. Isso é o O essencial de GIT.

Delta: Diff e Patch

O GIT funciona com base em alguns conceitos importantes. Vou começar falando de 2 comandos comuns do linux que são o ponto de partida. Para contextualizar, imagine que programadores remotamente estejam escrevendo um código, mas sem nenhum sistema de controle de versão. Cada um tem em sua casa uma cópia de todo o programa que estão fazendo. Quando um dos devs altera o arquivo header.cpp e envia para o outro por e-mail, haverá de se substituir o documento. O ponto é: o que houve de diferente entre a versão anterior e a atual?

O comando diff do Linux compara dois documentos texto e gera um formato padronizado como resultado. Veja a seguir o arquivo header.cpp de um dos desenvolvedores.

#include <iostream>

void printMessage() {
    std::cout << "Arquivo header.cpp com conteúdo aleatório." << std::endl;
}

int main() {
    printMessage();
    return 0;
}

Veja agora a versão do outro desenvolvedor.

#include <iostream>

void printMessage() {
    std::cout << "Hello World" << std::endl;
}

int main() {
    printMessage();
    return 0;
}

Agora, veja o arquivo gerado através do comando diff -u header1.cpp header2.cpp > header.patch

--- header1.cpp 2024-01-25 22:39:25.082396000 -0300
+++ header2.cpp 2024-01-25 22:39:36.522396000 -0300
@@ -1,7 +1,7 @@
 #include <iostream>

 void printMessage() {
-    std::cout << "Arquivo header.cpp com conteúdo aleatório." << std::endl;
+    std::cout << "Hello World" << std::endl;
 }

 int main() {

Note que o arquivo possui o nome dos arquivos comparados e a hora dos arquivos. Logo após ele tem dois arrobas indicando diferenças entre os arquivos. -1,7 significa que uma linha foi removida, a linha 7. E depois +1,7 significa que uma linha também foi adicionada nesse mesmo ponto. O exemplo é simples mas funciona perfeitamente com estruturas muito maiores.

Há por outro lado o comando patch que lê esse arquivo diferente e enxerta no arquivo completo do código. Essa solução é especialmente boa porque ao alterar um arquivo muito grande, com milhares de linhas, apenas esse pequeno trecho é trazido no patch. Do ponto de vista do GIT a diferença entre os dois arquivos é um delta.

Commit

Para o GIT, um commit representa uma instantânea do estado de um repositório em um determinado momento. Ao realizar um commit, o desenvolvedor registra as alterações feitas em seus arquivos, adicionando uma mensagem explicativa. Por exemplo, após implementar uma nova funcionalidade em um projeto web, o desenvolvedor pode realizar um commit com a mensagem “Adiciona recurso de login com autenticação de dois fatores”. Este commit não apenas documenta as mudanças efetuadas, mas também cria um ponto específico no histórico do projeto, facilitando o acompanhamento da evolução do código.

git add header.cpp
git commit -m "Adicionando a mensagem de hello no header.cpp"
Imagem simples mostrando um commit do git exemplificando o hashing / hash

O desenvolvedor pode realizar vários commits, com vários arquivos em cada um deles, armazenando todas as diferenças dos códigos do servidor do git. Isso é especialmente útil para comparar alterações com versões antigas, para reverter alterações quando necessário e etc.

Hash

O hash é outro item fundamental para o GIT. Todos os patches, ou commits, são submetidos a um processo de hashing, gerando uma identificação única. Isso é fundamental para saber se houve violação na estrutura do código. Era muito comum em outros sistemas de controle de versão momentos em que a ferramenta simplesmente corrompia parte do código. Isso era realmente muito desagradável.

commit abcdef0123456789abcdef0123456789abcdef01 (HEAD -> master)
Author: Thiago Anselme <[email protected]>
Date:   Tue Jan 25 14:30:00 2024 -0300

    Commit 3 de O essencial de GIT

commit 0123456789abcdef0123456789abcdef01234567
Author: Thiago Anselme <[email protected]>
Date:   Tue Jan 25 14:15:00 2024 -0300

    Commit 2 de O essencial de GIT

commit 9876543210abcdef9876543210abcdef98765432
Author: Thiago Anselme <[email protected]>
Date:   Tue Jan 25 14:00:00 2024 -0300

    Commit 1 de O essencial de GIT

Veja acima o comando git log, que mostra informações sobre 3 commits. Note que cada um deles possui uma linha com o seu hash que utiliza SHA-1.

Merge 3-way

Ao realizar um merge em outros versionadores, geralmente apenas as versões 1 e 2 são consideradas. No entanto, o processo de merge no GIT adota uma abordagem que também incorpora o commit anterior. Essa técnica, conhecida como merge 3-way, é simplificada no GIT devido à utilização eficiente de hashes. Quando versionadores utilizavam apenas 2 versões eles exigiam intervenção do desenvolvedor para escolher a melhor versão em alguns casos, já o GIT como tem uma terceira, esse processo é transparente em quase todos os casos. Esses são detalhes não tão conhecidos de o essencial de GIT.

Branches

Branches, ou ramos (ou troncos), são um dos maiores trunfos do GIT. Imagine que você desenvolveu um sistema que está no ar há 4 meses, sem qualquer problema. Nesse meio tempo você está melhorando-o incluindo novas funcionalidades. Se nesse instante houver uma falha na versão em produção que exija manutenção, você terá a versão em produção em algum commit anterior. Mas voltar até ela te fará perder seu código atual.

Exibição de commits e branches os dividindo

Pois bem, branches resolvem esse problema. Eles são linhas de tempo alternativas do seu código. Ele também pode ser muito útil em outras situações. Imagine que um mesmo código está sendo alterado por 5 desenvolvedores ao mesmo tempo. Uma prática comum é que cada um desenvolva em uma branch específica. Apenas após pronto que ele faz merge com uma linha de tempo de referência (chamada de master ou main).

Merkle Dag

A estrutura interna do GIT é fundamentada em uma estrutura de dados chamada Merkle Directed Acyclic Graph (DAG), ou simplesmente Merkle DAG. Essa estrutura é simplesmente um grafo que nunca retorna, ou seja, ele sempre vai para frente podendo se bifurcar em multiplas branches.

Note que há uma cadeia de blocos com hashes SHA1 que representa o commit. Todos esses blocos são relacionados com os anteriores de modo que se uma alteração ocorrer em um commit anterior o seu hash será alterado impactando todos os demais commits.

Stages

Os stages, ou estágios, são os diferentes locais onde o seu código está, na estrutura do GIT. Por exemplo, há uma área onde o desenvolvedor está escrevendo o código, há outra onde ele guarda os commits e outra que é o local central onde o código fica armazenado de modo coletivo.

  1. Working Directory (Diretório de Trabalho): É onde você faz as modificações nos arquivos do seu projeto.
  2. Staging Area (Área de Staging): As alterações selecionadas no Diretório de Trabalho são adicionadas à Área de Staging usando o comando git add. Isso permite uma preparação seletiva das mudanças que serão incluídas no próximo commit.
  3. Repository (Repositório): As alterações na Área de Staging são finalmente confirmadas como parte de um commit e armazenadas no repositório GIT

O essencial de GIT: Cliente servidor x distribuido

No passado o desenvolvimento era essencialmente centralizado, mas o GIT veio como uma solução distribuída extremamente elegante. Toda vez que se faz commits eles ficam armazenados localmente na staging area. Após executar o comando git push esses códigos são movidos para o repositório do código, que via de regra fica disponível remotamente.

Mas é legal entender que o GIT pode facilmente ter seu repositório remoto alterado. O GIT não foi feito para ficar amarrado a uma estrutura específica, portanto é possível ter um repositório no próprio computador, ter clones de projetos em pendrives (alguém ainda uso isso?) ou coisas do tipo.

Github não é git

Algumas pessoas não sabem mas o Github é uma ferramente que provê um serviço de GIT mas com funcionalidades que vão além da ferramenta em si, como por exemplo um Pull Request. Essa funcionalidade não existe no GIT, mas no github sim. Além disso também vejo o github também como uma rede social de códigos fonte.

Comandos

Esses são os comandos mais fundamentais de GIT que é obrigatório conhecer, mesmo para um iniciante.

  1. git init: Inicia um novo repositório Git em um diretório, estabelecendo a estrutura necessária para o controle de versão.
  2. git clone [URL]: Clona um repositório Git existente para o seu ambiente local, permitindo colaboração e trabalho em equipe.
  3. git add [arquivo]: Adiciona alterações específicas ao Index (ou Área de Staging), preparando-as para o próximo commit.
  4. git commit -m "mensagem": Registra as alterações adicionadas ao Index como um novo commit no histórico do projeto, com uma mensagem descritiva.
  5. git push: Envia os commits locais para um repositório remoto, mantendo-o atualizado com as últimas alterações.

Conclusão de ‘O essencial de GIT’

As limitações das ferramentas anteriores deram lugar a uma abordagem distribuída, onde o Git se destaca, proporcionando eficiência, rastreabilidade e uma colaboração harmoniosa. O post ‘O essencial de GIT’ explorou a compreensão dos comandos essenciais, conceitos como delta e hash, juntamente com a flexibilidade oferecida pelos branches, e como isso fortalece os desenvolvedores na construção e evolução de projetos. É verdade que não existe bala de prata, mas até o momento ninguém, na minha visão, superou o GIT.


Thiago Anselme
Thiago Anselme - Gerente de TI - Arquiteto de Soluções

Ele atua/atuou como Dev Full Stack C# .NET / Angular / Kubernetes e afins. Ele possui certificações Microsoft MCTS (6x), MCPD em Web, ITIL v3 e CKAD (Kubernetes) . Thiago é apaixonado por tecnologia, entusiasta de TI desde a infância bem como amante de aprendizado contínuo.