En la lección anterior desmontamos el misterio: una rama es un fichero de 41 bytes con un hash dentro, y HEAD es otro fichero que dice en qué rama estás. Ahora vamos a usar esa maquinaria. Ana abrirá por fin funcionalidad/contador-tareas, Bruno hará lo propio con funcionalidad/filtro-pendientes, y el repositorio dejará de tener una sola línea de trabajo.
Crear ramas en Git es trivial —lo verás en un segundo—, pero moverse entre ellas tiene matices que conviene dominar desde el principio: qué ocurre con el trabajo que tienes a medias, por qué a veces Git te deja cambiar de rama y otras se niega, y qué es ese estado de HEAD desacoplado que aparece cuando menos lo esperas y que asusta a todo el mundo la primera vez.
También aclararemos una duda que arrastra cualquiera que aprendiera Git antes de 2019: la diferencia entre git checkout y git switch, y por qué Git decidió partir un comando en dos.
Contenido
git branch: crear sin moversegit switch: moversegit switch -c: crear y moverse en un pasogit switchfrente agit checkout- Crear una rama desde un punto concreto del historial
git switch -: alternar entre dos ramas- Cambios sin confirmar al cambiar de rama
- HEAD desacoplado (detached HEAD)
- El repositorio del equipo, ya con ramas
git branch: crear sin moverse
git branch: crear sin moverseAna está en main, con el repositorio tal como lo dejamos:
c5d9b1e (HEAD -> main) Documentar la instalación en el README 4e7f2a9 Añadir borrado de tareas al listado 8b6d3c2 Añadir estilos base del listado 1a4c8d6 Estructura inicial del gestor de tareas
Quiere una rama para el contador de tareas:
Silencio absoluto. En Git, el silencio suele significar éxito. Veamos qué ha pasado exactamente:
Un fichero nuevo, con el hash del commit en el que estaba Ana. (Nota lateral interesante: como el nombre de la rama lleva una barra, Git ha creado un directorio funcionalidad/ dentro de refs/heads/. Los nombres de rama con barras son simplemente rutas de fichero; volveremos sobre las convenciones de nombres en la lección 03-06.)
Ahora la parte importante:
HEAD no se ha movido. Ana sigue en main. Ha creado un puntero nuevo, pero no se ha situado en él:
El asterisco marca la rama actual. Hay dos ramas y las dos apuntan al mismo commit.
gitGraph commit id: "1a4c8d6" commit id: "8b6d3c2" commit id: "4e7f2a9" commit id: "c5d9b1e" branch contador-tareas
Regla:
git branch <nombre>crea el puntero y no toca nada más. NiHEAD, ni el directorio de trabajo, ni el área de preparación.
Si Ana confirmase ahora, el commit iría a main, porque es donde sigue apuntando HEAD. Es un error clásico: crear la rama y olvidarse de cambiarse a ella. Por eso, en la práctica, casi nunca se usa git branch a secas para empezar a trabajar; se usa la variante del apartado 3.
git switch: moverse
git switch: moversePara situarse en la rama recién creada:
Y ahora sí:
Eso es todo lo que ha hecho Git en este caso: reescribir el fichero .git/HEAD. Como las dos ramas apuntaban al mismo commit, el directorio de trabajo no ha cambiado ni un byte.
Cuando las ramas apuntan a commits distintos, git switch hace algo más: además de reescribir HEAD, actualiza el directorio de trabajo y el área de preparación para que reflejen el árbol del commit de destino. Es decir, cambia los ficheros de tu disco. Eso es lo que ocurre en las tres operaciones internas de un cambio de rama:
| Paso | Qué hace Git |
|---|---|
| 1 | Comprueba que el cambio es seguro (ver apartado 7) |
| 2 | Actualiza el directorio de trabajo y el índice al árbol del commit de destino |
| 3 | Reescribe .git/HEAD con la nueva rama |
Ana escribe el contador de tareas en app.js y confirma:
[funcionalidad/contador-tareas 6f2b9d4] Añadir el contador de tareas pendientes 1 file changed, 12 insertions(+)
Fíjate en el nombre entre corchetes: [funcionalidad/contador-tareas 6f2b9d4]. Git te está diciendo en qué rama ha caído el commit. Es una comprobación gratuita que conviene leer siempre.
Ana añade después el marcado de completadas:
[funcionalidad/contador-tareas 9d1e4b7] Marcar tareas como completadas al hacer clic 2 files changed, 18 insertions(+), 2 deletions(-)
Estado actual:
* 9d1e4b7 (HEAD -> funcionalidad/contador-tareas) Marcar tareas como completadas al hacer clic * 6f2b9d4 Añadir el contador de tareas pendientes * c5d9b1e (main) Documentar la instalación en el README * 4e7f2a9 Añadir borrado de tareas al listado * 8b6d3c2 Añadir estilos base del listado * 1a4c8d6 Estructura inicial del gestor de tareas
main se ha quedado quieta en c5d9b1e, exactamente como estaba. El trabajo de Ana está aislado: puede confirmar cuanto quiera sin tocar la línea principal del proyecto.
git switch -c: crear y moverse en un paso
git switch -c: crear y moverse en un pasoCrear una rama y no cambiarse a ella es raro. Por eso existe el atajo:
La -c viene de create. Es exactamente equivalente a:
Esta es la forma que usarás el 95 % de las veces. git branch <nombre> a secas queda reservado para los casos en los que quieres marcar un punto del historial sin abandonar lo que estás haciendo (por ejemplo, dejar un marcador antes de una operación arriesgada).
Un detalle importante que se pasa por alto: la rama nueva se crea desde donde estás ahora. Si Ana ejecutase ese comando estando en funcionalidad/contador-tareas, la rama nueva partiría de 9d1e4b7 y arrastraría el contador de tareas, que no es lo que quiere. Para el trabajo de Bruno, el punto de partida correcto es main:
O, mejor aún, indicándolo explícitamente en un solo comando —la forma que veremos en el apartado 5.
Hay una variante mayúscula, -C, que crea la rama o la reinicia si ya existe:
Úsala con cuidado: si la rama ya existía apuntando a otro sitio, -C mueve el puntero sin preguntar y puedes dejar commits huérfanos. -c en cambio falla con fatal: a branch named '…' already exists, que casi siempre es lo que quieres.
git switch frente a git checkout
git switch frente a git checkoutSi has visto Git en cualquier tutorial anterior a 2019, o en la mayoría de los que hay por internet hoy, habrás visto esto:
git checkout funcionalidad/contador-tareas # equivale a git switch
git checkout -b funcionalidad/filtro-pendientes # equivale a git switch -cFunciona. git checkout no está obsoleto ni va a desaparecer. Pero desde Git 2.23 (agosto de 2019) existen git switch y git restore, y para trabajo nuevo son la opción recomendada. La razón es sencilla: git checkout hacía demasiadas cosas distintas.
| Necesidad | Comando antiguo | Comando moderno |
|---|---|---|
| Cambiar de rama | git checkout <rama> |
git switch <rama> |
| Crear rama y cambiarse | git checkout -b <rama> |
git switch -c <rama> |
| Crear rama desde un commit | git checkout -b <rama> <commit> |
git switch -c <rama> <commit> |
| Ir a un commit suelto | git checkout <commit> |
git switch --detach <commit> |
| Volver a la rama anterior | git checkout - |
git switch - |
| Descartar cambios de un fichero | git checkout -- <fichero> |
git restore <fichero> |
| Quitar del área de preparación | git reset HEAD <fichero> |
git restore --staged <fichero> |
| Recuperar un fichero de otro commit | git checkout <commit> -- <fichero> |
git restore --source=<commit> <fichero> |
Mira las tres últimas filas, que son las importantes. git checkout servía tanto para navegar entre ramas (una operación inofensiva y reversible) como para destruir cambios del directorio de trabajo (una operación irreversible). El mismo verbo para dos cosas de riesgo opuesto.
Y el desastre potencial estaba en la ambigüedad. Imagina que existe un fichero llamado informe y también una rama llamada informe:
¿Cambia de rama o descarta los cambios del fichero? Git resolvía a favor de la rama y había que escribir git checkout -- informe (con el doble guion separador) para forzar la interpretación de fichero. Si te olvidabas del --, podías perder trabajo sin aviso.
La separación en Git 2.23 elimina la ambigüedad de raíz:
git switchsolo trabaja con ramas. No puede tocar ficheros.git restoresolo trabaja con ficheros. No puede cambiar de rama.
Ya usaste git restore en la lección 02-04, así que ya conoces la mitad de la reforma. Esta lección te da la otra mitad.
Qué hacer en la práctica:
- Escribe
git switchygit restoreen tu trabajo diario. - Aprende a leer
git checkout, porque lo verás en todas partes: documentación antigua, respuestas de Stack Overflow, scripts de integración continua y compañeros con costumbres arraigadas. - No conviertas los scripts existentes que funcionan solo por moda;
checkoutsigue siendo perfectamente válido.
- Crear una rama desde un punto concreto del historial
Por defecto, una rama nueva parte de HEAD. Pero puedes indicarle otro punto de partida como segundo argumento, y eso vale para las dos formas del comando:
El punto de partida puede ser cualquier cosa que Git sepa resolver a un commit, con toda la sintaxis que aprendiste en la lección 02-06:
# Desde otra rama, sin tener que cambiarte primero
git switch -c funcionalidad/filtro-pendientes main
# Desde un commit concreto por su hash
git switch -c experimento/rediseno 8b6d3c2
# Desde una posición relativa
git switch -c correccion/regresion HEAD~2
# Desde el padre de un commit concreto
git switch -c prueba 4e7f2a9^El primero es el caso de Bruno y es la forma correcta de hacerlo: crea la rama a partir de main sin necesidad de cambiarse a main antes. Un comando en lugar de dos, y sin riesgo de olvidarse.
Un caso real donde esto salva la tarde: has estado programando durante media hora en main por despiste, has hecho tres commits, y te das cuenta de que deberían haber ido en una rama. Solución:
# 1. Crear la rama aquí mismo, con los tres commits dentro
git branch funcionalidad/lo-que-sea
# 2. Devolver main a donde debía estar
git switch main
git reset --hard HEAD~3
# 3. Irse a la rama, donde está todo el trabajo intacto
git switch funcionalidad/lo-que-seaFíjate en que el paso 1 usa git branch sin cambiarse: crea el marcador antes de mover nada. (git reset --hard es potente y peligroso; lo estudiaremos a fondo en la lección 09-02. Aquí es seguro porque los commits siguen alcanzables desde la rama nueva.)
git switch -: alternar entre dos ramas
git switch -: alternar entre dos ramasCuando estás revisando el trabajo de un compañero y saltas continuamente entre dos ramas, escribir el nombre completo cansa. El guion solo significa "la rama anterior":
Es el mismo concepto que cd - en la shell. Funciona con nombres de rama tan largos como funcionalidad/exportar-listado-a-csv y ahorra muchísimas pulsaciones.
Por debajo, el guion es un alias de @{-1}, que significa "la rama en la que estuve hace un cambio". Y hay más:
Esta información sale del reflog, el registro que Git mantiene de todos los movimientos de HEAD. Es la misma herramienta que permite recuperar trabajo aparentemente perdido, y la estudiaremos en la lección 09-04.
- Cambios sin confirmar al cambiar de rama
Aquí está el matiz que más confusión genera. ¿Qué pasa con lo que tienes a medias cuando cambias de rama?
La respuesta corta: Git intenta llevárselo contigo, y si no puede hacerlo sin perder nada, se niega.
La respuesta larga depende de si los cambios afectan a ficheros que difieren entre las dos ramas.
Caso A: Git te deja pasar y se lleva los cambios
Ana está en funcionalidad/contador-tareas y ha empezado a retocar README.md, un fichero que es idéntico en las dos ramas:
Ha funcionado, y esa línea M README.md es Git avisándote: "ese fichero venía modificado y sigue modificado". El cambio ha viajado con Ana a la otra rama. Sigue sin confirmar, en su directorio de trabajo:
Esto es útil a propósito: te permite empezar a escribir algo, darte cuenta de que estás en la rama equivocada y corregirlo sin perder nada.
Caso B: Git se niega
Ahora Ana está en main y modifica app.js, que sí difiere entre main y la rama del contador (el contador lo cambió allí):
error: Your local changes to the following files would be overwritten by checkout: app.js Please commit your changes or stash them before you switch branches. Aborting
Git se ha negado, y ha hecho bien. Para situarse en la otra rama tendría que sobrescribir app.js con la versión de allí, y eso destruiría los cambios de Ana sin posibilidad de recuperarlos, porque nunca han estado en la base de datos de objetos.
Lo importante es que la operación se ha abortado por completo. Sigues donde estabas y tus cambios están intactos.
La regla, resumida
| Situación | Comportamiento |
|---|---|
| El fichero modificado es igual en las dos ramas | Cambia de rama y se lleva las modificaciones |
| El fichero modificado difiere entre las dos ramas | Rechaza el cambio de rama y aborta |
| Hay cambios en el área de preparación sobre ficheros que no difieren | Cambia de rama y conserva el área de preparación |
| Hay ficheros nuevos sin seguimiento que no existen en el destino | Cambia de rama y los deja donde están |
| Hay ficheros sin seguimiento que sí existen en el destino | Rechaza, para no sobrescribirlos |
Las tres salidas cuando Git se niega
-
Confirmar. Si el trabajo tiene sentido en la rama actual, confírmalo. Si está a medias, puedes hacer un commit provisional y arreglarlo después con
git commit --amend(lección 02-04) o con el rebase interactivo (lección 05-02). -
Descartar. Si el cambio no vale nada,
git restore app.jslo elimina y el cambio de rama pasa a estar permitido. -
Guardarlo temporalmente. Para el caso real —trabajo a medias que quieres conservar pero que aún no merece un commit— Git tiene una herramienta específica:
git stash, que aparta los cambios a un almacén temporal, deja el directorio limpio y permite recuperarlos después congit stash pop:
git stash # aparta los cambios
git switch funcionalidad/contador-tareas # ahora sí puede
# … lo que fuera a hacer …
git switch main
git stash pop # recupera los cambiosEs un comando con bastante más profundidad de la que sugiere este ejemplo (varias entradas apiladas, incluir ficheros sin seguimiento, aplicar en otra rama…). Lo estudiaremos entero en la lección 05-04; por ahora quédate con que existe y con que es la respuesta estándar a este problema.
Forzar el cambio de rama (con cuidado)
Existe la opción de descartar los cambios y cambiar de rama de todas formas:
(El equivalente antiguo era git checkout -f.) Es destructivo e irreversible: los cambios no confirmados no están en ningún objeto de Git, así que no hay ningún reflog ni ninguna recuperación posible. Úsalo solo cuando estés absolutamente seguro de que quieres tirar ese trabajo.
- HEAD desacoplado (detached HEAD)
Recuerda la lección anterior: HEAD normalmente contiene ref: refs/heads/<rama>. Pero puede contener directamente un hash. A ese estado se le llama HEAD desacoplado.
Cómo se llega
La forma deliberada es pedirlo:
Note: switching to '4e7f2a9'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by switching back to a branch. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -c with the switch command. Example: git switch -c <new-branch-name> HEAD is now at 4e7f2a9 Añadir borrado de tareas al listado
Git te suelta un discurso porque el estado es inusual, no porque sea peligroso. De hecho ese texto explica ya casi todo lo que necesitas saber.
Las formas accidentales de llegar, que son las que asustan:
git checkout <hash>(sin-b), la más frecuente con el comando antiguo.git checkout v1.2.0, al situarse en una etiqueta —las etiquetas apuntan a commits, no son ramas.- Durante un
git rebaseo ungit bisect, que trabajan internamente en este estado (módulos 5 y 6). - Al situarse en
origin/mainen lugar de enmain, un error muy común que veremos en el módulo 4.
Qué se ve
Un hash directamente, sin ref:. HEAD apunta al commit, no a una rama.
El directorio de trabajo contiene los ficheros tal como estaban en ese commit. Es un estado perfectamente legítimo: puedes compilar, ejecutar las pruebas, mirar el código antiguo o incluso confirmar.
Para qué sirve de verdad
No es un accidente que haya que evitar; es una herramienta:
- Revisar cómo estaba el proyecto en un momento dado. Ana quiere ver la aplicación tal como estaba el día del primer commit:
git switch --detach 1a4c8d6, abreindex.htmlen el navegador y ya está. - Probar si un fallo existía en una versión antigua, situándose en distintos puntos del historial.
- Experimentar sin comprometerse. Puedes confirmar en este estado, y si el resultado no vale, te vuelves a una rama y los commits quedan huérfanos.
El peligro real
El peligro no es estar en este estado. Es confirmar en él y luego irse sin dejar rastro:
# En detached HEAD
echo "// prueba de rendimiento" >> app.js
git commit -am "Probar una optimización del pintado"Ese commit 7d2f9a1 es real y está en la base de datos de objetos, pero ninguna rama lo alcanza. Si Ana ahora hace git switch main:
Warning: you are leaving 1 commit behind, not connected to any of your branches: 7d2f9a1 Probar una optimización del pintado If you want to keep it by creating a new branch, this may be a good time to do so with: git branch <new-branch-name> 7d2f9a1 Switched to branch 'main'
Git avisa, y hasta te da el comando exacto. Pero si no lees ese aviso y cierras la terminal, el hash desaparece de tu vista y el commit queda huérfano. Sigue recuperable durante un tiempo mediante el reflog (módulo 9), pero es una situación que no quieres.
Cómo salir sin perder nada
Si no has confirmado nada (solo mirabas):
O git switch main, o cualquier rama. No hay nada que salvar.
Si has confirmado y quieres conservarlo, crea una rama antes de irte:
Ahora HEAD vuelve a apuntar a una rama, esa rama apunta a 7d2f9a1, y el commit ya es alcanzable. Ya no hay nada huérfano.
Si ya te has ido y el commit quedó atrás, todavía puedes crear la rama a posteriori, siempre que tengas el hash (aparece en el aviso, y si lo perdiste, en git reflog):
graph TD
A["En una rama<br/>HEAD → ref: refs/heads/main"] -->|"git switch --detach abc123"| B["HEAD desacoplado<br/>HEAD → abc123"]
B -->|"git switch -<br/>(sin confirmar nada)"| A
B -->|"confirmar aquí"| C["Commits huérfanos<br/>ninguna rama los alcanza"]
C -->|"git switch -c mi-rama"| D["Trabajo salvado<br/>en una rama nueva"]
C -->|"irse sin crear rama"| E["Solo recuperable<br/>vía reflog (módulo 9)"]
- El repositorio del equipo, ya con ramas
Recapitulemos dónde estamos. Ana tiene su rama con dos commits. Bruno ha estado trabajando en la suya, con los tres commits que ya vimos al final del módulo 2 —el filtro de pendientes, el estilo de las completadas y la corrección del foco—, ahora en funcionalidad/filtro-pendientes en lugar de directamente sobre main.
Para el resto del módulo vamos a razonar como si las tres ramas estuvieran en un mismo repositorio local, el de Ana. Cómo consigue el equipo que el trabajo viaje entre el portátil de Ana y el MacBook de Bruno es precisamente el tema del módulo 4; aquí nos concentramos en la mecánica de las ramas, que es idéntica venga el trabajo de donde venga.
funcionalidad/contador-tareas 9d1e4b7 Marcar tareas como completadas al hacer clic funcionalidad/filtro-pendientes b2e6d3f Corregir el foco del campo tras añadir una tarea * main c5d9b1e Documentar la instalación en el README
* b2e6d3f (funcionalidad/filtro-pendientes) Corregir el foco del campo tras añadir una tarea * 7c1f4a9 Aplicar estilo a las tareas completadas * 3d5b8e1 Añadir filtro de tareas pendientes | * 9d1e4b7 (funcionalidad/contador-tareas) Marcar tareas como completadas al hacer clic | * 6f2b9d4 Añadir el contador de tareas pendientes |/ * c5d9b1e (HEAD -> main) Documentar la instalación en el README * 4e7f2a9 Añadir borrado de tareas al listado * 8b6d3c2 Añadir estilos base del listado * 1a4c8d6 Estructura inicial del gestor de tareas
gitGraph commit id: "1a4c8d6" commit id: "8b6d3c2" commit id: "4e7f2a9" commit id: "c5d9b1e" branch contador-tareas checkout contador-tareas commit id: "6f2b9d4" commit id: "9d1e4b7" checkout main branch filtro-pendientes checkout filtro-pendientes commit id: "3d5b8e1" commit id: "7c1f4a9" commit id: "b2e6d3f"
Tres líneas de trabajo, tres punteros, 123 bytes de coste total. Los cuatro commits de base están una sola vez en la base de datos de objetos y los comparten las tres ramas.
Y aquí está el problema que resolveremos en la lección siguiente: las dos funcionalidades están terminadas y probadas, pero el proyecto real —main— sigue exactamente donde estaba hace una semana. Nada de lo que han hecho Ana y Bruno está en la línea principal.
Errores Comunes y Consejos
Error 1: git branch <nombre> y ponerse a trabajar. Creas la rama, se te olvida el git switch, haces cinco commits y todos caen en main. Prevención doble: usa git switch -c en lugar de git branch, y lee la línea entre corchetes que imprime git commit, que te dice la rama de destino.
Error 2: crear la rama desde el sitio equivocado. git switch -c correccion/urgente estando en una rama de funcionalidad a medias arrastra todo ese trabajo sin terminar dentro de la corrección urgente. Antes de crear una rama, comprueba dónde estás con git branch --show-current, o indica el punto de partida explícitamente: git switch -c correccion/urgente main.
Error 3: entrar en pánico con detached HEAD. No es un error ni una corrupción: es un estado normal. El mensaje es largo porque explica, no porque avise de un desastre. Si solo mirabas, git switch - y listo. Si confirmaste, git switch -c <nombre> antes de irte.
Error 4: usar git checkout -f o --discard-changes para "desatascar". Ese comando destruye los cambios no confirmados de forma irreversible: no están en ningún objeto de Git y no hay reflog que valga. Cuando Git se niegue a cambiar de rama, la respuesta correcta es confirmar, descartar conscientemente con git restore o guardar con git stash.
Error 5: git switch -C en lugar de -c. La mayúscula reinicia una rama existente sin preguntar. Si la rama ya tenía trabajo, mueve el puntero y ese trabajo queda huérfano. -c falla con un error claro, que en el 99 % de los casos es lo que quieres que pase.
Consejo 1: una rama por tarea, y de vida corta. Las ramas son gratis, así que no acumules trabajo en ellas. Una rama que vive tres semanas acumula divergencia y garantiza conflictos; una que vive dos días casi nunca los tiene.
Consejo 2: activa el autocompletado de Git. Con nombres como funcionalidad/filtro-pendientes, escribir git switch fun<TAB> es la diferencia entre trabajar cómodo y equivocarse de rama. El script git-completion.bash viene con la instalación de Git y muchas distribuciones lo activan por defecto.
Consejo 3: comprueba dónde estás antes de confirmar. El coste es un segundo:
Consejo 4: no reutilices ramas viejas para tareas nuevas. Es tentador (ya tengo la rama pruebas, la reaprovecho), pero acaba con historiales que no se entienden y con fusiones que arrastran cambios que nadie pidió. Crea una rama nueva y borra la vieja cuando ya no sirva; la limpieza es el tema de la lección 03-06.
Ejercicios
Ejercicio 1: crear ramas sin moverse
En un repositorio de pruebas con al menos tres commits, crea dos ramas —experimento/uno y experimento/dos— sin cambiarte a ninguna de ellas, de forma que:
experimento/unoparta del commit actual.experimento/dosparta de dos commits atrás.
Después, demuestra con comandos que HEAD no se ha movido y que las dos ramas apuntan donde corresponde.
Ejercicio 2: provocar y resolver un rechazo de cambio de rama
Diseña la secuencia mínima de comandos que provoque el error Your local changes to the following files would be overwritten by checkout, y después resuélvelo de tres formas distintas, explicando cuándo usarías cada una.
Ejercicio 3: salir de un HEAD desacoplado sin perder trabajo
Sitúate deliberadamente en un HEAD desacoplado, haz un commit ahí y consérvalo en una rama nueva. Después repite el proceso pero abandonando el estado sin crear la rama, y observa qué te dice Git. ¿Qué dato del mensaje es imprescindible anotar?
Soluciones
Solución 1:
# Las dos ramas, con git branch (que NO mueve HEAD)
git branch experimento/uno
git branch experimento/dos HEAD~2Comprobación:
experimento/dos 8b6d3c2 Añadir estilos base del listado experimento/uno c5d9b1e Documentar la instalación en el README * main c5d9b1e Documentar la instalación en el README
El asterisco sigue en main: HEAD no se ha movido. experimento/uno coincide con main y experimento/dos está dos commits atrás, como pedía el enunciado.
Verificación byte a byte:
Solución 2:
Para provocar el error hacen falta dos condiciones: un fichero que difiera entre las dos ramas y una modificación sin confirmar en ese mismo fichero.
# 1. Rama con un cambio confirmado en app.js
git switch -c rama-x
echo "// cambio de la rama X" >> app.js
git commit -am "Cambiar app.js en la rama X"
# 2. Volvemos a main y tocamos el MISMO fichero, sin confirmar
git switch main
echo "// cambio sin confirmar en main" >> app.js
# 3. Intentamos volver
git switch rama-xerror: Your local changes to the following files would be overwritten by checkout: app.js Please commit your changes or stash them before you switch branches. Aborting
Las tres resoluciones:
# A) Confirmar: el trabajo pertenece a main y está listo
git commit -am "Añadir una nota en app.js"
git switch rama-xCuándo: cuando el cambio es válido y tiene sentido en la rama actual.
Cuándo: pruebas, trazas de depuración o cualquier cosa que no quieras conservar. Es irreversible.
# C) Guardar temporalmente: trabajo a medias que quiero conservar
git stash
git switch rama-x
# … trabajo en rama-x …
git switch main
git stash popCuándo: el caso más habitual en el día a día, y el motivo de que git stash exista (lección 05-04).
Solución 3:
# Confirmamos algo aquí
echo "/* prueba desde HEAD desacoplado */" >> estilos.css
git commit -am "Probar un ajuste de estilos"El commit ya está alcanzable desde una rama: no hay nada huérfano.
Segunda parte, abandonando sin crear rama:
git switch --detach HEAD~2
echo "/* otra prueba */" >> estilos.css
git commit -am "Otra prueba que voy a abandonar"
git switch mainWarning: you are leaving 1 commit behind, not connected to any of your branches: 3c9d4a2 Otra prueba que voy a abandonar If you want to keep it by creating a new branch, this may be a good time to do so with: git branch <new-branch-name> 3c9d4a2 Switched to branch 'main'
El dato imprescindible es el hash: 3c9d4a2. Mientras lo tengas, recuperar el trabajo es un solo comando:
Si además pierdes el hash, la situación no es desesperada: git reflog guarda todos los movimientos de HEAD durante 90 días por defecto y permite encontrarlo. Es el tema de la lección 09-04.
Conclusión
Ya sabes crear ramas y moverte entre ellas con criterio:
git branch <nombre>crea el puntero y no toca nada más: niHEAD, ni el directorio de trabajo, ni el índice. Útil para marcar un punto sin abandonar lo que estás haciendo.git switch <rama>te sitúa en una rama: reescribe.git/HEADy actualiza el directorio de trabajo al árbol de destino.git switch -c <nombre> [punto-de-partida]es la forma habitual de empezar una tarea: crea y se cambia en un paso, y admite cualquier referencia como origen (otra rama, un hash,HEAD~2…).git switchygit restoresustituyen agit checkoutdesde Git 2.23, separando lo que antes era un único comando ambiguo: navegar entre ramas y destruir cambios ya no comparten verbo.checkoutsigue funcionando y lo seguirás viendo en documentación y scripts.git switch -alterna con la rama anterior, igual quecd -.- Al cambiar de rama, Git se lleva los cambios sin confirmar si puede hacerlo sin perder nada, y aborta limpiamente si el fichero difiere entre las dos ramas. Las salidas son confirmar, descartar con
git restoreo apartar congit stash(lección 05-04). - El HEAD desacoplado es
HEADconteniendo un hash en lugar deref: refs/heads/…. No es un error: sirve para inspeccionar y experimentar. La única precaución es crear una rama congit switch -cantes de abandonarlo si has confirmado algo.
Lo que viene
El equipo tiene ahora tres ramas: main parada donde estaba, funcionalidad/contador-tareas con dos commits de Ana y funcionalidad/filtro-pendientes con tres de Bruno. Dos funcionalidades terminadas que no están en el proyecto.
Aislar el trabajo era la mitad del problema; la otra mitad es volver a juntarlo. En la siguiente lección, Fusionando Ramas, Ana integrará por fin las dos funcionalidades en main. Veremos los dos escenarios que Git distingue —el fast-forward, en el que el puntero simplemente avanza, y la fusión a tres bandas, que crea un commit especial con dos padres—, cómo encuentra Git el ancestro común que estudiamos en la lección anterior, y por qué a veces conviene forzar un commit de fusión aunque no haga falta.
Dominando Git: De Principiante a Avanzado
Módulo 1: Introducción a Git
- ¿Qué es Git?
- Instalando Git
- Terminología Básica de Git
- El Modelo de Datos de Git
- Configurando Git
- Configuración Inicial
Módulo 2: Operaciones Básicas de Git
- Creando un Repositorio
- Clonando un Repositorio
- Flujo de Trabajo Básico de Git
- Preparando y Confirmando Cambios
- Inspeccionando Cambios con git diff
- Visualizando el Historial de Confirmaciones
Módulo 3: Ramas y Fusión
- Entendiendo las Ramas
- Creando y Cambiando Ramas
- Fusionando Ramas
- Estrategias de Fusión
- Resolviendo Conflictos de Fusión
- Gestión de Ramas
Módulo 4: Trabajando con Repositorios Remotos
- Entendiendo los Repositorios Remotos
- Agregando un Repositorio Remoto
- Autenticación con Repositorios Remotos
- Obteniendo y Extrayendo Cambios
- Enviando Cambios
- Rastreando Ramas
Módulo 5: Operaciones Avanzadas de Git
- Rebase
- Rebase Interactivo
- Cherry-Picking de Confirmaciones
- Guardando Cambios Temporales
- Etiquetando Confirmaciones
- Revirtiendo Confirmaciones
Módulo 6: Herramientas y Técnicas de Git
- Usando Git Hooks
- Git Bisect
- Git Blame
- Git Log y Alias
- Submódulos de Git
- Múltiples Copias de Trabajo con git worktree
Módulo 7: Estrategias de Colaboración y Flujo de Trabajo
- Forks y Pull Requests
- Revisiones de Código con Git
- Flujo de Trabajo Git Flow
- GitHub Flow
- Trunk Based Development
- Integración Continua con Git
Módulo 8: Mejores Prácticas y Consejos de Git
- Escribiendo Buenos Mensajes de Confirmación
- Manteniendo un Historial Limpio
- Ignorando Archivos con .gitignore
- Atributos de Fichero con .gitattributes
- Mejores Prácticas de Seguridad
- Consejos de Rendimiento
Módulo 9: Solución de Problemas y Depuración
- Problemas Comunes de Git
- Deshaciendo Cambios
- Resolviendo Divergencias con el Remoto
- Recuperando Confirmaciones Perdidas
- Tratando con Repositorios Corruptos
- Técnicas Avanzadas de Depuración
