Git (2/3): ramas

Git (2/3): ramas

En la primera parte aprendimos a llevar el control de versiones de un proyecto en local: las áreas de git, la configuración inicial y el flujo de commits. En este segundo capítulo nos sumergimos en las **ramas**: cómo crearlas, moverte entre ellas, fusionarlas y resolver conflictos. 🎸

📚 **Esta entrada es parte de la serie _Guía de Git_**, dividida en tres capítulos que se leen en orden:

> * Parte 1: Control de versiones en local

* 👉 **Parte 2: Ramas**

* Parte 3: Repositorios remotos, stash y tags

Ramaslink image 1

branches

El uso de ramas es muy útil, ya que podemos empezar a desarrollar una nueva característica sin tener que modificar el código original

Crear una rama (git branch <branch name>)link image 2

**Nota**: Antes de nada hay que decir que para crear una rama en un repositorio debe haber al menos un commit, si se intenta crear una rama antes de hacer el primer commit recibiremos un error

Para crear una rama utilizamos el comando git branch <nombre de la rama>

	
< > Input
Python
!cd notebook_git && git branch new_branch
Copied

Listar ramas (git branch)link image 3

Hemos creado nuestra primera rama, podemos comprobar todas las ramas que tenemos creadas escribiendo solo git branch

	
< > Input
Python
!cd notebook_git && git branch
Copied
>_ Output
			
* master
new_branch

Además de listar nos dice con un asterisco * en qué rama estamos, en este caso en la rama master

Renombrar ramas, adiós a la rama master (git branch -m <old name> <new name>)link image 4

Históricamente en git se ha llamado master a la rama principal, pero esto tiene unas connotaciones históricas malas por el concepto de master-slave (maestro-esclavo), debido al sufrimiento que recibieron muchas personas, por lo que ahora se suele catalogar la rama principal como main, así que para cambiarle el nombre usamos git branch -m master main

	
< > Input
Python
!cd notebook_git && git branch -m master main
Copied

Listamos las ramas

	
< > Input
Python
!cd notebook_git && git branch
Copied
>_ Output
			
* main
new_branch

Como vemos, hemos podido cambiar el nombre de la rama principal de master a main

Cambiar de rama (git switch <branch>)link image 5

Si queremos cambiar de rama solo hay que escribir git switch <nombre de la rama>

	
< > Input
Python
!cd notebook_git && git switch new_branch
Copied
>_ Output
			
Cambiado a rama 'new_branch'

Vamos a ver en qué rama estamos con git branch

	
< > Input
Python
!cd notebook_git && git branch
Copied
>_ Output
			
main
* new_branch

Como vemos, hemos cambiado de rama a new_branch

Si queremos crear y cambiar de rama en un solo comando podemos usar git switch -c <nombre de la rama>

	
< > Input
Python
!cd notebook_git && git switch -c new_branch2
Copied
>_ Output
			
Cambiado a nueva rama 'new_branch2'

Vamos a ver en qué rama estamos

	
< > Input
Python
!cd notebook_git && git branch
Copied
>_ Output
			
main
new_branch
* new_branch2

Hemos creado y cambiado de rama con un solo comando

Obtener la rama en la que estamos (git branch --show-current)link image 6

Como hemos visto hasta ahora con git branch podemos obtener una lista de todas las ramas y además ver en la que estamos actualmente, pero en el caso en el que tengamos una gran cantidad de ramas, cosa que se puede dar en un equipo de trabajo con mucha gente, está bien obtener la rama y no obtener una lista de todas, para eso usamos git branch --show-current

	
< > Input
Python
!cd notebook_git && git branch --show-current
Copied
>_ Output
			
new_branch2

Lista de ramas más recientes (git branch --sort=-committerdate)link image 7

En caso de tener muchas ramas, a lo mejor nos interesa saber cuáles son las más recientes para ver cuáles han sido las últimas creadas y dónde debería estar lo último en desarrollo, para eso usamos git branch --sort=-committerdate

	
< > Input
Python
!cd notebook_git && git branch --sort=-committerdate
Copied
>_ Output
			
* new_branch2
new_branch
main

Como vemos, las ha ordenado en orden inverso a cuando las hemos creado

El comando deprecado git checkoutlink image 8

Hasta hace un tiempo el comando para crear ramas y cambiar entre ellas era git checkout, pero este comando no solo hace esto, sino que también restaura el directorio de trabajo. Pero esto va en contra de la filosofía de Linux, por lo que se crearon los comandos git branch, git switch y git restore para dividir esta funcionalidad

Fusionando ramas (git merge)link image 9

Como hemos dicho, crear ramas es muy útil para desarrollar nuevas características sin afectar al resto del equipo. Pero cuando estas están terminadas hay que llevarlas a la rama principal, para ello utilizamos el comando git merge <rama>

**Importante**: Tenemos que estar en la rama que va a adoptar los cambios, es decir, si queremos fusionar los cambios realizados en la rama new_branch2 en la rama main, primero tenemos que asegurarnos de estar en la rama main

Primero comprobamos en qué rama estamos

	
< > Input
Python
!cd notebook_git && git branch --show-current
Copied
>_ Output
			
new_branch2

Eliminamos archivo7.py

	
< > Input
Python
!cd notebook_git && git rm archivo7.py
Copied
>_ Output
			
rm 'archivo7.py'

Hacemos un commit con los cambios

	
< > Input
Python
!cd notebook_git && git commit -am "Eliminado archivo7.py"
Copied
>_ Output
			
[new_branch2 5168f78] Eliminado archivo7.py
1 file changed, 1 deletion(-)
delete mode 100644 archivo7.py

Si hacemos un ls vemos que archivo7.py ya no está

	
< > Input
Python
!cd notebook_git && ls | grep archivo7
Copied

Creamos un nuevo archivo y hacemos un commit

	
< > Input
Python
!cd notebook_git && touch archivo8.py && git add archivo8.py && git commit -m "Commit con el archivo 8"
Copied
>_ Output
			
[new_branch2 564ccfb] Commit con el archivo 8
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 archivo8.py

