29  Importar, pero tienes trabajo local

Problema: desea importar cambios desde el upstream, pero ha realizado algunos trabajos nuevos localmente desde la última vez que los realizó. Esto suele surgir porque lo que realmente quieres hacer es enviar, pero Git no te lo permitirá hasta que incorpores por primera vez los cambios anteriores.

En aras de la simplicidad, supongamos que estamos tratando con la rama upstream y la remota se llama origin.

Historial reciente de confirmaciones de origin/main:

A--B--C

Historial de confirmaciones recientes de la rama upstream local:

A--B--D

o tal vez

A--B--(cambios no confirmados)

Su objetivo: obtener la confirmación C en su rama local, manteniendo al mismo tiempo el trabajo en la confirmación D o sus cambios no confirmados.

Priorizamos enfoques simples que son buenos para el uso inicial de Git, pero mencionamos alternativas más agradables a largo plazo.

29.1 El trabajo local no está confirmado.

El estado remoto es A--B--C.
El estado local es A--B--(cambiosnoconfirmados).

29.1.1 Casos simples felices

Hay dos escenarios felices en los que git pull “simplemente funcionará”:

  • Ha introducido archivos completamente nuevos que no existen en la rama remota y, por lo tanto, no es posible que tengan cambios conflictivos. ¡Estás de suerte! Puedes simplemente git pull.
  • Los archivos afectados por su trabajo local tienen CERO superposición con los archivos afectados por los cambios que necesita importar del control remoto. ¡Tú también estás de suerte! Puedes simplemente git pull.

Resumen de estos felices escenarios de git pull:

                 Remote: A--B--C

Local before 'git pull': A--B--(cambiosnoconfirmados)
 Local after 'git pull': A--B--C--(cambiosnoconfirmados)

Lo que realmente sucedió aquí es que git pull resultó en una fusión rápida, es decir, colocamos la confirmación C justo al final de su historial. Este también sería el caso en la situación más simple en la que el historial local reciente fuera simplemente A--B, es decir, no se hubiera agregado ningún trabajo local desde la última sincronización con origin/main.

29.1.2 git stash funciona, a veces

Si sus cambios afectan un archivo (foo.R en el ejemplo siguiente) que también se modificó en la confirmación C, no puede git pull. No está de más intentarlo, pero fallarás y se verá así:

jenny@2015-mbp ethel $ git pull
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 1), reused 1 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), done.
From github.com:jennybc/ethel
   db046b4..2d33a6f  main     -> origin/main
Updating db046b4..2d33a6f
error: Your local changes to the following files would be overwritten by merge:
        foo.R
Please commit your changes or stash them before you merge.
Aborting

¿Ahora que? En primer lugar, debe salvaguardar los cambios locales guardándolos o confirmándolos. (Yo personalmente elegiría confirmar y ejecutar un flujo de trabajo descrito en Sección 29.2.)

No soy un gran admirador de git stash; Creo que normalmente es mejor aprovechar todas las oportunidades posibles para solidificar sus habilidades en torno a conceptos y operaciones centrales, por ejemplo, confirmar, posiblemente en una rama. Pero si desea utilizar git stash, esta oportunidad es la mejor posible.

git stash es una forma de almacenar temporalmente algunos cambios para eliminarlos. Ahora puedes hacer otra cosa, sin mucho problema. En nuestro caso, “hacer algo más” es obtener los cambios ascendentes con un simple y agradable git pull. Luego vuelves a aplicar, eliminas el stash y continúas donde lo dejaste.

Para obtener más detalles sobre el almacenamiento, recomiendo

Este es el mejor escenario para “stash, pull, unstash” en el ejemplo anterior:

git stash save
git pull
git stash pop

Y aquí está el resultado de nuestro ejemplo:

jenny@2015-mbp ethel $ git stash save
Saved working directory and index state WIP on main: db046b4 Merge branch 'main'of github.com:jennybc/ethel

jenny@2015-mbp ethel $ git pull
Updating db046b4..2d33a6f
Fast-forward
 foo.R | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

