Para conseguir acesso aos projetos da InfoTIC 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
$ git init
Com isso, inicia-se um repositório local do Git. Esse comando cria uma pasta oculta .git
com vários arquivos de configuração.
Isso deve ser feito em uma nova pasta, onde ficará o projeto. Portanto, antes de criar o repositório, deve fazer algo como:
$ mkdir <nome_pasta> $ cd pasta
Pode-se também criar um novo repositório em um projeto já inciado contendo arquivos. Ao criar o repositório, o ramo master será criado automaticamente. Se não quiser ter um ramo com esse nome, crie outro a partir desse e apague-o.
A grosso modo, este comando baixa o conteúdo de um repositório na máquina local. Seu modo de uso básico é da seguinte forma:
$ git clone <repositório> [<diretório>]
Onde repositório é o endereço onde o serviço git e o projeto em questão está alocado, por exemplo: git@git.ufrj.br:infotic/dotproject.git
E onde diretório é o endereço do diretório local em que serão baixados os arquivos do repositório. Se este parâmetro não for passado, o Git criará um diretório com o mesmo nome do repositório indicado. No exemplo citado, criará um diretório denominado dotproject
Logo, o comando, seguindo o exemplo apresentado, seria:
$ git clone git@git.ufrj.br:infotic/dotproject.git DotProject
ou somente:
$ git clone git@git.ufrj.br:infotic/dotproject.git
Para adicionar um ou mais arquivos ao repositório local, que estarão marcados para o próximo commit:
$ git add <nome_arquivo_1> <nome_arquivo_2> ... <nome_arquivo_n>
Para adicionar todos os arquivos que ainda não estejam no repositório, desconsiderando os do .gitignore
(que também será adicionado caso ainda esteja fora do repositório):
$ git add .
Para adicionar um arquivo vazio, faça
$ touch <nome_arquivo> $ git add <nome_arquivo>
Esse comando é útil para, por exemplo, comitar uma pasta vazia. Se a pasta passar a ter arquivos válidos, ele pode ser removido. É muito comum esse tipo de arquivo ter como nome delete.me
.
Todo arquivo modificado, para poder ser comitado no próximo commit, precisa passar pelo git add
.
Como visto anteriormente, o git add
serve para marcar arquivos para o próximo commit (stage). Só que o git add
é muito limitado para isso. Para facilitar, esse processo pode ser iterativo e, assim, inclusive partes de um arquivo podem ser marcados (usando a opção patch
durante o processo), fazendo com que um commit possa ser atômico. Para rodar o stage iterativo, faça:
$ git add -i
Para remover arquivos comitados indevidamente, mas que não devem ser removidos do sistema de arquivos:
$ git rm --cached <nome_arquivo>
Para renomear ou mover arquivos para outro diretório, para que o Git perceba que o arquivo foi renomeado ou movido, faça:
$ git mv <nome_ou_caminho_antigo> <nome_ou_caminho_novo>
Alguns arquivos que estão na pasta do projeto não devem fazer parte do repositório. Como exemplo, são os arquivos temporários, da IDE, auto-gerados, compilações, logs, etc. Para fazer isso, adicione o arquivo ou um padrão de arquivos para o arquivo .gitignore
. Cada linha do arquivo deve ser um arquivo ou padrão que será ignorado pelo Git. Um exemplo tirado de um projeto Laravel pode ter este conteúdo:
/node_modules
/public/hot
/public/storage
/storage/*.key
/storage/temp/*.zip
/storage/temp/*.xlsx
/vendor
/.idea
/.vscode
/.vagrant
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
.env
*.lock
phpunit
/nbproject
Há casos onde se quer ignorar um determinado arquivo em todos os projetos Git. Neste caso, pode-se passar um arquivo texto com os arquivos a serem ignorados para a configuração global do Git, fazendo, por exemplo:
$ git config --global core.excludesfile ~/.gitignore_global
$ git commit -m "<mensagem>"
Esse comando adiciona os dados dos arquivos ao HEAD, assim os arquivos ficam prontos para serem enviados para um repositório remoto. Quem vem do SVN e do CVS pode estranhar, pois em ambos o commit já envia os dados diretamente para o servidor remoto.
Para alterar o último commit, faça:
$ git commit --ammend
Esse comando permite que novos arquivos marcados para serem comitados possam fazer parte do último commit, além de alterar a mensagem do mesmo. Um ammend é como se fosse um pequeno rebase, que muda o hash do commit. Portanto, não altere um commit que já tenha sido mandado ao repositório remoto!
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
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.
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 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.
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
Para ver as mudanças desde o último commit, juntamente com os arquivos que não estão no repositório, faça:
$ git status
Para comparar dois ramos ou dois commits num mesmo ramo, faça:
$ git diff <nome_ramo_1> <nome_ramo_2> $ git diff <commit_1> <commit_2>
onde <commit_1>
e <commit_2>
são os hashes que identificam cada commit.
Se não tiver parâmetros, o comando acima mostra as diferenças entre o que está no diretório e o que foi marcado para ser comitado.
Para ver as diferenças entre o que foi marcado para ser comitado e o último commit, faça:
$ diff --staged
ou
$ diff --cached
Para ver apenas os nomes dos aquivos modificados, use a opção –name-only
.
Caso prefira, para fazer comparações usando uma ferramenta externa, use:
$ git difftool
Para ver o histórico de todos os commits juntamente com os arquivos alterados, faça:
$ git log
Já para ver quais commits há num ramo que não estão em outro ramo, faça:
$ git log A..B
Dessa forma, saberá quais commits estão em B
e não foram mesclados em A
. Se quiser saber quais estão em A
que não foram mescladas em B
, é só inverter a ordem dos ramos no comando. Já se quiser ver todos os commits que estão em A
e não em B
e vice-versa ao mesmo tempo, é só trocar os 2 pontos por 3 pontos. Para saber qual o sentido das mudanças (o que está em A
e não em B
e vice-versa), use o parâmetro –left-right
:
$ git log --left-right A...B
Para verificar as mudanças em um commit específico, faça:
$ git show <commit_hash>
Como se sabe, o hash é grande. Para facilitar, é possível colocar apenas os caracteres do código desde que seja único. Em geral, entre 8 e 10 caracteres é mais do que suficiente para acessar um commit único. Aliás, a chance de um hash (que é randômico) se repetir em um projeto é extremamente improvável.
Se quiser ver o último commit de um ramo, ao invés de colocar o hash do commit, pode-se colocar o nome do ramo. Já se quiser ver o hash completo do último commit de um ramo, faça:
$ git rev-parse <nome_ramo>
Para verificar as mudanças ocorridas em um arquivo em um commit específico, faça:
$ git show <commit_hash> <nome_arquivo>
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.
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>
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}
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
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>
Uma ferramenta visual útil de Git é o Git Gui. O Git já vem instalado com o Ubuntu mas não com o Windows. Se instalar o Git no Windows, o Git Gui é automaticamente. Já no Ubuntu é necessário instalá-lo com:
$ sudo apt-get install git-gui
Para abri-lo em um repositório, vá para a pasta de algum projeto que tenha Git e na linha de comando digite:
$ git gui
Com essa ferramenta é possível comitar, alterar commits, fazer mesclagens, desfazer alterações, resetar ramos (mas apenas para o último commit e não para determinado ramo), puxar alterações dos ramos remotos, enviar alterações para ramos remotos, visualizar históricos, etc.
Contudo, não é possível fazer stashs, rebases
, etiquetas (tags) ou outros comandos avançados via essa ferramenta.
Muitas das funcionalidades do Git Gui podem ser feitas usando o NetBeans ou o Eclipse, cujos plugins para o Git possuem funcionalidades mais avançadas do que as do Git Gui.
Ferramentas gráficas como o Git Gui ou outra de sua preferência agilizam muito o trabalho com o Git. Porém, é muito importante conhecer os comandos.
No Windows, para usar os comandos do Git, é preciso usar a ferramenta Git Bash, que abrirá um prompt de comando onde todos os comandos de Git se encontram disponíveis. Clicando com o botão direito na pasta do projeto, é possível abrir o Git Bash diretamente nela.
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
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.
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.
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.
Para saber detalhes sobre qualquer comando do Git, faça:
$ git <nome_comando> --help
$ git fetch --all $ git reset --hard origin/<nome_do_ramo>