Tabela de conteúdos
Git
Git é um sistema de controle de versionamento de código aberto projetado para a manipulação de projetos com rapidez e eficiência. Git é um sistema de controle de versionamento rápido, escalável e distribuído com um conjunto rico de comandos para prover um alto nível de operabilidade.
Este link apresenta as abstrações empregadas pelo Git.
Instalando
Os binários do Git podem ser obtidos no endereço https://git-scm.com/download.
No Windows é comum o Git ser executado no Git Bash que é um terminal com mais recursos que o terminal nativo do windows cmd.exe
.
Verificando a versão
Para verificar a versão do Git instalada em sua máquina podemos utilizar o comando
$ git --version
Clientes gráficos
Apesar do Git ser uma ferramenta de linha de comando, dispomos de alguns programas com interface gráfica para trabalhar com o Git:
Configurando
Para usar o Git você precisa ter no mínimo a sua identidade configurada:
$ git config --global user.name "John Doe" $ git config --global user.email "johndoe@example.com"
Configurações Globais
(mover para página infotic:git)
Para conseguir acesso aos projetos da TIC em https://git.ufrj.br, faça as seguintes configurações:
$ git config --global user.email "<e_mail>" $ git config --global user.name "<nome>"
Onde e_mail
e nome
são seu e-mail e nome cadastrados no GitLab da TIC, respectivamente.
Crie um par de chaves SSH sem senha caso não tenha uma. Em geral, estarão no diretório .ssh
de seu $HOME
no Linux. Se nesse diretório tiver um par de arquivos id_rsa
e id_rsa.pub
, então não precisa criar um novo par. Para criar um novo par, faça:
$ ssh-keygen
Vá para a sessão SSH Keys de seu perfil do GitLab e crie uma nova chave, copiando o conteúdo de id_rsa.pub
para a caixa de texto 'Key'.
Para não haver conflitos com espaços em branco ao mesclar ramos, caso espaços em branco não sejam relevantes em seu projeto, faça:
$ git config –-global core.whitespace nowarn
Utilizando
Um repositório Git está contido em um diretório .git, que contem os históricos de revisão e outros metadados. O diretório rastreado pelo repositório, por padrão o diretório onde o .git está contido, é chamado de diretório de trabalho. Mudanças no diretório de trabalho devem ser marcadas (stagged) antes de serem gravadas (committed) no repositório. O Git também permite que você restaure árvores de diretórios previamentes gravadas (committed).
Obtendo um repositório
Registrando mudanças
Os projetos Git possuem uma área de preparação (staging area), um arquivo de índice no diretório .git
que armazena as mudanças que irão para o seu próximo commit. Para registrar um arquivo modificado você precisa primeiro adicioná-lo a esse índice. O comando git commit
então irá gravar o índice atual de mudanças em um novo commit.
Preparando as mudanças
Adicionar mudanças no índice | git add pathspec |
Remover mudanças do índice | git reset pathspec |
Mostrar mudanças a serem committed, mudanças unstaged, e arquivos não rastreados | git status |
Além de adicionar arquivos com o comando git add
, podemos remover o rastreamento de um arquivo com o comando git rm
e adicionar uma renomeação ou movimentação de arquivo com o comando git mv
.
Mudanças em determinados arquivos e diretórios podem ser ignoradas pelo Git através de um arquivo especial .gitignore
.
Gravando as mudanças
O comando git commit
grava as mudanças no repositório (diretório .git
).
-m
– fornece a mensagem do commit como um argumento ao invés de abrir o seu editor de textos para escrevê-la.-a
– automaticamente adiciona ao índice arquivos que foram modificados ou deletados.–amend
– refaz o último commit, consertando a mensagem ou os arquivos committed.
Seleção da revisão
O Git oferece diversas formas de especificar revisões. Muitos comandos Git recebem revisões como argumentos. Um commit pode ser identificado por qualquer umas das formas a seguir:
- O código hash do commit (todo ou apenas os 7 primeiros dígitos).
- Qualquer rótulo de commit como um nome de ramo (branch) ou etiqueta (tag).
- O rótulo
HEAD
sempre se refere ao commit respectivo do diretório de trabalho ativo (checked-out). Ou seja, pode ser o último commit do ramo, ou um commit antigo se você usou ogit checkout
para saltar para uma revisão antiga. - Qualquer uma das opções acima combinada com o
~
. Por exemplo,HEAD~
refere-se à um commit antes doHEAD
eHEAD~5
refere-se à cinco commits antes doHEAD
.
Visualizando as mudanças
O comando a seguir mostra a diferença entre dois commits:
$ git diff HEAD HEAD~3
ou entre a área de preparação (staging area) e a árvore do diretório de trabalho:
$ git diff
O comando a seguir mostra o histórico de mudanças:
$ git log -p
Veja mais aqui
Ramos
Para ver a lista de ramos e o ramo atual:
$ git branch
Para ver mais informações sobre os ramos, inclusive se os mesmos estão à frente, atrás, ambos (há modificações locais e no remoto), igual ou é um ramo novo, em relação ao que está nas últimas referências aos ramos
do repositório (pegos no último fetch
), use o parâmetro -vv
.
Para criar um ramo baseado no ramo atual (o novo será idêntico ao atual):
$ git checkout -b <novo_ramo>
Para apagar um ramo:
$ git branch -d <ramo_a_apagar>
Esse comando não permite apagar um ramo que não tenha sido mesclado em outro. Para apagar mesmo assim, troque o -d
por -D
(cuidado: se o ramo não tiver sido jogado para o servidor, não poderá ser recuperado!).
Note que o ramo atual não pode ser o que será apagado. Para isso, é necessário mudar para outro ramo, o que pode ser feito com:
$ git checkout <nome_ramo>
Para mesclar dois ramos, faça:
$ git merge <nome_ramo>
o que fará com que as mudanças do ramo com nome_ramo
sejam jogadas no ramo atual. Se a mesclagem não puder ser feita automaticamente por causa de conflitos (alterações em ambos os ramos nas mesmas partes de determinados arquivos), terá que abrir os arquivos e resolver os conflitos. Haverá marcas no texto mostrando os conflitos («««<
). Resolva-os, marque os arquivos para serem comitados e comite-os para finalizar o processo de mesclagem. Se a mesclagem for feita automaticamente, não será necessário comitá-la, pois isso será feito automaticamente.
Quando há conflitos numa mesclagem, é possível que se queira pegar apenas o que foi feito em um dos ramos e descartar as alterações do outro. Para fazer isso automaticamente, use a opção -Xours
para ficar com as alterações conflitantes do ramo corrente ou -Xtheirs
, caso queira as do ramo passado como parâmetro durante a mesclagem.
Caso queira abortar o processo de resolução de conflitos por algum motivo, faça:
$ git merge --abort
Infelizmente não é possível abortar uma mesclagem que tenha bem sucedida (sem conflitos) com esse comando. Será necessário fazer um reset –hard
Rebase
Quando se faz mesclagem de ramos, ao olhar o histórico de commits, nota-se que ele muitas vezes não fica linear, ficando parecendo mais um grafo. Para deixar o histórico mais organizado, é possível usar o rebase
, que joga os commits do ramo que será juntado após o outro. Suponha que um ramo B
tenha sido criado a partir do A
, foi alterado, e o A
também tenha sido alterado por outro desenvolvedor. Ao invés de aplicar o comando merge
para jogar o conteúdo de B
em A
, ao aplicar o rebase
, todos os commits que foram feitas em B
serão colocados por último em cima do A
. É como se o A
fosse pego já com as alterações do outro desenvolvedor e só aí as alterações que foram feitas em B
fossem feitas. Então, ao invés de fazer:
$ git checkout A $ git merge B
faz-se:
$ git checkout B $ git rebase A $ git checkout A $ git merge B
Para simplificar, pode-se fazer:
$ git rebase A B $ git checkout A $ git merge B
O primeiro comando acima basicamente diz: “reaplique as alterações de B
no topo de A
”.
Com rebase
dá para fazer operações mais sofisticadas para deixar a árvore de commits mais organizada. Dê uma olhada no manual para ver mais opções.
Cuidado! Não faça rebase
em commits que já foram para o servidor remoto se trabalhar com outros desenvolvedores. Se fizer isso poderá bagunçar o trabalho de seus colegas!
Assim como no caso do merge
, o rebase
também pode gerar conflitos, mas a maneira de lidar com eles é diferente. A medida que o Git vai reaplicando os commits, se der um conflito em um, ele para para que o desenvolvedor resolva os conflitos e só aí continua com o comando. O rebase
pode facilitar bastante caso um merge
gere muitos conflitos, pois os mesmos serão resolvidos aos poucos.
O rebase
também pode servir para simplesmente alterar commits antigos do histórico, já que o commit –ammend
não permite alterar commits do histórico. Basta fazer:
$ git rebase -i HEAD~<n>
Esse comando vai permitir alterar os últimos n + 1
commits de maneira iterativa. Novamente, não deve se fazer isso para commits que já foram para o repositório remoto, pois isso vai alterar os hashes dos commits (para o Git serão novos objetos de commit).
O rebase
iterativo também permite a remoção, mudança de ordem e junção (squash) de commits.
Alterar Vários Commits de uma vez
Suponha que seja necessário tirar ou adicionar um arquivo em todo o histórico de commits do Git, ou ainda alterar informações em um arquivo em todo esse histórico. Isso é possível com o comando filter-branch
. Esse comando pode ser muito útil. Porém, novamente, não deve ser usado se o repositório já tiver sido publicado e outro desenvolvedor tenha trabalhado no projeto. Esse comando tem várias opções que podem ser exploradas para alterar toda uma árvore de commits.
Um exemplo importante de uso dele é se um arquivo com uma senha tiver sido comitado por engano; deve-se tirá-lo de todo o histórico do repositório.
Consulte a documentação para conhecer todos os parâmetros e comandos possíveis, para ver as alterações que o comando permite fazer em todo o repositório.
Como as alterações são radicais, rode o comando num ramo de teste e depois redefina (hard-reset) o ramo original para ficar com o mesmo estado desse ramo teste após se certificar que as alterações saíram como o esperado. Esse comando também permite alterar todos os ramos de uma vez usando a opção –all
.
Cherry-Pick
Cherry-pick significa escolher a dedo. Esse comando no Git serve para pegar um commit de um ramo e jogar em outro. Isso pode ser útil quando apenas se quer pegar uma funcionalidade/correção de um ramo e jogar em outro. Por exemplo, há um ramo com várias alterações e apenas uma delas quer que vá para a próxima versão. Por isso é tão importante que cada commit seja algo único e completo.
Para rodar o comando, estando no ramo de destino, faça:
$ git cherry-pick <hash_do_commit>
Ao fazer isso, o commit será aplicado no ramo de destino. Ao fazer isso, será criado um novo commit no histórico, com outro hash.
Rerere
Muitas vezes a maneira de se resolver conflitos manualmente durante merges e rebases é semelhante. Portanto, seria interessante que o Git “aprendesse” e aplicasse algumas dessas soluções automaticamente. Para isso, existe o rerere
. Para permitir isso, faça:
$ git config --global rerere.enabled true
Ao invés de ligar o rerere
automaticamente, é possível também rodar o comando durante um merge:
$ git merge
Repositório remoto
Para associar o repositório local a um remoto, faça:
$ git remote add origin <uri>
onde origin
é um alias
para o repositório remoto. É possível usar outro nome, mas origin
é o nome padrão que se usa quando há apenas um repositório remoto (o que ocorre na maioria dos casos).
Para ver a URL do repositório remoto, faça:
$ git remote show origin
Para trocar a URL do repositório remoto, faça:
$ git remote set-url origin <nova_url>
Para acessar algo de um ramo remoto, como para fazer uma comparação, use:
origin/<nome_ramo>
Já se quiser criar um repositório local a partir de um remoto, faça:
$ git clone [origin] <nova_pasta>
Neste caso, apenas o ramo master
local será criado como uma cópia idêntica do remoto (se ele existir). Para criar uma cópia local de outro ramo remoto, crie um ramo local a partir do ramo remoto desejado, preferencialmente com o mesmo nome.
Para mandar os commits no ramo local para o respectivo ramo remoto, faça:
$ git push origin <nome_ramo>
Já para mandar os commits de todos os ramos locais para os respectivos ramos remotos, faça:
$ git push origin --all
Para obter as atualizações do repositório remoto para o correspondente ramo local, faça:
$ git pull origin
Deve-se usá-lo sempre antes de começar a alterar os arquivo para não ter conflito com a versão que está no repositório remoto.
Já para pegar as atualizações do repositório remoto mas sem atualizar os ramos locais, faça:
$ git fetch origin
Dessa forma, as alterações vão ficar na referência ao ramos remotos e precisarão ser mescladas ao ramos locais.
Para apagar ramos remotos, quando não precisar mais deles, faça:
$ git fetch origin --delete <nome_ramo>
Muito cuidado com esse comando para não apagar um ramo indevidamente! Todavia, se o ramo tiver sido apagado recentemente, talvez seja possível resgatá-lo caso o coletor de lixo do Git ainda não tenha rodado.
Desfazer alterações
Para reverter um ou mais commit, faça
$ git revert <commit_hash>
onde commit_hash
é o hash do commit para o qual o ramo atual irá apontar.
Para voltar ao commit anterior, mas criando um novo commit, faça:
$ git revert HEAD
Para retirar um arquivo que foi marcado para ser comitado, faça:
$ git reset HEAD <nome_arquivo>
Para fazer um arquivo modificado ser sobrescrito com o conteúdo do último commit, faça (cuidado pois todas as modificações serão perdidas!):
$ git checkout -- <nome_arquivo>
Para voltar ao commit anterior, mas descartando por completo todas as alterações, faça:
$ git reset --hard HEAD
Cuidado com esse procedimento pois não há como recuperar!
Já para fazer o ramo atual voltar ao estado de outro ramo, faça:
$ git reset --hard <nome_ramo>
Stash
Quando se está no meio de alterações em um ramo e é necessário trocar de ramo para alterá-lo, e depois voltar ao trabalho, não é necessário desfazer tudo ou comitar pela metade no ramo de origem. Existe um comando muito útil que evita essa trabalheira toda. No ramo de origem, faça:
$ git stash
Com isso, seu ramo voltará ao estado do último commit. As alterações ficarão salvas em uma pilha. Note que é possível fazer vários stashs, um em cima do outro, mas é algo que não costuma ser necessário. Agora, é possível fazer as alterações no outro ramo, comitá-las e voltar ao ramo anterior, onde deverá ser feito o comando:
$ git stash pop
Atenção: Cuidado para não fazer o comando acima no ramo errado. Desfazer esse tipo de erro costuma ser bem complicado.
Desta forma, as modificações em andamento voltarão e poderão ser continuadas. Com o pop
, o que foi parar na pilha é removido. Já se usar apply
ao invés de pop
, as alterações são reaplicadas e ainda podem ser utilizadas. Se a pilha tiver mais de um grupo de alterações, é possível aplicar uma delas com o comando:
$ git stash apply stash@{i}
onde i
é o índice do histórico na pilha, começado em 0
. Para remover da pilha um histórico de alterações não comitadas, faça:
$ git stash drop stash@{i}
Submódulos
Muitas vezes é interessante que um projeto possua código de um outro projeto dentro do diretório. Ou seja, internamente, dentro do repositório há uma pasta que possuirá outro repositório. Para isso, use o comando:
$ git submodule add <link_do_sub_projeto> <caminho>
Com isso, será criada uma pasta dentro do diretório do projeto principal de acordo com o caminho passado, juntamente com o conteúdo do repositório. Se quiser usar o nome padrão do subprojeto, é só omitir o segundo parâmetro. Também será criado um arquivo .gitmodules
, semelhante ao .gitignore
, com uma entrada para cada submódulo utilizado pelo projeto.
Ao clonar um projeto com submódulos, é possível baixar todos os submódulos de uma vez ou não. Se passar a opção –recursive
ao comando clone
, todos eles serão baixados. Caso contrário, terá que dar os seguintes comandos nas pastas que até então estarão vazias para baixar cada módulo:
$ git submodule init $ git submodule update
Buscas
O grep
é um comando Linux útil para buscar ocorrências de strings no projeto, mas não funciona para buscar ocorrências no histórico do repositório. Para isso, existe o comando git grep
, que é similar ao do terminal do Linux.
Para buscar quando determinada ocorrência passou a ou deixou de existir no repositório, deve-se usar o git log
com o parâmetro -S
concatenado com a ocorrência a ser buscada:
$ git log -S<string_a_ser_buscada>
Etiquetas
Quando uma versão é lançada, ou seja, quando o conteúdo do ramo master
(ou outro ramo usado para produção) é posto em produção, é interessante guardar essa versão, caso alguma correção precise ser feita em versões anteriores ou o ramo master
possa ser modificado logo após o lançamento com algo que não seja a correção de pequenos problemas. Se outro ramo for usado como sendo o de produção, a etiqueta deve ser feita nesse ramo.
Para fazer isso, apenas use o comando:
$ git tag -a <nome_versao> -m "<mensagem_adicional>"
A tag é uma etiqueta e pode ser usada como se fosse um ramo. Porém, não é aconselhável modificá-la e mantê-la com o mesmo nome. Para listar todas as etiquetas, use o comando acima sem nenhum parâmetro.
Para listar todas as tags, faça:
$ git tag
Uma tag não é mandada para o repositório remoto automaticamente. Para fazê-lo (algo altamente recomendável), faça:
$ git push origin --tags
Apelidos
Com o alias
, você pode criar apelidos aos comando do GIT. Desta forma, comandos usados com frequência podem ser encurtados. Alguns exemplos:
$ git config –global alias.st status $ git config –global alias.co checkout
Com isso, ao invés de digitar git status
e git checkout
, poderá digitar git st
e git co
, respectivamente.
Forks
Suponha que haja um projeto no Git a partir do qual queira iniciar um projeto novo que deve ter o desenvolvimento desvinculado do atual (não querendo fazer mesclagem com o mesmo). Ou seja, serão projetos que seguirão trajetórias distintas. Para isso, é possível fazer um fork, gerando um projeto idêntico do atual.
Usando o GitLab, pode-se clicar no botão Fork.
Ao fazer isso, terá que mover o projeto para outro namespace. Após fazer isso, se for o caso, renomeie o projeto e mova-o para o namespace original.
Limpeza
As operações no Git com o tempo podem deixá-lo com vários objetos soltos e arquivos que não estão no repositórios e que não serão usados perdidos. Além disso, há a criação de vários ramos que muitas vezes são criados e ficam esquecidos. Com isso, o Git fica lento e o repositório excessivamente grande. Para resolver o problema, há os comandos:
$ git gc --aggressive
para realizar coleta de lixo de objetos soltos.
$ git clean -d -f
para remover todos os arquivos que não fazem do repositório. Útil para jogar fora sobras de build, etc.
$ git clean -f -X
para remover apenas arquivos que são ignorados pelo Git; útil para limpar o diretório sem remover junto arquivos que foram criados manualmente. Com isso é possível, por exemplo, limpar o diretório de todos os arquivos gerados automaticamente (porque eles são ignorados pelo Git), sem apagar arquivos que você mesmo criou, como uma nova classe que ainda não foi comitada. Cuidado com os arquivos de configuração, como o .env
do Laravel.
Para saber o que será excluído, use o -n
para fazer um dry-run, que apenas mostrará o que será excluído. Outra opção é o -i
, para a exclusão ser iterativa.
$ git remote prune origin
Apaga a referência a ramos do seu origin
local que já foram removidos do repositório remoto. Caso haja a possibilidade de algum ramo ter sido removido indevidamente do origin
, faça novo push
a partir do seu Git antes de dar esse comando, pois ele não pode ser desfeito e se algo tiver sido apagado do origin
indevidamente, será perdido para sempre. Se não tiver certeza de que pode remover as referências dos ramos remotos, use a opção –dry-run
que mostrará quais ramos serão eliminados com essa opção.
Ajuda
Para saber detalhes sobre qualquer comando do Git, faça:
$ git <nome_comando> --help
Troubleshooting
Forçar sobrescrita do repositório local pelo remoto
$ git fetch --all $ git reset --hard origin/<nome_do_ramo>
Referências
- Git. Disponível em: https://git-scm.com/. Acessado em: 19/03/2019
- Git - ArchWiki. Disponível em: https://wiki.archlinux.org/title/Git. Acessado em: 26/08/2021