jenny@2015-mbp ethel $ git stash pop
Auto-merging foo.R
On branch main
Your branch is up-to-date with 'origin/main'.

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   foo.R

no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (012c4dcd3a4c3af6757c4c3ca99a9eaeff1eb2a4)

Así es como se ve el éxito. Has logrado esto:

      Remote: A--B--C

Local before: A--B--(cambiosnoconfrimados)
 Local after: A--B--C--(cambiosnoconfrimados)

Como se indicó anteriormente, acabamos de disfrutar de una combinación de avance rápido, posible al ocultar temporalmente y luego desbloquear los cambios locales no confirmados.

29.1.3 git stash con conflictos

Si sus cambios locales se superponen con los cambios que está realizando, obtendrá un conflicto de fusión de git stash pop. Ahora tienes que hacer algunos trabajos de recuperación. En este caso, no has ganado nada al usar git stash en primer lugar, lo que explica mi falta general de entusiasmo por git stash.

Aquí se explica cómo ejecutar el flujo de trabajo git stash en nuestro ejemplo, frente a conflictos (basado en esta respuesta de Stack Overflow):

jenny@2015-mbp ethel $ git stash save
Saved working directory and index state WIP on main: 2d33a6f Back to 5

jenny@2015-mbp ethel $ git pull
Updating 2d33a6f..1eddf9e
Fast-forward
 foo.R | 1 +
 1 file changed, 1 insertion(+)
 
jenny@2015-mbp ethel $ git stash pop
Auto-merging foo.R
CONFLICT (content): Merge conflict in foo.R

En este punto, debes resolver el conflicto de fusión Sección 22.4. Literalmente, en cada lugar de conflicto, elija una versión u otra (anticipada o oculta) o cree un híbrido usted mismo. Retire todos los marcadores insertados para delimitar los conflictos. Guarde.

Dado que git stash pop no funcionó correctamente, debemos restablecer manualmente Sección 27.3 y eliminar el stash para finalizar.

jenny@2015-mbp ethel $ git reset
Unstaged changes after reset:
M       foo.R

jenny@2015-mbp ethel $ git stash drop
Dropped refs/stash@{0} (7928db50288e9b4d934803b6b451a000fd7242ed)

Uf, hemos terminado. Hemos logrado esto:

      Remote: A--B--C

Local before: A--B--(cambiosnoconfirmados)
 Local after: A--B--C--(cambiosnoconfirmados*)

El asterisco en cambiosnoconfirmados* indica que sus cambios no confirmados ahora podrían reflejar los ajustes realizados cuando resolvió los conflictos.

29.2 El trabajo local está cnfirmado

El estado remoto es A--B--C.
El estado local es A--B--D.

29.2.1 Importar (buscar y fusionar)

La opción más sencilla es recuperar las confirmaciones desde arriba y fusionarlas, que es lo que hace git pull. Esta es una buena opción si eres nuevo en Git. Esto lleva a una historia más complicada, pero cuando eres nuevo, esta es la menor de tus preocupaciones. Fúndete, sé feliz y continúa.

Aquí está el mejor caso, la versión sin conflictos de fusión de git pull:

jenny@2015-mbp ethel $ git pull

< PROBABLEMENTE SERÁS ENVIADO A UN EDITOR AQUÍ RE: ¡MENSAJE DE COMUNICACIÓN DE FUSIÓN! >

Merge made by the 'recursive' strategy.
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

Dependiendo de su versión de Git, su configuración y su uso de una GUI, es posible que deba confirmar/editar un mensaje de confirmación para la confirmación de fusión.

¿O qué pasa si las cosas no van tan bien? Si la confirmación C (en el control remoto) y la confirmación D (local) tienen cambios en las mismas partes de uno o más archivos, es posible que Git no pueda fusionarse automáticamente y obtendrá conflictos de fusión. Se verá algo como esto:

jenny@2015-mbp ethel $ git pull
Auto-merging foo.R
CONFLICT (content): Merge conflict in foo.R
Automatic merge failed; fix conflicts and then commit the result.