Hemos hecho dos commits nuevos en esta rama, veámoslo con git log

	
< > Input
Python
!cd notebook_git && git log --graph --oneline --decorate
Copied
>_ Output
			
* 564ccfb (HEAD -&gt; new_branch2) Commit con el archivo 8
* 5168f78 Eliminado archivo7.py
* 4bb9d75 (new_branch, main) Commit con el archivo 7
* ea615a9 Eliminado archivo5.py
* e3153a5 Commit con los archivos 4 y 5
* 0b09cfa Añadido .gitignore
* 04ebd1f Commit con los archivos 1 y 2
* c4930d7 Tercer commit, hola.py
* 6e99e73 Segundo commit, hola.py
* 1c95e4f Primer commit, hola.py

Cambiamos a la rama principal

	
< > Input
Python
!cd notebook_git && git switch main
Copied
>_ Output
			
Cambiado a rama 'main'

Si ahora hacemos otra vez ls veremos que archivo7.py sí está

	
< > Input
Python
!cd notebook_git && ls | grep archivo7
Copied
>_ Output
			
archivo7.py

Fusionamos las ramas, traemos los cambios de new_branch2 a main

	
< > Input
Python
!cd notebook_git && git merge new_branch2
Copied
>_ Output
			
Actualizando 4bb9d75..564ccfb
Fast-forward
archivo7.py | 1 -
archivo8.py | 0
2 files changed, 1 deletion(-)
delete mode 100644 archivo7.py
create mode 100644 archivo8.py

Hacemos un git status

	
< > Input
Python
!cd notebook_git && git status
Copied
>_ Output
			
En la rama main
nada para hacer commit, el árbol de trabajo está limpio

Vemos que al hacer el merge no es necesario hacer ningún commit, veamos con un git log qué ha pasado

	
< > Input
Python
!cd notebook_git && git log --graph --oneline --decorate
Copied
>_ Output
			
* 564ccfb (HEAD -&gt; main, new_branch2) Commit con el archivo 8
* 5168f78 Eliminado archivo7.py
* 4bb9d75 (new_branch) Commit con el archivo 7
* ea615a9 Eliminado archivo5.py
* e3153a5 Commit con los archivos 4 y 5
* 0b09cfa Añadido .gitignore
* 04ebd1f Commit con los archivos 1 y 2
* c4930d7 Tercer commit, hola.py
* 6e99e73 Segundo commit, hola.py
* 1c95e4f Primer commit, hola.py

Vemos que el commit de la rama new_branch2 se ha incorporado a esta rama

Fast-forwardlink image 10

Este caso creamos una nueva rama, la principal no la tocamos y solo modificamos la nueva, realizando varios commits. Por lo que al unir la nueva en la principal se verán todos los commits que se habían hecho en la nueva. Para realizar este tipo de merge escribimos git merge --ff-only <rama>

	
< > Input
Python
### Este código es para crear el gráfico de las ramas, no es necesario para el curso
import graphviz
# Crear el gráfico con la dirección de las flechas de izquierda a derecha
orin = graphviz.Digraph('G', graph_attr={'rankdir': 'LR'})
fast_foward = graphviz.Digraph('G', graph_attr={'rankdir': 'LR'})
# Agregar nodos (pelotas) A, B, C y D en la rama principal
orin.node('A', shape='circle', label='A', color='blue')
orin.node('B', shape='circle', label='B', color='blue')
orin.node('C', shape='circle', label='C', color='blue')
orin.node('D', shape='circle', label='D', color='blue')
orin.node('E', shape='circle', label='', color='transparent')
fast_foward.node('A', shape='circle', label='A', color='blue')
fast_foward.node('B', shape='circle', label='B', color='blue')
fast_foward.node('C', shape='circle', label='C', color='blue')
fast_foward.node('D', shape='circle', label='D', color='blue')
# Agregar nodos (pelotas) X e Y en la rama secundaria
orin.node('X', shape='circle', label='X', color='green')
orin.node('Y', shape='circle', label='Y', color='green')
fast_foward.node('X', shape='circle', label='X', color='magenta')
fast_foward.node('Y', shape='circle', label='Y', color='magenta')
# Agregar flechas entre los nodos en la rama principal
orin.edges(['AB', 'BC', 'CD'])
orin.edge('D', 'E', color='transparent') # Hacer la flecha de C a D transparente
fast_foward.edges(['AB', 'BC', 'CD', 'DX', 'XY'])
# Agregar flechas entre los nodos en la rama secundaria
orin.edges(['DX', 'XY'])
# Mostrar el diagrama de flujo en la celda de código de Jupyter Notebook
display(orin)
display(fast_foward)
Copied
>_ Output
			
&lt;graphviz.graphs.Digraph at 0x7f58f80c09a0&gt;
>_ Output
			
&lt;graphviz.graphs.Digraph at 0x7f58f9203fa0&gt;

Vamos primero a comprobar que estamos en la rama principal

	
< > Input
Python
!cd notebook_git && git branch --show-current
Copied
>_ Output
			
main

Creamos una nueva rama

	
< > Input
Python
!cd notebook_git && git branch branch_fast_forward
Copied

Cambiamos a ella

	
< > Input
Python
!cd notebook_git && git switch branch_fast_forward
Copied
>_ Output
			
Cambiado a rama 'branch_fast_forward'
	
< > Input
Python
!cd notebook_git && git branch --show-current
Copied
>_ Output
			
branch_fast_forward

Vamos a ver el log

	
< > Input
Python
!cd notebook_git && git log --graph --oneline --decorate --all
Copied
>_ Output
			
* 564ccfb (HEAD -&gt; branch_fast_forward, new_branch2, main) Commit con el archivo 8
* 5168f78 Eliminado archivo7.py
* 4bb9d75 (new_branch) Commit con el archivo 7
* ea615a9 Eliminado archivo5.py
* e3153a5 Commit con los archivos 4 y 5
* 0b09cfa Añadido .gitignore
* 04ebd1f Commit con los archivos 1 y 2
* c4930d7 Tercer commit, hola.py
* 6e99e73 Segundo commit, hola.py
* 1c95e4f Primer commit, hola.py

