Este es el tercer post de la serie de post sobre Git y GitHub en el que vamos a tratar conflictos a la hora de poner nuestro código en el código principal, o master.

 

Para este post voy a tratar todos los ejemplos sobre el mismo branch de los ejemplos anteriores. el cual esta disponible en github.

Para poder realizar lo que quiero mostrar. He creado un branch llamado Conflicto el cual parte del estado actual de nuestro branch principal.

En este branch lo que haremos será modificar la clase vehiculo y llamarla desde el método main.

 

Posteriormente he creado un cambio en el branch principal y hecho un commit, haciendo un cambio en el método main; el cambio es el siguiente:

static void Main(string[] args)
{
    Console.WriteLine("Hello World!");
    Console.ReadKey();
}
//Cambio a 
static void Main(string[] args)
{
    Console.WriteLine("Introudce tu nombre:");
    string nombre = Console.ReadLine();
    Console.WriteLine($"El nombre es {nombre}");
}

Como vemos es un cambio muy sencillo y la estructura actual es la siguiente:

branch estado1

Para el ejemplo únicamente he realizado un commit, pero podrían ser tantos como quisiéramos. 

Para continuar nos movemos a nuestro nuevo branch haciendo el comando git checkout conflicto.

 

En este branch lo que vamos a hacer es realizar nuestro cambio de código, únicamente añadimos una nueva propiedad a la clase, que contendrá los cv del coche, posteriormente los imprimimos en nuestro método main

class Vehiculo
{
    public decimal VelocidadMaxima { get; set; }
    public int NumeroPuertas { get; set; }
    public int Cv { get; set; }

    public Vehiculo(decimal velocidadMaxima, int numeroPuertas, int cv)
    {
        VelocidadMaxima = velocidadMaxima;
        NumeroPuertas = numeroPuertas;
        Cv = cv;
    }
}

var coche = new Vehiculo(200, 5, 90);
Console.WriteLine($"El coche tiene {coche.Cv}"); 
Console.ReadKey();

Como podemos observar, modificamos en ambas versiones la misma parte de código dentro de nuestro método main. Lo cual nos va a llevar irreversiblemente a un conflicto. 

 

Veremos más adelante cómo modificar el fichero para tratar el conflicto. Primero debemos poner nuestra mente en marcha para pensar que siempre vamos a tener conflictos.

Nota: Recordad que todos los comandos son ejecutados en la terminal en la carpeta de nuestro repositorio.

 

 

1 - Merge con Git

Ya vimos en el capítulo anterior  qué es un merge en git y cómo hacerlo. 

 

Cuando creamos la pull request GitHub automáticamente realizará un merge en el branch principal, pero el tema que vamos a tratar hoy va antes de la pull request.

El motivo por el que la pull request realiza un merge es porque así se mantiene el historial.

 

Si realizáramos un merge desde nuestro branch, al branch principal en este caso causaremos conflictos, y estos conflictos estarían en master, lo que implica que si tenemos CI/CD el código se iría a producción sin funcionar correctamente. 

 

1.1 - Evitar conflictos en main

Para evitar esta situación (deploy con errores) debemos asegurarnos que nuestro branch no tiene ningún conflicto cuando se hace el merge. 

Para ello tenemos dos opciones, la primera es la nombrada merge, y la segunda es una nueva característica, llamada rebase

 

 

2 - Qué es Rebase en git

Para comprender cómo funciona git rebase, nos pondremos en nuestro supuesto anterior. Tenemos nuestro branch llamado conflicto y a sabiendas de que otro developer ha hecho un commit en el branch principal ejecutaremos el comando

git rebase master 

 

Lo que git rebase hace es reescribir todos los cambios del branch que hemos indicado en nuestro branch actual.

branch status 2

Y una vez tenemos el branch con rebase podemos hacer nuestro cambio en el código y el merge a través de la pull request.

branch status 3

 

3 - Conflictos en GIT

Pero, este ejemplo es un escenario ideal que no sucede demasiadas veces, lo más normal, es que desarrollemos nuestra funcionalidad y otros desarrolladores hayan cambiado algo antes, siendo un escenario similar al siguiente:

branch status 4

Llegados a este punto deberemos pararnos a pensar ya que tenemos varias opciones, la que yo recomiendo personalmente y más fácil me resulta trabajar con ella es, antes de ejecutar el comando commit realizar el rebase.

 

Esto es debido a que si hay conflictos los trataremos todos de una sola vez, por norma, rebase trata los conflictos commit a commit. 

Pero, si lo intentamos veremos que git no nos deja, ya que tenemos cambios pendientes

PS C:\repos\ejemploGithub> git rebase master
error: cannot rebase: You have unstaged changes.
error: Please commit or stash them.

Vemos que nos indica que podemos tanto hacer un merge, o stash;

Pero qué realiza el comando stash  en git?

 

3.1 - Qué es stash en git

Pongámonos en situación, no solo en el caso actual, sino uno más sencillo para poderlo ver desde otro punto de vista. 

 

Cuándo estamos trabajando en un branch, no siempre trabajamos en él todo el tiempo hasta que realizamos la pull request y el merge, sino que en ciertas ocasiones debemos cambiar para arreglar un bug en producción o quizá una funcionalidad que tiene una prioridad mayor. 

 

Obviamente no vamos a descartar todos los cambios que hemos realizado en nuestro branch actual, pero tampoco vamos a hacer commit, ya que el código lo hemos dejado en un punto que ni siquiera compila. 

Para arreglar esta situación tenemos el comando stash el cual nos permite dejar “apartados” los cambios para poder trabajar en otro branch. 

branch status 5

Para realizar esta acción debemos ejecutar el comando git shash

Y ya está, con este simple cambio, Git almacena los cambios y devolverá el branch a su estado anterior. 

