Operaciones avanzadas de commits en Git

GitGitBeginner
Practicar Ahora

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

¡Bienvenido de nuevo, aventurero de Git! Has dado tus primeros pasos en el mundo del control de versiones, y ahora es el momento de elevar tus habilidades. En este laboratorio, vamos a explorar algunas de las operaciones de confirmación (commits) más avanzadas de Git. Estas técnicas te darán aún más control sobre la historia de tu proyecto, permitiéndote corregir errores, reorganizar tu trabajo y colaborar de manera más efectiva.

Piensa en este laboratorio como una actualización de tu máquina del tiempo. No solo podrás viajar en el tiempo, sino que ahora aprenderás cómo alterar la propia línea de tiempo. No te preocupes si esto suena intimidante; te guiaremos en cada paso, explicando no solo cómo realizar estas operaciones, sino también por qué son útiles en escenarios del mundo real.

Al final de este laboratorio, serás capaz de modificar confirmaciones (amend commits), revertir cambios, seleccionar específicamente confirmaciones (cherry-pick specific commits), realizar rebase interactivo y combinar confirmaciones (squash commits). Estas son herramientas poderosas que los desarrolladores profesionales utilizan todos los días para mantener historias de proyectos limpias y organizadas. ¡Sumergámonos y llevemos tus habilidades de Git al siguiente nivel!


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL git(("Git")) -.-> git/DataManagementGroup(["Data Management"]) git(("Git")) -.-> git/BranchManagementGroup(["Branch Management"]) git(("Git")) -.-> git/SetupandConfigGroup(["Setup and Config"]) git(("Git")) -.-> git/BasicOperationsGroup(["Basic Operations"]) git/SetupandConfigGroup -.-> git/init("Initialize Repo") git/BasicOperationsGroup -.-> git/add("Stage Files") git/BasicOperationsGroup -.-> git/commit("Create Commit") git/DataManagementGroup -.-> git/reset("Undo Changes") git/BranchManagementGroup -.-> git/branch("Handle Branches") git/BranchManagementGroup -.-> git/checkout("Switch Branches") git/BranchManagementGroup -.-> git/log("Show Commits") git/BranchManagementGroup -.-> git/cherry_pick("Cherry Pick") git/BranchManagementGroup -.-> git/rebase("Reapply Commits") subgraph Lab Skills git/init -.-> lab-387471{{"Operaciones avanzadas de commits en Git"}} git/add -.-> lab-387471{{"Operaciones avanzadas de commits en Git"}} git/commit -.-> lab-387471{{"Operaciones avanzadas de commits en Git"}} git/reset -.-> lab-387471{{"Operaciones avanzadas de commits en Git"}} git/branch -.-> lab-387471{{"Operaciones avanzadas de commits en Git"}} git/checkout -.-> lab-387471{{"Operaciones avanzadas de commits en Git"}} git/log -.-> lab-387471{{"Operaciones avanzadas de commits en Git"}} git/cherry_pick -.-> lab-387471{{"Operaciones avanzadas de commits en Git"}} git/rebase -.-> lab-387471{{"Operaciones avanzadas de commits en Git"}} end

Configurando tu espacio de trabajo

Antes de comenzar nuestras operaciones avanzadas, configuremos un nuevo espacio de trabajo. Crearemos un nuevo directorio e inicializaremos un repositorio de Git en él. Esto nos dará un espacio limpio para experimentar sin afectar ninguno de tus proyectos existentes.

Abre tu terminal y escribe estos comandos, presionando Enter después de cada línea:

cd ~/project
mkdir git-advanced-lab
cd git-advanced-lab
git init

Desglosemos lo que hacen estos comandos paso a paso:

  1. cd ~/project: cd significa "cambiar directorio" (change directory). ~/project es una ruta que generalmente apunta a una carpeta "project" dentro de tu directorio de inicio (~). Este comando dirige tu terminal a ese directorio. Si el directorio "project" no existe en tu directorio de inicio, es posible que encuentres un error. En ese caso, créalo primero usando mkdir ~/project y luego intenta cd ~/project de nuevo.
  2. mkdir git-advanced-lab: mkdir significa "crear directorio" (make directory). Este comando crea una nueva carpeta llamada "git-advanced-lab" dentro de tu directorio actual (que debería ser ~/project después del comando anterior). Este nuevo directorio será la raíz de nuestro repositorio de Git para este laboratorio.
  3. cd git-advanced-lab: Este comando cambia tu directorio actual nuevamente, esta vez moviéndote dentro del directorio "git-advanced-lab" recién creado. Ahora, cualquier comando que ejecutes se ejecutará dentro de este directorio.
  4. git init: Este es el comando crucial de Git para inicializar un repositorio. git init configura todas las estructuras necesarias de Git dentro del directorio actual (git-advanced-lab), convirtiéndolo en un repositorio de Git. Verás una carpeta oculta llamada .git creada dentro de git-advanced-lab. Esta carpeta .git es el corazón de tu repositorio de Git y almacena toda la historia de versiones y la configuración.

