-
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.13 Ferramentas do Git - Substituir (Replace)
Substituir (Replace)
Como já enfatizamos anteriormente, os objetos no banco de dados de objetos do Git são imutáveis, mas o Git fornece uma maneira interessante de fingir substituir objetos em seu banco de dados por outros objetos.
O comando replace permite especificar um objeto no Git e dizer "toda vez que você se referir a este objeto, finja que é um objeto diferente".
Isto é mais comumente útil para substituir um commit no seu histórico por outro, sem ter que reconstruir todo o histórico com, digamos, o git filter-branch.
Por exemplo, digamos que você tenha um enorme histórico de código e deseje dividir o seu repositório em um histórico curto para novos desenvolvedores e um histórico muito maior e mais longo para pessoas interessadas em garimpar dados. Você pode enxertar um histórico no outro "substituindo" o primeiro commit da linha nova pelo último commit da antiga. Isto é ótimo porque significa que você não precisa realmente reescrever todos os commits do histórico novo, como você normalmente teria que fazer para juntá-los (já que a descendência afeta os SHA-1s).
Vamos tentar isso.
Vamos pegar um repositório existente, dividi-lo em dois repositórios, um recente e um histórico, e então veremos como nós podemos recombiná-los sem modificar os valores de SHA-1 do repositório recente usando o replace.
Nós vamos usar um repositório simples com cinco commits simples:
$ git log --oneline
ef989d8 Fifth commit
c6e1e95 Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit
Nós queremos quebrar isso em duas linhas de histórico. A primeira linha vai do commit um ao commit quatro - esta será a histórica. A segunda linha será composta apenas pelos commits quatro e cinco - esta será a de histórico recente.
Bom, criar o histórico histórico é fácil, nós podemos apenas colocar uma branch no histórico e depois dar um push desta branch para a branch master de um novo repositório remoto.
$ git branch history c6e1e95
$ git log --oneline --decorate
ef989d8 (HEAD, master) Fifth commit
c6e1e95 (history) Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit
history
Agora nós podemos dar um push da nova branch history para a branch master do nosso novo repositório:
$ git remote add project-history https://github.com/schacon/project-history
$ git push project-history history:master
Counting objects: 12, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (12/12), 907 bytes, done.
Total 12 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (12/12), done.
To git@github.com:schacon/project-history.git
* [new branch] history -> master
OK, nosso histórico está publicado. Agora a parte mais difícil é truncar (encurtar) nosso histórico recente para que ele fique menor. Nós precisamos de uma sobreposição para que possamos substituir um commit em um deles por um commit equivalente no outro, então nós vamos truncar isto deixando somente os commits quatro e cinco (fazendo com que o commit quatro se sobreponha).
$ git log --oneline --decorate
ef989d8 (HEAD, master) Fifth commit
c6e1e95 (history) Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit
É útil neste caso criar um commit base que tenha instruções de como expandir o histórico, assim os outros desenvolvedores saberão o que fazer se baterem no primeiro commit do histórico truncado e precisarem de mais. Então, o que vamos fazer é criar um objeto commit inicial, sendo o nosso ponto base com instruções, e então realizar um rebase nos commits remanescentes (quatro e cinco) em cima dele.
Para se fazer isso, precisamos escolher um ponto em que faremos a divisão, que para nós é o terceiro commit, que é o 9c68fdc no jargão do SHA.
Assim, nosso commit base será baseado nesta árvore (tree).
Nós podemos criar nosso commit base utilizando o comando commit-tree, o qual simplesmente apanha a tree (árvore) e nos devolve um brand new SHA-1 de objeto de commit e que não tem ascendência (parentless).
$ echo 'Get history from blah blah blah' | git commit-tree 9c68fdc^{tree}
622e88e9cbfbacfb75b5279245b9fb38dfea10cf
|
Note
|
O comando |
commit-tree
Agora que temos um commit-base, podemos fazer rebase do restante do histórico sobre ele com git rebase --onto.
O argumento --onto será o SHA-1 que acabamos de receber de commit-tree, e o ponto do rebase será o terceiro commit (o pai do primeiro commit que queremos manter, 9c68fdc):
$ git rebase --onto 622e88 9c68fdc
First, rewinding head to replay your work on top of it...
Applying: fourth commit
Applying: fifth commit
Muito bem, agora reescrevemos nosso histórico recente sobre um commit-base descartável que contém instruções para reconstituir todo o histórico, caso quiséssemos. Podemos fazer push desse novo histórico para um novo projeto; quando alguém clonar esse repositório, verá apenas os dois commits mais recentes e, em seguida, um commit-base com instruções.
Agora vamos assumir o papel de alguém que clona o projeto pela primeira vez e quer obter todo o histórico. Para obter os dados do histórico depois de clonar esse repositório truncado, essa pessoa teria de adicionar um segundo remote para o repositório histórico e fazer fetch:
$ git clone https://github.com/schacon/project
$ cd project
$ git log --oneline master
e146b5f Fifth commit
81a708d Fourth commit
622e88e Get history from blah blah blah
$ git remote add project-history https://github.com/schacon/project-history
$ git fetch project-history
From https://github.com/schacon/project-history
* [new branch] master -> project-history/master
Sendo desta forma o companheiro estaria retendo num braço de código dito master suas mais novas submissões (commits), mantendo as de enredo dantescas nesse que é o ramo nomeado de project-history/master.
$ git log --oneline master
e146b5f Fifth commit
81a708d Fourth commit
622e88e Get history from blah blah blah
$ git log --oneline project-history/master
c6e1e95 Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit
A fim da união recíproca dos ditos arranjos, chame o git replace conjugando aquele commit tencionado em ser substituído com a peça do que em seu lugar fará a devida permuta.
No presente ensaio o alvo de toda essa ação é aquele quarto commit abrigado na envergadura atrelada à branch master, que sairá afim do commit quarto advindo de project-history/master:
$ git replace 81a708d c6e1e95
Agora, se você examinar o histórico do branch master, ele terá esta aparência:
$ git log --oneline master
e146b5f Fifth commit
81a708d Fourth commit
9c68fdc Third commit
945704c Second commit
c1822cf First commit
Legal, não é? Sem precisar alterar todos os SHA-1s do upstream, conseguimos substituir um commit do histórico por outro completamente diferente, e todas as ferramentas comuns (bisect, blame etc.) funcionarão como esperado.
git replace
Curiosamente, ele ainda mostra 81a708d como o SHA-1, embora esteja usando os dados do commit c6e1e95 pelo qual o substituímos.
Mesmo que você execute um comando como cat-file, ele mostrará os dados substitutos:
$ git cat-file -p 81a708d
tree 7bc544cf438903b65ca9104a1e30345eee6c083d
parent 9c68fdceee073230f19ebb8b5e7fc71b479c0252
author Scott Chacon <schacon@gmail.com> 1268712581 -0700
committer Scott Chacon <schacon@gmail.com> 1268712581 -0700
fourth commit
Lembre-se de que o pai real de 81a708d era nosso commit substituto (622e88e), e não 9c68fdce, como aparece aqui.
Outro aspecto interessante é que esses dados ficam armazenados em nossas referências:
$ git for-each-ref
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/heads/master
c6e1e95051d41771a649f3145423f8809d1a74d4 commit refs/remotes/history/master
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/remotes/origin/HEAD
e146b5f14e79d4935160c0e83fb9ebe526b8da0d commit refs/remotes/origin/master
c6e1e95051d41771a649f3145423f8809d1a74d4 commit refs/replace/81a708dd0e167a3f691541c7a6463343bc457040
Isso significa que é fácil compartilhar nossa substituição com outras pessoas, pois podemos fazer push dela para o servidor e elas podem baixá-la facilmente. Isso não é tão útil no cenário de enxerto de histórico que examinamos aqui (afinal, todos baixariam ambos os históricos; então, por que separá-los?), mas pode ser útil em outras circunstâncias.