Ramificaciones en Git
Branching, Merging, Colaboración y RebasePara entender realmente como ramifica Git, tenemos que examinar la forma en que almacena sus datos.
Recordemos que en cada confirmacion de cambios, Git almacena una instantanea (snapshot). Dicha snapshot contiene ademas unos metadatos con el autor, mensaje explicativo, y una o varias referencias a las confirmaciones que sean padres directos de esta.
Un padre en los casos de confirmacion normal, y multiples padres en los casos de estar confirmando una fusion merge de dos o mas ramas.
Supongamos que tenemos una carpeta con 3 archivos, preparamos (stage) todos ellos y confirmamos. Al preparar los archivos, Git realiza un checksum para cada uno, almacena una copia de cada uno en el repositorio (las copias se denominan blobs), y guarda cada checksum en el area de preparacion (staging):
git add README test.rb LICENSE
git commit -m 'initial commit of my project'
Cuando creas una confirmación con el comando git commit,
Git realiza checksums de cada subdirectorio,
y las guarda como objetos arbol (tree) en el repositorio Git.
Despues, Git crea un objeto de confirmación con los metadatos pertinentes
y una referencia al objeto arbol raiz del proyecto.
En este momento, el repositorio de Git contendra cinco objetos:
Si hacemos mas cambios y volvemos a confirmar, la siguiente confirmacion guardara una referencia a su confirmacion precedente.
Una rama de Git es simplemente una referencia movil apuntando a una de estas confirmaciones (commit).
La rama por defecto de Git es la rama master.
Con la primera confirmacion de cambios que realicemos, se creara esta rama principal master apuntando a dicha confirmacion.
En cada confirmacion de cambios que realicemos, la rama ira avanzando automaticamente.
¿Que sucede cuando creamos una nueva rama?.
Simplemente se crea una nueva referencia para que la puedas mover libremente.
Por ejemplo supongamos que creamos una nueva rama testing.
Para eso usamos el comando git branch:
git branch testing
¿Como sabe Git en que rama estamos en este momento?.
Lo hace mediante una referencia/puntero especial denominado HEAD.
Este apunta a la rama local en la que estemos en este momento,
en este caso la rama master, ya que el comando git branch solamente crea una nueva rama,
pero no salta a dicha rama.
Esto se puede ver facil con git log --decorate:
git log --oneline --decorate
f30ab (HEAD, master, testing) add feature #32 - ability to add
34ac2 fixed bug #1328 - stack overflow under certain conditions
98ca9 initial commit of my project
Para saltar de una rama a otra, tienes que utilizar el comando git checkout.
El siguiente comando mueve la referencia HEAD a la rama testing:
git checkout testing
¿Cual es el significado de todo esto?. Lo veremos tras realizar otra confirmacion:
git commit -am 'small changes'Observamos algo interesante: la rama testing avanza, mientras que la rama master permanece en la confirmacion donde estaba
cuando lanzamos el comando git checkout para saltar.
Ahora volvamos a la rama master:
git checkout masterEste comando realiza 2 acciones:
HEAD de nuevo a la rama master.master.
Hagamos algunos cambios mas y confirmemoslos:
git commit -am 'thanks for the opportunity'Ahora el historial del proyecto diverge.
Los cambios realizados en ambas sesiones de trabajo (master y testing)
estan aislados en ramas independientes.
Podemos saltar libremente entre una y otra segun estimemos oportuno.
Y todo con 3 simples comandos: git branch, git checkout y git commit
Tambien podemos ver la bifurcacion facilmente utilizando el comando git log.
Ejecutaremos el siguiente comando para ver el historial de las confirmaciones,
indicando donde estan las referencias a las ramas y como ha divergido el historial.
git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) made other changes
| * 87ab2 (testing) made a change
|/
* f30ab add feature #32 - ability to add new formats to the
* 34ac2 fixed bug #1328 - stack overflow under certain conditions
* 98ca9 initial commit of my project
Debido a que una rama de Git es realmente un simple archivo que contiene 40 caracteres de un checksum
SHA-1, no cuesta nada crear y destruir ramas en Git. Crear una nueva rama es tan rapido y simple como escribir 41 bytes en un archivo.
Procedimientos Basicos
Ramificar y FusionarVamos a presentar un ejemplo simple de ramificar y fusionar. Imaginemos que seguimos los siguientes pasos:
En este momento, recibes una llamada avisandote de un problema critico que debes resolver. Y sigues los siguientes pasos:
Imagina que estas trabajando en un proyecto y tienes algunos commits realizados.
Decides trabajar en el problema #53.
Para crear una nueva rama y saltar a ella, en un solo paso utilizamos git checkout -b:
git checkout -b iss53
Switched to a new branch "iss53"
Comparemos ahora el Antes y Despues:
git checkout -b <rama>es un atajo paragit branch <rama>y luegogit checkout <rama>.
Trabajas en el sitio web y haces algunos commits.
Con ello avanzas en la rama iss53, que es la que tienes activa en este momento.
nvim index.html # Editas un archivo
git commit -am 'added a new footer [issue 53]'Entonces, recibes la llamada avisandote de otro problema urgente en el sitio web y debes resolverlo inmediatamente.
Al usar Git, no necesitas mezclar el nuevo problema con los cambios que ya habias realizado sobre el problema #53,
ni tampoco perder tiempo revirtiendo esos cambios para podeer trabajar sobre el contenido que esta en produccion.
Basta con saltar de nuevo a master y seguir trabajando desde ahi.
Pero, antes de poder hacer eso, debemos tomar en cuenta que si tenemos cambios aun no confirmados en el directorio de trabajo o en el area de preparacion,
Git no nos permitira saltar a otra rama con la que podriamos tener conflictos.
Lo mejor siempre es tener un estado de trabajo limpio y despejado antes de saltar entre ramas.
Entonces con todo confirmado, cambiamos a la rama master:
git checkout master
Switched to branch 'master'
Recordemos que Git añade, quita y modifica archivos automaticamente para asegurar que tu copia de trabajo luce exactamente como lucia en la rama en la ultima confirmacion.
A continuacion, es momento de resolver el problema urgente.
Vamos a crear una nueva rama hotfix, sobre la que trabajar hasta resolverlo:
git checkout -b hotfix
Switched to a new branch 'hotfix'
vim index.html
git commit -am 'fixed the broken email address'
[hotfix 1fb7853] fixed the broken email address
1 file changed, 2 insertions(+)Ahora puedes incorporar los cambios a la rama master para ponerlos en produccion.
Para fusionar se utiliza el comando git merge:
git checkout master # Cambiar a la rama master
git merge hotfix # Fusionar hotfix a la rama actual
Updating f42c576..3a0874c
Fast-forward
index.html | 2 ++
1 file changed, 2 insertions(+)
Notar que se utilizo el metodo Fast-forward, esto significa que mueve los commits divergentes de la rama hotfix
adelante de los commits de la rama master. Comparemos Antes de fusionar vs Despues de fusionar:
Luego de haber resuelto el problema urgente, podemos volver donde estabamos trabajando.
Pero antes, eliminemos la rama hotfix que ya no nos va a servir mas. Para esto usamos git branch -d:
git branch -d hotfix
Deleted branch hotfix (3a0874c).
Ahora volvamos al trabajo:
git checkout iss53 # Cambiar a iss53
Switched to branch "iss53"
vim index.html # Editar index.html
git commit -am 'finished the new footer [issue 53]'
[iss53 ad82d7a] finished the new footer [issue 53]
1 file changed, 1 insertion(+)Pero hay un detalle, los cambios fusionados de hotfix no existen en la rama iss53.
Supongamos que tu trabajo con el problema #53 ya esta completo y listo para fusionarlo.
Asi que procedemos a utilizar git merge:
git checkout master # Cambiar a master
Switched to branch 'master'
git merge iss53 # Fusionar iss53 a la rama actual
Merge made by the 'recursive' strategy.
index.html | 1 +
1 file changed, 1 insertion(+)
Esta fusion es diferente a la de hotfix -> master, como podemos ver esta utiliza recursive en vez de fast-forward.
Dado que la confirmacion en la rama actual no es ancestro directo de la rama que pretendemos fusionar,
Git tiene cierto trabajo extra que hacer.
En lugar de simplemente avanzar la referencia de la rama, Git crea una snapshot resultante de la fusion, y crea automaticamente un nuevo commit que apunta a ella. A este proceso se le conoce como merge commit (fusion confirmada) y su particularidad es que tiene mas de un padre.
Git automaticamente determina cual es el mejor ancestro comun para realizar la fusion.
Ahora que todo tu trabajo ya esta fusionado, podemos eliminar iss53 con:
git branch -d iss53
En ocasiones, los procesos de fusion no suelen ser fluidos. Si hiciste cambios en la misma parte de un archivo en las dos ramas distintas que pretendes fusionar, Git no sera capaz de fusionarlas directamente y producira un conflicto:
git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.
Git pausa la fusion y espera a que resuelvas el conflicto.
Puedes ver que archivos estan sin fusionar en cualquier momento tras un conflicto con git status:
git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: index.html
no changes added to commit (use "git add" and/or "git commit -a")
Git añade marcadores de resolucion de conflictos en los archivos con problemas. El archivo afectado tendra una seccion similar a esta:
<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
please contact us at support@github.com
</div>
>>>>>>> iss53:index.html
<<<<<<< HEAD y ======= es la version en tu rama actual (master).======= y >>>>>>> iss53 es la version de la rama que estas fusionando.Debes elegir una de las dos versiones, o combinarlas manualmente. Por ejemplo, podrias reemplazar todo el bloque anterior por esto:
<div id="footer">
please contact us at email.support@github.com
</div>
Una vez resueltos todos los conflictos, ejecuta git add sobre cada archivo afectado
para marcarlo como resuelto. Luego confirma la fusion:
git add index.html
git commit
Git sabe que archivos tenian conflictos y los marca automaticamente al abrir el editor de confirmacion.
El comando git branch tiene mas funciones que solo crear y borrar ramas.
Si lo ejecutas sin parametros, obtienes una lista de todas las ramas del proyecto:
git branch
iss53
* master
testing
El caracter * delante de master indica la rama activa en este momento (la que apunta HEAD).
Si haces un commit ahora, esa sera la rama que avance.
Para ver el ultimo commit de cada rama, puedes usar git branch -v:
git branch -v
iss53 93b412c fix javascript issue
* master 7a98805 Merge branch 'iss53'
testing 782fd34 add scott to the author list in the readmes
Otra opcion util es filtrar las ramas segun si ya han sido fusionadas o no con la rama activa. Para ver las ramas ya fusionadas con la rama actual:
git branch --merged
iss53
* master
La rama iss53 aparece porque ya fue fusionada. Las ramas que no llevan el * delante
pueden eliminarse sin problemas con git branch -d, ya que su contenido ya esta incorporado.
Para ver las ramas con trabajo aun sin fusionar:
git branch --no-merged
testing
Si intentas borrar una rama con trabajo sin fusionar, Git te dara un error:
git branch -d testing
error: The branch 'testing' is not fully merged.
If you are sure you want to delete it, run 'git branch -D testing'.
Si realmente deseas borrarla y perder ese trabajo, puedes forzar el borrado con git branch -D.
3.1 a 3.3