Ramas
Un 'branch' (rama) en git es simplemente un apuntador móvil a un commit.
La rama por defecto que GIT crea por nosotros es la rama 'master'
Como buenas practicas, deberia ser ésta la versión estable de la aplicación y deberá usarse otras para el desarrollo.
Con la primera confirmación de cambios, se creará automaticamente apuntando a el commit realizado. A medida que realicemos mas confirmaciones de cambios la rama irá avanzando sola y 'master' irá puntando siempre al ultimo commit hecho.
¿Qué sucede si creamos una nueva rama?
Se creara un nuevo apuntador para que se pueda mover libremente sin afectar a 'master', todas las modificaciones y commits que hagamos 'master' no las verá reflejas.
En la siguiente imagen se ven 3 commits realizados(en verde) a los cuales tanto la rama 'master' puede verlos y la nueva rama 'testing' tambien, pero de aqui en mas harán camino separados.
Para crear una nueva rama utilizamos branch seguido del nombre de la nueva rama:
$ git branch testing
Por defecto git branch listara todas las ramas existente, al igual que con el parametro --list
¿Cómo sabemos entonces en que rama estamos parado?
Al ejecutar el comando git branch debemos obtener algo similiar a :
$ git branch
* master
testing
Pero existe un apuntador especial llamado HEAD (Cabeza) que apunta a la rama local actual.
Para poder intercambiar de ramas se utliza el comando chekout
$ git checkout testing
Switched to branch 'testing'
Ahora la situacion del repositorio a pasado, a tener 2 ramas, y se ha movido el HEAD a 'testing'
Tambien podremos observar que en la lineas de comandos donde se encuetra el repositorio a cambio de (master) a (testing) indicando en que rama estamos parado:
La idea de branches se clarifica mas al continuar realizando commiteos en esta nueva rama. Una forma rápita de realizar muchos commits es hacerlos vacios, lo cual no tiene mucho sentido pero en este caso de aprendizaje es ideal, para hacerlo:
$ git commit --allow-empty -m "1er commit vacio"
[testing 29fa38e] 1er commit vacio
Ahora la situación gráficamente se vuelve:
Hay que notar que la rama testing avanza, mientra que master continua en el ultimo commit que se realizó.
Si nos movemos a la rama master...
$ git checkout master
Al realizar este comando estariamos movilizando el HEAD al ultimo commit realizado en master, pudiendo observar todos los commits que realizaron aqui, pero no las modificaciones realizadas en testing.
Este comando realiza 2 aciones en simultaneo:
- Mueve el apuntador HEAD a la rama master.
- Revierte los archivos de tu directorio de trabajo al momento de realizar el ultimo commit en master
Esto supone que los cambios realizados de acá en adelante solo rigiran para la rama master y solo en esta dirección.
De continuar trabajando sobre esta rama, se revierte las modificaciones hechas en testing y habra 2 versiones de un mismo proyecto.
Al realizar modificaciones, incluirlas en el staging index, y confirmar los cambios, la situacion gráficamente se verá tal como:
Una rama en git es solamente un archivo que contiene 40 caracteresde una suma de control SHA-1 (basado en el contenido de los archivos y no en su metadata) no cuesta nada al construir y destruir ramas en GIT.
Esto aventaja a GIT como software de control de versiones, ya que otros para crear una rama nueva, se necesitaba copiar el contenido existente del ultimo commit, y depediendo del proyecto podria tardar minutos si es lo suficientemente grande.
Para GIT es rápido y sencillo, solo le lleva 41 bytes entre los 40 caracteres y un retorno de carro.
Diferentes formas de llamar a un branch
Existen varias formas de apuntar a un branch:
- A través de su nombre:
- A través de su codigo abreviado de 7 caracteres:
- A través de su codigo de 40 caracteres:
-
$ git branch 29fa38e8b214015dc6e289d13e802b346b66d931
- A través del HEAD si este lo esta apuntando:
- Y atraves de los operadores de GIT
Fusión de Branchs (Merge)
Unificar ramas supone haber cumplido con la tarea (issue, track, ticket, etc), la cual nos incentivo a generar la rama en si. Pero esta fusión de ramas trae aparejada que estrategia seguir ya que no todos los casos de repositorios son iguales:
- Podriamos tener una branch con commits hechos a partir de la separación y el otro no.
- Tener un branch con archivos 'a', 'b', 'c' y el otro con 'd', 'e' y 'g'.
- O podriamos tener los mismos archivos pero de diferentes tamaños y con distintas metadata.
Basicamente nuestro procedimiento deberia de ser:
- Creamos una rama a partir de otra
- Realizamos nuestra tarea, requerimiento, etc
- Comprometemos los cambios
-
$ git commit -m "implementacion hecha"
- Nos movemos a la rama para fusionar sobre la que trabajamos(gralmente suele fusionarse contra master)
- Realizamos el mergeo
- También en este punto podríamos eliminar la rama que ya no necesitaremos
Ahora, después que nuestro merge
se realiza con éxito en nuestro repositorio puede darse dos posibles casos:
- Una unión de ramas con una estrategia fast-forward(avance rápido)
- O ua estrategia 3-way (merge a tres bandas).
Fast-Forward (ff)
La estrategia de fast-forward es cuando al momento de hacer el nuevo brnach, se deja de hacer modificaciones en master y por ende al momento de mergear solo se le incluyen los commits de la rama altenativa como propia.
Para GIT lo unico que hace es mover el HEAD de master a el HEAD del nuevo branch:
Tambien existe el caso, de hacer un commit al momento de hacer el merge poniendo los parametros --no-ff en el comando para hacer la union de ramas.
$ git merge --no-ff test
Visualmente el repositorio quedaría asi:
Three ways (3-way)
Pero por otro lado, si al momento de hacer el mergeo, exiten mas confirmaciones de cambio en una de las ramas, (master en nuestro caso) nya no podra ser posible la estrategia fast-forward y será necesario hacer un commit (como en el caso anterior --no-ff) para unificar las ramas:
GIT realiza un merge 3-way (tres bandas), es decir que genera un 'commit' para unificar las ramas, tomando en cuenta el HEAD de cada una de ellas y el antepasado común, de ahi su nombre.
De aquí en mas, dependiendo del proyecto, será necesaria la intervención humana para la resolución de conflictos y decidir con cual archivo conflictivo quedarnos, del mismo que al pegar el contenido de una carpeta nuestro sistema operativo nos advierte y nos da la opcion de reemplazar u omitir.
Por ejemplo al intentar fusionar 2 branchs (master y feature) con un solo archivo llamado README.txt donde varian el contenido en una sola linea, la consola se veria de la siguiente forma:
$ git merge feature
Auto-merging README.txt
CONFLICT (content): Merge conflict in README.txt
Automatic merge failed; fix conflicts and then commit the result.
$ git status
On branch master
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add ..." to mark resolution)
both modified: README.txt
no changes added to commit (use "git add" and/or "git commit -a")
$ git diff
diff --cc README.txt
index 6007a7c,e8d6d88..0000000
--- a/README.txt
+++ b/README.txt
@@@ -1,1 -1,1 +1,5 @@@
++<<<<<<< HEAD
+Unica linea en master
++=======
+ linea conflictiva de feature
++>>>>>>> feature
$ git commit -a
[master caaf175] Merge branch 'feature'
Al realizar esta acción forzamos a realizar el mergeo, simplemente nos genera un archivo "híbrido" con el contenido de ambos, tal como nos muestra en la consola, resultado final es:
<<<<<<< HEAD
+Unica linea en master
+ linea conflictiva de feature
++>>>>>>> feature
Por lo cual es recomendable realizar al obtener el error de merge, modificar el archivo tal como queremos, en este caso bastara con:
$ vim README.txt
<<<<<<< HEAD
+Unica linea en master
+ linea conflictiva de feature
++>>>>>>> feature
'i' para editar:
+Unica linea en master
salimos con esc ':wq'
Resolviendo conflictos: Ours - Theirs
Otra manera de resolver estos conflictos, es avisarle a GIT con cual archivo queremos quedarnos sabiendo o no que pueden haber conflicto, con ours(nuestros) y theirs(suyos) hablando de cambios.
$ git merge -s recursive -X ours testing
o
$ git merge -s recursive -X theirs testing
Cancelando un merge
Tal como nos decia la ayuda, si al momento de hacer un merge nos rejectea podemos deshacer los nuevos cambios con:
$ git merge --abort
Deshaciendo un merge
Para deshacer un merge, podemos intentar:
$ git reset --merge ORIG_HEAD