Ahora que tenemos un repositorio de Git inicializado, creemos un archivo simple con el que trabajar y hagamos nuestro primer commit:

echo "Hello, Advanced Git" > hello.txt
git add hello.txt
git commit -m "Initial commit"

Esto es lo que sucede en estos comandos:

  1. echo "Hello, Advanced Git" > hello.txt: echo es un comando que muestra texto. "Hello, Advanced Git" es el texto que queremos mostrar. > es un operador de redirección. Toma la salida del comando echo y la redirige a un archivo llamado "hello.txt". Si "hello.txt" no existe, se creará. Si existe, se sobrescribirá con el nuevo contenido. Entonces, este comando crea un nuevo archivo llamado "hello.txt" con el contenido "Hello, Advanced Git".
  2. git add hello.txt: Antes de que Git pueda seguir los cambios en un archivo, debes decirle explícitamente a Git que comience a seguirlo. git add hello.txt prepara (stages) el archivo "hello.txt". Preparar un archivo significa prepararlo para ser incluido en el próximo commit. Puedes pensar en el área de preparación (staging area) como una zona de preparación para tu commit.
  3. git commit -m "Initial commit": git commit toma todos los cambios que están actualmente preparados (en nuestro caso, la adición de "hello.txt") y los guarda como un nuevo commit en la historia del repositorio. -m "Initial commit" agrega un mensaje de commit. "Initial commit" es el mensaje en sí, que debe ser una descripción breve de los cambios que estás confirmando. Es una buena práctica escribir mensajes de commit claros y concisos.

¡Genial! Ahora tenemos un repositorio con un commit. Veamos el estado para confirmar que todo está configurado correctamente:

git status

Después de ejecutar git status, deberías ver un mensaje en tu terminal que se vea algo así:

On branch master
nothing to commit, working tree clean

Este mensaje indica que tu árbol de trabajo (working tree) está limpio. "Árbol de trabajo" se refiere al directorio donde se encuentran los archivos de tu proyecto. "Limpio" significa que no hay cambios en tu directorio de trabajo que aún no se hayan confirmado. Esto confirma que estamos listos para comenzar nuestras operaciones avanzadas con una pizarra en blanco.

Modificar tu último commit

Imagina que acabas de hacer un commit, pero luego te das cuenta de que olvidaste incluir un cambio en un archivo, o cometiste un pequeño error tipográfico en el mensaje de commit. En lugar de crear un nuevo commit entero para una corrección tan menor, Git te permite corregir el commit más reciente utilizando la opción --amend. Esto es como retroceder un poco en el tiempo para ajustar tu última acción.

Vamos a probarlo. Primero, modifiquemos nuestro archivo hello.txt agregando otra línea:

echo "This is an important file." >> hello.txt

Este comando agrega una nueva línea al archivo "hello.txt". Entendamos el operador >>:

  • > sobrescribiría todo el archivo con el nuevo contenido.
  • >> agrega el nuevo contenido al final del archivo existente, preservando el contenido original.

Entonces, después de ejecutar este comando, "hello.txt" contendrá dos líneas:

Hello, Advanced Git
This is an important file.

Ahora, digamos que nos dimos cuenta de que deberíamos haber incluido esta nota de "archivo importante" en nuestro primer commit. Podemos modificar nuestro commit anterior para incluir este cambio y actualizar el mensaje de commit para reflejar la adición.

git add hello.txt
git commit --amend -m "Initial commit with important note"

A continuación, se desglosan estos comandos:

  1. git add hello.txt: Hemos modificado hello.txt, por lo que necesitamos preparar (stage) los cambios nuevamente utilizando git add. Esto le dice a Git que queremos incluir el estado actual de "hello.txt" en nuestro commit.
  2. git commit --amend -m "Initial commit with important note": Aquí es donde sucede la magia.
    • git commit --amend: La bandera --amend le dice a Git que en lugar de crear un nuevo commit, queremos modificar el último commit.
    • -m "Initial commit with important note": Esto proporciona un nuevo mensaje de commit. Si omites la opción -m, Git abrirá tu editor de texto predeterminado, permitiéndote editar el mensaje de commit original.

¿Qué acaba de pasar?

