GIT: Ramificaciones

  • Cualquier sistema de control de versiones moderno tiene algún mecanismo para soportar el uso de ramas. Cuando hablamos de ramificaciones, significa que tú has tomado la rama principal de desarrollo (master) y a partir de ahí has continuado trabajando sin seguir la rama principal de desarrollo

¿Qué es una rama?

  • Para entender que es una rama rama, debemos entender como git almacena sus datos. Git almacena los datos como instantáneas (copias puntuales de los archivos completos)
  • Para ilustrar esto si tenemos un proyecto que cuenta con 3 archivos:
$ git add README test.rb LICENSE
$ git commit -m 'initial commit of my project'
  • al hacer el commit el repositorio de GIT contendrá 5 objetos:
    • Un blob con el contenido de cada archivo, es decir 3 blob’s
    • Un árbol con la lista de contenidos del directorio y su relación a los blob’s de cada uno
    • Y un commit apuntando a la raíz de ese árbol que contiene los metadatos (autor, mensaje,etc)
  • De forma ilustrada
  • Ahora si creas otro commit dentro de este se almacena una referencia a su padre , de forma:
  • Entonces: ¿que es una rama git? es un apuntador a un commit. Por lo que la rama defecto es «master» y es el primer commit del proyecto, el cual va ir avanzando según los commit’s. Algo así:

Crear una Rama Nueva

  • ¿Qué sucede cuando creamos una nuevas rama? simplemente creamos un nuevo apuntador que se puede mover libremente
  • Vamos a ingresar en nuestro proyecto «primer_proyecto_git»
cd /var/www/html/primer_proyecto_git/
  • Ahora para crear una nueva rama vamos hacer
git branch testing
  • Esto creará un nuevo apuntador apuntando a la misma confirmación donde estés actualmente.
  • Si ahora contamos con 2 ramas como sabe git en cual rama estamos pues con un apuntador especial llamado «HEAD» que es un simple apuntador a la rama local y ¿en qué rama estamos? en la rama master ya que «git branch» solamente crea la rama no salta a ella.
  • ¿Como podemos saber donde estamos? con «git log»
git log --oneline --decorate
23a8cce (HEAD -> master, tag: v1.2-lw, tag: v1.2, tag: v1.1, origin/master, testing) Primera versión del poryecto a ser subido a GIT
1c2c2e3 Modificado el estilo
7b8f18e se recupero archivo README
94e7ed3 creacion 2 de tmp
70f7a90 eliminado fisico de archivo tmp
afbe8a5 Creación de archivo tmp.html
94bce8d Creación de archivo .gitignore
17b842c (tag: v1.0) Incio del proyecto: V1.001 / estructura inicial
  • O simplemente con
git log
commit 23a8cce276dc3765cb5f79e782353068c94821b0 (HEAD -> master, tag: v1.2-lw, tag: v1.2, tag: v1.1, origin/master, testing)
Author: abelhongo1983 <gustavo.matamoros.gonzalez@una.ac.cr>
Date: Fri Mar 9 09:07:15 2018 -0600

 Primera versión del poryecto a ser subido a GIT
  • Aquí puedes ver que «HEAD», apunta a «master» y que de este commit salen las 2 ramas «master» y «testing»

Cambiar de Rama

  • Para pasar de una rama a otra se usa el comando «git checkout»
git checkout testing
tavo@SER-DESARROLLO1:/var/www/html/primer_proyecto_git$ git checkout testing
Switched to branch 'testing'
  • Es lo que hace es mover el apuntador «HEAD» a «testing»
  • ¿Qué significa esto? bueno vamos a crear un nuevo archivo y vamos a confirmar los cambios
touch login.html
git commit -m "creación archivo login.html"
  • Esto crea un nuevo commit el cual solo se aplica sobre la rama «testing», es decir «master» se mantiene y «testing» avanza
  • Ahora regresamos a «master»
