Git
Chapters ▾ 2nd Edition

7.13 Herramientas de Git - Replace

Replace

Los objetos de Git son inmutables, pero proporciona una manera interesante de pretender reemplazar objetos en su base de datos con otros objetos.

El comando replace le permite especificar un objeto en Git y decir" cada vez que vea esto, fingir que es esta otra cosa ". Esto es más útil para reemplazar un commit en tu historia con otro.

Por ejemplo, supongamos que tiene un gran historial de códigos y desea dividir su repositorio en un breve historial para nuevos desarrolladores y una historia mucho más larga para las personas interesadas en la minería de datos. Puede injertar una historia en la otra mediante `replace`ing el commit más antiguo en la nueva línea con el último commit en el anterior. Esto es bueno porque significa que en realidad no tienes que reescribir cada commit en la nueva historia, como normalmente tendrías que hacer para unirlos juntos (porque el parentesco efectúa los SHA-1s).

Vamos a probar esto. Tomemos un repositorio existente, lo dividimos en dos repositorios, uno reciente y otro histórico, y luego veremos cómo podemos recombinarlos sin modificar los repositorios recientes SHA-1 a través de replace.

Usaremos un repositorio sencillo con cinco compromisos simples:

$ git log --oneline
ef989d8 fifth commit
c6e1e95 fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit

Queremos dividir esto en dos líneas de la historia. Una línea pasa de comprometer uno a cometer cuatro - que será el histórico. La segunda línea sólo se compromete cuatro y cinco - que será la historia reciente.

replace1

Bueno, la creación de la historia histórica es fácil, sólo podemos poner una rama en la historia y luego empujar esa rama a la rama principal de un nuevo repositorio 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
replace2

Ahora podemos hacer push a la nueva rama history a la rama` master` de nuestro nuevo repositorio:

$ 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, así que nuestra historia se publica. Ahora la parte más difícil es truncar nuestra historia reciente por lo que es más pequeño. Necesitamos una superposición para que podamos reemplazar un commit en uno con un commit equivalente en el otro, por lo que vamos a truncar esto a sólo comete cuatro y cinco (así cometer cuatro superposiciones).

$ git log --oneline --decorate
ef989d8 (HEAD, master) fifth commit
c6e1e95 (history) fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit

En este caso, es útil crear un commit de base que tenga instrucciones sobre cómo expandir el historial, por lo que otros desarrolladores saben qué hacer si acceden al primer commit en el historial truncado y necesitan más. Por lo tanto, lo que vamos a hacer es crear un objeto de confirmación inicial como nuestro punto base con instrucciones, luego rebase los compromisos restantes (cuatro y cinco) encima de él.

Para ello, debemos elegir un punto para dividir en, que para nosotros es el tercer commit, que es 9c68fdc en SHA-speak. Por lo tanto, nuestra comisión base se basará en ese árbol. Podemos crear nuestro commit base con el comando commit-tree, que solo toma un árbol y nos dará un nuevo objeto de commit sin padres SHA-1.

$ echo 'get history from blah blah blah' | git commit-tree 9c68fdc^{tree}
622e88e9cbfbacfb75b5279245b9fb38dfea10cf
Note

El comando commit-tree es uno de un conjunto de comandos que comúnmente se denominan comandos plumbing. Estos son comandos que no suelen ser utilizados directamente, sino que son utilizados por ** otros comandos Git para hacer trabajos más pequeños. En ocasiones, cuando estamos haciendo cosas más extrañas como estas, nos permiten hacer cosas de nivel muy bajo, pero no son para uso diario. Puede leer más acerca de los comandos de plomería en Fontanería y porcelana

replace3

OK, así que ahora que tenemos un commit de base, podemos rebase el resto de nuestra historia encima de eso con 'rebase de git --onto`. El argumento --onto será el SHA-1 que acabamos de regresar de` commit-tree` y el punto de rebase será el tercer commit (el padre del primer commit que queremos mantener, 9c68fdc):

$ git rebase --onto 622e88 9c68fdc
First, rewinding head to replay your work on top of it...
Applying: fourth commit
Applying: fifth commit
replace4

Así que ahora hemos re-escrito nuestra historia reciente en la parte superior de un tiro de base de comisión que ahora tiene instrucciones sobre cómo reconstruir toda la historia si queríamos. Podemos empujar esa nueva historia a un nuevo proyecto y ahora, cuando las personas clonen ese repositorio, solo verán los dos compromisos más recientes y luego un commit de base con instrucciones.

Cambiemos de roles a alguien que clonara el proyecto por primera vez y que quiere toda la historia. Para obtener los datos del historial después de clonar este repositorio truncado, habría que añadir un segundo mando a distancia para el repositorio histórico y buscar:

$ 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

Ahora el colaborador tendría sus compromisos recientes en la rama master y los compromisos históricos en la rama 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

Para combinarlos, simplemente puede llamar a git replace con el commit que desea reemplazar y luego el commit con el que desea reemplazarlo. Así que queremos reemplazar el "cuarto" commit en la rama maestra con el "cuarto" commit en la rama project-history/master:

$ git replace 81a708d c6e1e95

Ahora bien, si nos fijamos en la historia de la rama master, parece que se ve así:

$ git log --oneline master
e146b5f fifth commit
81a708d fourth commit
9c68fdc third commit
945704c second commit
c1822cf first commit

Genial, ¿verdad? Sin tener que cambiar todos los SHA-1s upstream, pudimos reemplazar un commit en nuestra historia con un commit totalmente diferente y todas las herramientas normales (bisect,` blame`, etc.) funcionarán como esperamos .

replace5

Curiosamente, todavía muestra 81a708d como el SHA-1, a pesar de que en realidad está utilizando los datos de confirmación` c6e1e95` con los que lo reemplazamos. Incluso si ejecuta un comando como cat-file, le mostrará los datos reemplazados:

$ 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

Recuerde que el padre real de 81a708d fue nuestro placeholder commit (622e88e), no 9c68fdce, como se indica aquí.

Otra cosa interesante es que estos datos se guardan en nuestras referencias:

$ 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

Esto significa que es fácil compartir nuestro reemplazo con otros, porque podemos empujar esto a nuestro servidor y otras personas pueden descargarlo fácilmente. Esto no es tan útil en el escenario de injerto de historia que hemos pasado aquí (ya que todo el mundo estaría descargando ambas historias de todos modos, ¿por qué separarlas?), Pero puede ser útil en otras circunstancias.