Git Básico
Para iniciantes no mundo Git
O Git é o mais famoso versionador de código fonte (e também de outros tipos de dados) atualmente no mercado. Trata-se de um software robusto e indispensável para todos os desenvolvedores na atualidade, principalmente àqueles que trabalham em equipe.
O Git “foi criado em 2005 por Linus Torvalds, o mesmo criador do Linux, que estava descontente com o BitKeeper, o sistema de controle de versão utilizado no desenvolvimento do kernel do Linux.” (AQUILES & FERREIRA).
Neste artigo, você conhecerá os truques desta incrível ferramenta e como utilizá-la “like a boss”.
1. Os termos do mundo Git
Para entender o funcionamento de todo o processo, é necessário conhecer os termos que envolvem a manipulação do código fonte gerido pelo Git. A seguir, tentarei explicar, de forma bem concisa, os principais termos usados:
- Repositórios: são uma espécie de “recipientes” onde todo o código-fonte é armazenado. Diferente de um diretório comum, um repositório é capaz de armazenar toda a história de cada arquivo. Dessa forma, é possível obter uma modificação efetuada há um ano atrás e descobrir tudo o que já foi feito em um arquivo.
- Repositório Remoto: onde os arquivos permanecem versionados. O repositório remoto pode ser acessado por outros desenvolvedores que, por ventura, tenham permissão para isso;
- Repositório Local: é um clone do repositório remoto, onde o desenvolvedor pode fazer suas alterações no código fonte, localmente, em seu computador;
- Arquivos: os arquivos podem possuir dois estados diferentes. Quando um novo arquivo é criado, ele possui o estado de “não rastreado”. Para que um arquivo possa ser versionado, ele deve passar para o estado de “rastreado” (Staged). Apenas arquivos rastreados podem ser adicionados como parte do repositório;
- Commit: é o processo que acrescenta um arquivo com estado de “rastreado” no repositório local. Cada grupo de alterações deve ser “comitado” (juntamente com um texto descritivo) para que as modificações efetuadas possam ser identificadas no histórico do repositório. Durante o desenvolvimento, o programador efetua vários “commits”, tudo localmente, em seu repositório local;
- Push: após concluir o seu trabalho, o desenvolvedor deve submeter todos os commits efetuados em seu repositório local para que seja adicionados no repositório remoto. Esse processo se chama “push”;
- Pull: é o processo inverso do “push”. Ao efetuar um “pull”, o código que existe no repositório remoto é sincronizado com o repositório local, garantindo que o desenvolvedor esteja usando o código fonte atualizado;
- Branchs: muitas vezes, não se deseja trabalhar diretamente no código fonte definitivo. Modificações diretas poderiam quebrar a execução da aplicação e inutilizá-la. Para permitir que o desenvolvedor faça alterações com segurança, sem afetar o código atualmente em uso, o git permite a criação de “branchs”. Tratam-se de ramificações do projeto real, que, a partir do ponto onde são criadas, possuem código fonte diferente da ramificação principal (geralmente chamada de “branch master”);
- Merge: após fazer todas as alterações desejadas em um branch, é preciso realizar o “merge” deste branch trabalhado com o branch principal. O “merge” irá mesclar os códigos existentes, juntando tudo no branch de destino;
- Conflitos: durante o processo de mesclagem, poderão ocorrer conflitos (quando duas pessoas alteram o mesmo trecho de código). O Git irá notificar o usuário, adicionando anotações no código para apontar onde os conflitos ocorreram e nortear o desenvolvedor para efetuar a devida correção;
- Tags: são marcações que podem ser efetuadas em um determinado “commit” na ramificação principal. As tags geralmente são usadas para definir onde ocorream as mudanças de versões (releases) em uma aplicação.
2. Instalando o Git
No Windows
Faça o download e instale de http://msysgit.github.io/. Após a instalação, acesse o Git Bash e será aberto um terminal, com o prompt de comando.
No Mac
Faça o download e instale de https://code.google.com/p/git-osx-installer/downloads. Após a instalação, abra um terminal e o git estará disponível.
No Linux
Para instalar o Git no Ubuntu, ou em uma outra distribuição baseada no Debian, execute no terminal:
$ sudo apt-get install git
Para as demais distribuições do Linux, veja o comando em http://git-scm.com/download/linux
3. Configurando o usuário desenvolvedor
3.1. O usuário commiter
Para que seja possível enviar alterações (commits) para um repositório, é preciso configurar o usuário desenvolvedor.
As informações deste usuário aparecerão no histórico de envios e servem para identificar quem foi o autor de determinada funcionalidade ou correção submetida ao repositório.
Isso pode ser feito de duas maneiras, como será explicado a seguir.
3.2. Configuração global
Configurando dessa forma, as informações do desenvolvedor serão usadas por padrão para todos os repositórios criados com Git no computador pessoal:
$ git config --global user.name "Nome do Desenvolvedor"
$ git config --global user.email usuario.padrao@gmail.com
3.3. Configuração local
Existem casos onde o desenvolvedor deseja enviar suas alterações ao repositório de um projeto com uma outra identificação. Isso é comum quando um mesmo computador é usado para trabalho e para projetos pessoais.
O Git permite que, além de configurar um desenvolvedor globalmente, seja possível configurar o desenvolvedor para cada projeto.
Estando no diretório do projeto (que já deve ser um repositório do Git), basta executar os seguintes comandos:
$ git config user.name "Nome do Usuário"
$ git config user.email usuario.local@gmail.com
Estas informações são gravadas no arquivo /caminho/ate/meu/repositorio/.git/config, com o seguinte formato:
[user]
email = usuario@gmail.com
name = Nome do Usuário
4. Gerindo repositórios
4.1. Criando um repositório local
Para transformar um diretório qualquer em um repositório local do Git, basta executar o comando git init:
$ cd /home/ricardo/projeto
$ git init
Initialized empty Git repository in /home/ricardo/projeto/.git/
Pronto, o projeto já é um repositório local vazio. Observe que foi criada uma pasta oculta com o nome .git. Nela estão todos os arquivos necessários para o repositório local funcionar.
$ cd /home/ricardo/projeto
$ ls -la
total 11
drwxr-xr-x 3 ricardo ricardo 4096 jan 21 13:29 .
drwxr-xr-x 32 ricardo ricardo 4096 jan 21 13:50 ..
drwxr-xr-x 7 ricardo ricardo 4096 jan 21 13:29 .git
4.2. Vinculando um repositório local com um repositório remoto
Após criar novos arquivos e codificar alguma coisa, será preciso enviar este novo conteúdo para um repositório remoto. No entanto, se você criou seu novo repositório como explicado no item 4.1, ele ainda não possui um repositório remoto configurado.
Para que seja possível enviar seu código-fonte para o repositório remoto, é preciso que o Git saiba onde ele está. Isso é feito com o comando git remote:
$ git remote add origin https://github.com/fulano/meu-repo.git
Ao executar o comando acima, o repositório local estará vinculado ao repositório remoto, que chamamos aqui de de “origin” (poderia ser qualquer nome). Esse nome é importante, pois será usado para identificar o repositório nas operações com arquivos.
Por questões de organização, será explicado como enviar e receber arquivos do repositório remoto no item 5 deste artigo.
4.3. Clonando um repositório remoto
No cenário explicado no item 4.1, criou-se um novo repositório localmente e depois vinculou-se com um repositório remoto. Isso é útil, especificamente, para quando ambos os repositórios (local e remoto) são novos, mas quando um repositório remoto já contém código fonte, é mais interessante utilizar o comando git clone:
$ cd /home/ricardo/projeto
$ git clone https://github.com/fulano/meu-repo.git .
Cloning into 'projeto'...
remote: Enumerating objects: 9, done.
remote: Counting objects: 100% (9/9), done.
remote: Compressing objects: 100% (8/8), done.
remote: Total 9 (delta 0), reused 5 (delta 0), pack-reused 0
Unpacking objects: 100% (9/9), 61.81 KiB | 393.00 KiB/s, done.
Durante a execução, o git clone irá fazer o download do repositório remoto (https://github.com/fulano/meu-repo.git), gerando uma cópia local. Além disso, ele criará automaticamente um vínculo chamado “origin”, ligando os dois repositórios.
Em outras palavras, o git clone é mais prático na maioria dos casos.
4.4. Gerindo os vínculos com repositórios remotos
É possível vincular um mesmo repositório local com vários repositórios remotos, a fim de manipular arquivos entre eles.
Para exibir os repositórios remotos atualmente vinculados, faça:
$ git remote
$ git remote -v
origin https://github.com/fulano/meu-repo.git (fetch)
origin https://github.com/fulano/meu-repo.git (push)
Com base no item 4.3, existe apenas um vínculo chamado “origin” que aponta para o URL “https://github.com/fulano/meu-repo.git”.
Para ver informações sobre um repositório remoto, usa-se o comando git remote show seguido do nome do vínculo:
$ git remote show origin
* remote origin
Fetch URL: https://github.com/fulano/meu-repo.git
Push URL: https://github.com/fulano/meu-repo.git
HEAD branch: master
Remote branch:
master tracked
Local branch configured for 'git pull':
master merges with remote master
Local ref configured for 'git push':
master pushes to master (up to date)
Para renomear um repositório remoto vinculado:
$ git remote rename origin nome-legal
E para desvincular um repositório remoto:
$ git remote rm nome-legal
4.5. Removendo um repositório local
Para remover um repositório local, basta apagar o diretório /home/ricardo/projeto/.git e o projeto deixará de ser um repositório para tornar-se um diretório comum:
$ cd /home/ricardo/projeto
$ rm -Rf .git$ git status
fatal: not a git repository (or any of the parent directories): .git
4.6. Listando conteúdo de um repositório
Para listar os arquivos que se encontram no repositório usa-se git ls-files
Apenas os arquivos que estejam em estado de Staged, Committed ou Pushed serão listados:
$ git ls-files
Obs: Será abordado mais sobre os estados no decorrer do artigo.
4.5. Verificando o status do repositório
É muito útil verificar como está o repositório local antes de submeter alterações para o repositório remoto. Isso inclui saber quais são os arquivos novos, os que foram modificados e os que foram excluídos. Pode-se fazer isso usando o comando git status:
$ git status
No ramo master
No commits yet
nada para enviar (crie/copie arquivos e use "git add" para registrar)
5. Gerindo arquivos
Os arquivos no Git possuem quatro estados, por onde passam desde a sua criação até serem enviados ao repositório remoto:
- Unstaged: arquivo novo foi salvo dentro do diretório, mas ainda não é rastreado pelo Git;
- Staged: arquivo está sendo rastreado pelo Git localmente mas ainda não está vinculado ao repositório local;
- Committed: arquivo está vinculado apenas ao repositório local;
- Pushed: arquivo está vinculado também com o repositório remoto.
5.1. Rastreando um arquivo (Staged)
Para mudar o estado de um arquivo Unstaged (não rastreado) para Staged (rastreado), usa-se o comando git add:
// restreia um único arquivo
$ git add heroes/ironman.txt // rastreia um diretório inteiro
$ git add heroes/ // rastreia todos os que não estiverem rastreados
$ git add . //ou$ git add -A
5.2. Não-rastrear um arquivo (Unstaged)
Existem casos onde nos arrependemos de rastrear determinados arquivos, pois não queremos que sejam versionados no dado momento, mas só futuramente. Para removê-los do rastreio (voltando para o estado inicial), usa-se o comando git reset:
// remove o rastreio de um único arquivo
$ git reset heroes/ironman.txt // remove o rastreio de um diretório inteiro
$ git reset heroes/// remove o rastreio de todos que ainda não foram vinculados
$ git reset
5.3. Atualizando o histórico local (Commited)
Após rastrear os arquivos desejados, é preciso pedir ao Git para vinculá-los ao repositório local. Toda vez que fazemos isso, cada alteração efetuada até o momento é armazenada no histórico de alterações do Git, permitindo que possamos acessá-las posteriormente sempre que precisarmos.
O histórico do Git funciona como a funcionalidade de “Desfazer” (Ctrl + Z), presente na maioria dos programas. A diferença é que o “Desfazer” do Git é armazenado de forma permanente e pode ser executado a qualquer momento, mesmo que seja 5 anos depois.
Para vincular o conteúdo rastreado (Staged) ao repositório local (Commited), usa-se o comando git commit:
// Vinculando arquivos Staged (rastreados) ao repositório local
$ git commit -m “Meu comentário bem legal”
Também é possível vincular diretamente arquivos Unstaged (não rastreados), pulando assim uma etapa. Isso é feito acrescentando a opção -a:
// Vinculando arquivos Unstaged (não rastreados) diretamente
$ git commit -a -m “Meu comentário bem legal”
5.4. Enviando para o histórico remoto (Push)
Uma vez que seus novos arquivos e implementações estejam no histórico local (Commited), é possível sincronizar esse histórico local com o histórico do repositório remoto. Isso é feito usando o comando git push:
// envia o histórico local para o repositório remoto vinculado
$ git push
Se existir mais de um repositório vinculado, será necessário especificar o nome do repositório de destino:
// O mesmo comando especificando o repositório "origin"
$ git push origin
5.6. Recebendo arquivos do repositório remoto (Pull)
Em projetos onde várias pessoas usam o mesmo repositório, certamente enquanto uma pessoa efetua suas alterações, outras pessoas já atualizaram o repositório remoto com coisas novas. Por esse motivo, antes de submeter o que foi feito, é preciso obter todas as alterações feitas pelos outros membros do time. Isso é feito usando o comando git pull:
// recebe o histórico do repositório remoto vinculado
$ git pull
Da mesma forma que o comando git push, se existir mais de um repositório vinculado, será necessário especificar o nome do repositório remoto:
// O mesmo comando especificando o repositório "origin"
$ git pull origin
Ao executar esse comando, as alterações das outras pessoas serão obtidas para o histórico local. Nesse processo, o Git irá tentar resolver automaticamente quaisquer conflitos, comparando o que a pessoa fez, com o que o resto do time efetuou.
Mas isso nem sempre é possível! Existem momentos onde o Git precisa de uma intervenção humana, para escolher o que deve ou não ser mudado no processo de mesclagem.
6. Gerindo conflitos
6.1. Resolvendo manualmente
Como nem tudo são mil maravilhas, os conflitos acontecem. Ideal seria que a equipe se organizasse de tal forma (e o código fonte fosse implementado de maneira tão desacoplada) que o mínimo de conflitos pudesse existir.
Porém, mesmo nas melhores equipes, nem sempre isso é possível. Conformar-se irá tornar esse fardo mais leve. Fato é que, uma hora ou outra, teremos que lidar com os conflitos.
Imagine que no repositório remoto exista um arquivo chamado “teste.txt”. Depois que o repositório remoto foi clonado no computador do João, o Marcos (que havia clonado o mesmo repositório) alterou o arquivo “teste.txt” e submeteu-o ao repositório remoto com o seguinte conteúdo:
1. Que legal
2.
O João, em seu repositório local, também alterou o arquivo “teste.txt”, deixando a mesma linha com o seguinte conteúdo:
1. Que coisa
2.
Quando o João efetuar um git pull, o processo de mesclagem não conseguirá decidir qual dos dois textos é o correto, pois estão na mesma linha. Isso é um conflito entre o código do João e do Marcos!
$ git pull
remote: Counting objects: 3, done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 1), reused 0 (delta 0)
Unpacking objects: 100% (3/3), 277 bytes | 92.00 KiB/s, done.
From https://github.com/fulano/meu-repo
c25c1aa..1c3b328 master -> origin/master
Mesclagem automática de teste.txt
CONFLITO (conteúdo): conflito de mesclagem em teste.txt
Automatic merge failed; fix conflicts and then commit the result.
Para não perder nenhuma das duas informações, o Git irá atualizar o arquivo “teste.txt”, adicionando informações sobre os conflitos ocorridos. Após isso, o arquivo ficará como a seguir:
<<<<<<< HEAD
Que coisa
=======
Que legal
>>>>>>> 1c3b3280714b6866f20f023947ae37f9c174c97b
Uma divisória separa o que existe no repositório local do que existe no repositório remoto.
O bloco HEAD identifica a alteração existente no repositório local do programador local, enquanto o bloco 1c3b3280… corresponde à última alteração feita no repositório remoto, ou seja, o último git commit que alguém efetuou.
A resolução do conflito consiste em editar adequadamente o arquivo “teste.txt”, removendo o bloco a ser descartado e submetendo o arquivo modificado para o repositório remoto:
$ git add teste.txt
$ git commit -m “Resolução do conflito”
$ git push origin
Após isso os repositórios estarão novamente consolidados.
6.2. Resolvendo conflitos automaticamente
Em casos específicos, após a geração de conflitos, pode-se optar por escolher um lado do conflito e aceitar todas as suas modificações, poupando bastante tempo.
Mas esteja avisado: escolher cegamente um dos dois lados poderá causar a perda de dados. Ou seja, use as opções abaixo com consciência!
Suponha o mesmo caso explicado no item anterior:
<<<<<<< HEAD
Que coisa
=======
Que legal
>>>>>>> 1c3b3280714b6866f20f023947ae37f9c174c97b
Para manter apenas o conteúdo de HEAD e descartar os outros conflitos, usa-se o comando git checkout-index:
mv $(git checkout-index --temp --stage="2" "teste.txt")
O comando acima irá extrair o conteúdo do arquivo “teste.txt” em seu estado LOCAL (identificado pela opção stage=“2”) e gerar um arquivo temporário. Em seguida, o comando mv irá mover o arquivo temporário, sobrescrevendo o arquivo “teste.txt” com seu conteúdo. Ou seja, o arquivo “teste.txt” ficará como a seguir:
1. Que coisa
2.
Para manter o conteúdo REMOTO (1c3b3280…), basta passar a opção stage com o valor 3:
mv $(git checkout-index --temp --stage="3" "teste.txt")
Ou seja, o arquivo “teste.txt” ficará com o conteúdo do estado REMOTO:
1. Que legal
2.
Os estágios possíveis para a opção stage são:
- Código BASE: valor 1;
- Conteúdo LOCAL: valor 2;
- Conteúdo REMOTO: valor 3;
Conclusão
Embora este artigo tenha explanado as funcionalidades básicas do Git, o leitor já conseguirá começar a trabalhar com código versionado.
Em um próximo artigo, explicarei como trabalhar de uma forma mais “intermediária”, usando recursos que permitirão um melhor gerenciamento do código fonte, principalmente para trabalho em equipe.
Espero que o conteúdo tenha sido útil. Até a próxima!
Referências para Aprofundamento
AQUILES, Alexandre; FERREIRA, Rodrigo. Controlando Versões com Git e GitHub, São Paulo, SP. Casa do Código.
MORAES, Gleicon. Caixa de Ferramentas DevOps. São Paulo, SP. Casa do Código.