git branch master
  • Interpretado:
  • Esta acción va a realizar 2 acciones:
    • Va mover el apuntador «HEAD» al master
    • Y va a remplazar todos los archivos del proyecto a esta instantánea. Por loq ue si hacemos el comando «ls» para listar los archivos podemos ver que el archivo «login.html» no existe
tavo@SER-DESARROLLO1:/var/www/html/primer_proyecto_git$ ls
css index.html index.php js README
  • Ahora creamos otro archivo en la rama «master» y confirmamos los cambios
touch inscripcion.html
git add .
git commit -m "creación de archivo inscripción.html"
  • Lo que sucede es:
  • Ahora podemos ver es con el comando:
git log --oneline --decorate --graph --all
* eaddb8b (HEAD -> master) creación de archivo inscripción.html
| * dffb3be (testing) creación archivo login.html
|/ 
* 23a8cce (tag: v1.2-lw, tag: v1.2, tag: v1.1, origin/master) Primera versión del poryecto a ser subido a GIT
* 1c2c2e3 Modificado el estilo
* 7b8f18e se recupero archivo README
* 94e7ed3 creacion 2 de tmp
* 70f7a90 eliminado fisico de archivo tmp
* afbe8a5 Creación de archivo tmp.html
* 94bce8d Creación de archivo .gitignore
* 17b842c (tag: v1.0) Incio del proyecto: V1.001 / estructura inicial
  • Aquí podemos ver la bifurcación del proyecto
  • Si deseamos crear una nueva rama y ingresar a ella directamente podemos usar el parámetro «-b» con «checkout»
git checkout -b hotfix

Procedimientos Básicos para Ramificar y Fusionar

  • Cuando terminamos los cambios por ejemplo en la rama «testing» y deseamos unirla con la «master» debemos usar el comando «merge», para esto vamos a ingresar a la rama «master» y luego las fusionamos
git checkout master
git merge testing -m "fusion testing =>master" 
  • Con esto si hacemos el comando «ls» para listar los archivos podemos ver que ya contamos con los archivos «login y inscripción» en la rama «master»
tavo@SER-DESARROLLO1:/var/www/html/primer_proyecto_git$ ls
css index.html index.php inscripcion.html js login.html README
  • Ahora como ya hemos terminado de utilizar la rama «testing» ya podemos eliminarla para esto
git branch -d testing
Eliminada la rama testing (era dffb3be)

Principales Conflictos que Pueden Surgir en las Fusiones

  • En el ejemplo anterior no hubo problemas de fusión ya que todos eran archivos nuevos, pero que pasaría si creamos una nueva rama «error53» y ingresamos a ella.
git checkout -b error53
  • Luego modificamos el archivo «index.html», por ejemplo modificando el título
<title>Primer Proyecto GIT</title>
X
<title>Primer Proyecto GIT cambio de error53</title>
  • Confirmamos los cambios
git status
git add .
git commit -m "cambio titulo error 53"
  • Ahora ingresamos a la rama «master»
git checkout master
  • Y modificamos el título:
<title>Primer Proyecto GIT</title>
X
<title>Primer Proyecto GIT titulo de master</title>
  • Confirmamos los cambios
git status
git add .
git commit -m "Cambio titulo de master"
  • Ahora como queremos fusionar la rama «master» con «error53» y ya estamos en «master» no es necesario pasarnos de rama y podemos hacer la fusión:
git merge error53
  • Solo que ahora nos va a mostrar un error:
Automezclado index.html
CONFLICTO(contenido): conflicto de fusión en index.html
Automatic merge failed; fix conflicts and then commit the result.
  • Donde nos indica que no se puedo fusionar el archivo «index.html» y si vemos el código veremos:
<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
<<<<<<< HEAD
    <title>Primer Proyecto GIT titulo de master</title>
=======
    <title>Primer Proyecto GIT cambio de error53</title>