En lugar de crear un nuevo commit en la historia de tu repositorio de Git, Git ha reemplazado efectivamente el commit anterior "Initial commit" con una nueva y mejorada versión. El commit antiguo se ha ido, y el nuevo commit incorpora los cambios que preparamos y el mensaje de commit actualizado.

Verifiquemos esto comprobando nuestro registro de commits:

git log --oneline

Deberías ver solo un commit en la salida, pero el mensaje de commit ahora debe ser "Initial commit with important note".

<commit_hash> Initial commit with important note

Presiona q para salir de la vista del registro si no se cierra automáticamente.

Consideraciones importantes al modificar un commit:

  • Modifica solo commits no enviados (unpushed): Debes solo modificar commits que aún no hayas enviado a un repositorio remoto compartido (como GitHub, GitLab, etc.). Modificar un commit que ya se ha enviado reescribe la historia en la que otros pueden estar dependiendo, lo que puede causar problemas importantes y confusión en entornos de colaboración.
  • Limpieza de la historia local: Modificar un commit es principalmente útil para limpiar la historia de tus commits locales antes de compartir tu trabajo. Es una excelente manera de perfeccionar tus commits mientras todavía estás trabajando en una rama de función (feature branch), por ejemplo.

Revertir un commit

A veces, haces un commit y luego te das cuenta de que introdujo un error o simplemente quieres deshacer esos cambios. Podrías pensar en usar git reset para retroceder en el tiempo y eliminar el commit. Sin embargo, git reset puede ser destructivo, especialmente si ya has enviado tus cambios o si otros están trabajando en la misma rama. Una forma más segura y colaborativa de deshacer cambios es usando git revert.

git revert crea un nuevo commit que deshace los cambios introducidos por un commit específico. No borra el commit original de la historia; en cambio, agrega un nuevo commit que invierte efectivamente los efectos del commit no deseado. Esto preserva la historia y es mucho más seguro para repositorios compartidos.

Vamos a crear un nuevo commit que luego revertiremos:

echo "This line will be reverted" >> hello.txt
git add hello.txt
git commit -m "Add line to be reverted"

Estos comandos son similares a los que hemos hecho antes:

  1. echo "This line will be reverted" >> hello.txt: Agrega una nueva línea a nuestro archivo "hello.txt".
  2. git add hello.txt: Prepara (stages) el archivo modificado.
  3. git commit -m "Add line to be reverted": Crea un nuevo commit con el mensaje "Add line to be reverted" que incluye la adición de esta nueva línea a "hello.txt".

Ahora, digamos que decidimos que agregar "This line will be reverted" fue un error y queremos eliminarlo. Podemos revertir el último commit usando:

git revert HEAD

Desglosemos este comando:

  • git revert: Este es el comando para revertir cambios.
  • HEAD: En Git, HEAD es un puntero al commit actual de tu rama actual. En la mayoría de los casos, HEAD se refiere al commit más reciente. Entonces, git revert HEAD significa "revertir el commit más reciente". También puedes especificar un hash de commit específico para revertir un commit más antiguo.

Cuando ejecutas git revert HEAD, Git hará lo siguiente:

  1. Analizar los cambios: Observa los cambios introducidos por el commit HEAD (en nuestro caso, "Add line to be reverted").
  2. Crear cambios inversos: Descubre cómo deshacer esos cambios (en nuestro caso, eliminar la línea "This line will be reverted" de "hello.txt").
  3. Crear un nuevo commit: Crea automáticamente un nuevo commit que aplica estos cambios inversos.
  4. Abrir el editor de texto (opcional): Git generalmente abrirá tu editor de texto predeterminado (como Vim, Nano, el editor de VS Code, etc.) con un mensaje de commit prellenado para el commit de reversión. El mensaje predeterminado suele ser algo como "Revert 'Add line to be reverted'". Puedes aceptar el mensaje predeterminado o modificarlo para proporcionar más contexto.

Manejar el editor de texto durante git revert:

Si se abre tu editor predeterminado y no estás familiarizado con él (especialmente si es Vim), ¡no te preocupes! El proceso suele ser sencillo:

  • Para aceptar el mensaje predeterminado y completar la reversión:
    • Si es Vim: Presiona la tecla Esc (para entrar en modo comando), luego escribe :wq (escribir y salir), y presiona Enter.
    • Si es Nano: Presiona Ctrl + X (para salir), luego Y (para guardar los cambios) y Enter (para confirmar el nombre de archivo).
    • Para otros editores: Busca opciones para "Guardar" y "Cerrar" o "Salir".

Después de guardar y cerrar el editor (si se abrió), Git finalizará el commit de reversión.

Veamos ahora nuestra historia de commits:

