Índice
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:
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.
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.
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:
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.
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
stash@{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/stash@{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í:
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.
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:
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