Vale, estamos en una rama creada a partir de la principal con todo su historial de logs, hacemos dos nuevos commits

	
< > Input
Python
!cd notebook_git && git rm archivo4.py && git commit -am "Eliminado archivo4.py"
Copied
>_ Output
			
rm 'archivo4.py'
[branch_fast_forward 4484e70] Eliminado archivo4.py
1 file changed, 1 deletion(-)
delete mode 100644 archivo4.py
	
< > Input
Python
!cd notebook_git && git rm hola.py && git commit -am "Eliminado hola.py"
Copied
>_ Output
			
rm 'hola.py'
[branch_fast_forward 94149fc] Eliminado hola.py
1 file changed, 3 deletions(-)
delete mode 100644 hola.py

Hacemos un nuevo log para ver que en esta nueva rama se han creado

	
< > Input
Python
!cd notebook_git && git log --graph --oneline --decorate
Copied
>_ Output
			
* 94149fc (HEAD -&gt; branch_fast_forward) Eliminado hola.py
* 4484e70 Eliminado archivo4.py
* 564ccfb (new_branch2, main) Commit con el archivo 8
* 5168f78 Eliminado archivo7.py
* 4bb9d75 (new_branch) Commit con el archivo 7
* ea615a9 Eliminado archivo5.py
* e3153a5 Commit con los archivos 4 y 5
* 0b09cfa Añadido .gitignore
* 04ebd1f Commit con los archivos 1 y 2
* c4930d7 Tercer commit, hola.py
* 6e99e73 Segundo commit, hola.py
* 1c95e4f Primer commit, hola.py

Como vemos, los dos últimos commits son los que hemos creado y podemos comprobar que esos commits no están en la rama principal (para eso especifico que lo haga sobre la rama main)

	
< > Input
Python
!cd notebook_git && git log main --graph --oneline --decorate
Copied
>_ Output
			
* 564ccfb (new_branch2, main) Commit con el archivo 8
* 5168f78 Eliminado archivo7.py
* 4bb9d75 (new_branch) Commit con el archivo 7
* ea615a9 Eliminado archivo5.py
* e3153a5 Commit con los archivos 4 y 5
* 0b09cfa Añadido .gitignore
* 04ebd1f Commit con los archivos 1 y 2
* c4930d7 Tercer commit, hola.py
* 6e99e73 Segundo commit, hola.py
* 1c95e4f Primer commit, hola.py

Ahora nos vamos a la rama main para hacer el merge

	
< > Input
Python
!cd notebook_git && git switch main
Copied
>_ Output
			
Cambiado a rama 'main'

Por último, hacemos el merge de tipo fast forward

	
< > Input
Python
!cd notebook_git && git merge --ff-only branch_fast_forward
Copied
>_ Output
			
Actualizando 564ccfb..94149fc
Fast-forward
archivo4.py | 1 -
hola.py | 3 ---
2 files changed, 4 deletions(-)
delete mode 100644 archivo4.py
delete mode 100644 hola.py

Se ha hecho el merge, veamos qué ha pasado con el log en la rama main

	
< > Input
Python
!cd notebook_git && git log --graph --oneline --decorate
Copied
>_ Output
			
* 94149fc (HEAD -&gt; main, branch_fast_forward) Eliminado hola.py
* 4484e70 Eliminado archivo4.py
* 564ccfb (new_branch2) Commit con el archivo 8
* 5168f78 Eliminado archivo7.py
* 4bb9d75 (new_branch) Commit con el archivo 7
* ea615a9 Eliminado archivo5.py
* e3153a5 Commit con los archivos 4 y 5
* 0b09cfa Añadido .gitignore
* 04ebd1f Commit con los archivos 1 y 2
* c4930d7 Tercer commit, hola.py
* 6e99e73 Segundo commit, hola.py
* 1c95e4f Primer commit, hola.py

Como se ve, se han mantenido los dos commits hechos en la rama branch_fast_forward

No fast-forward o true mergelink image 11

En este caso creamos una nueva rama, la principal no la tocamos y modificamos la nueva, realizando varios commits. A continuación realizamos un commit en la principal. Por lo que al unir la nueva en la principal se verá un único commit en la nueva. Para hacer este tipo de merge escribimos git merge <rama> --no-ff

	
< > Input
Python
### Este código es para crear el gráfico de las ramas, no es necesario para el curso
import graphviz
# Crear el gráfico con la dirección de las flechas de izquierda a derecha
orin = graphviz.Digraph('G', graph_attr={'rankdir': 'LR'})
not_fast_forward = graphviz.Digraph('G', graph_attr={'rankdir': 'LR'})
# Agregar nodos (pelotas) A, B, C y D en la rama principal
orin.node('A', shape='circle', label='A', color='blue')
orin.node('B', shape='circle', label='B', color='blue')
orin.node('C', shape='circle', label='C', color='blue')
orin.node('D', shape='circle', label='D', color='blue')
orin.node('E', shape='circle', label='', color='transparent')
not_fast_forward.node('A', shape='circle', label='A', color='blue')
not_fast_forward.node('B', shape='circle', label='B', color='blue')
not_fast_forward.node('C', shape='circle', label='C', color='blue')
not_fast_forward.node('D', shape='circle', label='D', color='blue')
not_fast_forward.node('E', shape='circle', label='E', color='blue')
# Agregar nodos (pelotas) X e Y en la rama secundaria
orin.node('X', shape='circle', label='X', color='green')
orin.node('Y', shape='circle', label='Y', color='green')
not_fast_forward.node('X', shape='circle', label='X', color='green')
not_fast_forward.node('Y', shape='circle', label='Y', color='green')
# Agregar nodo (pelota) M en la rama principal
not_fast_forward.node('M', shape='circle', label='M', color='magenta')
# Agregar flechas entre los nodos en la rama principal
orin.edges(['AB', 'BC', 'CD'])
orin.edge('D', 'E', color='transparent') # Hacer la flecha de C a D transparente
not_fast_forward.edges(['AB', 'BC', 'CD', 'DE'])
# Agregar flechas entre los nodos en la rama secundaria
orin.edges(['DX', 'XY'])
not_fast_forward.edges(['DX', 'XY', 'YM'])
# Agregar flechas de la rama principal al nodo M
not_fast_forward.edge('E', 'M')
# Mostrar el diagrama de flujo en la celda de código de Jupyter Notebook
display(orin)
display(not_fast_forward)
Copied
>_ Output
			