git log --oneline

Deberías ver tres commits en tu registro, en orden cronológico inverso (el más reciente primero):

  1. Un commit que comienza con "Revert" (el commit de reversión que acabamos de crear).
  2. "Add line to be reverted" (el commit que revertimos).
  3. "Initial commit with important note" (nuestro commit original modificado).
<commit_hash_revert> Revert "Add line to be reverted"
<commit_hash_original> Add line to be reverted
<commit_hash_initial> Initial commit with important note

Si miras el contenido de hello.txt ahora, la línea "This line will be reverted" debería haber desaparecido, deshaciendo efectivamente los cambios del commit "Add line to be reverted".

¿Por qué se prefiere git revert sobre git reset en repositorios compartidos?

  • Historia no destructiva: git revert no reescribe la historia. Agrega un nuevo commit para deshacer cambios, lo que lo hace seguro para la colaboración. Todos pueden ver el error original y la corrección en la historia.
  • Evita el empuje forzado (force pushing): git reset a menudo requiere un empuje forzado (git push --force) a un repositorio remoto si ya has enviado los commits. El empuje forzado puede ser muy disruptivo y sobrescribir el trabajo de otros. git revert evita este problema.
  • Rastro de auditoría claro: git revert proporciona un registro claro de cuándo y por qué se deshicieron cambios. Esto es valioso para entender la evolución del proyecto y depurar problemas.

Selección individual de commits (Cherry-picking)

La selección individual de commits (cherry-picking) en Git es como elegir una cereza de un árbol: seleccionas un commit específico de una rama y lo aplicas a tu rama actual. Esto es útil cuando quieres incorporar una característica o corrección en particular de otra rama sin fusionar toda la rama.

Simulemos un escenario en el que tenemos una característica desarrollada en una rama separada y queremos traer solo esa característica a nuestra rama principal.

Primero, necesitamos crear una nueva rama y hacer un commit en ella:

git checkout -b feature-branch
echo "This is a new feature" >> feature.txt
git add feature.txt
git commit -m "Add new feature"

Desglosemos estos comandos:

  1. git checkout -b feature-branch:
    • git checkout: Este comando se utiliza para cambiar de rama o crear nuevas ramas.
    • -b feature-branch: La bandera -b le dice a git checkout que cree una nueva rama llamada "feature-branch" y cambie inmediatamente a ella. Entonces, este comando crea una nueva rama y la convierte en tu rama activa.
  2. echo "This is a new feature" >> feature.txt: Crea un nuevo archivo llamado "feature.txt" con el contenido "This is a new feature" en nuestra "feature-branch".
  3. git add feature.txt: Prepara (stages) el archivo "feature.txt" recién creado.
  4. git commit -m "Add new feature": Crea un commit en la "feature-branch" con el mensaje "Add new feature", incluyendo la adición de "feature.txt".

Ahora, imaginemos que hemos terminado con esta característica por ahora y queremos integrarla en nuestra rama principal (master). Primero, necesitamos cambiar de nuevo a la rama master:

git checkout master

Este comando simplemente cambia tu rama activa de nuevo a "master".

Ahora queremos aplicar el commit "Add new feature" de "feature-branch" a nuestra rama master. Usamos git cherry-pick para esto:

git cherry-pick feature-branch

Espera, ¿por qué feature-branch y no un hash de commit? En este caso simple, feature-branch como un objeto commit-ish se resolverá al último commit en feature-branch. Dado que solo hicimos un commit en feature-branch, esto efectivamente selecciona individualmente ese commit. Si tuvieras múltiples commits en feature-branch y quisieras seleccionar uno específico, usarías el hash de commit de ese commit específico en su lugar. Por ejemplo, podrías encontrar el hash de commit usando git log feature-branch --oneline y luego usar git cherry-pick <commit_hash>.

Cuando ejecutas git cherry-pick feature-branch, Git hace lo siguiente:

  1. Encontrar el commit: Identifica el último commit en "feature-branch" (o el commit que especificaste por hash).
  2. Aplicar cambios: Intenta aplicar los cambios introducidos por ese commit a tu rama actual (master).
  3. Crear un nuevo commit: Si tiene éxito (sin conflictos), Git crea un nuevo commit en tu rama master que tiene los mismos cambios que el commit seleccionado individualmente y generalmente el mismo mensaje de commit.

Veamos nuestro registro de commits en la rama master:

git log --oneline

Deberías ver un nuevo commit en tu rama master con el mensaje "Add new feature". Es importante destacar que este es un nuevo commit con un hash de commit diferente al del commit en feature-branch, aunque los cambios sean los mismos.

