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
te permite especificar un objeto en Git y decirle "cada vez que vea esto, fingir que es esta otra cosa". Esto es más útil para reemplazar un ``commit'' en tu historial con otro.
Por ejemplo, supongamos que tienes un gran historial de códigos y deseas dividir tu 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. Puedes injertar una historia en la otra mediante replace
ingresando 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 lo efectúan 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 compromete cuatro y cinco - que será la historia reciente.
Bueno, la creación del historial histórico es fácil, sólo tenemos que 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
Ahora podemos hacer push de 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 [email protected]: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 para hacerla más pequeña. Necesitamos una superposición para que podamos reemplazar un commit'' en una con un
commit'' equivalente en la otra, por lo que vamos a truncar esto a sólo cometer cuatro y cinco (y 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 hacemos un ``rebase'' de los compromisos restantes (cuatro y cinco) encima de él.
Para ello, debemos elegir un punto para dividir, que es 9c68fdc
en SHA-speak (para nosotros es el tercer commit). Por lo tanto, nuestra comisión base se basará en ese árbol. Podemos crear nuestro commit base'' con el comando
commit'' sin padres SHA-1.commit-tree
, que solo toma un árbol y nos dará un nuevo objeto de
$ echo 'get history from blah blah blah' | git commit-tree 9c68fdc^{tree}
622e88e9cbfbacfb75b5279245b9fb38dfea10cf
Note
|
El comando |
OK, así que ahora que tenemos un commit'' de base, podemos hacer
rebase'' al resto de nuestra historia encima de éste 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
Así que 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 quisiéramos. 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 .
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 <[email protected]> 1268712581 -0700
committer Scott Chacon <[email protected]> 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.