-
1. Primeiros Passos
- 1.1 Sobre Controle de Versão
- 1.2 Uma Breve História do Git
- 1.3 O que é Git?
- 1.4 A Linha de Comando
- 1.5 Instalando o Git
- 1.6 Configuração inicial do Git
- 1.7 Obtendo Ajuda
- 1.8 Resumo
-
2. Fundamentos do Git
-
3. Ramificação (Branching) no Git
-
4. Git no Servidor
- 4.1 Os Protocolos
- 4.2 Colocando o Git em um Servidor
- 4.3 Gerando a sua Chave Pública SSH
- 4.4 Configurando o Servidor
- 4.5 Git Daemon
- 4.6 Smart HTTP
- 4.7 GitWeb
- 4.8 GitLab
- 4.9 Opções de Hospedagem de Terceiros
- 4.10 Resumo
-
5. Git Distribuído
-
6. GitHub
-
7. Ferramentas do Git
- 7.1 Seleção de Revisão
- 7.2 Área de Stage Interativa
- 7.3 Fazendo Stash e Limpando
- 7.4 Assinando Seu Trabalho
- 7.5 Pesquisando (Searching)
- 7.6 Reescrevendo o Histórico
- 7.7 Reset Desmistificado
- 7.8 Merging Avançado
- 7.9 Rerere
- 7.10 Depurando com Git
- 7.11 Submódulos
- 7.12 Empacotando (Bundling)
- 7.13 Substituir (Replace)
- 7.14 Armazenamento de Credenciais
- 7.15 Resumo
-
8. Customizando o Git
- 8.1 Configuração do Git
- 8.2 Atributos do Git
- 8.3 Hooks do Git
- 8.4 Uma Política de Exemplo Imposta pelo Git
- 8.5 Resumo
-
9. Git e Outros Sistemas
- 9.1 Git como Cliente
- 9.2 Migrando para o Git
- 9.3 Resumo
-
10. Git Internals (Por Dentro do Git)
- 10.1 Encanamento e Porcelana (Plumbing and Porcelain)
- 10.2 Objetos do Git
- 10.3 Referências do Git
- 10.4 Arquivos de pacote (Packfiles)
- 10.5 Refspec
- 10.6 Protocolos de Transferência
- 10.7 Manutenção e Recuperação de Dados
- 10.8 Variáveis de Ambiente
- 10.9 Resumo
-
A1. Appendix A: O Git em Outros Ambientes
- A1.1 Interfaces Gráficas
- A1.2 Git no Visual Studio
- A1.3 Git no Visual Studio Code
- A1.4 Git no IntelliJ / PyCharm / WebStorm / PhpStorm / RubyMine
- A1.5 Git no Sublime Text
- A1.6 Git no Bash
- A1.7 Git no Zsh
- A1.8 Git no PowerShell
- A1.9 Resumo
-
A2. Appendix B: Incorporando o Git nos seus Aplicativos
- A2.1 Linha de Comando do Git
- A2.2 Libgit2
- A2.3 JGit
- A2.4 go-git
- A2.5 Dulwich
-
A3. Appendix C: Comandos do Git
- A3.1 Configuração e Ajustes
- A3.2 Obtendo e Criando Projetos
- A3.3 Snapshots Básicos
- A3.4 Branches e Merges
- A3.5 Compartilhando e Atualizando Projetos
- A3.6 Inspeção e Comparação
- A3.7 Depuração
- A3.8 Patches
- A3.9 E-mail
- A3.10 Sistemas Externos
- A3.11 Administração
- A3.12 Comandos de Encanamento
7.6 Ferramentas do Git - Reescrevendo o Histórico
Reescrevendo o Histórico
Muitas vezes, ao trabalhar com o Git, você pode querer revisar seu histórico de commits local.
Uma das grandes coisas sobre o Git é que ele permite que você tome decisões no último momento possível.
Você pode decidir quais arquivos vão em quais commits logo antes de comitar usando a área de stage, pode decidir que não queria estar trabalhando em algo ainda com o git stash e pode reescrever commits que já aconteceram para que pareçam ter acontecido de uma maneira diferente.
Isso pode envolver alterar a ordem dos commits, alterar mensagens ou modificar arquivos em um commit, juntar (squash) ou dividir commits, ou remover commits inteiramente — tudo antes de você compartilhar o seu trabalho com outras pessoas.
Nesta seção, você verá como realizar essas tarefas para que possa fazer com que seu histórico de commits fique do jeito que você quer antes de compartilhá-lo com outras pessoas.
|
Note
|
Não faça push do seu trabalho até que esteja feliz com ele
Uma das regras fundamentais do Git é que, como grande parte do trabalho é local no seu clone, você tem uma grande liberdade para reescrever o seu histórico localmente. No entanto, depois de fazer push do seu trabalho, a história é completamente diferente, e você deve considerar o trabalho empurrado (pushed) como final, a menos que tenha um bom motivo para alterá-lo. Resumindo, você deve evitar fazer push do seu trabalho até que esteja feliz com ele e pronto para compartilhá-lo com o resto do mundo. |
Alterando o Último Commit
Alterar o seu commit mais recente é provavelmente a reescrita de histórico mais comum que você fará. Muitas vezes, você desejará fazer duas coisas básicas no seu último commit: simplesmente alterar a mensagem do commit ou alterar o conteúdo real do commit adicionando, removendo e modificando arquivos.
Se você simplesmente quiser modificar a mensagem do seu último commit, isso é fácil:
$ git commit --amend
O comando acima carrega a mensagem do commit anterior em uma sessão de editor, onde você pode fazer alterações na mensagem, salvar essas alterações e sair. Quando você salva e fecha o editor, o editor grava um novo commit contendo a mensagem de commit atualizada e o torna o seu novo último commit.
Se, por outro lado, você quiser alterar o conteúdo real do seu último commit, o processo funciona basicamente da mesma maneira — primeiro faça as alterações que você acha que esqueceu, prepare (stage) essas alterações, e o subsequente git commit --amend substitui o último commit pelo seu novo e aprimorado commit.
Você precisa ter cuidado com essa técnica porque o amend (alteração) muda o SHA-1 do commit. É como um rebase muito pequeno — não faça amend no seu último commit se já tiver feito push dele.
|
Tip
|
Um commit alterado pode (ou não) precisar de uma mensagem de commit alterada
Quando você altera um commit, tem a oportunidade de mudar tanto a mensagem do commit quanto o conteúdo dele. Se você alterar o conteúdo do commit de forma substancial, quase certamente deverá atualizar a mensagem do commit para refletir esse conteúdo alterado. Por outro lado, se as suas alterações forem suficientemente triviais (corrigir um erro de digitação bobo ou adicionar um arquivo que você esqueceu de preparar) de modo que a mensagem de commit anterior esteja boa, você pode simplesmente fazer as alterações, prepará-las (stage) e evitar totalmente a sessão desnecessária do editor com:
|
Alterando Várias Mensagens de Commit
Para modificar um commit que está mais atrás no seu histórico, você deve usar ferramentas mais complexas.
O Git não tem uma ferramenta de modificar o histórico, mas você pode usar a ferramenta de rebase para fazer o rebase de uma série de commits no HEAD em que eles se basearam originalmente, em vez de movê-los para outro.
Com a ferramenta de rebase interativo, você pode parar após cada commit que deseja modificar e alterar a mensagem, adicionar arquivos ou fazer o que quiser.
Você pode rodar o rebase interativamente adicionando a opção -i ao git rebase.
Você deve indicar o quão longe no passado você quer reescrever os commits, dizendo ao comando em qual commit fazer o rebase.
Por exemplo, se quiser alterar as três últimas mensagens de commit, ou qualquer uma das mensagens de commit nesse grupo, você fornece como argumento para o git rebase -i o pai do último commit que deseja editar, que é HEAD~2^ ou HEAD~3.
Pode ser mais fácil lembrar do ~3 porque você está tentando editar os últimos três commits, mas lembre-se de que, na verdade, você está designando o quarto commit atrás, o pai do último commit que você deseja editar:
$ git rebase -i HEAD~3
Lembre-se novamente que este é um comando de rebasing — cada commit no intervalo HEAD~3..HEAD com uma mensagem alterada e todos os seus descendentes serão reescritos.
Não inclua nenhum commit que você já tenha feito push para um servidor central — fazer isso confundirá outros desenvolvedores ao fornecer uma versão alternativa da mesma alteração.
Executar este comando dá a você uma lista de commits no seu editor de texto que se parece com isto:
pick f7f3f6d Change my name a bit
pick 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file
# Rebase 710f0f8..a5f4a0d onto 710f0f8
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
É importante notar que esses commits são listados na ordem oposta daquela que você normalmente vê usando o comando log.
Se você rodar um log, verá algo como isto:
$ git log --pretty=format:"%h %s" HEAD~3..HEAD
a5f4a0d Add cat-file
310154e Update README formatting and add blame
f7f3f6d Change my name a bit
Observe a ordem inversa.
O rebase interativo fornece um script que ele vai rodar.
Ele começará no commit que você especificou na linha de comando (HEAD~3) e reproduzirá (replay) as alterações introduzidas em cada um desses commits, de cima para baixo.
Ele lista o mais antigo no topo, em vez do mais novo, porque esse é o primeiro que ele reproduzirá.
Você precisa editar o script para que ele pare no commit que deseja editar. Para fazer isso, altere a palavra “pick” para a palavra “edit” para cada um dos commits que você deseja que o script pare depois. Por exemplo, para modificar apenas a terceira mensagem de commit, você altera o arquivo para que fique assim:
edit f7f3f6d Change my name a bit
pick 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file
Quando você salvar e sair do editor, o Git voltará para o último commit nessa lista e o deixará na linha de comando com a seguinte mensagem:
$ git rebase -i HEAD~3
Stopped at f7f3f6d... Change my name a bit
You can amend the commit now, with
git commit --amend
Once you're satisfied with your changes, run
git rebase --continue
Estas instruções dizem exatamente o que fazer. Digite:
$ git commit --amend
Altere a mensagem do commit e saia do editor. Então, rode:
$ git rebase --continue
Este comando aplicará os outros dois commits automaticamente e então você terminou.
Se você alterar pick para edit em mais linhas, pode repetir essas etapas para cada commit que você alterou para edit.
Cada vez, o Git irá parar, deixará você alterar o commit e continuará quando você terminar.
Reordenando Commits
Você também pode usar rebases interativos para reordenar ou remover commits completamente. Se você quiser remover o commit “Add cat-file” e alterar a ordem em que os outros dois commits são introduzidos, você pode alterar o script de rebase disto:
pick f7f3f6d Change my name a bit
pick 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file
para isto:
pick 310154e Update README formatting and add blame
pick f7f3f6d Change my name a bit
Quando você salva e sai do editor, o Git retrocede a sua branch para o pai desses commits, aplica 310154e e depois f7f3f6d, e então para.
Você efetivamente alterou a ordem desses commits e removeu o commit “Add cat-file” completamente.
Juntando Commits (Squashing)
Também é possível pegar uma série de commits e juntá-los em um único commit com a ferramenta de rebase interativo. O script coloca instruções úteis na mensagem de rebase:
#
# Commands:
# p, pick <commit> = use commit
# r, reword <commit> = use commit, but edit the commit message
# e, edit <commit> = use commit, but stop for amending
# s, squash <commit> = use commit, but meld into previous commit
# f, fixup <commit> = like "squash", but discard this commit's log message
# x, exec <command> = run command (the rest of the line) using shell
# b, break = stop here (continue rebase later with 'git rebase --continue')
# d, drop <commit> = remove commit
# l, label <label> = label current HEAD with a name
# t, reset <label> = reset HEAD to a label
# m, merge [-C <commit> | -c <commit>] <label> [# <oneline>]
# . create a merge commit using the original merge commit's
# . message (or the oneline, if no original merge commit was
# . specified). Use -c <commit> to reword the commit message.
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out
Se, em vez de “pick” ou “edit”, você especificar “squash”, o Git aplica tanto essa alteração quanto a alteração imediatamente anterior a ela e faz com que você mescle as mensagens de commit. Então, se você quiser fazer um único commit a partir desses três commits, faça o script parecer com isto:
pick f7f3f6d Change my name a bit
squash 310154e Update README formatting and add blame
squash a5f4a0d Add cat-file
Quando você salva e sai do editor, o Git aplica todas as três alterações e então coloca você de volta no editor para mesclar as três mensagens de commit:
# This is a combination of 3 commits.
# The first commit's message is:
Change my name a bit
# This is the 2nd commit message:
Update README formatting and add blame
# This is the 3rd commit message:
Add cat-file
Quando você salva isso, tem um único commit que introduz as alterações de todos os três commits anteriores.
Dividindo um Commit
Dividir um commit desfaz um commit e então prepara (stages) e comita parcialmente quantas vezes forem os commits com os quais você deseja terminar.
Por exemplo, suponha que você queira dividir o commit do meio dos seus três commits.
Em vez de “Update README formatting and add blame”, você quer dividi-lo em dois commits: “Update README formatting” para o primeiro, e “Add blame” para o segundo.
Você pode fazer isso no script do rebase -i alterando a instrução no commit que deseja dividir para “edit”:
pick f7f3f6d Change my name a bit
edit 310154e Update README formatting and add blame
pick a5f4a0d Add cat-file
Então, quando o script deixar você na linha de comando, você reseta esse commit, pega as alterações que foram resetadas e cria vários commits a partir delas.
Quando você salva e sai do editor, o Git volta (rewinds) para o pai do primeiro commit na sua lista, aplica o primeiro commit (f7f3f6d), aplica o segundo (310154e) e deixa você no console.
Lá, você pode fazer um reset misto (mixed) desse commit com git reset HEAD^, que efetivamente desfaz esse commit e deixa os arquivos modificados sem stage (unstaged).
Agora você pode preparar e comitar arquivos até ter vários commits, e rodar git rebase --continue quando terminar:
$ git reset HEAD^
$ git add README
$ git commit -m 'Update README formatting'
$ git add lib/simplegit.rb
$ git commit -m 'Add blame'
$ git rebase --continue
O Git aplica o último commit (a5f4a0d) no script, e o seu histórico fica assim:
$ git log -4 --pretty=format:"%h %s"
1c002dd Add cat-file
9b29157 Add blame
35cfb2b Update README formatting
f7f3f6d Change my name a bit
Isso altera os SHA-1s dos três commits mais recentes na sua lista, então certifique-se de que nenhum commit alterado apareça nessa lista que você já tenha feito push para um repositório compartilhado.
Observe que o último commit (f7f3f6d) na lista permanece inalterado.
Apesar deste commit ser mostrado no script, por ter sido marcado como “pick” e ter sido aplicado antes de quaisquer alterações de rebase, o Git deixa o commit inalterado.
Excluindo um commit
Se você quiser se livrar de um commit, pode excluí-lo usando o script rebase -i.
Na lista de commits, coloque a palavra “drop” (descartar) antes do commit que deseja excluir (ou simplesmente exclua essa linha do script de rebase):
pick 461cb2a This commit is OK
drop 5aecc10 This commit is broken
Devido à forma como o Git constrói objetos de commit, excluir ou alterar um commit fará com que todos os commits subsequentes sejam reescritos. Quanto mais para trás você for no histórico do seu repositório, mais commits precisarão ser recriados. Isso pode causar muitos conflitos de merge se você tiver muitos commits posteriores na sequência que dependam daquele que acabou de excluir.
Se você chegar no meio de um rebase como este e decidir que não é uma boa ideia, pode parar a qualquer momento.
Digite git rebase --abort, e seu repositório retornará ao estado em que estava antes de você começar o rebase.
Se você terminar um rebase e decidir que não é o que queria, pode usar git reflog para recuperar uma versão anterior do seu branch.
Veja Recuperação de Dados para obter mais informações sobre o comando reflog.
|
Note
|
Drew DeVault criou um guia prático (hands-on) com exercícios para aprender a usar o |
A Opção Nuclear: filter-branch
Existe outra opção para reescrever o histórico que você pode usar se precisar reescrever um grande número de commits de alguma forma que possa ser roteirizada (scriptable) — por exemplo, alterar seu endereço de e-mail globalmente ou remover um arquivo de todos os commits.
O comando é filter-branch, e ele pode reescrever enormes partes do seu histórico, então você provavelmente não deveria usá-lo a menos que seu projeto ainda não seja público e outras pessoas não tenham baseado seu trabalho nos commits que você está prestes a reescrever.
No entanto, pode ser muito útil.
Você aprenderá alguns de seus usos comuns para ter uma ideia de algumas das coisas que ele é capaz de fazer.
|
Caution
|
O |
Removendo um Arquivo de Todos os Commits
Isso ocorre com bastante frequência.
Alguém comita acidentalmente um arquivo binário enorme com um git add . sem pensar, e você deseja removê-lo de todos os lugares.
Talvez você tenha comitado acidentalmente um arquivo que continha uma senha e queira tornar o seu projeto open source.
O filter-branch é a ferramenta que você provavelmente quer usar para limpar todo o seu histórico.
Para remover um arquivo chamado passwords.txt de todo o seu histórico, você pode usar a opção --tree-filter do comando filter-branch:
$ git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
Rewrite 6b9b3cf04e7c5686a9cb838c3f36a8cb6a0fc2bd (21/21)
Ref 'refs/heads/master' was rewritten
A opção --tree-filter executa o comando especificado após cada checkout do projeto e depois comita os resultados novamente.
Neste caso, você remove um arquivo chamado passwords.txt de cada snapshot, independentemente de ele existir ou não.
Se você deseja remover todos os arquivos de backup do editor comitados acidentalmente, pode executar algo como git filter-branch --tree-filter 'rm -f *~' HEAD.
Você será capaz de assistir o Git reescrevendo árvores e commits e depois movendo o ponteiro do branch no final.
Geralmente, é uma boa ideia fazer isso num branch de testes e depois fazer um hard-reset no seu branch master depois de determinar que o resultado é o que você realmente quer.
Para rodar o filter-branch em todas as suas branches, você pode passar a opção --all ao comando.
Tornando um Subdiretório na Nova Raiz (Root)
Suponha que você fez uma importação de outro sistema de controle de versão e tem subdiretórios que não fazem sentido (trunk, tags, e assim por diante).
Se você quiser tornar o subdiretório trunk a nova raiz do projeto para todos os commits, o filter-branch pode ajudar com isso, também:
$ git filter-branch --subdirectory-filter trunk HEAD
Rewrite 856f0bf61e41a27326cdae8f09fe708d679f596f (12/12)
Ref 'refs/heads/master' was rewritten
Agora, sua nova raiz do projeto é o que estava no subdiretório trunk a cada vez.
O Git também removerá automaticamente commits que não afetaram o subdiretório.
Alterando Endereços de E-mail Globalmente
Outro caso comum é você ter esquecido de rodar git config para configurar seu nome e endereço de e-mail antes de começar a trabalhar, ou talvez você queira abrir o código de um projeto no trabalho (open-source) e alterar todos os seus endereços de e-mail de trabalho para o seu endereço pessoal.
De qualquer forma, você também pode alterar os endereços de e-mail em vários commits em lote (batch) com o filter-branch.
Você precisa tomar cuidado para alterar apenas os endereços de e-mail que são seus, então você usa --commit-filter:
$ git filter-branch --commit-filter '
if [ "$GIT_AUTHOR_EMAIL" = "schacon@localhost" ];
then
GIT_AUTHOR_NAME="Scott Chacon";
GIT_AUTHOR_EMAIL="schacon@example.com";
git commit-tree "$@";
else
git commit-tree "$@";
fi' HEAD
Isso passará e reescreverá cada commit para ter o seu novo endereço. Como os commits contêm os valores SHA-1 de seus pais, esse comando altera cada SHA-1 de commit no seu histórico, e não apenas aqueles que têm o endereço de e-mail correspondente.