Además, si listas los archivos en tu rama master (por ejemplo, usando el comando ls), también deberías ver el nuevo archivo "feature.txt" allí.

Consideraciones importantes para la selección individual de commits (cherry-picking):

  • Nuevo commit: La selección individual de commits siempre crea un nuevo commit. No mueve ni copia el commit original. Esto es importante recordar cuando se piensa en la historia de los commits.
  • Posibles conflictos: La selección individual de commits a veces puede provocar conflictos, especialmente si las bases de código de las dos ramas se han divergido significativamente. Si se producen conflictos, Git pausará el proceso de selección individual de commits y te pedirá que los resuelvas, similar a los conflictos de fusión.
  • Usar con moderación: Aunque es una herramienta poderosa, la selección individual de commits debe usarse con prudencia. El uso excesivo de la selección individual de commits puede hacer que tu historia sea más difícil de seguir, especialmente si luego fusionas toda la rama de características. Es más efectiva para aplicar cambios o correcciones específicos y aislados de una rama a otra cuando no se desea o no es adecuada una fusión completa.

Rebase interactivo

El rebase interactivo es una de las características más poderosas y, potencialmente, complejas de Git. Te permite reescribir la historia de tus commits de manera muy flexible. Puedes reordenar commits, combinar (squash) commits, editar mensajes de commit o incluso eliminar commits por completo. Es como tener un editor detallado para la historia de tus commits.

Advertencia: El rebase interactivo, especialmente en ramas compartidas, puede ser riesgoso porque reescribe la historia. Nunca realices un rebase de commits que ya se hayan enviado a un repositorio compartido a menos que comprendas completamente las implicaciones y hayas coordinado con tu equipo. El rebase es generalmente más seguro y adecuado para limpiar tus commits locales antes de compartirlos.

Vamos a crear una serie de commits con los que trabajar para ver el rebase interactivo en acción:

echo "First change" >> hello.txt
git commit -am "First change"
echo "Second change" >> hello.txt
git commit -am "Second change"
echo "Third change" >> hello.txt
git commit -am "Third change"

Estos comandos crean tres nuevos commits seguidos rápidamente, cada uno agregando una línea a nuestro archivo "hello.txt". La abreviatura -am en git commit -am combina la preparación (staging) de todos los archivos modificados y eliminados (-a) con el commit (-m). Es un atajo conveniente cuando ya estás siguiendo archivos y solo quieres hacer un commit de los cambios en ellos.

Ahora, digamos que queremos limpiar estos tres commits. Quizás "First change" y "Second change" en realidad formaban parte del mismo cambio lógico y queremos combinarlos en un solo commit. Y quizás queremos mejorar el mensaje de commit de "Third change". El rebase interactivo es perfecto para esto.

Inicia una sesión de rebase interactivo usando:

git rebase -i HEAD~3

Entendamos este comando:

  • git rebase -i: git rebase es el comando para realizar un rebase. La bandera -i significa "interactivo", lo que le dice a Git que queremos realizar un rebase interactivo.
  • HEAD~3: Esto especifica el rango de commits que queremos rebasar. HEAD se refiere al commit actual. ~3 significa "retroceder tres commits desde HEAD". Entonces, HEAD~3 selecciona los últimos tres commits. También podrías especificar un hash de commit en lugar de HEAD~3 para comenzar el rebase desde un punto específico de la historia.

Cuando ejecutas este comando, Git abrirá tu editor de texto predeterminado con una lista de los últimos tres commits. La interfaz del editor es donde le indicas a Git cómo modificar tu historia. Verás algo como esto (los hashes de commit serán diferentes):

1 pick 63c95db First change
2 pick 68e7909 Second change
3 pick 5371424 Third change
4
5 ## Rebase 3bf348d..5371424 onto 3bf348d (3 commands)
6 #
7 ## Commands:
8 ## p, pick <commit> = use commit
9 ## r, reword <commit> = use commit, but edit the commit message
10 ## e, edit <commit> = use commit, but stop for amending
11 ## s, squash <commit> = use commit, but meld into previous commit
12 ## f, fixup [-C | -c] <commit> = like "squash" but keep only the previous
13 ##                    commit's log message, unless -C is used, in which case
14 ##                    keep only this commit's message; -c is same as -C but
15 ##                    opens the editor
16 ## x, exec <command> = run command (the rest of the line) using shell
17 ## b, break = stop here (continue rebase later with 'git rebase --continue')
18 ## d, drop <commit> = remove commit
19 ## l, label <label> = label current HEAD with a name
20 ## t, reset <label> = reset HEAD to a label
21 ## m, merge [-C <commit> | -c <commit>] <label> [## <oneline>]
22 #.       create a merge commit using the original merge commit's
23 #.       message (or the oneline, if no original merge commit was
24 #.       specified); use -c <commit> to reword the commit message
25 #
26 ## These lines can be re-ordered; they are executed from top to bottom.
27 #
28 ## If you remove a line here THAT COMMIT WILL BE LOST.
29 #
30 ## However, if you remove everything, the rebase will be aborted.