Debes resolver estos conflictos Sección 22.4. Literalmente, en cada lugar de conflicto, elija una versión u otra (ascendente o local) o cree un híbrido usted mismo. Retire todos los marcadores insertados para delimitar los conflictos. Guarde.

Marque el archivo afectado foo.R como resuelto mediante git add y haga un git commit explícito para finalizar esta fusión.

jenny@2015-mbp ethel $ git add foo.R
jenny@2015-mbp ethel $ git commit
[main 20b297b] Merge branch 'main' of github.com:jennybc/ethel

Nuevamente, no se sorprenda si, durante el git commit, se encuentra en un editor, confirmando/editando el mensaje de confirmación para la confirmación de fusión.

Hemos logrado esto:

      Remote: A--B--C

Local before: A--B--D
 Local after: A--B--D--(confirmación de fusión)
                  \_C_/

29.2.2 Importar y rebase

git pull --rebase crea una historia mejor que git pull al integrar confirmaciones locales y remotas. Evita una confirmación de fusión, por lo que el historial está menos desordenado y es lineal. Puede hacer que los conflictos de fusión sean más difíciles de resolver, por lo que sigo recomendando git pull como solución básica.

Aquí está el mejor caso, la versión sin conflictos de fusión de git pull --rebase:

jenny@2015-mbp ethel $ git pull --rebase
First, rewinding head to replay your work on top of it...
Applying: Take max

Tenga en cuenta que NO lo enviaron a un editor para manipular el mensaje de confirmación para la confirmación de fusión, ¡porque no hay confirmación de fusión! Ésta es la belleza del rebase.

Hemos logrado esto:

      Remote: A--B--C

Local before: A--B--D
 Local after: A--B--C--D

Es como si sacáramos el trabajo ascendente en la confirmación C y luego hiciéramos el trabajo local incorporado en la confirmación D. No tenemos confirmaciones de fusión desordenadas y un historial lineal. ¡Lindo!

La mala noticia: al igual que con git pull simple, todavía es posible tener conflictos de fusión con git pull --rebase. Si tiene varias confirmaciones locales, incluso puede encontrarse resolviendo conflictos una y otra vez, ya que estas confirmaciones se reproducen secuencialmente. Por lo tanto, esto es más adecuado para usuarios de Git más experimentados y en situaciones donde los conflictos son poco probables (en realidad, tienden a estar correlacionados).

En este punto, si intentas hacer git pull --rebase y te atascas en conflictos de fusión, te recomiendo git rebase --abort para retroceder. Por ahora, simplemente siga una estrategia más sencilla.

29.3 Otros enfoques

Hay muchas más formas de manejar esta situación, que puedes descubrir y explorar a medida que adquieres experiencia y empiezas a preocuparte más por la historia. Esbozamos aquí algunas ideas.

29.3.1 Utilice una rama temporal para el trabajo local

Recordar:
El estado remoto es A--B--C.
El estado local es A--B--(cambiosnoconfirmados).

Esta es una alternativa al flujo de trabajo oculto que tiene la ventaja de brindarle práctica con técnicas de Git que son más útiles en general. También conduce a una bonita historia.

Cree una nueva rama temporal y confirme allí los cambios no confirmados. Consulte main y git pull para obtener cambios desde arriba. Ahora necesitas recuperar el trabajo de la confirmación en la rama temporal. Opciones:

  • Fusiona la rama temporal en main.
  • Cherry elige la confirmación de la rama temporal a main.

En cualquier caso, aún es posible que tengas que lidiar con conflictos de fusión.

En cualquier caso, si se sintió obligado a confirmar antes de estar listo o a aceptar una confirmación de fusión fea, puede realizar un reinicio mixto para “desconfirmar” pero mantener los cambios en main o seguir modificando hasta que esté satisfecho con la confirmación.

29.4 Algunos trabajos locales están confirmados, otros no.

Esta es una situación híbrida incómoda que se puede manejar con una combinación de estrategias vistas anteriormente: realizar una confirmación pragmática en main o una rama temporal. Integre los cambios locales y ascendentes en main. Si no está satisfecho con la confirmación pragmática final (que solo existe localmente), reiníciela o modifíquela hasta que lo esté.