Módulo 5: Ramas, integración, remotos y colaboración
Unidad: GIT-06–07
Público: alumnado de DAW, DAM, ASIR y grados de informática.
Itinerario: tronco, bloque de colaboración; tras el ciclo local completo.
Prerrequisitos: módulos 2 a 4; manejar status, add, commit, log, diff y restore.
Objetivo observable: crear una rama de tarea, integrar su trabajo resolviendo un conflicto y sincronizarla con un remoto, distinguiendo en todo momento qué hace Git y qué aporta una forja como GitHub.
Fuente técnica: documentación oficial de git switch, git merge, git fetch y git push.
Duración orientativa: 4 horas.
Idea clave
Una rama es una referencia móvil que apunta a un commit. Crear una rama no duplica el proyecto ni copia archivos: solo añade un nombre nuevo que apunta al commit actual y que avanzará con cada commit que hagas sobre ella.
Un remoto es otro repositorio Git, normalmente en un servidor. Tu repositorio local guarda además unas referencias de seguimiento (origin/main) que recuerdan dónde estaba cada rama del remoto la última vez que hablaste con él.
De ahí salen las tres ideas que este módulo tiene que dejar firmes:
mergeintegra historial dentro de tu repositorio local.fetchdescarga sin integrar: mueveorigin/main, no muevemainni toca tus archivos.pushpublica commits y referencias locales, y puede ser rechazado si el remoto ha avanzado.
Las pull requests no son un comando de Git: son una función de GitHub, GitLab u otra forja.
Parte 1 — Ramas e integración
Crear una rama de tarea
Partimos de un repositorio nuevo con un commit:
mkdir tienda && cd tienda
git init
printf "# Tienda\n\nProyecto de práctica.\n" > README.md
git add README.md && git commit -m "docs: crea README"Crea una rama y sitúate en ella:
git switch -c feat/catalogoSalida esperada:
Switched to a new branch 'feat/catalogo'-c significa create. Sin -c, git switch cambia a una rama que ya existe. Comprueba en cualquier momento dónde estás:
git branch --show-current
git branchfeat/catalogo
* feat/catalogo
mainEl asterisco marca la rama activa. Ahora trabaja con normalidad:
printf "Camiseta\nSudadera\n" > catalogo.txt
git add catalogo.txt && git commit -m "feat: añade catálogo"Integrar: fast-forward
Vuelve a main e integra:
git switch main
git merge feat/catalogoSalida esperada, con hashes distintos a los tuyos:
Updating e9827d4..f3a4a50
Fast-forward
catalogo.txt | 2 ++
1 file changed, 2 insertions(+)
create mode 100644 catalogo.txtFast-forward significa que main no había avanzado desde que creaste la rama, así que Git no ha tenido que fusionar nada: simplemente ha adelantado la referencia main hasta el commit de la rama. No se crea ningún commit nuevo y el historial queda en línea recta:
git log --oneline --graph --all* f3a4a50 feat: añade catálogo
* e9827d4 docs: crea READMEIntegrar: commit de fusión
A veces interesa que la integración quede registrada aunque el fast-forward sea posible, para que el historial muestre que hubo una rama. Eso es --no-ff:
git switch -c feat/envios
printf "Envío en 24 h\n" > envios.txt
git add envios.txt && git commit -m "feat: añade condiciones de envío"
git switch main
git merge --no-ff feat/envios -m "merge: integra condiciones de envío"Merge made by the 'ort' strategy.
envios.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 envios.txtort es el nombre de la estrategia de fusión que usa Git por defecto; no hay que configurar nada. Ahora el grafo sí muestra la bifurcación:
git log --oneline --graph --all* 43aa33b merge: integra condiciones de envío
|\
| * 5025fa5 feat: añade condiciones de envío
|/
* f3a4a50 feat: añade catálogo
* e9827d4 docs: crea README| Fast-forward | Merge con commit de fusión | |
|---|---|---|
| Cuándo ocurre | main no ha avanzado desde que se creó la rama |
main ha avanzado, o se fuerza con --no-ff |
| Commits nuevos | Ninguno | Uno, con dos padres |
| Historial | Línea recta | Muestra la bifurcación y la integración |
Parte 2 — Conflictos
Un conflicto no es un error ni un fallo de Git: es Git avisando de que dos ramas han cambiado las mismas líneas de forma distinta y que la decisión es humana.
Provocar un conflicto controlado
Desde main, crea una rama que cambie una línea del README:
git switch -c feat/horario
printf "# Tienda\n\nAbrimos de 9:00 a 14:00.\n" > README.md
git add README.md && git commit -m "docs: añade horario de mañana"Vuelve a main y cambia esa misma línea de otra forma:
git switch main
printf "# Tienda\n\nAbrimos de 10:00 a 20:00.\n" > README.md
git add README.md && git commit -m "docs: añade horario continuo"Intenta integrar:
git merge feat/horarioAuto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.Leer el estado durante el conflicto
Lo primero, siempre, es git status:
git statusOn branch main
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: README.mdboth modified es la marca del conflicto: las dos ramas tocaron el archivo. Fíjate en que Git te dice literalmente las dos salidas posibles, resolver o abortar.
Los marcadores de conflicto
Git ha escrito ambas versiones dentro del archivo:
cat README.md# Tienda
<<<<<<< HEAD
Abrimos de 10:00 a 20:00.
=======
Abrimos de 9:00 a 14:00.
>>>>>>> feat/horarioSe lee así:
- Entre
<<<<<<< HEADy=======está tu versión, la de la rama en la que estás (main). - Entre
=======y>>>>>>> feat/horarioestá la versión de la rama que integras.
Los marcadores son texto normal dentro del archivo. Si los dejas ahí y confirmas, quedan guardados en el historial: ese es el error más habitual del módulo.
Resolver y cerrar el merge
Resolver significa dejar el archivo como debe quedar, sin marcadores. No estás obligado a elegir una de las dos versiones: puedes escribir una tercera.
printf "# Tienda\n\nAbrimos de 9:00 a 20:00, jornada continua.\n" > README.md
git add README.md
git statusOn branch main
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
Changes to be committed:
modified: README.mdgit add sobre un archivo en conflicto significa «doy esta resolución por buena». Cierra el merge:
git commitGit abre el editor con un mensaje de fusión ya redactado; puedes aceptarlo tal cual. Si prefieres no abrir el editor, git commit --no-edit usa ese mensaje directamente.
[main c054319] Merge branch 'feat/horario'Comprueba el resultado:
git log --oneline --graph --all
git status --short* c054319 Merge branch 'feat/horario'
|\
| * 9649ec1 docs: añade horario de mañana
* | 74b279c docs: añade horario continuo
|/
* 43aa33b merge: integra condiciones de envíogit status --short no imprime nada: el árbol de trabajo está limpio.
Qué ha cambiado en cada zona
| Zona | Durante el conflicto | Tras git add |
Tras git commit |
|---|---|---|---|
| Directorio de trabajo | README.md con marcadores |
Contenido resuelto | Contenido resuelto |
| Área de preparación | Ruta en conflicto, sin resolver | Resolución preparada | Vacía |
| Historial local | Merge a medias, sin commit | Merge a medias, sin commit | Commit de fusión con dos padres |
| Remoto | Sin cambios | Sin cambios | Sin cambios hasta git push |
La última fila es la que conviene repetir: nada de esto ha salido de tu equipo.
Cancelar: git merge --abort
Si el conflicto es mayor de lo esperado o no tienes la información para decidir, puedes deshacer el intento:
git merge --abort
git status --shortGit devuelve el repositorio al estado anterior al merge. Es la opción segura cuando dudas, y es preferible a improvisar con comandos destructivos. Empieza un merge siempre con el árbol de trabajo limpio: si tienes cambios sin confirmar, revísalos antes.
Parte 3 — Remotos
Un remoto de práctica
Para practicar no hace falta GitHub. Un repositorio bare (sin directorio de trabajo) sirve de remoto local:
git init --bare ../tienda-remoto.git
git remote add origin ../tienda-remoto.git
git push -u origin main * [new branch] main -> main
branch 'main' set up to track 'origin/main'.origin es solo un nombre convencional para el remoto principal. -u establece el seguimiento: a partir de ahora main sabe que se compara con origin/main, y eso es lo que permite que git status te informe de si vas por delante o por detrás.
git remote -vorigin ../tienda-remoto.git (fetch)
origin ../tienda-remoto.git (push)Otra persona publica
Simula a un compañero clonando el remoto en otra carpeta:
cd ..
git clone tienda-remoto.git tienda-compa
cd tienda-compa
printf "Devoluciones en 30 días\n" > devoluciones.txt
git add devoluciones.txt && git commit -m "docs: añade política de devoluciones"
git push origin main c054319..7510d6a main -> mainEl remoto ha avanzado. Tu repositorio original todavía no sabe nada.
fetch: descargar sin integrar
Vuelve a tu repositorio y mira el estado antes de nada:
cd ../tienda
git status -sb## main...origin/mainNo informa de ninguna diferencia, porque tu repositorio aún cree que origin/main está donde lo dejaste. Descarga:
git fetch originFrom ../tienda-remoto
c054319..7510d6a main -> origin/mainLee bien esa flecha: lo que se ha actualizado es origin/main, no main. Compruébalo:
git status -sb
git log --oneline -1 main
git log --oneline -1 origin/main
ls devoluciones.txt## main...origin/main [behind 1]
c054319 Merge branch 'feat/horario'
7510d6a docs: añade política de devoluciones
ls: devoluciones.txt: No such file or directoryTres hechos en una sola pantalla:
[behind 1]: vas un commit por detrás del remoto.mainsigue en su commit;origin/mainestá en otro.- El archivo del compañero no existe en tu directorio de trabajo.
Eso es exactamente lo que significa «descargar sin integrar». Y como ya lo tienes descargado, puedes revisarlo antes de aceptarlo:
git log --oneline main..origin/main
git diff --stat main origin/main7510d6a docs: añade política de devoluciones
devoluciones.txt | 1 +
1 file changed, 1 insertion(+)Integrar de forma explícita
Cuando has revisado y estás conforme, integras como cualquier otra rama:
git merge origin/main
ls devoluciones.txt
git status -sbUpdating c054319..7510d6a
Fast-forward
devoluciones.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 devoluciones.txt
devoluciones.txt
## main...origin/mainAhora sí: el archivo aparece y status vuelve a estar a la par.
Sobre git pull
git pull es fetch seguido de una integración automática, en un solo comando. Funciona, y con el tiempo lo usarás a diario. Pero mientras estés aprendiendo, hacer los dos pasos por separado tiene una ventaja concreta: puedes mirar qué te llega antes de aceptarlo. Con pull la integración ocurre antes de que hayas visto nada, y si aparece un conflicto te encuentras resolviéndolo sin haber elegido el momento.
Cuando push se rechaza
Si el remoto ha avanzado y tú también, push falla:
git push origin main ! [rejected] main -> main (fetch first)
error: failed to push some refs to '../tienda-remoto.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref.Esto no es un problema que se arregle con fuerza. El rechazo te está protegiendo de sobrescribir el trabajo de otra persona. La salida correcta es siempre la misma secuencia:
git fetch origin
git status -sb
git log --oneline main..origin/main
git merge origin/main -m "merge: integra el trabajo del remoto"
git push origin main## main...origin/main [ahead 1, behind 1]
b2a761c docs: añade contacto
Merge made by the 'ort' strategy.
b2a761c..cbeea59 main -> main[ahead 1, behind 1] describe la situación exacta: hay un commit tuyo que el remoto no tiene y uno del remoto que tú no tienes. Se llama historial divergente, y se resuelve integrando, no forzando. git push --force sobre una rama compartida borra commits de otras personas del remoto; no se usa en este curso.
Parte 4 — Colaboración en GitHub
Qué pone Git y qué pone la forja
Todo lo anterior ha funcionado sin GitHub. Lo que aporta una forja es el trabajo alrededor del código:
| Lo pone Git | Lo pone la forja (GitHub) |
|---|---|
| Ramas, commits, merge, conflictos | Alojamiento del repositorio remoto |
fetch, push, referencias de seguimiento |
Pull requests y revisión con comentarios |
| Todo el historial | Protección de ramas y permisos |
| Integración continua (módulo 11) |
No existe git pull-request. Una PR es un objeto de GitHub que envuelve algo que Git ya sabía hacer: comparar dos ramas.
El flujo con pull request
Con el repositorio alojado en GitHub, el ciclo de una tarea es:
git switch main
git fetch origin
git merge origin/main
git switch -c feat/precios
# … trabajar y hacer commits …
git push -u origin feat/preciosA partir del último comando, GitHub ofrece abrir la pull request desde su interfaz. Allí se revisa, se comenta y se aprueba; al aceptarla, GitHub ejecuta en su servidor el mismo merge que habrías hecho tú. Después, en tu equipo:
git switch main
git fetch origin
git merge origin/main
git branch -d feat/preciosgit branch -d borra la rama local ya integrada. Si no lo estuviera, Git se niega y avisa: es una red de seguridad, no un obstáculo.
Revisar una contribución antes de aceptarla
Para ver qué aporta una rama sin ruido de otros cambios, se usa el diff de tres puntos:
git diff --stat main...feat/precios
git log --oneline main..feat/precios catalogo.txt | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
ce5d415 feat: añade preciosmain...feat/precios, con tres puntos, compara la rama contra el punto en que ambas se separaron. Responde a «qué ha hecho esta rama», que es la pregunta de una revisión. Con dos puntos, main..feat/precios en git log lista los commits que tiene la rama y no tiene main.
Errores frecuentes
«He trabajado directamente en
mainsin darme cuenta».
Comprueba congit branch --show-currentantes de empezar. Si ya has hecho commits, aún puedes crear la rama desde donde estás:git switch -c feat/tareala crea en el commit actual y se lleva el trabajo.«He confirmado el archivo con los marcadores
<<<<<<<dentro».
Los marcadores son texto normal: Git no los borra por ti. Revisa el archivo antes degit addy, si ya has confirmado, corrígelo con un commit nuevo.«Mi
pushha sido rechazado, uso--force».
El rechazo indica que el remoto tiene trabajo que tú no tienes.fetch, revisar, integrar y volver a publicar. En una rama compartida,--forcedestruye commits ajenos.«He hecho
fetchy no ha cambiado nada».
Ha cambiadoorigin/main, no tu rama ni tus archivos. Es el comportamiento correcto: compruébalo congit status -sbygit log --oneline main..origin/main.«Creo la pull request desde la terminal».
No es un comando de Git. Publica la rama conpushy abre la PR en GitHub.«Resuelvo el conflicto quedándome siempre con mi versión».
Lee las dos y decide con criterio; a veces la resolución correcta no es ninguna de las dos. Si no tienes información para decidir,git merge --aborty pregunta.
Ejercicio y criterios
Sobre un repositorio nuevo llamado agenda-taller con un commit inicial:
- Crea la rama
feat/clientes, añade un archivoclientes.mdy confírmalo. Intégrala enmaincon fast-forward. - Crea
feat/horariosyfeat/tarifasdesde el mismo commit, y haz que ambas modifiquen la misma línea deREADME.md. Integra la primera y resuelve el conflicto de la segunda escribiendo una tercera redacción. - Crea un remoto bare de práctica y publica
maincon seguimiento. - Clónalo en otra carpeta, haz allí un commit y publícalo.
- En el repositorio original, ejecuta
git fetchy demuestra con salidas de comandos queorigin/mainavanzó ymainno. Después integra. - Provoca un
pushrechazado y resuélvelo sin usar--force.
Criterios de revisión:
git log --oneline --graph --allmuestra el fast-forward, el commit de fusión y la resolución del conflicto.- Ningún archivo del historial contiene marcadores de conflicto (compruébalo con
git log -S "<<<<<<<" --oneline). - El estado final es limpio y
git status -sbno indicaaheadnibehind. - Se explica, con la salida concreta, qué cambió al hacer
fetchy qué cambió al integrar. - El
pushrechazado se resuelve confetche integración, y se justifica por qué no se usó fuerza.
Resumen
Al finalizar, el alumnado debe saber:
- Crear ramas de tarea con
git switch -cy saber siempre en cuál está. - Distinguir un fast-forward de un commit de fusión, y provocar cada uno.
- Leer
git statusdurante un conflicto, interpretar los marcadores y resolverlo. - Cancelar una integración con
git merge --abortcuando no puede decidir con seguridad. - Explicar que
fetchmueveorigin/mainy no toca nimainni el directorio de trabajo. - Integrar de forma explícita y entender qué hace
git pullpor debajo. - Resolver un
pushrechazado sin recurrir a--force. - Situar las pull requests como función de GitHub, y revisar una contribución con
git diff main...rama.
Siguiente módulo: Módulo 6 · Recuperación segura.
Estado editorial: listo.
Registro de revisión técnica
Revisado el 4 de agosto de 2026 con Git 2.47.1. Toda la secuencia se ha reproducido desde una carpeta vacía, con identidad de práctica y configuración aislada. Se ha comprobado que:
git merge feat/catalogoproduce un fast-forward ygit merge --no-ff feat/enviosun commit de fusión con la estrategiaort; el grafo degit log --oneline --graph --allrefleja ambos casos.- El conflicto en
README.mdgeneraCONFLICT (content),git statusinforma deboth modified, el archivo contiene los marcadores<<<<<<< HEAD,=======y>>>>>>> feat/horario, y trasgit addel estado pasa aAll conflicts fixed but you are still merging. git merge --abortdevuelve el repositorio al estado previo, con el contenido del archivo intacto.- Con un segundo clon que publica un commit,
git fetch originactualizaorigin/mainy dejamainen su commit anterior:git status -sbpasa de## main...origin/maina## main...origin/main [behind 1]y el archivo del segundo clon no existe en el directorio de trabajo hasta ejecutargit merge origin/main. - Con historial divergente,
git pushdevuelve! [rejected] main -> main (fetch first),git status -sbmuestra[ahead 1, behind 1]y la secuenciafetch→ revisión →merge→pushla resuelve sin fuerza.
Los hashes que aparecen en las salidas son los de esta ejecución y serán distintos en cada repositorio. Las rutas del remoto se han abreviado a ../tienda-remoto.git.
Pendiente al llevar el módulo a GitHub: sustituir el remoto bare de práctica por el repositorio real del curso en el punto 4, una vez creado, y comprobar el flujo de pull request con dos cuentas.