>>>>>>> error53
</head>
<body>
  • Donde nos indica «<<<<<<< HEAD» esto es el código que esta en la rama «HEAD», es la rama actual donde queremos fusionar en nuestro caso «master» el fin del código de esta rama se representa con «=======» así como también representa el inicio del código de «error53» el cual termina en «>>>>>>> error53».
  • Así que aquí nosotros debemos tomar la decisión de que queremos hacer, para nuestro ejemplo, vamos a cambiar el titulo completamente, pero podríamos dejar el del «HEAD» o «error53», según sea el caso.
<title>Título de unión</title>
  • Guardamos los cambios
git status
git add .
git commit -m "Fusión erro53=>master"
  • Y ya que corregimos el «error53» podemos eliminar la rama
git branch -d error53
  • Si quieres ver los conflictos con una herramienta de visualización puede usar «git mergetool» que lanzará la herramienta respectiva
git mergetool

Gestión de Ramas

  • Listar ramas:
git branch
* master
  • Note que el asterisco (*) indica en cual rama se encuentra
  • Para listar los últimos comit’s de cada rama:
git branch -v
* master 6659a48 Fusión erro53=>master
  • Ver la ramas que han sido fusionadas con la actual:
git branch --merged
iss53
* master
  • Cualquier rama que aparezca aquí que no lleve (*) puede ser eliminada con tranquilad
  • Para ver las ramas que no han sido fusionadas
git branch --no-merged
testing
  • Si la intentamos eliminar git nos dará un error, donde si deseamos eliminarla debemos hacer: «git branch -D testing»
$ 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'.

Ramas Remotas

  • Hasta este momento hemos hablado de las ramas de forma local ¿Pero que pasa cuando trabajamos con servidores remotos?
  • Recordemos que «master» y «origin» no tienen ningún significado especial para GIT, solo que cuando ejecutar git init le da el nombre a la rama «master» y cuando clonas un proyecto git le da el nombre de «origin» a menos que los cambies con «git clone -o nuevo_nombre».
  • Entonces si clonamos un proyecto en este punto el apuntador de rama «master» tanto en el repositorio local como el remoto están apuntando al mismo commit
  • Si haces algún trabajo en tu rama master local, y al mismo tiempo, alguien más lleva (push) su trabajo al servidor git.ourcompany.com, actualizando la rama master de allí, te encontrarás con que ambos registros avanzan de forma diferente. Además, mientras no tengas contacto con el servidor, tu apuntador a tu rama origin/master no se moverá.
  • Para sincronizarte, puedes utilizar el comando git fetch origin. Este comando localiza en qué servidor está el origen (en este caso git.ourcompany.com), recupera cualquier dato presente allí que tú no tengas, y actualiza tu base de datos local, moviendo tu rama origin/master para que apunte a la posición más reciente.
  • Para sincronizarte, puedes utilizar el comando git fetch origin. Este comando localiza en qué servidor está el origen (en este caso git.ourcompany.com), recupera cualquier dato presente allí que tú no tengas, y actualiza tu base de datos local, moviendo tu rama origin/master para que apunte a la posición más reciente.
  • Para ilustrar mejor el caso de tener múltiples servidores y cómo van las ramas remotas para esos proyectos remotos, supongamos que tienes otro servidor Git; utilizado por uno de tus equipos sprint, solamente para desarrollo. Este servidor se encuentra en git.team1.ourcompany.com. Puedes incluirlo como una nueva referencia remota a tu proyecto actual, mediante el comando git remote add, tal y como vimos en Fundamentos de Git. Puedes denominar teamone a este remoto al asignarle este nombre a la URL.
  • Ahora, puedes usar el comando git fetch teamone para recuperar todo el contenido del remoto teamone que tú no tenias. Debido a que dicho servidor es un subconjunto de los datos del servidor origin que tienes actualmente, Git no recupera (fetch) ningún dato; simplemente prepara una rama remota llamada teamone/master para apuntar a la confirmación (commit) que teamone tiene en su rama master