Entendiendo el editor de rebase interactivo:

  • Lista de commits: La parte superior del archivo lista los commits que forman parte de la operación de rebase. Por defecto, se listan en orden cronológico inverso (el más antiguo en la parte superior, el más reciente en la parte inferior).
  • Comandos: Debajo de la lista de commits, Git proporciona una lista de comandos disponibles que puedes usar para modificar cada commit. Los más comunes son:
    • pick (o p): Utiliza el commit tal como está. Este es el valor predeterminado.
    • reword (o r): Utiliza el commit, pero te permite cambiar el mensaje de commit.
    • edit (o e): Utiliza el commit, pero detiene el proceso de rebase en este commit para que puedas hacer más cambios (por ejemplo, modificar archivos, agregar más cambios).
    • squash (o s): Combina este commit con el commit anterior en la lista. El mensaje de commit de este commit se agregará al mensaje del commit anterior (luego podrás editar el mensaje combinado).
    • fixup (o f): Similar a squash, pero descarta el mensaje de commit de este commit y solo utiliza el mensaje del commit anterior.
    • drop (o d): Elimina este commit por completo de la historia.

Nuestro plan de rebase interactivo:

Queremos:

  1. Combinar "Second change" con "First change".
  2. Cambiar el mensaje de "Third change" por uno mejor.

Para lograr esto, modifica el archivo del editor para que se vea así:

pick abc1234 First change
squash def5678 Second change
reword ghi9101 Third change

No cambies los hashes de commit en el archivo del editor.

Instrucciones para editar en Vim (si es tu editor):

  1. Entrar en modo de inserción: Presiona la tecla i. Esto te permite escribir y editar el archivo.
  2. Hacer cambios: Utiliza las flechas de tu teclado para navegar a las líneas y cambiar "pick" por "squash" y "reword" como se muestra arriba.
  3. Salir del modo de inserción: Presiona la tecla Esc.
  4. Guardar y salir: Escribe :wq y presiona Enter.

Después de guardar y cerrar el archivo del editor, Git comenzará a realizar las operaciones de rebase según tus instrucciones.

Primero, combinar commits:

Git procesará primero el comando "squash". Combinará los cambios de "Second change" con "First change". Luego, abrirá tu editor de texto nuevamente, esta vez para que puedas editar el mensaje de commit combinado para el commit combinado. Verás algo como esto en el editor:

## This is a combination of 2 commits.
## This is the 1st commit message:

First change

## This is the 2nd commit message:

Second change

## Please enter the commit message for your changes. Lines starting
## with '#' will be ignored, and an empty message aborts the commit.
#
## Date:      Tue Oct 24 10:30:00 2023 +0000
#
## interactive rebase in progress; onto <base_commit_hash>
#
## Last commands done (2 commands done):
##    pick abc1234 First change
##    squash def5678 Second change
## Next command to do (1 remaining command):
##    reword ghi9101 Third change
## You are currently rebasing branch 'master' on '<base_commit_hash>'.

Este editor muestra los mensajes de commit originales de los commits que se están combinando. Ahora puedes editar este texto para crear un solo mensaje de commit coherente para el commit combinado. Por ejemplo, podrías cambiarlo a:

Combined first and second changes: Initial setup of hello.txt

Después de editar el mensaje, guarda y cierra el editor.

A continuación, cambiar el mensaje de un commit:

Luego, Git pasará al comando "reword" para "Third change". Abrirá el editor nuevamente, esta vez para que puedas editar el mensaje de commit de "Third change". Verás el mensaje original:

Third change

## Please enter the commit message for your changes. Lines starting
## with '#' will be ignored, and an empty message aborts the commit.
#
## Date:      Tue Oct 24 10:35:00 2023 +0000
#
## interactive rebase in progress; onto <base_commit_hash>
#
## Last commands done (3 commands done):
##    pick abc1234 First change
##    squash def5678 Second change
##    reword ghi9101 Third change
## Next command to do (0 remaining commands):
##    (finish)
## You are currently rebasing branch 'master' on '<base_commit_hash>'.

Cambia el mensaje a algo más descriptivo, como:

Improved third change: Added a more descriptive line to hello.txt

Guarda y cierra el editor.