&lt;graphviz.graphs.Digraph at 0x7f58f80c2fb0&gt;
>_ Output
			
&lt;graphviz.graphs.Digraph at 0x7f58f80c2230&gt;

Vamos primero a comprobar que estamos en la rama principal

	
< > Input
Python
!cd notebook_git && git branch --show-current
Copied
>_ Output
			
main

Creamos una nueva rama

	
< > Input
Python
!cd notebook_git && git branch branch_no_fast_forward
Copied

Cambiamos a ella

	
< > Input
Python
!cd notebook_git && git switch branch_no_fast_forward
Copied
>_ Output
			
Cambiado a rama 'branch_no_fast_forward'
	
< > Input
Python
!cd notebook_git && git branch --show-current
Copied
>_ Output
			
branch_no_fast_forward

Vale, estamos en una rama creada a partir de la principal con todo su historial de logs, hacemos dos nuevos commits

	
< > Input
Python
!cd notebook_git && touch file1 && git add file1 && git commit -m "file1"
Copied
>_ Output
			
[branch_no_fast_forward e4e23c9] file1
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 file1
	
< > Input
Python
!cd notebook_git && touch file2 && git add file2 && git commit -m "file2"
Copied
>_ Output
			
[branch_no_fast_forward 8df3429] file2
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 file2

Hacemos un nuevo log para ver qué en esta nueva rama se han creado

	
< > Input
Python
!cd notebook_git && git log --graph --oneline --decorate
Copied
>_ Output
			
* 8df3429 (HEAD -&gt; branch_no_fast_forward) file2
* e4e23c9 file1
* 94149fc (main, branch_fast_forward) Eliminado hola.py
* 4484e70 Eliminado archivo4.py
* 564ccfb (new_branch2) Commit con el archivo 8
* 5168f78 Eliminado archivo7.py
* 4bb9d75 (new_branch) Commit con el archivo 7
* ea615a9 Eliminado archivo5.py
* e3153a5 Commit con los archivos 4 y 5
* 0b09cfa Añadido .gitignore
* 04ebd1f Commit con los archivos 1 y 2
* c4930d7 Tercer commit, hola.py
* 6e99e73 Segundo commit, hola.py
* 1c95e4f Primer commit, hola.py

Como vemos, los dos últimos commits son los que hemos creado y podemos comprobar que esos commits no están en la rama principal (para eso especifico que lo haga sobre la rama main)

	
< > Input
Python
!cd notebook_git && git log main --graph --oneline --decorate
Copied
>_ Output
			
* 94149fc (main, branch_fast_forward) Eliminado hola.py
* 4484e70 Eliminado archivo4.py
* 564ccfb (new_branch2) Commit con el archivo 8
* 5168f78 Eliminado archivo7.py
* 4bb9d75 (new_branch) Commit con el archivo 7
* ea615a9 Eliminado archivo5.py
* e3153a5 Commit con los archivos 4 y 5
* 0b09cfa Añadido .gitignore
* 04ebd1f Commit con los archivos 1 y 2
* c4930d7 Tercer commit, hola.py
* 6e99e73 Segundo commit, hola.py
* 1c95e4f Primer commit, hola.py

Ahora nos vamos a la rama main

	
< > Input
Python
!cd notebook_git && git switch main
Copied
>_ Output
			
Cambiado a rama 'main'

Creamos un nuevo commit

	
< > Input
Python
!cd notebook_git && touch file3 && git add file3 && git commit -m "file3"
Copied
>_ Output
			
[main 8bdf4d8] file3
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 file3

Por último, hacemos el merge de tipo no fast forward

	
< > Input
Python
!cd notebook_git && git merge branch_no_fast_forward --no-ff
Copied
>_ Output
			
ommit. comenzando con '#' serán ignoradas, y un mensaje vacío abortasaria esta&gt;# especialmente si esto fusiona un upstream actualizado en una rama de tópico. /home/wallabot/Documentos/web/portafolio/posts/notebook_git/.git/MERGE_MSG [ línea 1/7 (14%), col 1/48 (2%), car 0/301 (0%) ] [ Párrafo justificado ]...llabot/Documentos/web/portafolio/posts/notebook_git/.git/MERGE_MSG Modificado

Como vemos, nos abre el editor para introducir un mensaje de commit y un mensaje por defecto. Aceptamos el mensaje y vemos qué ha pasado

	
< > Input
Python
!cd notebook_git && git status
Copied
>_ Output
			
En la rama main
nada para hacer commit, el árbol de trabajo está limpio
	
< > Input
Python
!cd notebook_git && git log --graph --oneline --decorate
Copied
>_ Output
			
* 274529c (HEAD -&gt; main) Merge branch 'branch_no_fast_forward' into main
|\
| * 8df3429 (branch_no_fast_forward) file2
| * e4e23c9 file1
* | 8bdf4d8 file3
|/
* 94149fc (branch_fast_forward) Eliminado hola.py
* 4484e70 Eliminado archivo4.py
* 564ccfb (new_branch2) Commit con el archivo 8
* 5168f78 Eliminado archivo7.py
* 4bb9d75 (new_branch) Commit con el archivo 7
* ea615a9 Eliminado archivo5.py
* e3153a5 Commit con los archivos 4 y 5
* 0b09cfa Añadido .gitignore
* 04ebd1f Commit con los archivos 1 y 2
* c4930d7 Tercer commit, hola.py
* 6e99e73 Segundo commit, hola.py
* 1c95e4f Primer commit, hola.py
	