Podemos ejecutar el comando git status para comprobar el estado del branch.

PS C:\repos\ejemploGithub> git stash
Saved working directory and index state WIP on conflicto: 1fc0d6c Merge pull request #3 from ElectNewt/Tarea002
PS C:\repos\ejemploGithub> git status
On branch conflicto
nothing to commit, working tree clean
PS C:\repos\ejemploGithub>

 

Y vemos como nos indica que no tenemos ningún cambio pendiente.

Ahora podemos hacer checkout del hotfix y realizar los cambios necesarios. Una vez terminados volvemos al branch de ejemplo. 

 

3.1.1 - Recuperar los cambios de git stash

Obviamente al volver queremos recuperar todos los ficheros que habíamos modificado.

Primero podemos ver la lista de todos los stash que tenemos ejecutando el comando git stash list

PS C:\repos\ejemploGithub> git stash list
[email protected]{0}: WIP on conflicto: 1fc0d6c Merge pull request #3 from ElectNewt/Tarea002

Y para recuperar los cambios utilizaremos git stash pop

PS C:\repos\ejemploGithub> git stash pop                                                                                
On branch conflicto
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   ConsoleApp/ConsoleApp/Program.cs
        modified:   ConsoleApp/ConsoleApp/Vehiculo.cs

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/[email protected]{0} (93ae3f2082d0db5c02e58ebbd7309cd525df755a)

Y vemos como nos indica los ficheros que hemos recuperado de nuestro “stash” temporal para que podamos volver a trabajar con ellos. 

 

3.2 - Resolver conflictos en Git

Una vez hemos entendido el término stash entendemos porque nos sirve a la hora de hacer rebase, ya que antes de nuestro commit haremos git stash para almacenar los cambios en nuestro espacio temporal. Seguiremos con git rebase master y finalmente git stash pop el cual nos devolverá los cambios.

PS C:\repos\ejemploGithub> git stash                                                                                    
Saved working directory and index state WIP on conflicto: 1fc0d6c Merge pull request #3 from ElectNewt/Tarea002
PS C:\repos\ejemploGithub> git rebase master                                                                            
First, rewinding head to replay your work on top of it...
Fast-forwarded conflicto to master.
PS C:\repos\ejemploGithub> git stash pop                                                                                
Auto-merging ConsoleApp/ConsoleApp/Program.cs
CONFLICT (content): Merge conflict in ConsoleApp/ConsoleApp/Program.cs
The stash entry is kept in case you need it again.
PS C:\repos\ejemploGithub>    

LLegados a este punto los cambios NO se pueden convertir en un commit porque como vemos en el mensaje, tenemos un conflicto. 

 

Nuestra estructura ahora mismo es tal que así:

branch status 6

 

La forma en la que git nos muestra el conflicto es con los caracteres <<<<<<<<, ======= >>>>>> como en el ejemplo:

static void Main(string[] args)
{
<<<<<<< Updated upstream
    Console.WriteLine("Introudce tu nombre:");
    string nombre = Console.ReadLine();
    Console.WriteLine($"El Tu nombre es {nombre}");
=======
    Console.WriteLine("Hello World!");
    var coche = new Vehiculo(200, 5, 90);
    Console.WriteLine($"El coche tiene {coche.Cv}"); 
    Console.ReadKey();
>>>>>>> Stashed changes
}

Lo que debemos hacer es arreglar este conflicto.

 

3.2.1 - Herramienta para arreglar conflictos.

Para arreglar conflictos hay mil herramientas, de hecho podemos hacerlo a mano con el bloc de notas, pero personalmente mi herramienta preferida es Visual Studio Code ya que da una forma visual muy sencilla de ver cuales son dichos conflictos. 

Únicamente debemos ir a dicho fichero y abrirlo con visual studio code. 

visual studio code git conflict

Como vemos tenemos los siguientes apartados:

  • En verde: Los cambios que están ya en el branch principal
  • En azul: Nuestros cambios
  • Menú superior con varias opciones:
    • Aceptar los cambios del branch principal (parte verde)
    • Aceptar nuestros cambios (parte azul)
    • Aceptar Ambos
    • Compararlos; nos abre otra pestaña y nos muestra únicamente estas diferencias.

En nuestro caso particular, aceptaremos los dos cambios (y borraremos hello world) pero esta parte es 100% dependiente de la lógica de los cambios que se hayan realizado, y cada caso es diferente.

 

Una vez terminado de arreglar nuestros conflictos, debemos añadir los ficheros que hemos tenido con conflictos con git add [fichero]

Ahora únicamente debemos hacer nuestro commit, y el push al branch remoto para poder tener nuestra pull request

PS C:\repos\ejemploGithub> git add ConsoleApp\ConsoleApp\Program.cs                                                     
PS C:\repos\ejemploGithub> git commit -m "add cv en el vehiculo"                                                        
[conflicto 0b6540f] add cv en el vehiculo
2 files changed, 6 insertions(+), 1 deletion(-)
PS C:\repos\ejemploGithub> git push   

 

Finalmente tendremos una estructura como la siguiente:

git branch final status

Nota: En caso de hacer el commit antes del rebase (sin stash) tendremos que lidiar con los conflictos individualmente por cada commit y deberemos ejecutar git rebase --continue cada vez que terminemos con los conflictos de dicho commit.

 

Conclusión

En este post hemos visto un caso de cómo tratar con conflictos. pero Primero debemos anticiparnos a ellos. 

  • Hemos visto qué es el comando git stash
  • Hemos visto cuándo ejecutar git stash
  • Hemos visto cómo utilizar rebase
  • Y qué aplicación utilizar para arreglar nuestros conflictos. 

Probablemente para el siguiente post veamos las diferencias entre git merge y git rebase