Finalización del rebase:

Después de procesar todos los comandos de tus instrucciones de rebase interactivo, Git terminará el proceso de rebase. Por lo general, verás un mensaje como "Successfully rebased and updated refs/heads/master."

Verificando la historia rebasada:

Ahora, vuelve a comprobar tu registro de commits:

git log --oneline

Deberías ver una historia que se vea algo así (los hashes de commit serán diferentes):

<commit_hash_third_revised> Improved third change: Added a more descriptive line to hello.txt
<commit_hash_combined> Combined first and second changes: Initial setup of hello.txt
<commit_hash_cherry_pick> Add new feature
<commit_hash_revert> Revert "Add line to be reverted"
<commit_hash_original_reverted> Add line to be reverted
<commit_hash_initial_amended> Initial commit with important note

Observa que:

  • Ahora tienes menos commits que antes (hemos combinado dos commits en uno).
  • El mensaje de commit de lo que era "First change" y "Second change" ahora está combinado y es más descriptivo.
  • El mensaje de commit de lo que era "Third change" se ha modificado.
  • Los hashes de commit de los commits rebasados han cambiado porque hemos reescrito la historia.

Recordatorios importantes sobre el rebase interactivo:

  • Herramienta para la historia local: El rebase interactivo es principalmente una herramienta para limpiar la historia de tus commits locales.
  • Evita el rebase en ramas compartidas: No realices un rebase de ramas que se hayan enviado a un repositorio compartido a menos que estés absolutamente seguro de lo que estás haciendo y hayas coordinado con tu equipo. Reescribir la historia compartida puede causar serios problemas para los colaboradores.
  • git rebase --abort: Si cometemos un error durante un rebase interactivo, o si te confundes o encuentras conflictos que no puedes resolver, siempre puedes usar git rebase --abort para cancelar la operación de rebase y devolver tu rama al estado en el que se encontraba antes de comenzar el rebase. Esta es una forma segura de salir de un rebase si algo sale mal.

Rebase desde la raíz

En el Paso 5, usamos git rebase -i HEAD~3 para hacer un rebase de los últimos tres commits. Pero el rebase interactivo puede ser aún más poderoso. git rebase -i --root te permite hacer un rebase interactivo de todos los commits en la historia de tu repositorio, comenzando desde el primer commit (el commit raíz). Esto te da el control total para remodelar toda la historia de tu proyecto.

Extrema precaución: El rebase desde la raíz es una operación extremadamente poderosa, y con gran poder viene gran responsabilidad (y el potencial de causar problemas significativos si se usa incorrectamente). Hacer un rebase desde la raíz en repositorios compartidos es casi siempre una mala idea y debe evitarse a menos que haya una razón excepcional y bien comprendida para hacerlo, y solo después de una coordinación cuidadosa con todo tu equipo. Esto reescribe por completo toda la historia, cambiando los hashes de commit de todos los commits desde la raíz en adelante. Esto causará grandes problemas si otros están colaborando en el mismo repositorio.

Para este laboratorio, estamos trabajando en un repositorio local y aislado, así que es seguro experimentar con el rebase desde la raíz para aprender cómo funciona. Solo recuerda las advertencias para proyectos colaborativos del mundo real.

Intentemos un rebase desde la raíz:

git rebase -i --root

Este comando es muy similar al rebase anterior, pero en lugar de HEAD~3, usamos --root. --root le dice a Git que considere todos los commits desde el principio de la historia del repositorio para el rebase interactivo.

Cuando ejecutes esto, tu editor de texto predeterminado se abrirá nuevamente, pero esta vez listará todos los commits en tu repositorio, comenzando desde el primer commit ("Initial commit with important note" o como lo hayas modificado). Ahora verás una lista más larga que en el paso anterior.

Podrías ver algo como esto (tus hashes de commit y mensajes pueden variar según cómo hayas seguido los pasos anteriores):

pick <hash_initial> Initial commit with important note
pick <hash_reverted> Add line to be reverted
pick <hash_revert_commit> Revert "Add line to be reverted"
pick <hash_new_feature> Add new feature
pick <hash_combined> Combined first and second changes: Initial setup of hello.txt
pick <hash_third_revised> Improved third change: Added a more descriptive line to hello.txt

Nuestro plan de rebase desde la raíz:

Para este ejercicio, hagamos un par de cosas con este rebase desde la raíz:

  1. Cambiar el mensaje del primer commit. Digamos que queremos perfeccionar aún más el mensaje de nuestro primer commit.
  2. Combinar el commit "Revert" con el commit que introdujo la línea que revertimos. Este es un ejemplo un poco forzado, pero demuestra cómo puedes manipular la historia. Podríamos argumentar que el commit "Revert" es realmente solo parte de la limpieza del error inicial, así que podríamos combinarlo con el commit que introdujo el error. (En un escenario real, es posible que no siempre quieras combinar los commits de reversión, ya que proporcionan un registro claro de la deshacer cambios).