< > Input
Python
!cd notebook_git && ls
Copied
>_ Output
			
api_keys.py archivo1.py archivo2.py archivo8.py file1 file2 file3

Como vemos, se ha creado un nuevo commit con los cambios de la nueva rama en la rama principal

Squashlink image 12

En este tipo de fusión, todos los commits de una nueva rama se juntan en uno solo en la rama principal, para esto escribimos git merge <rama> --squash

	
< > Input
Python
### Este código es para crear el gráfico de las ramas, no es necesario para el curso
import graphviz
# Crear el gráfico con la dirección de las flechas de izquierda a derecha
orin = graphviz.Digraph('G', graph_attr={'rankdir': 'LR'})
squash = graphviz.Digraph('G', graph_attr={'rankdir': 'LR'})
# Agregar nodos (pelotas) A, B, C y D en la rama principal
orin.node('A', shape='circle', label='A', color='blue')
orin.node('B', shape='circle', label='B', color='blue')
orin.node('C', shape='circle', label='C', color='blue')
orin.node('D', shape='circle', label='D', color='blue')
orin.node('E', shape='circle', label='', color='transparent')
squash.node('A', shape='circle', label='A', color='blue')
squash.node('B', shape='circle', label='B', color='blue')
squash.node('C', shape='circle', label='C', color='blue')
squash.node('D', shape='circle', label='D', color='blue')
# Agregar nodos (pelotas) X e Y en la rama secundaria
orin.node('X', shape='circle', label='X', color='green')
orin.node('Y', shape='circle', label='Y', color='green')
# Agregar nodo (pelota) M en la rama principal
squash.node('M', shape='circle', label='M', color='magenta')
# Agregar flechas entre los nodos en la rama principal
orin.edges(['AB', 'BC', 'CD'])
orin.edge('D', 'E', color='transparent') # Hacer la flecha de C a D transparente
squash.edges(['AB', 'BC', 'CD', 'DM'])
# Agregar flechas entre los nodos en la rama secundaria
orin.edges(['DX', 'XY'])
# Mostrar el diagrama de flujo en la celda de código de Jupyter Notebook
display(orin)
display(squash)
Copied
>_ Output
			
&lt;graphviz.graphs.Digraph at 0x7f58f80e6470&gt;
>_ Output
			
&lt;graphviz.graphs.Digraph at 0x7f58f80e6f80&gt;

Vamos primero a comprobar que estamos en la rama principal

	
< > Input
Python
!cd notebook_git && git branch --show-current
Copied
>_ Output
			
main

Creamos una nueva rama

	
< > Input
Python
!cd notebook_git && git branch branch_squash
Copied

Cambiamos a ella

	
< > Input
Python
!cd notebook_git && git switch branch_squash
Copied
>_ Output
			
Cambiado a rama 'branch_squash'
	
< > Input
Python
!cd notebook_git && git branch --show-current
Copied
>_ Output
			
branch_squash

Vamos a ver el log

	
< > Input
Python
!cd notebook_git && git log --graph --oneline --decorate --all
Copied
>_ Output
			
* 274529c (HEAD -&gt; branch_squash, main) Merge branch 'branch_no_fast_forward' into main
|\
| * 8df3429 (branch_no_fast_forward) file2
| * e4e23c9 file1
* | 8bdf4d8 file3
|/
* 94149fc (branch_fast_forward) Eliminado hola.py
* 4484e70 Eliminado archivo4.py
* 564ccfb (new_branch2) Commit con el archivo 8
* 5168f78 Eliminado archivo7.py
* 4bb9d75 (new_branch) Commit con el archivo 7
* ea615a9 Eliminado archivo5.py
* e3153a5 Commit con los archivos 4 y 5
* 0b09cfa Añadido .gitignore
* 04ebd1f Commit con los archivos 1 y 2
* c4930d7 Tercer commit, hola.py
* 6e99e73 Segundo commit, hola.py
* 1c95e4f Primer commit, hola.py

Vale, estamos en una rama creada a partir de la principal con todo su historial de logs, hacemos tres nuevos commits

	
< > Input
Python
!cd notebook_git && git rm file1 && git commit -am "Eliminado file1"
Copied
>_ Output
			
rm 'file1'
[branch_squash 767b632] Eliminado file1
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 file1
	
< > Input
Python
!cd notebook_git && git rm file2 && git commit -am "Eliminado file2"
Copied
>_ Output
			
rm 'file2'
[branch_squash a47f771] Eliminado file2
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 file2
	
< > Input
Python
!cd notebook_git && git rm file3 && git commit -am "Eliminado file3"
Copied
>_ Output
			
rm 'file3'
[branch_squash 85f8c9f] Eliminado file3
1 file changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 file3

Hacemos un nuevo log para ver que en esta nueva rama se han creado

	
< > Input
Python
!cd notebook_git && git log --graph --oneline --decorate
Copied
>_ Output
			
* 85f8c9f (HEAD -&gt; branch_squash) Eliminado file3
* a47f771 Eliminado file2
* 767b632 Eliminado file1
* 274529c (main) Merge branch 'branch_no_fast_forward' into main
|\
| * 8df3429 (branch_no_fast_forward) file2
| * e4e23c9 file1
* | 8bdf4d8 file3
|/
* 94149fc (branch_fast_forward) Eliminado hola.py
* 4484e70 Eliminado archivo4.py
* 564ccfb (new_branch2) Commit con el archivo 8
* 5168f78 Eliminado archivo7.py
* 4bb9d75 (new_branch) Commit con el archivo 7
* ea615a9 Eliminado archivo5.py
* e3153a5 Commit con los archivos 4 y 5
* 0b09cfa Añadido .gitignore
* 04ebd1f Commit con los archivos 1 y 2
* c4930d7 Tercer commit, hola.py
* 6e99e73 Segundo commit, hola.py
* 1c95e4f Primer commit, hola.py

Como vemos, los tres últimos commits son los que hemos creado y podemos comprobar que esos commits no están en la rama principal (para eso especifico que lo haga sobre la rama main)

	
< > Input
Python
!cd notebook_git && git log main --graph --oneline --decorate
Copied
>_ Output
			
