¿Cómo deshacer una operación git cherry-pick?

GitBeginner
Practicar Ahora

Introducción

La función cherry-pick de Git te permite aplicar commits específicos de una rama a otra. Aunque es poderosa, a veces es necesario revertir una operación de cherry-pick debido a errores o conflictos. En este laboratorio, aprenderás a realizar un cherry-pick y luego a utilizar varios métodos para deshacerlo cuando sea necesario. Al final, tendrás experiencia práctica con las operaciones de cherry-pick y las habilidades para recuperarte de problemas comunes.

Configuración de un Repositorio de Prueba

En este paso, crearemos un repositorio de Git de prueba para practicar las operaciones de cherry-pick. Esto proporcionará un entorno seguro para experimentar con varios comandos de Git.

Creación de un Nuevo Repositorio Git

Comencemos creando un nuevo directorio para nuestro repositorio de prueba e inicializándolo como un repositorio Git:

mkdir -p ~/project/cherry-pick-lab
cd ~/project/cherry-pick-lab
git init

Deberías ver una salida similar a:

Initialized empty Git repository in /home/labex/project/cherry-pick-lab/.git/

Configuración de la Configuración del Usuario de Git

Antes de poder hacer commits, necesitamos configurar un nombre de usuario y un correo electrónico para Git:

git config --local user.name "LabEx User"
git config --local user.email "labex@example.com"

Creación de Commits Iniciales en la Rama Principal (Main Branch)

Creemos algunos commits iniciales en la rama principal:

## Create and commit the first file
echo "## Cherry Pick Lab" > README.md
git add README.md
git commit -m "Initial commit with README"

## Create and commit a second file
echo "console.log('Hello, world!');" > app.js
git add app.js
git commit -m "Add main application file"

Visualización del Historial de Commits

Comprobemos nuestro historial de commits para asegurarnos de que todo está configurado correctamente:

git log --oneline

Deberías ver una salida similar a:

abcd123 (HEAD -> main) Add main application file
efgh456 Initial commit with README

Los hashes de commit reales serán diferentes en tu sistema. Toma nota del hash de commit para "Add main application file" ya que lo usaremos más tarde.

Creación de una Rama de Funcionalidad (Feature Branch)

Ahora, creemos una rama de funcionalidad donde haremos algunos cambios adicionales:

git checkout -b feature-branch

Deberías ver una salida similar a:

Switched to a new branch 'feature-branch'

Ahora agreguemos algunos commits a esta rama de funcionalidad:

## Create and commit a new feature file
echo "function newFeature() { return 'awesome'; }" > feature.js
git add feature.js
git commit -m "Add new feature function"

## Modify the README file
echo -e "## Cherry Pick Lab\n\nThis repo demonstrates git cherry-pick operations." > README.md
git add README.md
git commit -m "Update README with project description"

Ahora tenemos una rama principal con dos commits y una rama de funcionalidad con dos commits adicionales. En el siguiente paso, usaremos cherry-pick para aplicar uno de los commits de la rama de funcionalidad a la rama principal.

Realización de una Operación de Cherry-pick

En este paso, aprenderemos a usar el comando cherry-pick para aplicar un commit específico de una rama a otra. Esto es útil cuando deseas incorporar selectivamente cambios de una rama a otra.

Entendiendo Cherry-pick

El cherry-picking en Git te permite seleccionar un commit específico de una rama y aplicarlo a otra rama. A diferencia de la fusión (merging) o el rebase, que típicamente aplican múltiples commits, el cherry-picking aplica solo un commit a la vez.

Volviendo a la Rama Principal (Main Branch)

Primero, volvamos a la rama principal donde queremos aplicar un commit de la rama de funcionalidad:

git checkout main

Deberías ver una salida confirmando el cambio:

Switched to branch 'main'

Visualización de los Commits de la Rama de Funcionalidad

Antes de hacer cherry-pick, examinemos los commits en la rama de funcionalidad que podríamos querer aplicar a la rama principal:

git log feature-branch --oneline

Esto mostrará todos los commits en la rama de funcionalidad, incluyendo aquellos compartidos con la rama principal. Verás una salida similar a:

1234abc Update README with project description
5678def Add new feature function
abcd123 Add main application file
efgh456 Initial commit with README