Publicar

  • Cuando desea compartir una rama con el resto del mundo, debes subirla (push) a un servidor remoto donde tengas permiso de escritura, ya que no se sincronizan de forma automática, esto para que puedas usas ramas privadas y solo subir las que deseas.
  • Entonces para subir nuestro a github trabajo hacemos
git remote
tavo@SER-DESARROLLO1:/var/www/html/primer_proyecto_git$ git remote 
origin
  • Aquí podemos ver que tenemos un servidor remoto asociado
  • Entonces
git push origin master
Username for 'https://github.com': abelhongo1983
Password for 'https://abelhongo1983@github.com': 
Counting objects: 15, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (15/15), done.
Writing objects: 100% (15/15), 1.50 KiB | 1.50 MiB/s, done.
Total 15 (delta 10), reused 0 (delta 0)
remote: Resolving deltas: 100% (10/10), completed with 2 local objects.
To https://github.com/abelhongo1983/git_trabajar_remotos.git
 23a8cce..6659a48 master -> master
  • Con esto ya hemos subido los archivos a github
  • Ahora vamos a crear una nueva rama local «error54»
git checkout -b erro54
  • Vamos hacer un cambio en el archivo login agregándole:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>

</body>
  • Guardamos los cambios
git status
git add .
git commit -m "Se agrego codigo a login.html"
  • Ahora si consultamos las ramas:
git branch
* erro54
 master
  • Ahora podemos subir la rama al servidor remoto:
git push origin erro54
Username for 'https://github.com': abelhongo1983
Password for 'https://abelhongo1983@github.com': 
Counting objects: 3, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 377 bytes | 377.00 KiB/s, done.
Total 3 (delta 1), reused 0 (delta 0)
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To https://github.com/abelhongo1983/git_trabajar_remotos.git
 * [new branch] erro54 -> erro54
  • Y si lo vemos en github podemos ver que ya contamos con esa nueva rama
  • También se podía hacer:
git push origin erro54:erro54
  • Que significa: coge mi erro54 y hazlo el erro54 remoto esto permite crear una rama remota con un nombre distinto

NOTA:

  • Cada vez que hemos subido el servidor remoto nos pregunta el usuario y contraseña de github si no quieres estar haciendo esto puedes establecer un “cache de credenciales”. La manera más sencilla de hacerlo es estableciendo en memoria por unos minutos, lo que puedes lograr fácilmente al ejecutar git config –global credential.helper cache

Continuando..

  • En este momento si algún compañero del equipo hace un fetch para obtener cambios desde el servidor
git fetch origin
  • Va indicarte que existe una nueva rama y donde esta guardada
De https://github.com/abelhongo1983/git_trabajar_remotos
 * [rama nueva] erro54 -> origin/erro54
  • Pero NO obtienes una copia editable de la misma, es decir no tienes una nueva rama «erro54», de hecho si hacemos:
git branch
  • Vemos que solo tenemos la master
* master
  • Esto es porque solo tenemos un puntero no editable a «origin/erro55»
  • Aquí hora tenemos 2 opción deseamos integrar (merge) nuestro repositorio local con esta rama para poderla trabajar entonces haríamos:
git merge origin/erro54
  • O sí deseamos tener la rama de forma local para poderla trabajar podemos crear la rama local basándonos en la remota
git checkout -b erro54 origin/erro54
Branch erro55 set up to track remote branch erro55 from origin.
Se ha cambiado a una rama nueva, «erro55»
  • Ahora si el compañero hace un «git branch» puede ver que tiene las dos ramas de forma local
git branch
* erro54
 master

Hacer Seguimiento a las Ramas

  • Al hacer lo anterior «crear una rama local a partir de una remota», se crea automáticamente una «rama de seguimiento» (traking branch) que es una rama local que tiene relación con una rama remota y si tecleas el comando git pull, Git sabe de cuál servidor recuperar (fetch) y fusionar (merge) datos.
  • En el ejemplo anterior cuando hicimos:
git checkout -b erro54 origin/erro54
  • creamos una rama de seguimiento a «origin/erro54» y esto es tan común que git ofrece el comando
git checkout --track origin/erro54
  • Si ya tienes una rama local y quieres asignarla a una rama remota que acabas de traerte, o quieres cambiar la rama a la que le haces seguimiento, puedes usar en cualquier momento las opciones -u o –set-upstream-to del comando git branch
git branch -u origin/serverfix

NOTA:

  • Atajo al upstream
    Cuando tienes asignada una rama de seguimiento, puedes hacer referencia a ella mediante @{upstream} o mediante el atajo @{u}. De esta manera, si estás en la rama master y esta sigue a la rama origin/master, puedes hacer algo como git merge @{u} en vez de git merge origin/master.

Continuando

  • Si quieres ver las ramas de seguimiento puede usar el comando:
git branch -vv
* erro55 3bdeca3 [origin/erro55] Se agrego codigo a login.html
 master 6659a48 [origin/master] Fusión erro53=>master

Traer y Fusionar

  • Cuando ejecutamos «git fetch» git trae todos los cambios del servidor que no tienes, pero no modifica tu directorio de trabajo es decir no los fusiona.
  • Si desea traer los cambios y fusionarlos se usa el comando:
git pull <remote> <branch>
  • esto haría una «git fetch» seguido de un «git merge»

Eliminar Ramas Remotas

  • Para eliminar una rama remota:
git push origin --delete erro54
Username for 'https://github.com': abelhongo1983
Password for 'https://abelhongo1983@github.com': 
To https://github.com/abelhongo1983/git_trabajar_remotos.git
 - [deleted] erro54

Reorganizar el Trabajo Realizado

  • En Git tenemos dos formas de integrar cambios de una rama en otra: la fusión (merge) y la reorganización (rebase). En esta sección vas a aprender en qué consiste la reorganización, cómo utilizarla, por qué es una herramienta sorprendente y en qué casos no es conveniente utilizarla.

Reorganización Básica

  • Imaginemos que hemos creado dos ramas «experiment» y «master».
  • La manera más sencilla es «git merge» que realiza una fusión a tres bandas entre las dos últimas instantáneas de cada rama (C3 y C4) y el ancestro común a ambas (C2); creando una nueva instantánea (snapshot) y la correspondiente confirmación (commit).
  • Sin embargo, también hay otra forma de hacerlo: puedes coger los cambios introducidos en C3 y reaplicarlos encima de C4. Esto es lo que en Git llamamos reorganizar (rebasing, en inglés). Con el comando git rebase, puedes coger todos los cambios confirmados en una rama, y reaplicarlos sobre otra.
git checkout experiment
git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command
  • Haciendo que Git vaya al ancestro común de ambas ramas (donde estás actualmente y de donde quieres reorganizar), saque las diferencias introducidas por cada confirmación en la rama donde estás, guarde esas diferencias en archivos temporales, reinicie (reset) la rama actual hasta llevarla a la misma confirmación en la rama de donde quieres reorganizar, y, finalmente, vuelva a aplicar ordenadamente los cambios.
  • En este momento, puedes volver a la rama master y hacer una fusión con avance rápido (fast-forward merge).
git checkout master
git merge experiment
  • Así, la instantánea apuntada por C4′ es exactamente la misma apuntada por C5 en el ejemplo de la fusión. No hay ninguna diferencia en el resultado final de la integración, pero el haberla hecho reorganizando nos deja un historial más claro. Si examinas el historial de una rama reorganizada, este aparece siempre como un historial lineal: como si todo el trabajo se hubiera realizado en series, aunque realmente se haya hecho en paralelo.  