* 274529c (main) Merge branch 'branch_no_fast_forward' into main
|\
| * 8df3429 (branch_no_fast_forward) file2
| * e4e23c9 file1
* | 8bdf4d8 file3
|/
* 94149fc (branch_fast_forward) Eliminado hola.py
* 4484e70 Eliminado archivo4.py
* 564ccfb (new_branch2) Commit con el archivo 8
* 5168f78 Eliminado archivo7.py
* 4bb9d75 (new_branch) Commit con el archivo 7
* ea615a9 Eliminado archivo5.py
* e3153a5 Commit con los archivos 4 y 5
* 0b09cfa Añadido .gitignore
* 04ebd1f Commit con los archivos 1 y 2
* c4930d7 Tercer commit, hola.py
* 6e99e73 Segundo commit, hola.py
* 1c95e4f Primer commit, hola.py

Ahora nos vamos a la rama main para hacer el merge

	
< > Input
Python
!cd notebook_git && git switch main
Copied
>_ Output
			
Cambiado a rama 'main'

Hacemos el merge de tipo squash

	
< > Input
Python
!cd notebook_git && git merge branch_squash --squash
Copied
>_ Output
			
Actualizando 274529c..85f8c9f
Fast-forward
Commit de aplastamiento -- no actualizando HEAD
file1 | 0
file2 | 0
file3 | 0
3 files changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 file1
delete mode 100644 file2
delete mode 100644 file3

Se ha hecho el merge, veamos qué ha pasado con el log en la rama main

	
< > Input
Python
!cd notebook_git && git log --graph --oneline --decorate
Copied
>_ Output
			
* 274529c (HEAD -&gt; main) Merge branch 'branch_no_fast_forward' into main
|\
| * 8df3429 (branch_no_fast_forward) file2
| * e4e23c9 file1
* | 8bdf4d8 file3
|/
* 94149fc (branch_fast_forward) Eliminado hola.py
* 4484e70 Eliminado archivo4.py
* 564ccfb (new_branch2) Commit con el archivo 8
* 5168f78 Eliminado archivo7.py
* 4bb9d75 (new_branch) Commit con el archivo 7
* ea615a9 Eliminado archivo5.py
* e3153a5 Commit con los archivos 4 y 5
* 0b09cfa Añadido .gitignore
* 04ebd1f Commit con los archivos 1 y 2
* c4930d7 Tercer commit, hola.py
* 6e99e73 Segundo commit, hola.py
* 1c95e4f Primer commit, hola.py

No aparecen los cambios realizados en la rama, hagamos un git status

	
< > Input
Python
!cd notebook_git && git status
Copied
>_ Output
			
En la rama main
Cambios a ser confirmados:
(usa "git restore --staged &lt;archivo&gt;..." para sacar del área de stage)
borrados: file1
borrados: file2
borrados: file3

Vemos que tenemos que hacer el commit con la fusión. Esto es porque Git no sabe qué mensaje ponerle y nos deja a nosotros que lo hagamos, de modo que lo hacemos

	
< > Input
Python
!cd notebook_git && git commit -m "Merge squash de los commits de la rama branch_squash"
Copied
>_ Output
			
[main 52acb97] Merge squash de los commits de la rama branch_squash
3 files changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 file1
delete mode 100644 file2
delete mode 100644 file3

Volvemos a hacer un git log

	
< > Input
Python
!cd notebook_git && git log --graph --oneline --decorate
Copied
>_ Output
			
* 52acb97 (HEAD -&gt; main) Merge squash de los commits de la rama branch_squash
* 274529c Merge branch 'branch_no_fast_forward' into main
|\
| * 8df3429 (branch_no_fast_forward) file2
| * e4e23c9 file1
* | 8bdf4d8 file3
|/
* 94149fc (branch_fast_forward) Eliminado hola.py
* 4484e70 Eliminado archivo4.py
* 564ccfb (new_branch2) Commit con el archivo 8
* 5168f78 Eliminado archivo7.py
* 4bb9d75 (new_branch) Commit con el archivo 7
* ea615a9 Eliminado archivo5.py
* e3153a5 Commit con los archivos 4 y 5
* 0b09cfa Añadido .gitignore
* 04ebd1f Commit con los archivos 1 y 2
* c4930d7 Tercer commit, hola.py
* 6e99e73 Segundo commit, hola.py
* 1c95e4f Primer commit, hola.py

Conflictos a la hora de fusionarlink image 13

Cuando se fusionan ramas puede pasar que un mismo archivo haya sido modificado en varias ramas. Esto puede ser normal en proyectos donde varias personas están desarrollando, así que vamos a ver cómo solucionarlo

Creamos un conflictolink image 14

Comprobamos que estamos en la rama main

	
< > Input
Python
!cd notebook_git && git branch --show-current
Copied
>_ Output
			
main

Creamos una nueva rama a partir de esta

	
< > Input
Python
!cd notebook_git && git branch rama_con_conflicto
Copied

Añadimos una línea al archivo archivo1.py, recordamos que estamos en la rama main

	
< > Input
Python
!cd notebook_git && echo "print('rama main')" &gt;&gt; archivo1.py
Copied
	
< > Input
Python
!cd notebook_git && cat archivo1.py
Copied
>_ Output
			
print('Este es el archivo 1')
print('rama main')

archivo1.py está modificado, hacemos un commit

	
< > Input
Python
!cd notebook_git && git add archivo1.py && git commit -m "archivo1.py en rama main"
Copied
>_ Output
			
[main 53f909b] archivo1.py en rama main
1 file changed, 1 insertion(+)

Ahora nos vamos a la rama rama_con_conflicto y añadimos una nueva línea a archivo1.py

	
< > Input
Python
!cd notebook_git && git switch rama_con_conflicto
Copied
>_ Output
			
Cambiado a rama 'rama_con_conflicto'
	
< > Input
Python
!cd notebook_git && git branch --show-current
Copied
>_ Output
			