Toma nota del hash de commit para "Add new feature function" (en este ejemplo, es 5678def). Usaremos este hash en el siguiente paso.

Cherry-picking un Commit

Ahora hagamos cherry-pick del commit "Add new feature function" de la rama de funcionalidad a nuestra rama principal:

git cherry-pick [COMMIT_HASH]

Reemplaza [COMMIT_HASH] con el hash real que anotaste anteriormente. Por ejemplo:

git cherry-pick 5678def

Si el cherry-pick es exitoso, verás una salida similar a:

[main 98765ab] Add new feature function
 1 file changed, 1 insertion(+)
 create mode 100644 feature.js

Verificando el Cherry-pick

Confirmemos que el cherry-pick funcionó como se esperaba:

git log --oneline

Ahora deberías ver el commit de cherry-picked en el historial de tu rama principal:

98765ab (HEAD -> main) Add new feature function
abcd123 Add main application file
efgh456 Initial commit with README

También puedes verificar que el archivo existe:

ls -la

Deberías ver feature.js listado en la salida:

total 16
drwxr-xr-x 3 labex labex 4096 Jan 1 00:00 .
drwxr-xr-x 3 labex labex 4096 Jan 1 00:00 ..
drwxr-xr-x 8 labex labex 4096 Jan 1 00:00 .git
-rw-r--r-- 1 labex labex   29 Jan 1 00:00 app.js
-rw-r--r-- 1 labex labex   42 Jan 1 00:00 feature.js
-rw-r--r-- 1 labex labex   16 Jan 1 00:00 README.md

La operación de cherry-pick ha aplicado con éxito el commit de la rama de funcionalidad a la rama principal. En el siguiente paso, aprenderemos a deshacer este cherry-pick si es necesario.

Deshaciendo un Cherry-pick con Git Reset

Ahora que hemos hecho cherry-pick de un commit con éxito, aprendamos a deshacer esta operación. En este paso, usaremos el comando git reset, que es la forma más directa de deshacer un cherry-pick reciente.

Entendiendo Git Reset

El comando git reset mueve el puntero de la rama actual a un commit especificado, "deshaciendo" efectivamente cualquier commit que vino después de ese punto. Hay tres modos principales de git reset:

  • --soft: Mueve el puntero de la rama pero deja los cambios preparados (staged)
  • --mixed (por defecto): Mueve el puntero de la rama y des-prepara (unstages) los cambios
  • --hard: Mueve el puntero de la rama y descarta todos los cambios

Para deshacer un cherry-pick, usaremos la opción --hard para eliminar completamente el commit de cherry-picked y sus cambios.

Verificando el Estado Actual

Primero, verifiquemos nuestro estado actual para confirmar que estamos en la rama principal con el commit de cherry-picked:

git log --oneline -n 3

Deberías ver una salida similar a:

98765ab (HEAD -> main) Add new feature function
abcd123 Add main application file
efgh456 Initial commit with README

Deshaciendo el Cherry-pick con Git Reset

Para deshacer la operación de cherry-pick, usaremos git reset con la opción --hard para mover el puntero de la rama un commit hacia atrás:

git reset --hard HEAD~1

Este comando le dice a Git que restablezca la rama al commit anterior al HEAD actual (la parte ~1 significa "un commit antes").

Deberías ver una salida similar a:

HEAD is now at abcd123 Add main application file

Verificando el Reset

Verifiquemos que el cherry-pick se ha deshecho:

git log --oneline

Deberías ver que el commit de cherry-picked ya no está en el historial:

abcd123 (HEAD -> main) Add main application file
efgh456 Initial commit with README

También verifiquemos si el archivo feature.js ha sido eliminado:

ls -la

La salida no debería incluir feature.js:

total 12
drwxr-xr-x 3 labex labex 4096 Jan 1 00:00 .
drwxr-xr-x 3 labex labex 4096 Jan 1 00:00 ..
drwxr-xr-x 8 labex labex 4096 Jan 1 00:00 .git
-rw-r--r-- 1 labex labex   29 Jan 1 00:00 app.js
-rw-r--r-- 1 labex labex   16 Jan 1 00:00 README.md

¡Felicidades! Has deshecho con éxito una operación de cherry-pick usando git reset. Este método es limpio y simple, pero reescribe el historial, por lo que solo debe usarse para cambios locales que no se hayan subido (pushed) a un repositorio compartido.

