Trabajar en equipo con Git y GitHub: remotos, PRs y review
Del commit en local al repo compartido: modelo distribuido, remotos, el ciclo push/pull/fetch, Pull Requests y code review sin romper el trabajo de otro.
Sabes hacer commits. Ramificas, haces add, escribes un mensaje decente y sigues. En local, ese git commit es el punto final: tu trabajo ya está guardado. El día que te dan acceso a un repo compartido, esa misma acción pasa a ser la mitad de la historia. Falta lo difícil: que tu trabajo llegue a los demás sin pisar el suyo, y que el suyo llegue a ti sin borrar el tuyo.
Ese es el salto real de trabajar en equipo con Git, y lo que de verdad cambia es el modelo mental, más que un puñado de comandos nuevos. Aquí lo desmonto entero: qué es GitHub frente a Git, cómo viaja el código entre tu máquina y el servidor, cómo funciona un Pull Request de principio a fin, y por qué git push --force es la forma más rápida de que un compañero te odie con razón.
¿Qué diferencia hay entre Git y GitHub?
Git es un sistema de control de versiones distribuido que corre entero en tu máquina; GitHub es un servidor que aloja una copia del repositorio para que varias personas lo compartan. No son la misma cosa, y confundirlos es el primer malentendido que hay que quitarse de encima.
Cuando haces git init o clonas un repo, tienes el historial completo en tu disco: todos los commits, todas las ramas, todo. No necesitas conexión para ver el log, crear ramas o hacer commits. Eso es lo que significa “distribuido”: cada copia del repositorio es un repositorio completo por sí mismo, con todo el historial dentro, en vez de un cliente pobre que depende de un servidor central. Git funcionaría igual de bien si GitHub no existiera.
Entonces, ¿para qué sirve GitHub? Para tener un sitio común donde todos empujan y de donde todos tiran. Es un repositorio más, alojado en los servidores de una empresa, al que tu equipo trata como el punto de encuentro. Encima de eso, GitHub añade cosas que Git no tiene: la interfaz web, los Pull Requests, los permisos, la integración con CI. Pero esas son funciones de GitHub, no de Git.
La prueba de que son separables está en que puedes cambiar de casa sin cambiar de herramienta. GitLab y Bitbucket hacen el mismo papel de anfitrión, y hay equipos que se autoalojan su propio servidor Git. El día que migras de uno a otro, tus comandos de Git no cambian ni una letra. Solo cambia una URL.
Quédate con esto: Git es la herramienta, GitHub es un lugar donde poner una copia. Cuando alguien dice “súbelo a Git”, casi siempre quiere decir “súbelo a GitHub”. Es un abuso del lenguaje inofensivo, mientras tengas claro qué hace cada uno.
¿Qué es un remoto y qué es origin?
Un remoto es un alias que apunta a otra copia del repositorio, normalmente la que vive en GitHub. En lugar de escribir la URL completa cada vez que quieres sincronizar, le pones un nombre corto y trabajas con ese nombre.
Cuando clonas un repo, Git crea automáticamente un remoto llamado origin que apunta a la URL desde la que clonaste. Si quieres saber qué es el detalle práctico de traerte un repo a tu máquina por primera vez, lo tienes en la guía para clonar un repositorio. Aquí me interesa el resultado: después de clonar, ya tienes un remoto configurado sin haber hecho nada.
origin es solo una convención: el nombre por defecto que Git le da al remoto principal, el sitio “de origen” de tu copia. Podrías llamarlo github, central o pepe, y Git funcionaría igual. Pero todo el mundo lo llama origin, y romper esa convención solo sirve para confundir al siguiente que se siente en tu ordenador.
Para ver qué remotos tienes configurados y a dónde apuntan:
git remote -v
# origin git@github.com:acme/backend.git (fetch)
# origin git@github.com:acme/backend.git (push)
Fíjate en que aparecen dos líneas para origin: una para fetch (de dónde traes) y otra para push (a dónde envías). Casi siempre son la misma URL, pero Git te permite separarlas para casos avanzados.
Un repo puede tener varios remotos a la vez, y esto no es teórico. En proyectos open source es lo normal: tienes origin apuntando a tu propio fork y upstream apuntando al repositorio oficial del que forkeaste. Traes cambios de upstream para estar al día y empujas los tuyos a origin. Un mismo historial, dos servidores distintos, cada uno con su papel.
El ciclo clone / fetch / pull / push
Todo el trabajo en equipo se reduce a mover commits entre tu copia local y el remoto, y para eso solo hay cuatro verbos. Los repasas una vez y ya tienes el mapa mental completo.
git clone <url> es el punto de entrada. Copia el repositorio remoto entero a tu máquina, crea una carpeta con el proyecto, configura origin apuntando a esa URL y deja la rama por defecto lista para trabajar.
git clone git@github.com:acme/backend.git
# Cloning into 'backend'...
# remote: Enumerating objects: 1284, done.
# Receiving objects: 100% (1284/1284), 2.10 MiB, done.
cd backend
A partir de ahí sincronizas en las dos direcciones. Bajar cambios del remoto tiene dos comandos que la gente confunde constantemente, y esa confusión es fuente de sustos.
git fetch origin descarga los commits nuevos del remoto y actualiza tus ramas de seguimiento (origin/main, origin/feature-x), pero no toca ni un archivo de tu directorio de trabajo ni de tu rama actual. Es la operación segura por excelencia: te enteras de lo que ha pasado sin que nada se mueva bajo tus pies. Después de un fetch puedes mirar qué ha cambiado con calma y decidir.
git pull va un paso más allá. Primero hace exactamente lo mismo que fetch, y después integra esos cambios en tu rama actual, normalmente con un merge. Es decir, pull es fetch seguido de merge. Ese segundo paso es el que modifica tus archivos, y es el que puede generar un conflicto o un merge inesperado si no sabías lo que había en el remoto.
git fetch origin # descarga y actualiza origin/main; tus archivos NO cambian
git log origin/main # miras qué ha llegado antes de integrarlo
git pull # fetch + merge en tu rama actual (aquí SÍ cambian tus archivos)
Si esa diferencia todavía te resulta borrosa, merece un artículo entero: la tienes desmenuzada en fetch vs pull, la diferencia exacta. La regla corta para el día a día: usa fetch cuando quieras mirar antes de tocar, y pull cuando ya asumes que vas a integrar.
Para subir tu trabajo está git push, que envía tus commits locales al remoto. La primera vez que empujas una rama nueva, el flag -u (abreviatura de --set-upstream) enlaza tu rama local con la del remoto, de modo que a partir de entonces te basta con git push y git pull a secas.
Aquí tienes la tabla que quiero que memorices, porque separa las cuatro operaciones por lo único que importa cuando trabajas en equipo: la dirección del flujo y qué le pasa a tu código local.
| Comando | Dirección | Qué toca de tu working tree | Cuándo lo usas |
|---|---|---|---|
git clone | Remoto → local (primera vez) | Crea la carpeta y el repo desde cero | Una sola vez, al empezar con un repo |
git fetch | Remoto → local | Nada. Solo actualiza origin/* | Cuando quieres ver qué hay en el remoto sin arriesgarte |
git pull | Remoto → local | Modifica tu rama actual (fetch + merge) | Cuando vas a ponerte al día y asumes el merge |
git push | Local → remoto | Nada tuyo; actualiza el remoto | Cuando tu trabajo ya está listo para que lo vean los demás |
Con estos cuatro verbos ya puedes mover código en las dos direcciones. Lo que todavía no tienes es la parte social: cómo se propone un cambio para que otro lo revise antes de que entre en la rama principal.
¿Qué es un Pull Request y por qué no se hace push directo a main?
Un Pull Request es una propuesta de cambio revisable: en vez de meter tu código directamente en la rama principal, lo subes a una rama aparte y pides que alguien lo revise y lo apruebe antes de fusionarlo. El PR es el espacio donde vive esa conversación.
Este es el punto donde más gente se atasca, porque el Pull Request vive fuera de Git. Git solo sabe de ramas y merges. El PR lo inventa GitHub (y lo copian GitLab con sus merge requests y los demás) como una capa social encima: una pantalla donde se ve el diff, se comenta línea a línea, corren los tests automáticos y queda registro de quién aprobó qué. Si quieres el detalle fino de qué es y de dónde salió, lo tienes en qué es un Pull Request.
El flujo completo, de principio a fin, encadena cosas que ya sabes hacer con una que es nueva:
git switch -c feature/login # crea la rama y te cambia a ella (una operación)
# ...trabajas, haces add y commit como siempre...
git push -u origin feature/login # sube la rama al remoto y enlaza el upstream
# GitHub te devuelve una URL para abrir el Pull Request
Después de ese push, GitHub detecta la rama nueva y te ofrece un enlace para abrir el PR. Rellenas un título y una descripción, eliges quién revisa, y ya está: la propuesta está sobre la mesa. Los revisores comentan, tú respondes con nuevos commits si hace falta, y cuando hay luz verde se pulsa “Merge” y tu rama entra en main.
¿Por qué todo este baile en lugar de empujar directo a main? Porque cada paso te compra algo que echas de menos el día que lo pierdes. Nadie mira tu código antes de que llegue a la rama de la que sale producción, los tests automáticos dejan de tener un momento claro en el que están obligados a pasar en verde, y el historial de main se convierte en un vertedero donde no sabes quién metió qué ni por qué. El Pull Request pone una puerta con revisor entre tu trabajo y la rama que no te puedes permitir romper.
Este es buen momento para parar y hacerlo tú, porque leer el flujo y ejecutarlo son dos cosas distintas. Antes de tocar un repo real, prueba a decidir cada paso del primer PR de Alex: qué comando toca, cuándo abrir la propuesta, qué hacer cuando el revisor pide cambios. Equivócate aquí, que no cuesta nada.
Cargando ejercicio...
Cuando lo hayas resuelto, repítelo con las manos: crea una rama, súbela, abre un PR de prueba en un repo tuyo y mira cómo GitHub te muestra el diff. La primera vez que ves tu propio cambio presentado para revisión, el modelo mental encaja de golpe.
¿Cómo se hace un code review que sirva?
Un code review útil no es un sello de goma: es un compañero leyendo tu diff para entender qué cambias, por qué, y si va a romper algo. El objetivo es que el código entre a main en mejor estado del que entraría sin esos ojos extra, no que el revisor luzca encontrando fallos.
¿Qué mira un revisor en la práctica? Empieza por si el cambio hace lo que dice que hace y si es la forma más simple de hacerlo. Luego los bordes: casos límite, manejo de errores, qué pasa con datos raros. Después el mantenimiento, o sea, si dentro de seis meses alguien entenderá esto sin arqueología. Y por supuesto los tests: que existan y que cubran lo que el cambio toca. En GitHub esto se traduce en comentarios anclados a líneas concretas del diff, no en un correo genérico de “está bien”.
Cuando el revisor termina, GitHub le pide un veredicto, y aquí hay una distinción que conviene tener clara. Approve significa “por mí adelante, fusiónalo”. Request changes es un bloqueo explícito: hay algo que hay que corregir antes de fusionar. Y un comentario a secas es opinión sin veredicto, útil para sugerencias que no bloquean. Confundir “te dejo un comentario” con “te bloqueo el PR” genera roces evitables, así que usa cada uno para lo que es.
La parte que a los que empiezan les cuesta interiorizar es qué haces cuando te piden cambios. No abres un PR nuevo. Sigues en la misma rama, corriges lo que te han pedido, haces commit y push, y esos commits nuevos se suman al Pull Request existente automáticamente. El PR es una rama viva: todo lo que empujes a esa rama aparece en la conversación, y el revisor puede ver exactamente qué cambiaste desde su último comentario. Cuando queda satisfecho, cambia su “request changes” por un “approve” y se fusiona.
Un apunte sobre el tamaño, porque afecta directamente a la calidad de la revisión. Un PR de treinta líneas se revisa bien y se revisa de verdad. Un PR de dos mil líneas se aprueba con un “LGTM” y un suspiro, porque nadie tiene la energía de auditar eso a fondo. Si quieres reviews que te aporten algo, haz cambios pequeños y frecuentes. Un PR pequeño se revisa mejor y, de propina, se revierte con un clic si algo sale mal.
Un concepto nuevo cada semana
¿Cómo se protege la rama main?
La protección de rama es una configuración de GitHub que impide que nadie meta cambios en main saltándose las reglas, aunque quiera o se equivoque. Es la red de seguridad que convierte “por favor no empujéis directo a main” en algo que el servidor hace cumplir por ti.
La idea de fondo es que main debe estar siempre desplegable. En cualquier momento, el último commit de esa rama tendría que poder salir a producción sin sustos. Si cualquiera puede empujar código a medias directamente, esa garantía se evapora. Las reglas de protección la reconstruyen exigiendo condiciones antes de permitir un merge.
Las que vas a encontrar en casi todos los equipos serios:
- Exigir Pull Request antes de fusionar. Nada entra en
mainsin pasar por un PR. El push directo queda prohibido para todos, incluidos los admins si así se configura. - Exigir un número mínimo de aprobaciones. Normalmente una o dos. Hasta que no haya esos “approve”, el botón de merge está bloqueado. En repos delicados se combina con un fichero
CODEOWNERSy la opción “Require review from Code Owners” de la propia protección de rama: el fichero por sí solo solo asigna revisores automáticamente; es esa opción la que convierte la aprobación del dueño de esa parte del código en obligatoria. - Exigir que pasen los checks (status checks). Los tests, el linter y el build tienen que estar en verde. Si el CI falla, no se puede fusionar por mucho que un humano haya dado el visto bueno.
- Exigir que la rama esté al día con main antes de fusionar, para que los tests corran contra el estado real al que va a entrar el código, no contra una foto vieja.
El efecto conjunto es que main deja de depender de la disciplina individual. Ya no importa si alguien tiene un mal día y hace git push origin main por inercia: el servidor lo rechaza. La calidad de tu rama principal deja de depender de que todo el mundo se acuerde de hacer bien las cosas.
Todo esto (ramas, remotos, el ciclo de sincronización y el flujo del PR de punta a punta) se asienta al recorrerlo con las manos. En el curso Git de cero a profesional recorres el flujo de equipo completo en un entorno donde pisar el repo de alguien no tiene consecuencias, hasta que abrir un PR y sincronizar con el remoto te sale sin pensarlo.
¿Y si tengo que forzar un push?
Antes de forzar nada, entiende por qué Git te lo impide por defecto. git push normal rechaza actualizar el remoto si tu versión no es descendiente de lo que hay allí. Traducido: si alguien empujó commits que tú no tienes, Git no te deja machacar su trabajo. Te obliga a traerte primero lo suyo. Esa negativa te está protegiendo, aunque en el momento parezca un estorbo.
git push --force desactiva esa protección por completo. Le dice a Git “pon el remoto exactamente como está mi copia, me da igual lo que hubiera”. Si un compañero había subido tres commits entre tu último fetch y tu push, esos tres commits desaparecen del remoto. No hay conflicto, no hay aviso, no hay papelera. Simplemente ya no están, y su autor se entera cuando su siguiente pull le trae una historia que no reconoce. Por eso, un --force a secas sobre una rama compartida es de las pocas cosas en Git que producen pérdida de trabajo real y difícil de recuperar.
La alternativa segura es git push --force-with-lease. Hace el mismo force, pero con una comprobación: solo sobrescribe el remoto si su valor actual coincide con lo que tú tienes registrado en tu rama de seguimiento, es decir, con lo que viste en tu último fetch. Si el remoto ha avanzado desde entonces, porque alguien empujó algo, la comprobación falla y el push se aborta sin tocar nada.
git push --force-with-lease
# ! [rejected] feature/login -> feature/login (stale info)
# Alguien empujó al remoto desde tu último fetch: el push se abortó, nada se perdió.
Ese mensaje de rechazo es exactamente lo que quieres ver cuando había peligro. Te está diciendo “ibas a pisar algo, para”. Haces fetch, miras qué ha llegado, y decides con información. --force a secas puede borrar el trabajo ajeno en silencio; --force-with-lease se niega a hacerlo.
¿Y cuándo necesitas forzar, para empezar? El caso legítimo y habitual es después de reescribir tu propia rama de feature: hiciste un rebase para poner tus commits al día sobre main, o un commit --amend para arreglar el último mensaje. Ahora tu historia local no es descendiente de la que subiste, así que push normal la rechaza. Como es tu rama y nadie más trabaja sobre ella, forzar es correcto. Y como quieres asegurarte de que de verdad nadie más la ha tocado, lo haces con --force-with-lease. Nunca con --force pelado.
Errores comunes al empezar en equipo
Push directo a main
El clásico del primer día. Estás acostumbrado a que main sea tu rama de trabajo en local, haces tu commit y un git push por costumbre, y acabas de meter código sin revisar en la rama de la que sale producción. La solución técnica es la protección de rama que ya vimos, pero la solución mental es asumir que en equipo main es de todos, y que a todos se llega por PR.
Confundir pull con fetch y llevarte un merge de sorpresa
Alguien te dice “hazte un pull para tener lo último” y tú, en mitad de un cambio a medias, ejecutas git pull y de repente tienes un conflicto de merge que no esperabas. No hiciste nada mal con las manos, pero sí con el modelo mental: pull integra, y si tu working tree estaba en un estado delicado, integrar duele. Cuando no estés seguro de lo que hay en el remoto, fetch primero y mira. Integra cuando tú decidas, no cuando la costumbre te lo dicte.
git push --force sobre una rama compartida
Ya le he dedicado una sección entera, pero se repite en esta lista porque es el error con peor ratio daño/frecuencia. Un --force en la rama equivocada borra días de trabajo de otra persona. Si algo en tu cabeza dice “voy a forzar”, que lo siguiente sea siempre --force-with-lease.
PRs gigantes
Juntas dos semanas de trabajo en un solo Pull Request de mil quinientas líneas y se lo sueltas a un compañero. Nadie revisa eso de verdad. Lo aprueba por agotamiento, y los bugs pasan porque revisar bien un cambio enorme es imposible. Trocea. Un PR debería revisarse en una sentada, no en una tarde.
Trabajar días sin hacer pull
Te encierras una semana en tu rama sin traerte nada de main, y cuando por fin vas a fusionar, main ha avanzado un montón mientras estabas encerrado: decenas de conflictos, cada uno una pequeña arqueología. Si integras a menudo, cada conflicto es pequeño; si lo dejas para el final, se te acumulan todos de golpe. Trae los cambios de main a tu rama con frecuencia y los conflictos serán pequeños y digeribles.
Checklist para tu primer trabajo en equipo
- Sabes explicar en una frase la diferencia entre Git (tu máquina) y GitHub (el remoto)
- Tienes claro que
origines solo el nombre por convención del remoto principal - Distingues
fetch(mira, no toca) depull(trae y fusiona) antes de ejecutar cualquiera - Todo tu trabajo va en una rama propia con
git switch -c, nunca directo amain - Abres Pull Requests pequeños, revisables en una sentada
- Respondes al feedback con nuevos commits sobre la misma rama, no con un PR nuevo
- Si alguna vez fuerzas un push, es con
--force-with-leasey sobre tu propia rama -
maintiene protección de rama: PR obligatorio, aprobaciones y checks en verde
Practica el flujo de equipo sin miedo a romper nada
Un repo con gente detrás mete una presión que ningún tutorial reproduce. El curso Git de cero a profesional te lleva del primer commit al primer PR revisado y fusionado, con ejercicios interactivos para clonar, sincronizar con el remoto, abrir la propuesta y responder a un review con las manos. Cuando llegues a tu primer repo compartido de verdad, el modelo mental ya estará montado.
Preguntas Frecuentes
¿GitHub y Git son lo mismo?
No. Git es un sistema de control de versiones distribuido que corre en tu máquina y guarda todo el historial en local; GitHub es un servicio web que aloja una copia remota de repositorios Git y añade herramientas de colaboración como los Pull Requests. Puedes usar Git sin GitHub, y puedes alojar el mismo repositorio en GitHub, GitLab o Bitbucket sin cambiar un solo comando de Git.
¿Cuál es la diferencia entre git fetch y git pull?
git fetch descarga los commits nuevos del remoto y actualiza tus ramas de seguimiento (origin/main y demás), pero no modifica tus archivos ni tu rama actual: es una operación de solo mirar. git pull hace ese mismo fetch y, además, fusiona los cambios en tu rama actual, lo que sí modifica tu working tree y puede generar conflictos. Si quieres ver qué hay en el remoto antes de arriesgarte a integrar, usa fetch.
¿Puedo tener el mismo repo en GitHub y en GitLab a la vez?
Sí: un repositorio Git puede apuntar a varios remotos a la vez. Añades el segundo con git remote add gitlab <url> y a partir de ahí empujas o traes de cada uno por su nombre. Es el mismo mecanismo que usan los proyectos open source con origin (tu fork) y upstream (el repo oficial).
¿git push --force-with-lease es seguro?
Es la forma segura de forzar, pero “segura” no quiere decir “sin consecuencias”. Comprueba que el remoto no haya avanzado desde tu última sincronización y aborta el push si lo detecta, con lo que no borrarás el trabajo de nadie por sorpresa. Aun así, reescribes historia, así que resérvalo para tu propia rama de feature tras un rebase o un amend, nunca para una rama sobre la que colabora más gente.
¿Qué tamaño debería tener un Pull Request?
Lo bastante pequeño como para revisarse a fondo en una sentada. No hay un número mágico de líneas, pero un PR que un compañero pueda leer y entender en diez o quince minutos recibirá una revisión real; uno de miles de líneas recibirá un “LGTM” de agotamiento. Cuando un cambio crece demasiado, pártelo en varios PRs encadenados: cada uno se revisa mejor y, si algo sale mal, es más fácil de revertir.