rama_con_conflicto
	
< > Input
Python
!cd notebook_git && echo "print('rama rama_con_conflicto')" &gt;&gt; archivo1.py
Copied
	
< > Input
Python
!cd notebook_git && cat archivo1.py
Copied
>_ Output
			
print('Este es el archivo 1')
print('rama rama_con_conflicto')

Como vemos, archivo1.py no es igual en la rama main y en la rama rama_con_conflicto. Hacemos un commit con la modificación de archivo1.py en la rama rama_con_conflicto

	
< > Input
Python
!cd notebook_git && git add archivo1.py && git commit -m "archivo1.py en rama rama_con_conflicto"
Copied
>_ Output
			
[rama_con_conflicto 32851c3] archivo1.py en rama rama_con_conflicto
1 file changed, 1 insertion(+)

Volvemos a la rama main

	
< > Input
Python
!cd notebook_git && git switch main
Copied
>_ Output
			
Cambiado a rama 'main'
	
< > Input
Python
!cd notebook_git && git branch --show-current
Copied
>_ Output
			
main

Hacemos un merge de la rama rama_con_conflicto

	
< > Input
Python
!cd notebook_git && git merge rama_con_conflicto
Copied
>_ Output
			
Auto-fusionando archivo1.py
CONFLICTO (contenido): Conflicto de fusión en archivo1.py
Fusión automática falló; arregle los conflictos y luego realice un commit con el resultado.

Al hacer el merge ya nos avisa que hay un conflicto en archivo1.py y que no se ha podido hacer el merge. Hacemos un git status

	
< > Input
Python
!cd notebook_git && git status
Copied
>_ Output
			