Deshaciendo un Cherry-pick con Git Revert

En el paso anterior, usamos git reset para deshacer un cherry-pick. Sin embargo, git reset reescribe el historial, lo que puede ser problemático si ya has subido (pushed) tus cambios a un repositorio compartido. En este paso, aprenderemos a usar git revert para deshacer de forma segura un cherry-pick sin reescribir el historial.

Entendiendo Git Revert

El comando git revert crea un nuevo commit que deshace los cambios introducidos por un commit anterior. A diferencia de git reset, que elimina commits del historial, git revert agrega un nuevo commit que contrarresta los cambios, preservando el historial de commits.

Realizando un Cherry-pick de Nuevo

Primero, hagamos cherry-pick del commit de nuevo para tener algo que revertir:

## Get the commit hash from the feature branch
FEATURE_HASH=$(git log feature-branch --oneline | grep "new feature" | cut -d ' ' -f 1)

## Cherry-pick the commit
git cherry-pick $FEATURE_HASH

Deberías ver una salida similar a:

[main 98765ab] Add new feature function
 1 file changed, 1 insertion(+)
 create mode 100644 feature.js

Verificando el Estado Actual

Confirmemos que el cherry-pick fue exitoso:

git log --oneline -n 3
ls -la

Deberías ver el commit de cherry-picked en el historial y el archivo feature.js en el listado del directorio.

Revirtiendo el Cherry-pick

Ahora, usemos git revert para deshacer el cherry-pick mientras preservamos el historial:

git revert HEAD --no-edit

La bandera --no-edit le dice a Git que use el mensaje de commit predeterminado sin abrir un editor.

Deberías ver una salida similar a:

[main abc9876] Revert "Add new feature function"
 1 file changed, 1 deletion(-)
 delete mode 100644 feature.js

Verificando el Revert

Verifiquemos el historial de commits:

git log --oneline -n 4

Deberías ver tanto el commit de cherry-picked como el commit de revert:

abc9876 (HEAD -> main) Revert "Add new feature function"
98765ab Add new feature function
abcd123 Add main application file
efgh456 Initial commit with README

Ahora, verifiquemos si el archivo feature.js ha sido eliminado:

ls -la

La salida no debería incluir feature.js:

total 12
drwxr-xr-x 3 labex labex 4096 Jan 1 00:00 .
drwxr-xr-x 3 labex labex 4096 Jan 1 00:00 ..
drwxr-xr-x 8 labex labex 4096 Jan 1 00:00 .git
-rw-r--r-- 1 labex labex   29 Jan 1 00:00 app.js
-rw-r--r-- 1 labex labex   16 Jan 1 00:00 README.md

Aunque tanto git reset como git revert logran el mismo resultado final (eliminar los cambios introducidos por el cherry-pick), lo hacen de diferentes maneras:

  • git reset elimina el commit del historial, lo que puede causar problemas si el commit se ha compartido con otros.
  • git revert agrega un nuevo commit que deshace los cambios, preservando el historial de commits, lo cual es más seguro para repositorios compartidos.

Elegir entre estos métodos depende de tu situación específica:

  • Usa git reset para cambios locales que no se hayan compartido
  • Usa git revert para cambios que se hayan subido (pushed) a un repositorio compartido

Manejo de Conflictos en Cherry-pick

A veces, cuando haces cherry-pick de un commit, Git puede encontrar conflictos si los cambios en el commit entran en conflicto con los cambios en tu rama actual. En este paso, aprenderemos a manejar los conflictos de cherry-pick y cómo abortar una operación de cherry-pick.

Creando un Escenario con Posibles Conflictos

Primero, creemos un escenario que resultará en un conflicto de cherry-pick:

## Cambiar a la rama principal y modificar README.md
git checkout main
echo -e "## Cherry Pick Lab\n\nThis is the main branch README." > README.md
git commit -am "Update README in main branch"

## Cambiar a la rama feature y hacer un cambio en conflicto en README.md
git checkout feature-branch
echo -e "## Cherry Pick Lab\n\nThis README has been updated in the feature branch." > README.md
git commit -am "Update README in feature branch"

Intentando un Cherry-pick con Conflictos

Ahora, volvamos a la rama principal e intentemos hacer cherry-pick del commit de la rama feature:

git checkout main
CONFLICT_HASH=$(git log feature-branch --oneline | grep "Update README in feature" | cut -d ' ' -f 1)
git cherry-pick $CONFLICT_HASH

Dado que ambas ramas modificaron las mismas líneas en README.md, deberías ver un conflicto:

error: could not apply a1b2c3d... Update README in feature branch
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'

Visualizando el Conflicto

Examinemos el conflicto:

git status

Deberías ver una salida que indica un conflicto en README.md:

On branch main
You are currently cherry-picking commit a1b2c3d.
  (fix conflicts and run "git cherry-pick --continue")
  (use "git cherry-pick --abort" to cancel the cherry-pick operation)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
  both modified:   README.md

no changes added to commit (use "git add" and/or "git commit -a")

Veamos el contenido del archivo en conflicto:

cat README.md

Deberías ver algo como:

## Cherry Pick Lab

<<<<<<< HEAD
This is the main branch README.
=======
This README has been updated in the feature branch.
>>>>>>> a1b2c3d... Update README in feature branch

Resolviendo el Conflicto

Para resolver el conflicto, necesitamos editar el archivo y decidir qué cambios mantener. Modifiquemos README.md para incluir ambos cambios:

echo -e "## Cherry Pick Lab\n\nThis is the main branch README.\n\nThis README has also been updated with content from the feature branch." > README.md

Ahora, marquemos el conflicto como resuelto y continuemos el cherry-pick:

git add README.md
git cherry-pick --continue

Git abrirá un editor con un mensaje de commit predeterminado. Guarda y cierra el editor para completar el cherry-pick.

Abortando un Cherry-pick

A veces, podrías decidir que no quieres resolver los conflictos y preferirías cancelar la operación de cherry-pick. Creemos otro conflicto y luego abortemos el cherry-pick:

## Create another conflicting commit in feature branch
git checkout feature-branch
echo "// This will conflict with app.js in main" > app.js
git commit -am "Modify app.js in feature branch"

## Try to cherry-pick this commit to main
git checkout main
ANOTHER_CONFLICT=$(git log feature-branch --oneline | grep "Modify app.js" | cut -d ' ' -f 1)
git cherry-pick $ANOTHER_CONFLICT

Deberías ver otro conflicto. Esta vez, abortemos el cherry-pick:

git cherry-pick --abort

Deberías ver que la operación de cherry-pick ha sido cancelada y tu directorio de trabajo ha sido restaurado a su estado anterior:

git status

Salida:

On branch main
nothing to commit, working tree clean

Manejar los conflictos durante las operaciones de cherry-pick es una habilidad esencial para los usuarios de Git. Tienes tres opciones cuando encuentras un conflicto:

  1. Resuelve el conflicto manualmente, luego usa git add y git cherry-pick --continue
  2. Omite el commit en conflicto con git cherry-pick --skip
  3. Aborta toda la operación de cherry-pick con git cherry-pick --abort

El mejor enfoque depende de la situación específica y de los requisitos de tu proyecto.

Resumen

En este laboratorio, has ganado experiencia práctica con la función cherry-pick de Git y has aprendido múltiples formas de deshacer las operaciones de cherry-pick:

  1. Creaste un repositorio de prueba con múltiples ramas y commits para practicar.
  2. Realizaste una operación de cherry-pick para aplicar un commit específico de una rama a otra.
  3. Aprendiste a deshacer un cherry-pick usando git reset, que es adecuado para cambios locales.
  4. Exploraste cómo deshacer de forma segura un cherry-pick usando git revert, que preserva el historial.
  5. Practicaste el manejo de conflictos de cherry-pick y aprendiste a abortar una operación de cherry-pick.

Estas habilidades son invaluables cuando se trabaja con Git en proyectos del mundo real. El cherry-picking te permite aplicar selectivamente cambios entre ramas, mientras que saber cómo deshacer los cherry-picks te ayuda a recuperarte de errores y mantener un historial de Git limpio.

Recuerda que los diferentes métodos para deshacer sirven para diferentes propósitos:

  • Usa git reset para cambios locales que no se hayan compartido.
  • Usa git revert para cambios que se hayan subido (pushed) a un repositorio compartido.
  • Usa git cherry-pick --abort para cancelar una operación de cherry-pick en curso.

Al comprender estas opciones, puedes elegir el método más apropiado para tu situación específica.