Para implementar este plan, modifica el archivo del editor para que se vea así:

reword <hash_initial> Initial commit with important note
squash <hash_reverted> Add line to be reverted
pick <hash_revert_commit> Revert "Add line to be reverted"
pick <hash_new_feature> Add new feature
pick <hash_combined> Combined first and second changes: Initial setup of hello.txt
pick <hash_third_revised> Improved third change: Added a more descriptive line to hello.txt

Cambiamos "pick" a "reword" para el primer commit y "pick" a "squash" para el segundo commit ("Add line to be reverted"). Deja el commit "Revert" como "pick" por ahora.

Guarda y cierra el editor.

Primero, cambiar el mensaje del primer commit:

Git se detendrá primero para permitirte cambiar el mensaje del primer commit. El editor se abrirá con el mensaje actual "Initial commit with important note" (o como lo hayas modificado en el Paso 2). Cámbialo a algo como: "Initial setup and file creation". Guarda y cierra el editor.

A continuación, combinar "Add line to be reverted" con el primer commit:

Luego, Git procesará el comando "squash". Combinará los cambios de "Add line to be reverted" con el commit anterior (que ahora es "Initial setup and file creation"). El editor se abrirá nuevamente para que edites el mensaje de commit combinado. Verás los mensajes originales de ambos commits. Puedes crear un nuevo mensaje que refleje los cambios combinados. Por ejemplo, podrías cambiar el mensaje a:

Initial setup and file creation with subsequent line removal

Guarda y cierra el editor.

Finalización del rebase:

Git continuará procesando el resto de los comandos ("pick" para los commits restantes) y completará el rebase desde la raíz.

Verificando la historia rebasada desde la raíz:

Comprueba tu registro de commits una última vez:

git log --oneline

Deberías ver toda la historia de tus commits, pero con los cambios que indicaste en el rebase desde la raíz:

  • El primer commit debe tener el nuevo mensaje "Initial setup and file creation with subsequent line removal" (o similar).
  • El commit "Add line to be reverted" y el commit "Revert" pueden seguir estando presentes, o pueden haberse combinado dependiendo exactamente de cómo hayas indicado el rebase. (En las instrucciones de ejemplo anteriores, mantuvimos "Revert" como "pick", así que debería seguir estando allí).
  • Los hashes de commit habrán cambiado en toda la historia desde el commit raíz en adelante.

Una vez más, recuerda la extrema precaución con el rebase desde la raíz, especialmente en repositorios compartidos. Este paso es principalmente con fines educativos para demostrar el poder y la flexibilidad del rebase interactivo.

Resumen

¡Felicidades, maestro de Git! Acabas de mejorar aún más tus habilidades de control de versiones. Repasemos las poderosas técnicas que has aprendido en este laboratorio:

  1. Modificar commits: Ahora puedes corregir errores menores o agregar pequeños cambios a tu commit más reciente sin crear un nuevo commit, manteniendo tu historia limpia para ajustes menores.
  2. Revertir commits: Has aprendido cómo deshacer cambios de forma segura creando un nuevo commit que invierte uno anterior. Esta es la forma no destructiva de corregir errores en un entorno colaborativo.
  3. Selección individual de commits (Cherry-picking): Ahora puedes aplicar commits específicos de una rama a otra, lo que te da un control detallado sobre qué cambios incorporas, perfecto para traer selectivamente características o correcciones.
  4. Rebase interactivo: Has dominado el arte de reescribir la historia, lo que te permite limpiar y organizar tus commits antes de compartirlos con otros. Esto incluye reordenar, combinar (squash) y editar mensajes de commit para una historia más pulida.
  5. Rebase desde la raíz: Has aprendido cómo remodelar toda la historia de tu proyecto, comenzando desde el primer commit, lo que te da el control total sobre la narrativa de tu repositorio. ¡Recuerda el poder y el riesgo asociado con esto!

Estas operaciones avanzadas de Git son herramientas increíblemente poderosas en el arsenal de un desarrollador. Te permiten mantener una historia de commits limpia y organizada, lo cual es crucial cuando trabajas en proyectos grandes o colaboras con un equipo. Una historia de commits bien mantenida no es solo cuestión de estética; mejora significativamente la revisión de código, la depuración y la comprensión de la evolución del proyecto a lo largo del tiempo.