En la rama main
Tienes rutas no fusionadas.
(arregla los conflictos y ejecuta "git commit"
(usa "git merge --abort" para abortar la fusion)
Rutas no fusionadas:
(usa "git add &lt;archivo&gt;..." para marcar una resolución)
modificados por ambos: archivo1.py
sin cambios agregados al commit (usa "git add" y/o "git commit -a")

Nos dice lo mismo, pero nos da más información, nos dice que podemos abortar la fusión con git merge --abort. Pero en lugar de eso vamos a solucionarlo

Solucionar un conflictolink image 15

En nuestro caso sabemos dónde está el problema, pero en caso de no saberlo, mediante git diff podemos encontrar el problema

	
< > Input
Python
!cd notebook_git && git diff archivo1.py
Copied
>_ Output
			
diff --cc archivo1.py
index 8b4bf58,b5c003c..0000000
--- a/archivo1.py
+++ b/archivo1.py
@@@ -1,2 -1,2 +1,6 @@@
print('Este es el archivo 1')
++&lt;&lt;&lt;&lt;&lt;&lt;&lt; HEAD
+print('rama main')
++=======
+ print('rama rama_con_conflicto')
++&gt;&gt;&gt;&gt;&gt;&gt;&gt; rama_con_conflicto

git diff nos está diciendo que el problema está en la última línea. La versión del archivo de la rama HEAD (en la que estamos actualmente main) tiene print('rama main') en la última línea, mientras que la versión de la rama rama_con_conflicto tiene print('rama rama_con_conflicto') en la última línea. De modo que hay que abrir el fichero con cualquier editor y solucionar esto.

Tras editar el fichero con mi editor de código he quitado todas las líneas que sobran y se ha quedado así

	
< > Input
Python
!cd notebook_git && cat archivo1.py
Copied
>_ Output
			
print('Este es el archivo 1')
print('rama main')

Ahora hacemos un git status para ver qué tenemos que hacer

	
< > Input
Python
!cd notebook_git && git status
Copied
>_ Output
			
En la rama main
Tienes rutas no fusionadas.
(arregla los conflictos y ejecuta "git commit"
(usa "git merge --abort" para abortar la fusion)
Rutas no fusionadas:
(usa "git add &lt;archivo&gt;..." para marcar una resolución)
modificados por ambos: archivo1.py
sin cambios agregados al commit (usa "git add" y/o "git commit -a")

Nos dice que tenemos el archivo archivo1.py con modificaciones, por lo que lo añadimos al área de staged y luego hacemos un commit

	
< > Input
Python
!cd notebook_git && git add archivo1.py && git commit -m "archivo1.py con el merge resuelto"
Copied
>_ Output
			
[main 679bb49] archivo1.py con el merge resuelto

Volvemos a hacer un git status a ver si se ha solucionado

	
< > Input
Python
!cd notebook_git && git status
Copied
>_ Output
			
En la rama main
nada para hacer commit, el árbol de trabajo está limpio

Parece que se ha resuelto, hacemos un git log para comprobarlo

	
< > Input
Python
!cd notebook_git && git log --graph --oneline --decorate
Copied
>_ Output
			
* 679bb49 (HEAD -&gt; main) archivo1.py con el merge resuelto
|\
| * 32851c3 (rama_con_conflicto) archivo1.py en rama rama_con_conflicto
* | 53f909b archivo1.py en rama main
|/
* 52acb97 Merge squash de los commits de la rama branch_squash
* 274529c Merge branch 'branch_no_fast_forward' into main
|\
| * 8df3429 (branch_no_fast_forward) file2
| * e4e23c9 file1
* | 8bdf4d8 file3
|/
* 94149fc (branch_fast_forward) Eliminado hola.py
* 4484e70 Eliminado archivo4.py
* 564ccfb (new_branch2) Commit con el archivo 8
* 5168f78 Eliminado archivo7.py
* 4bb9d75 (new_branch) Commit con el archivo 7
* ea615a9 Eliminado archivo5.py
* e3153a5 Commit con los archivos 4 y 5
* 0b09cfa Añadido .gitignore
* 04ebd1f Commit con los archivos 1 y 2
* c4930d7 Tercer commit, hola.py
* 6e99e73 Segundo commit, hola.py
* 1c95e4f Primer commit, hola.py

Eliminar ramaslink image 16

La idea de una rama es abrirla para desarrollar una nueva funcionalidad o característica de manera que no afecte al resto del equipo de desarrollo. Por lo tanto, cuando esto está conseguido, se fusiona con la rama principal. Así que después de la fusión ya no tiene sentido mantener la rama, por lo que hay que eliminarla, pero se hará de una manera diferente si la rama se ha fusionado o no con la principal

Eliminar ramas que se han fusionado (git branch -d <rama>)link image 17

Para eliminar una rama sobre la que ya se ha hecho la fusión con la principal solo hay que hacer git branch -d <rama> o git branch --delete <rama>. Vamos a eliminar la última rama que hemos creado y fusionado

	
< > Input
Python
!cd notebook_git && git branch -d rama_con_conflicto
Copied
>_ Output
			
Eliminada la rama rama_con_conflicto (era 32851c3).

Si ahora listamos todas las ramas, podemos ver que ya no tendremos rama_con_conflicto

	
< > Input
Python
!cd notebook_git && git branch
Copied
>_ Output
			
branch_fast_forward
branch_no_fast_forward
branch_squash
* main
new_branch
new_branch2

Eliminar ramas que no se han fusionado con la rama principal (git branch -D <rama>)link image 18

Si intentamos hacer lo mismo que antes con una rama que nunca se ha fusionado con otra, obtendremos un error

Vamos a crear una nueva rama, vamos a hacer un commit y no la vamos a fusionar

	
< > Input
Python
!cd notebook_git && git branch branch_sin_fusion
Copied
	
< > Input
Python
!cd notebook_git && git switch branch_sin_fusion
Copied
>_ Output
			
Cambiado a rama 'branch_sin_fusion'
	
< > Input
Python
!cd notebook_git && touch file4 && git add file4 && git commit -m "file4"
Copied
>_ Output
			
[branch_sin_fusion 9506b0a] file4
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 file4
	
< > Input
Python
!cd notebook_git && git switch main
Copied
>_ Output
			
Cambiado a rama 'main'

Con todo lo que hemos aprendido hasta ahora, podemos ver que lo que hemos hecho ha sido crear una nueva rama, cambiar a ella, crear un nuevo archivo, hacer un commit y volver a la rama main. Ahora vamos a intentar eliminar esta nueva rama

	
< > Input
Python
!cd notebook_git && git branch -d branch_sin_fusion
Copied
>_ Output
			
error: La rama 'branch_sin_fusion' no ha sido fusionada completamente.
Si estás seguro de querer borrarla, ejecuta 'git branch -D branch_sin_fusion'.

Como vemos, nos dice que branch_sin_fusion no ha sido fusionada, por lo que no se ha eliminado, y que si estamos seguros de eliminarla tenemos que hacer git branch -D branch_sin_fusion, de modo que lo hacemos para eliminarla

	
< > Input
Python
!cd notebook_git && git branch -D branch_sin_fusion
Copied
>_ Output
			
Eliminada la rama branch_sin_fusion (era 9506b0a).

Ahora sí se ha eliminado

---

➡️ **Continúa en la Parte 3: repositorios remotos, stash y tags**, donde llevarás tu código a la nube y le sacarás partido a las herramientas avanzadas de Git.

Seguir leyendo

Últimos posts -->

¿Has visto estos proyectos?

Gymnasia

Gymnasia Gymnasia
React Native
Expo
TypeScript
FastAPI
Next.js
OpenAI
Anthropic

Aplicación móvil de entrenamiento personal con asistente de IA, biblioteca de ejercicios, seguimiento de rutinas, dieta y medidas corporales

Horeca chatbot

Horeca chatbot Horeca chatbot
Python
LangChain
PostgreSQL
PGVector
React
Kubernetes
Docker
GitHub Actions

Chatbot conversacional para cocineros de hoteles y restaurantes. Un cocinero, jefe de cocina o camaeror de un hotel o restaurante puede hablar con el chatbot para obtener información de recetas y menús. Pero además implementa agentes, con los cuales puede editar o crear nuevas recetas o menús

Naviground

Naviground Naviground
Ver todos los proyectos -->
>_ Disponible para proyectos

¿Tienes un proyecto con IA?

Hablemos.

maximofn@gmail.com

Especialista en Machine Learning e Inteligencia Artificial. Desarrollo soluciones con IA generativa, agentes inteligentes y modelos personalizados.

¿Quieres ver alguna charla?

Últimas charlas -->

¿Quieres mejorar con estos tips?

Últimos tips -->

Usa esto en local

Los espacios de Hugging Face nos permite ejecutar modelos con demos muy sencillas, pero ¿qué pasa si la demo se rompe? O si el usuario la elimina? Por ello he creado contenedores docker con algunos espacios interesantes, para poder usarlos de manera local, pase lo que pase. De hecho, es posible que si pinchas en alún botón de ver proyecto te lleve a un espacio que no funciona.

Flow edit

Flow edit Flow edit

Edita imágenes con este modelo de Flow. Basándose en SD3 o FLUX puedes editar cualquier imagen y generar nuevas

FLUX.1-RealismLora

FLUX.1-RealismLora FLUX.1-RealismLora
Ver todos los contenedores -->
>_ Disponible para proyectos

¿Tienes un proyecto con IA?

Hablemos.

maximofn@gmail.com

Especialista en Machine Learning e Inteligencia Artificial. Desarrollo soluciones con IA generativa, agentes inteligentes y modelos personalizados.

¿Quieres entrenar tu modelo con estos datasets?

short-jokes-dataset

HuggingFace

Dataset de chistes en inglés

Uso: Fine-tuning de modelos de generación de texto humorístico

231K filas 2 columnas 45 MB
Ver en HuggingFace →

opus100

HuggingFace

Dataset con traducciones de inglés a español

Uso: Entrenamiento de modelos de traducción inglés-español

1M filas 2 columnas 210 MB
Ver en HuggingFace →

netflix_titles

HuggingFace

Dataset con películas y series de Netflix

Uso: Análisis de catálogo de Netflix y sistemas de recomendación

8.8K filas 12 columnas 3.5 MB
Ver en HuggingFace →
Ver más datasets -->