Algunas Reorganizaciones Interesantes

  • También puedes aplicar una reorganización (rebase) sobre otra cosa además de sobre la rama de reorganización. Por ejemplo, considera un historial como el de Un historial con una rama puntual sobre otra rama puntual. Has ramificado a una rama puntual (server) para añadir algunas funcionalidades al proyecto, y luego has confirmado los cambios. Después, vuelves a la rama original para hacer algunos cambios en la parte cliente (rama client), y confirmas también esos cambios. Por último, vuelves sobre la rama server y haces algunos cambios más.
  • Imagina que decides incorporar tus cambios del lado cliente sobre el proyecto principal para hacer un lanzamiento de versión; pero no quieres lanzar aún los cambios del lado servidor porque no están aún suficientemente probados. Puedes coger los cambios del cliente que no están en server (C8 y C9) y reaplicarlos sobre tu rama principal usando la opción –onto del comando git rebase:
git rebase --onto master server client
  • Esto viene a decir: “Activa la rama client, averigua los cambios desde el ancestro común entre las ramas client y server, y aplicalos en la rama master”
  • Y, tras esto, ya puedes avanzar la rama principal
git checkout master
git merge client
  • Ahora supongamos que decides traerlos (pull) también sobre tu rama server. Puedes reorganizar (rebase) la rama server sobre la rama master sin necesidad siquiera de comprobarlo previamente, usando el comando git rebase [rama-base] [rama-puntual], el cual activa la rama puntual (server en este caso) y la aplica sobre la rama base (master en este caso):
git rebase master server
  • Esto vuelca el trabajo de server sobre el de master, tal y como se muestra en Reorganizando la rama server sobre la rama master.
  • Después, puedes avanzar rápidamente la rama base (master):
git checkout master
git merge server
  • Y por último puedes eliminar las ramas client y server porque ya todo su contenido ha sido integrado y no las vas a necesitar más
git branch -d client
git branch -d server

Los Peligros de Reorganizar

  • Nunca reorganices confirmaciones de cambio (commits) que hayas enviado (push) a un repositorio público.
  • Cuando reorganizas algo, estás abandonando las confirmaciones de cambio ya creadas y estás creando unas nuevas; que son similares, pero diferentes. Si envias (push) confirmaciones (commits) a alguna parte, y otros las recogen (pull) de allí; y después vas tú y las reescribes con git rebase y las vuelves a enviar (push); tus colaboradores tendrán que refusionar (re-merge) su trabajo y todo se volverá tremendamente complicado cuando intentes recoger (pull) su trabajo de vuelta sobre el tuyo.
  • Veamos con un ejemplo como reorganizar trabajo que has hecho público puede causar problemas. Imagínate que haces un clon desde un servidor central, y luego trabajas sobre él.
  • Ahora, otra persona trabaja también sobre ello, realiza una fusión (merge) y lleva (push) su trabajo al servidor central. Tú te traes (fetch) sus trabajos y los fusionas (merge) sobre una nueva rama en tu trabajo, con lo que tu historial quedaría parecido a esto:
  • A continuación, la persona que había llevado cambios al servidor central decide retroceder y reorganizar su trabajo; haciendo un git push –force para sobrescribir el registro en el servidor. Tu te traes (fetch) esos nuevos cambios desde el servidor.
  • Ahora los dos están en un aprieto. Si haces git pull crearás una fusión confirmada, la cual incluirá ambas líneas del historial, y tu repositorio lucirá así:

Si ejecutas git log sobre un historial así, verás dos confirmaciones hechas por el mismo autor y con la misma fecha y mensaje, lo cual será confuso. Es más, si luego tu envías (push) ese registro de vuelta al servidor, vas a introducir todas esas confirmaciones reorganizadas en el servidor central. Lo que puede confundir aún más a la gente. Era más seguro asumir que el otro desarrollador no quería que C4 y C6 estuviesen en el historial; por ello había reorganizado su trabajo de esa manera.