1.1. Control de versiones con Git
- 1.1.1. Sistemas de control de versiones
- 1.1.2. Uso básico de Git con plataformas remotas (GitHub/GitLab)
- 1.1.3. Creando e inicializando un repositorio
- 1.1.4. Flujo de trabajo cotidiano con Git
- 1.1.5. Algunas cosillas avanzadas: revertir cambios, merges, ramas
- 1.1.6. Flujo de ramas: trabajando con GitHub Flow
- 1.1.7. Pull Requests
- 1.1.8. Code Reviews
- 1.1.9. ¿Aún quieres saber más?
- 1.4.10 Práctica de Git
1.1.1. Sistemas de control de versiones
Es inconcebible que un desarrollador trabaje en la actualidad sin un sistema de control de versiones.
Fíjate que en la frase anterior no tiene cabida tu opinión. Lo siento, pero es lo que hay. No importa si te gustan estos sistemas o no. No importa si los usas de forma habitual o siempre has huido de ellos como de la peste. No importa si ni siquiera sabes qué son o cómo funcionan. Si quieres dedicarte profesionalmente al desarrollo de software, tienes que conocerlos porque te los vas a encontrar vayas donde vayas.
¿Qué es un sistema de control de versiones?
Un sistema de control de versiones (en inglés, VCS = Versions Control System) es un almacén en la nube pensado para equipos de desarrollo de software.
A diferencia de un servicio de almacenamiento tradicional como Google Drive o Dropbox, o del peligroso hábito de guardar carpetas con nombres tipo proyecto_final_v2_DEFINITIVO_FINAL_2.zip, un VCS está diseñado específicamente para tratar proyectos de software y te permite:
- Conservar el historial completo de cambios desde el día cero del proyecto.
- Documentar cada modificación, de manera que siempre sea posible saber quién, cuándo, cómo y por qué se cambió determinada línea de código.
- Revertir el software a un estado anterior o estable en cualquier momento tras una regresión o bug.
- Crear ramas (branches) para aislar el desarrollo de nuevas características sin romper la versión que ya funciona.
- Crear réplicas (forks) de proyectos existentes para contribuir a ellos o hacerlos evolucionar de forma independiente.
- Gestionar y resolver conflictos cuando dos o más desarrolladores editan los mismos archivos o líneas de código de manera simultánea.
Incluso para un desarrollador que trabaja en solitario en su cueva, trabajar sin un VCS es jugar a la ruleta rusa. Poder “viajar en el tiempo” cuando algo se rompe (y, créeme, antes o después algo se romperá) y documentar tus propios avances justifica por sí solo su uso diario.
¿Cómo funcionan los sistemas de control de versiones?
Aunque históricamente existieron sistemas centralizados (como CVS o Subversion/SVN), la industria viró completamente hacia los sistemas de control de versiones distribuidos (DVCS), donde Git es el rey indiscutible y ha desplazado casi por completo a alternativas como Mercurial o Bazaar.
Git, como cualquier sistema de este tipo, comparte estas características básicas:
- Cada desarrollador tiene una copia completa del repositorio en su máquina local, con todo su historial.
- Se trabaja siempre sobre el repositorio local. El trabajo es ultrarrápido porque no depende de la conexión a Internet para hacer commits, consultar historiales o crear ramas.
- El repositorio remoto (alojado en un servidor centralizado como GitHub o GitLab) actúa como punto de encuentro y “fuente de la verdad” (Source of Truth) para sincronizar el trabajo de todo el equipo.
- La sincronización con el repositorio remoto es un proceso explícito (push / pull). Jamás es automática, lo que te permite probar y validar tus cambios locales con calma antes de exponerlos al resto del equipo.
La sincronización con el repositorio remoto, por lo tanto, no puede ser automática (como en Google Drive o Dropbox), sino que hemos de hacerla explícita, momento en el cual el sistema nos avisará de posibles conflictos. Esta es la única manera de resolver adecuadamente esos conflictos en proyectos donde haya mucha gente trabajando simultáneamente.
1.1.2. Uso básico de Git con plataformas remotas (GitHub/GitLab)
Git es un software de código abierto (open source) creado originalmente por Linus Torvalds en 2005 para gestionar el desarrollo del kernel de Linux.
Es vital entender la diferencia desde ya: Git es la herramienta (el motor de control de versiones de línea de comandos en tu máquina) y GitHub / GitLab son plataformas en la nube que alojan repositorios Git y añaden capas de gestión de proyectos, seguridad y, recientmente, Inteligencia Artificial.
El entorno de trabajo
Para trabajar con Git necesitarás:
- Cliente de Git local: Instalado en tu sistema operativo (git-scm.com).
- Cuenta en GitHub o GitLab: Plataformas desde las que gestionarás tus proyectos.
- Autenticación segura: Hace años que las plataformas remotas no permiten autenticarse en terminal con usuario y contraseña tradicionales. Se requiere configurar claves SSH o Tokens de Acceso Personal (PAT).
¿Línea de comandos o cliente gráfico?
Existen multitud de clientes gráficos (GUI) como GitHub Desktop, GitKraken, Sourcetree o la propia integración nativa de Visual Studio Code.
Sin embargo, te recomiendo que priorices la línea de comandos (CLI). Aprender los comandos básicos de Git no solo te dará un entendimiento profundo del árbol de estados de tu código, sino que te salvará la vida en entornos de producción, servidores remotos en la nube (donde no hay interfaz gráfica) y pipelines de CI/CD.
Configuración inicial de Git (Solo se hace una vez)
Tras instalar Git, debes identificarte localmente para que tus commits lleven tu autoría correctamente asignada:
$ git config --global user.name "Tu Nombre o Usuario"
$ git config --global user.email "tu-email@ejemplo.com"
Para establecer main como el nombre por defecto de la rama principal en nuevos repositorios (estándar actual en la industria):
$ git config --global init.defaultBranch main
Puedes verificar tu configuración en cualquier momento con:
$ git config --list
1.1.3. Creando e inicializando un repositorio
Existen dos formas habituales de comenzar a trabajar con un repositorio Git:
Opción A: Crear un repositorio local e inscribirlo en un remoto
- Navega hasta la carpeta de tu proyecto en la terminal:
$ cd /ruta/a/tu/proyecto - Inicializa el repositorio local:
$ git init - Enlaza el repositorio local con el remoto previamente creado en GitHub/GitLab:
$ git remote add origin git@github.com:usuario/nombre-repositorio.git
Opción B: Clonar un repositorio existente (Recomendado)
Si creas el repositorio primero en la interfaz de GitHub/GitLab (opción más cómoda):
$ git clone git@github.com:usuario/nombre-repositorio.git
$ cd nombre-repositorio
Este comando crea la carpeta local, inicializa Git y deja configurada automáticamente la ruta remota (origin).
¡Atención indispensable! El archivo .gitignore
El archivo .gitignore se sitúa en la raíz del proyecto e instruye a Git sobre qué archivos o carpetas debe ignorar por completo.
Jamás de los jamases se deben subir al repositorio remoto:
- Credenciales y datos sensibles: Archivos
.env, claves privadas, certificados, contraseñas de BD. Subir esto a un repositorio público es la vía rápida hacia el desastre. - Dependencias descargadas: Las carpetas
vendor/(PHP/Composer) onode_modules/(Node/JS). Ocupan megabytes innecesarios y se gestionan con gestores de paquetes (npm install,composer install). - Archivos generados o de compilación: Logs, archivos temporales, binarios, imágenes subidas durante pruebas locales.
- Archivos de configuración del IDE: Carpetas
.idea/,.vscode/específicas del usuario.
Ejemplo de .gitignore para un proyecto Backend:
# Credenciales y variables de entorno
.env
.env.local
# Dependencias
/vendor/
/node_modules/
# Logs y temporales
*.log
/storage/*.key
# Configuración de IDEs
.idea/
.vscode/
1.1.4. Flujo de trabajo cotidiano con Git
Ya tenemos nuestros repositorios local y remoto inicializados y conectados, y el archivo .gitignore a punto. ¿Qué hacemos ahora?
Muy fácil: ponernos a trabajar como si Git no existiera.
Y luego, cuando des por finalizada una parte de la aplicación (un método, una clase, una funcionalidad concreta: tú decides cada cuánto tiempo haces esto), pasarla a la Staging Area.
Un momento… ¿Staging quéeee?
La Staging Area es como la pista de despegue de Git.
La idea es la siguiente: Git no quiere sincronizar tus archivos con el repositorio remoto de forma automática (como hacen las plataformas para el público general, como Google Drive o Dropbox), porque sabe que los programadores producimos mucha basura al cabo del día.
Si cada vez que escribimos una basurilla, Git la sincronizara con el remoto, el resto de personas del proyecto estarían recibiendo nuestra basura de forma permanente. Y nosotros la de esas personas.
Y esparcir basura no es una buena política.
Así que Git quiere que seas muy consciente de cuándo deseas sincronizar algo, y de qué es lo que deseas sincronizar. Quiere que te tomes el trabajo (que tampoco es para tanto, la verdad) de emplear medio minuto de tu tiempo para decirle: “eh, Git, he estado currándome estos dos archivos esta mañana y creo que ahora ya no son una basura”.
Para eso sirve la Staging Area.
Tienes que pasar los archivos que ya no son una basura a la Staging Area. Y tienes que hacerlo tú, generalmente cuando hayas terminado una funcionalidad y la hayas probado adecuadamente. Lo bastante como para que no te avergüence que otras personas del equipo reciban tu código.
Para añadir archivos a la Staging Area se usa el comando git add, así:
$ git add archivo1 archivo2 archivo3 ...
Se pueden añadir carpetas completas:
$ git add carpeta1 carpeta2 ...
Y también se pueden usar símbolos comodín, como el asterisco. De modo que, si estás muy, pero que muy seguro/a de que todos los archivos que han andado tocando desde el último commit están en un estado aceptable, puedes hacer esto para que Git se encargue de añadir todos los archivos modificados recientemente a la Staging Area:
$ git add *
Por fin, cuando tengas una o varias cosas preparadas en la Staging Area… Bueno, entonces llega el momento de hacer un commit.
Hacer commit
Un commit (palabra que podríamos traducir por “perpetrar”) consiste en empaquetar todos los cambios de la Staging Area para registrarlos en el historial del repositorio local.
Es decir, con el commit le decimos a Git: “quiero que dejes constancia permanente de todo el código que te he puesto en la Staging Area”.
Se puede hacer un commit por cada pequeña modificación que introducimos en la Staging Area, o se pueden preparar muchos archivos en la Staging Area y luego empaquetarlos en un único mega-commit. Eso lo decidís tú y tu equipo de desarrollo. Pero suele ser buena idea hacer commits de funcionalidades o tareas individuales, pequeños y frecuentes.
Es decir, si esta mañana he estado trabajando en dos funcionalidades, “Añadir usuarios nuevos” y “Modificar la vista de edición de usuarios”, es mejor que haga dos commits separados para cada una de esas funcionalidades.
Esto es así porque, a cada commit, le tengo que añadir obligatoriamente un texto descriptivo donde indique qué cambios estoy subiendo con ese commit.
El comando para hacer un commit es:
$ git commit -m "Mensaje"
Ahora saco mi bola de cristal y te digo: no tardarás ni una semana en empezar a hacer commits cuyo texto descriptivo será algo como “aslkdaslkjda”, “aaa”, “yoquésé”. Eso es una pésima idea. Antes o después, alguien del equipo meterá la pata, subirá un cambio indebido y todo el repositorio explotará. Entonces, intentaréis regresar a un estado en el que el código aún funcionaba, pero encontraréis que los últimos commits tienen explicaciones incomprensibles como “aslkdaslkjda”, “aaa” y “yoquésé”. Y sudaréis tinta para descubrir cuál fue el commit explosivo.
Los commits deben llevar textos descriptivos breves pero informativos. Por ejemplo: “Arreglo el fallo del id de usuario inexistente al actualizar foto de perfil” o “Elimino el botón de modificar de la vista de libros”. Si quieres ir un paso más allá (y en un entorno profesional te lo van a pedir), existen convenciones como los Conventional Commits, que estandarizan el formato del mensaje (fix:, feat:, docs:…) para que hasta una máquina pueda entender qué tipo de cambio has hecho.
Pero, ¡ojo!, hacer commit no sube los archivos al repositorio remoto. Todavía no. Recuerda que Git quiere que estés muy seguro/a de que subes lo que realmente tienes que subir, así que aún te falta un último paso: hacer push.
Subir el commit: hacer push
El último paso para enviar nuestros cambios locales al repositorio remoto (típicamente, GitHub o GitLab) consiste en hacer push. Es decir, literalmente, “empujar” los cambios al repositorio remoto.
La operación push enviará todos los commits que aún no se hayan enviado al repositorio remoto. A partir de ese momento, estarán disponibles para el resto de miembros del equipo. Pero solo a partir de ese momento.
Para hacer push, basta con escribir:
$ git push
Como ya te he comentado antes, GitHub y GitLab exigirán que te autentiques mediante un token de acceso personal o una clave SSH, no con tu contraseña habitual de la web.
Bajar la última versión del código: hacer pull
Si podemos subir nuestros cambios al repositorio remoto, tendremos que tener una forma de bajar los cambios del resto de miembros del equipo, ¿verdad?
Por supuesto, existe un comando para ello. Es este:
$ git pull
Es recomendable hacer pull antes de hacer push, por si alguien ha tocado alguno de los archivos que nosotros pretendemos subir. En ese caso, Git nos avisará del conflicto y nos ayudará a resolverlo (más adelante veremos cómo). No podremos hacer push hasta resolver ese conflicto, para evitar pérdidas de código.
Resumiéndolo todo: flujo de trabajo habitual con Git
Si resumimos lo dicho hasta ahora, tenemos que, después de inicializar el repositorio (cosa que hay que hacer solo una vez), el trabajo cotidiano con Git consiste en:
- Desarrollar nuestra aplicación con normalidad, como si Git no existiera.
- Cuando terminamos de hacer algo, añadirlo a la Staging Area (git add).
- Cada cierto tiempo, o cuando acabamos una funcionalidad, empaquetar todos los cambios que esperan en la Staging Area en un commit (git commit).
- Bajarnos los commits del resto de miembros del equipo (git pull).
- Subir nuestros commits al repositorio remoto (git push).
Podemos verlo gráficamente en el siguiente esquema. Las tres primeras columnas (workspace, Staging Area y Local Repo) están en nuestro ordenador de trabajo. El repositorio remoto (Remote Repo) está en un servidor, como GitHub o GitLab.
Workspace Staging area (INDEX) Local repo (HEAD) Remote repo
| | | |
| git add → → | | |
| | git commit → → → → | |
| | | |
| | | git push → → → → |
| | | |
| | | |
| ← ← ← ← ← ← | ← ← ← ← ← ← ← ← ← ← | ← ← ← ← git pull |
| | | |
Un último apunte: te voy a chivar un comando muy útil de Git cuando no estás muy seguro de qué archivos has estado tocando últimamente (¿a quién no le ha pasado eso? ¿Eh?). Este comando te resumirá el estado de tu repositorio local, indicándote qué archivos han sido modificados (pero no están en la Staging Area), qué archivos están preparados en la Staging Area (pero no en un commit) y, por supuesto, qué commits están hechos pero aún sin subir.
Todo eso, gratis y tecleando este humilde comando:
$ git status
¿Es potente o no es potente este Git? Pues aún no has visto nada.
1.1.5. Algunas cosillas avanzadas: revertir cambios, merges, ramas
Solo con lo que hemos visto hasta ahora (add, commit, push y pull) ya tienes suficiente para empezar a funcionar con Git. Luego, conforme te surjan otras necesidades, puedes ir curioseando por internet para profundizar en ciertos aspectos.
Una de esas “necesidades” que te surgirán antes o después consiste en lo siguiente:
Imagínate la escena: un día llegas a clase después de haberte acostado a las tantas trabajando en tu proyecto. Antes de acostarte hiciste un push para subir todos tus cambios y puedes jurar que todo funcionaba perfectamente. Pero ahora, tú y el resto de miembros de tu equipo acabáis de hacer pull y… ¡BUM! El proyecto entero salta por los aires. El homepage no carga. Otras rutas que estás seguro de que funcionaban hace unas horas ahora no responden.
¿Qué narices ha pasado?
Tranquilidad: ahí está Git para sacarte del embrollo.
Regreso al pasado: cómo revertir los cambios
Las causas de un desastre como ese pueden ser tantas que, en la práctica, es como si fueran infinitas. Un problema con el proxy, un merge mal hecho, una desconfiguración de uno de los servidores locales que ha afectado a algún archivo clave, un error de algún miembro del equipo que ha sobrescrito cientos de archivos con versiones incorrectas… Causas infinitas, como te digo.
No suele compensar el esfuerzo de buscar la razón última de lo que ha ocurrido, salvo que os pase esto con cierta regularidad: entonces sí que es cuestión de preocuparse.
La mayoría de las veces es un problema puntual que puede resolverse de un modo muy simple: volviendo a la última versión estable.
En primer lugar, si lo que quieres es revertir cambios de los que ya has hecho commit, es tan fácil como:
$ git reset --hard id_del_commit
¡Cuidado, porque todos los commits que hayas hecho hasta el commit al que retrocedas se perderán! Usa reser –hard solo como último recurso desesperado.
La mayor parte de las veces no necesitamos ser tan radicales y podemos descartar solo parte de los cambios, no eliminar commits enteros. ¿Cómo descartamos esos cambios para volver a un estado anterior de forma menos abrupta?
En primer lugar, si aún no lo has hecho, ejecuta un git pull para traerte la última versión del código a tu repositorio local.
Luego, utiliza el comando git log:
$ git log (muestra historial de cambios)
$ git log --oneline (muestra historial de cambios simplificado)
Con esto obtendrás una lista, ordenada por cronología inversa (de más reciente a más antiguo), de todos los commits que has hecho en el repositorio. Observa que cada commit está identificado con un id único en hexadecimal. Cada id de commit está acompañado de su descripción.
Si habéis sido cuidadosos con los commits y les habéis puesto descripciones representativas (y no “asdfasdf” o “aaa”), resultará fácil localizar en esa lista el commit causante del destrozo.
A continuación, usa el comando git revert para deshacer, sin reescribir el historial, los cambios introducidos por el commit problemático:
$ git revert id-del-commit
Lo que hace este comando es crear un nuevo commit que anula exactamente los cambios del commit indicado. No borra nada del historial: simplemente añade un commit nuevo que “hace lo contrario” del que causó el problema. Git te abrirá un editor de texto para que confirmes o modifiques el mensaje de ese nuevo commit (por defecto, algo como Revert "Mensaje del commit original"). En este nuevo commit habrá desaparecido todo el código conflictivo, y el proyecto volverá a estar en un estado estable.
Ahora bastará con hacer git push para subir el nuevo commit al repositorio remoto y que todos los miembros del equipo puedan replicarlo en sus máquinas.
Es posible que, en el proceso, hayáis perdido algo de código valioso: todo depende de cuánto hayáis tenido que retroceder en el historial de commits hasta alcanzar un estado válido. Pero ese código en realidad no se ha perdido, porque los commits siguen ahí, en el historial de Git. En cambio, si usas git reset –hard, el código sí que se perderá.
Existe una forma de poner el repositorio local en un commit concreto, sin crear ningún commit nuevo, solo para echar un vistazo. Si lo haces y abres cualquier archivo fuente, lo encontrarás como estaba en ese commit, no como está en el último. ¿No es maravilloso? Así, podrás recuperar manualmente el código que pudiera haberse perdido al hacer el git revert.
El comando clásico que te permite saltar momentáneamente a cualquier commit es git checkout, y el moderno es git switch:
$ git checkout id-del-commit
$ git switch --detach id-del-commit
Así “viajarás en el tiempo” para ver el código fuente como era en ese momento, pero no podrás cambiarlo, porque ese commit ya pertenece al pasado.
Para regresar al presente, es decir, al último commit de tu rama principal, puedes hacerlo también de dos formas:
$ git checkout main # Forma tradicional
$ git switch main # Forma moderna
git switch solo está disponible en versiones modernas de Git (2.23 en adelante) porque git checkout hacía demasiadas cosas diferentes. En la práctica, ambos comandos siguen funcionando y también se pueden usar para saltar de rama, no de commit:
$ git checkout -b otra-rama # Forma tradicional
$ git switch otra-rama # Forma moderna
Junto con git switch, también se introdujo el comando git restore para descartar cambios en archivos concretos (siempre que no estén ya en un commit).
$ git restore archivo.txt # Descarta cambios del archivo.txt
Ahora puedes ver y rescatar el código fuente válido sin temor: nada de lo que hagas en este estado afectará a tu proyecto, porque los cambios se perderán cuando salgas de este “viaje en el tiempo” (salvo que crees una nueva rama desde ese punto, pero de ramas hablamos ahora mismo).
Cuando dos personas se encaprichan del mismo archivo: cómo hacer un merge
Cuando ejecutas git pull, traes a tu repositorio local las versiones más recientes de todos los archivos del proyecto. Esto ya lo sabíamos.
Si git pull se ejecuta sin contratiempos, aparecerá un mensaje informándote de ello.
Pero los contratiempos existen, qué le vamos a hacer. La vida sería muy aburrida y predecible sin ellos.
El contratiempo más habitual, con diferencia, al hacer git pull es el aviso de un conflicto en alguno de los archivos modificados en el repositorio remoto. Eso quiere decir que tú has estado tocando el código de un archivo al mismo tiempo que otra persona de tu equipo.
Supongamos que, en un archivo A, tú has añadido las líneas A1, A2 y A3, mientras que otra persona ha añadido las líneas A4, A5 y A6. Si la otra persona ha subido el archivo A al repositorio remoto antes que tú, Git se dará cuenta cuando intentes hacer git pull de que tu copia local del archivo y la que hay en el repositorio remoto no coinciden: no solo porque la tuya tiene las nuevas líneas A1, A2 y A3, sino porque a la tuya le faltan las líneas A4, A5 y A6.
En ese caso, y para no perder ninguna de las nuevas líneas de código, Git te mostrará un mensaje de advertencia y creará una versión nueva del archivo A en la que estarán todas las líneas de código nuevas, tanto las tuyas como las de la otra persona, rodeadas de unas marcas de texto como estas:
<<<<<<< HEAD
(tus líneas)
=======
(las líneas de la otra persona)
>>>>>>> nombre-de-la-otra-rama
Ahora, lo único que tienes que hacer es buscar manualmente esas líneas conflictivas y resolverlas a mano, es decir, quedarte con las líneas correctas y borrar las que no lo sean. Borra también todas las marcas que ha puesto ahí Git para indicarte el conflicto.
Si usas cualquier editor de texto medianamente potente (Visual Studio Code, sin ir más lejos), te mostrará esas líneas resaltadas e incluso te ayudará a quedarte con una versión, con la otra, o con ambas, con un simple clic.
Una vez que hayas resuelto manualmente las líneas en conflicto, basta con guardar los cambios y hacer git add y git commit -m “Resolviendo el conflicto bla, bla, bla” para que el git pull y el git push vuelvan a funcionar a la perfección.
Proyectos que se complican: cómo crear ramas
Imagina esta situación: tienes un proyecto ya en marcha, con una versión más o menos estable funcionando, y entonces surge la necesidad de desarrollar una nueva funcionalidad.
Y esta nueva funcionalidad va a poner patas arriba una parte importante del código y va a dejar la aplicación hecha unos zorros durante un tiempo.
Si trabajas con tu repositorio como hemos hecho hasta ahora, el resultado es que, durante ese tiempo, todo tu proyecto dejará de funcionar. No podrás hacer demos a los clientes (ni a tus profesores/as), no podrás probar la aplicación, no podrás cargarla con datos reales, etc. ¡Todo quedará paralizado hasta que la nueva funcionalidad esté en marcha!
En un equipo de desarrollo grande, esta es una situación cotidiana que provocaría que gran parte de la gente se tuviera que quedar de brazos cruzados a la espera de la finalización de la nueva funcionalidad. Pero incluso en un equipo pequeño es un engorro llegar a este extremo.
Para evitarlo, existen las ramas (branches) de Git.
Una rama no es más que una copia del historial del proyecto que puede evolucionar por su cuenta mientras la rama original (normalmente main) permanece inalterada.
Los desarrolladores/as que trabajen en esa rama pueden así trabajar en la nueva funcionalidad sin que el resto del equipo se vea afectado. Cuando la nueva funcionalidad se termine, lo único que hay que hacer es fusionar las dos ramas. Esto puede ser un trabajo ímprobo si se han estado modificando los mismos archivos en la rama principal y en la rama nueva, pero no se trata de un fallo de Git, que quede claro, sino de un fallo de organización del equipo (o, sencillamente, de mala suerte).
Y si la nueva funcionalidad nunca llega a terminarse (cosa que puede ocurrir por miles de razones), no pasa nada: la rama se elimina, o simplemente se abandona, y la rama principal sigue intacta.
Crear una rama nueva es tan sencillo como usar este comando:
$ git branch nombre-nueva-rama
El comando git branch tiene muchas otras posibilidades. Aquí te pongo unas cuantas:
$ git branch --list (saca un listado de todas las ramas existentes)
$ git branch -d nombre-rama (elimina una rama)
$ git branch -D nombre-rama (elimina una rama a lo bestia, incluso si tiene cambios sin fusionar)
$ git branch -m nuevo-nombre (cambia el nombre de la rama actual)
Ten en cuenta que, cuando creas una rama, aún no estás trabajando en ella. Si quieres cambiar a esa rama para empezar a trastear con ella sin tocar a la principal, debes hacer:
$ git switch nombre-rama
(o, si prefieres el comando clásico, git checkout nombre-rama; hacen lo mismo en este caso).
Un atajo que se usa muchísimo es crear la rama y cambiarte a ella en un solo paso:
$ git switch -c nombre-nueva-rama
Por último, para fusionar una rama con otra (típicamente, con la rama principal o main), tienes que seguir estos pasos:
- Asegúrate de estar situado en la rama que va a recibir la fusión. Si esa rama es main, tienes que hacer:
$ git switch main - Haz un git pull para tener disponible la última versión del código.
- Realiza la fusión de las dos ramas con git merge:
$ git merge nombre-rama
En este punto, tendrás que resolver manualmente los conflictos que puedan surgir (si los hay), como hemos explicado más arriba.
Esto está muy bien para proyectos pequeños o para entender cómo funcionan las ramas por dentro. Pero, en un equipo real, fusionar ramas “a pelo” con git merge en tu máquina, sin que nadie más las revise, es jugar con fuego. Por eso, en el mundo profesional casi nadie fusiona una rama de funcionalidad directamente: se usa un Pull Request. Vamos a verlo.
1.1.6. Flujo de ramas: trabajando con GitHub Flow
Ya sabes crear ramas y fusionarlas. El problema es que “cuándo crear una rama nueva” y “cómo se llama” y “cuándo se fusiona” son preguntas que, si cada persona del equipo responde a su manera, acaban generando un caos de ramas con nombres como rama2, pruebas_pedro_final, pruebas_pedro_final_DEFINITIVA… Para evitarlo, los equipos adoptan una estrategia de ramificación (branching strategy) común, que no es más que un conjunto de reglas que todo el mundo sigue.
Existen varias estrategias (la más veterana y elaborada es Git Flow, con ramas develop, release, hotfix…), pero para el tipo de proyectos que vamos a manejar en este módulo (y, en general, para la inmensa mayoría de proyectos web actuales) nos va a bastar con una mucho más sencilla y muy extendida en la industria: GitHub Flow. Sus reglas son solo cuatro:
- La rama
mainsiempre tiene que estar en un estado desplegable. Es decir: lo que hay enmainfunciona, siempre. Nadie trabaja directamente sobre ella. - Para desarrollar cualquier cosa (una funcionalidad nueva, arreglar un error, lo que sea), se crea una rama nueva a partir de
main, con un nombre descriptivo de lo que se va a hacer en ella. Por ejemplo:feature/login-usuariosofix/error-calculo-precio. - Se trabaja en esa rama, haciendo tantos commits como haga falta y subiéndolos al repositorio remoto con normalidad (
git push), para que el resto del equipo pueda ver el progreso e incluso colaborar en la misma rama si es necesario. - Cuando el trabajo está terminado (o se quiere que alguien lo revise), se abre un Pull Request para fusionar esa rama con
main. Solo después de que el Pull Request se apruebe y se fusione, la rama se elimina.
Fíjate en la idea de fondo: cualquier cambio que llegue a main ha pasado, obligatoriamente, por una revisión. Esto es justo lo que vamos a ver en las dos siguientes secciones.
1.1.7. Pull Requests
Un Pull Request (a veces llamado Merge Request en GitLab; abreviado habitualmente como PR) es una petición formal para fusionar los cambios de una rama con otra, normalmente con main. No es un comando de Git: es una funcionalidad que añaden por encima plataformas como GitHub o GitLab, y se gestiona desde su interfaz web.
La idea es sencilla: en lugar de que cualquiera pueda hacer git merge y colar sus cambios en main sin que nadie se entere, el autor de la rama dice “oye, tengo esto terminado, ¿alguien le puede echar un vistazo antes de que lo fusionemos?”. A partir de ahí:
- El Pull Request muestra, de forma visual y muy clara, el diff: todas las líneas añadidas (en verde) y eliminadas (en rojo) respecto a
main, archivo por archivo. - Cualquier persona con acceso al repositorio (normalmente, el resto del equipo) puede añadir comentarios, tanto generales como sobre líneas concretas de código.
- Se pueden lanzar automáticamente comprobaciones automáticas (tests, linters, análisis de calidad de código…) que deben pasar en verde antes de poder fusionar. Esto se suele configurar mediante integración continua (lo veremos con más detalle en el tema de despliegue).
- El autor de la rama puede subir nuevos commits para corregir lo que se le indique en la revisión, y el Pull Request se actualiza automáticamente con ellos.
- Cuando todo el mundo está conforme, alguien con permisos (normalmente no el propio autor, para evitar que uno se autoapruebe sus cambios) aprueba y fusiona el Pull Request. En ese momento, y no antes, los cambios pasan a formar parte de
main.
Para crear un Pull Request en GitHub, el flujo típico es:
- Sube tu rama al repositorio remoto:
git push -u origin nombre-de-tu-rama. - Entra en la web del repositorio en GitHub. Verás un aviso ofreciéndote crear un Pull Request para la rama que acabas de subir (o puedes ir a la pestaña “Pull requests” → “New pull request”).
- Escribe un título y una descripción claros: qué hace este cambio, por qué era necesario, cómo se puede probar… Cuanto mejor documentado esté, más rápido y mejor será revisado.
- Asigna revisores (compañeros de equipo, tu profesor/a, si te lo ha indicado así, testers independientes…) y espera comentarios.
Los Pull Requests no son exclusivos de proyectos con más de una persona: aunque trabajes solo/a en un proyecto, es una buena práctica abrir un Pull Request antes de fusionar cualquier rama de funcionalidad con main. Te obliga a revisar tus propios cambios con otra perspectiva antes de darlos por bueno, y te deja un historial clarísimo de qué cambio hizo qué cosa y por qué.
1.1.8. Code Reviews
La revisión de código (Code Review) es el proceso de examinar el código de otra persona antes de que se integre en el proyecto, y suele hacerse precisamente dentro de un Pull Request, mediante los comentarios que hemos mencionado antes.
No se trata solo de buscar bugs (que también). Una buena revisión de código persigue varias cosas a la vez:
- Detectar errores que el autor, por estar metido de lleno en el problema, no ha visto.
- Mantener la coherencia del proyecto: que todo el código siga las mismas convenciones de nombres, de estilo, de organización de carpetas, etc.
- Compartir conocimiento: quien revisa aprende cómo se ha resuelto un problema, y quien ha escrito el código recibe otro punto de vista. En un equipo, esto evita que solo una persona conozca cómo funciona una parte crítica de la aplicación.
- Detectar problemas de diseño que no siempre saltan a la vista solo con ejecutar el código: código duplicado, funciones que hacen demasiadas cosas a la vez, nombres de variables poco claros, ausencia de tests, etc.
Algunas recomendaciones para que las revisiones de código sean útiles y no un suplicio para nadie:
- Revisa el código, no a la persona. Los comentarios deben ir sobre el código (“esta función podría simplificarse así”), nunca sobre quien lo ha escrito. Y, si te toca recibir las críticas de los demás, no te las tomes como algo personal: todo el mundo mejora su código gracias a otros ojos, incluidos los programadores con más experiencia.
- Pull Requests pequeños y frecuentes, mejor que uno gigantesco cada dos semanas. Un PR de 30 líneas se revisa en cinco minutos y con mucho cuidado. Uno de 3.000 líneas nadie lo revisa de verdad: la gente lo aprueba por cansancio, y ahí es donde se cuelan los problemas.
- Sé concreto y, si puedes, sugiere una alternativa, no te quedes en “esto está mal”. Muchas plataformas, como GitHub, te permiten incluso proponer directamente el cambio de código en el propio comentario, para que el autor lo acepte con un clic.
- No todo tiene que resolverse antes de fusionar. Es razonable distinguir entre comentarios bloqueantes (hay que corregirlos sí o sí) y sugerencias o mejoras que se pueden abordar en otro Pull Request posterior, para no eternizar la revisión.
En el contexto de este módulo, vamos a aplicar este mismo esquema en el “Protocolo de entregas” que veremos en el apartado 1.3: tus entregas de prácticas se van a gestionar, siempre que sea posible, mediante Pull Requests, y recibirás comentarios de revisión sobre tu código exactamente igual que ocurriría en un entorno de trabajo real.
1.1.9. ¿Aún quieres saber más?
Git es un sistema de control de versiones increíblemente completo. Sus creadores parecen haber pensado en escenarios de lo más aberrante y han tenido en cuenta casi cada cosa que puede suceder en un proyecto complejo. Si no, no se explica la enorme cantidad de comandos y posibilidades que ofrece.
Si necesitas saber más cosas sobre Git, internet está plagada de contenidos de calidad (y otros bastante penosos) sobre este sistema.
Como siempre te recomiendo, acude en primer lugar a la referencia oficial: https://git-scm.com/docs
Personalmente, a mí me gustan mucho los tutoriales de Atlassian. Aunque están orientados a BitBucket (un servicio competidor de GitHub o GitLab), casi todas sus recomendaciones son aplicables a cualquier servidor Git. Los puedes encontrar aquí: https://www.atlassian.com/es/git/tutorials
Y, si quieres profundizar específicamente en el flujo de trabajo con ramas y Pull Requests que hemos visto en este apartado, la propia documentación de GitHub sobre GitHub Flow es corta, clarísima y merece mucho la pena: https://docs.github.com/es/get-started/using-github/github-flow
1.4.10 Práctica de Git
Objetivos:
- Crear y conectar un repositorio local con uno remoto en GitHub.
- Trabajar con el ciclo add → commit → push / pull.
- Aplicar el flujo GitHub Flow: ramas de funcionalidad + Pull Request.
- Provocar y resolver un conflicto de merge.
- Revisar el código de un compañero mediante comentarios en un Pull Request.
- Deshacer un commit erróneo con git revert.
Requisitos previos:
- Tener una cuenta en GitHub o GitLab
- Tener Git instalado en el equipo local (teclea
git --versionen la terminal para comprobarlo)
Entregable final: el enlace a tu repositorio de GitHub, con el historial de commits, al menos dos Pull Requests cerrados y capturas de pantalla donde se verá el progreso.
Paso 1 — Crear el repositorio remoto
- Entra en GitHub y crea un repositorio nuevo llamado
practica-git-tu-usuario(por ejemplo,practica-git-pepitoperez). - Márcalo como público, no lo inicialices con README (lo crearemos nosotros desde local).
Paso 2 — Crear el repositorio local y conectar los dos repos
- En tu ordenador, crea una carpeta para el proyecto y ejecuta:
$ mkdir practica-git
$ cd practica-git
$ git init
$ git branch -M main
$ git remote add origin <URL-de-tu-repositorio>
- Comprueba que la conexión es correcta:
$ git remote -v
Deberías ver origin apuntando a tu URL, tanto para fetch como para push.
Paso 3 — Primer commit
- Crea un archivo
README.mdcon un contenido de este estilo:
# Práctica de Git
Repositorio de prácticas del módulo de Desarrollo de Aplicaciones Web.
Autor: <tu nombre>
- Crea también un archivo
.gitignorecon estas líneas:
/vendor/
/node_modules/
.env
- Añade ambos archivos con
git addy haz el primer commit:
$ git add README.md .gitignore
$ git commit -m "Primer commit: README y gitignore"
$ git push -u origin main
- Entra en GitHub y verifica que se han subido los dos archivos y el commit.
Paso 4 — Crear una nueva rama de funcionalidad
- Vamos a crear una nueva rama simulando que queremos hacer algún cambio en la aplicación:
$ git switch -c feature/pagina-index
- Crea un archivo
index.htmlsencillo (como una página de bienvenida o un “hola mundo” cualquiera). Guarda, añade y confirma:
$ git add index.html
$ git commit -m "feat: añado página de inicio"
$ git push -u origin feature/pagina-index
Paso 5 — Abrir el Pull Request
- Ve a GitHub. Aparecerá un aviso para crear el Pull Request desde tu rama.
- Pulsa Compare & pull request.
- Pon en el título algo como: Añadir página de inicio.
- En la descripción, explica brevemente qué hace el cambio.
- Pulsa Create pull request. ¡Pero no lo fusiones todavía!
Paso 6 — Autorrevisión
- Antes de fusionar, entra en la pestaña Files changed del Pull Request y repasa el diff como si fueras otra persona revisando el código.
- Añade al menos un comentario (aunque sea sobre tu propio código, para practicar la mecánica).
Paso 7 — Fusionar
- Pulsa Merge pull request → Confirm merge → Delete branch (para borrar la rama remota ya fusionada).
- De vuelta en tu terminal, actualiza tu copia local de main y borra la rama local:
$ git switch main
$ git pull
$ git branch -d feature/pagina-index
Paso 8 — Preparar el conflicto (a propósito)
Uno de los dos añade al otro como colaborador de su repositorio (Settings → Collaborators).
Ambos miembros de la pareja, a la vez, vais a modificar la misma línea del mismo archivo siguiendo estos pasos:
- Los dos hacéis
git pullpara tener la última versión de main. - Los dos creáis una rama distinta desde main:
- Alumno A:
git switch -c feature/titulo-alumnoA - Alumno B:
git switch -c feature/titulo-alumnoB
- Alumno A:
- Los dos editáis la misma línea del
README.md(por ejemplo, la línea del título), pero cada uno escribe algo distinto. - Cada uno hace commit y push de su rama.
- Cada uno abre su propio
Pull Requestcontra main. - El Alumno A fusiona su Pull Request con normalidad.
maindebe quedar actualizado con su cambio.
Paso 9 — Provocar el conflicto
El Alumno B, en su Pull Request, verá ahora un aviso de este estilo: “This branch has conflicts that must be resolved”. Puede resolverlo desde local:
$ git switch feature/titulo-alumnoB
$ git pull origin main
Git marcará el conflicto en el archivo con las marcas <<<<<<<, ======= y >>>>>>>.
- El Alumno B abre el archivo en su editor, decide qué línea final quiere dejar (incluso se pueden combinar ambas) y borra las marcas de conflicto.
- Guarda, añade y confirma la resolución:
$ git add README.md $ git commit -m "fix: resuelvo conflicto de merge en README" $ git push - Vuelve al Pull Request en GitHub: el aviso de conflicto debería haber desaparecido y ya se puede fusionar.
CAPTURA 3B: guarda una captura de pantalla del archivo con el Pull Request ya fusionado sin conflicto.
Paso 10 - Code Review realista
- El Alumno A crea una rama
feature/formulario-contactoy añade un archivocontacto.htmlcon un formulario simple que contendrá algún fallo pequeño a propósito, como un campo sin label, un nombre de variable confuso, etc. El Alumno B no debe saber cuál es el error. - El Alumno A sube la rama y abre el Pull Request.
- El Alumno B, como revisor, entra en el Pull Request del Alumno A y:
- Deja algunos comentarios sobre líneas concretas del código (usando el botón + que aparece al pasar el ratón sobre una línea en la pestaña Files changed).
- Marca la revisión como Request changes (botón Review changes) en lugar de aprobarla directamente.
- El Alumno A corrige lo indicado, hace un nuevo commit en la misma rama y lo sube (
git push). El Pull Request se actualiza solo. - El Alumno B vuelve a revisar los cambios y, si ya está conforme, aprueba con Approve y fusiona el Pull Request.
Paso 11 - Deshacer un error
Sobre la rama main, edita el README.md y, a propósito, borra por error una parte importante del contenido. O incluso el archivo entero. Haz commit y push directamente:
$ git add README.md
$ git commit -m "cambios varios"
$ git push
(Sí, sabemos que esto no debe hacerse, pero es justo el error que queremos provocar).
- Localizamos el commit problemático con
git log --oneline - Identificamos el hash del commit con el texto “cambios varios”.
- Revertimos (deshacemos) los cambios:
$ git revert <hash-del-commit>
Git abrirá un editor para el mensaje del commit de reversión; guarda y cierra tal cual. Sube el cambio:
$ git push
git log --oneline donde se vea el commit original y el commit de revert justo debajo. Entrega de la práctica
Sube a Moodle Centros lo siguiente:
- El enlace a tu repositorio de GitHub.
- Las 5 capturas de pantalla que has debido guardar a lo largo de la práctica. Insértalas en un único documento PDF, colocadas en orden e identificadas con su número (captura 1, captura 2, etc).
- Añade al final del documento PDF una respuesta breve a esta pregunta: ¿qué hubiera pasado si el Alumno A y el Alumno B hubieran fusionado sus cambios directamente en main con
git push, en lugar de usar ramas y Pull Requests?
Rúbrica de corrección
Esta práctica se calificará siguiendo la siguiente rúbrica:
- 0 = sin hacer o sin evidencia de esfuerzo
- 1 = hecho pero incorrecto
- 2 = hecho correctamente, pero mejorable
- 3 = hecho superando las espectativas
Se calificarán de 0 a 3, según la rúbrica anterior, los siguientes ítems evaluables:
- Repositorio inicializado y conectado correctamente
- Flujo de rama + Pull Request ejecutado correctamente
- Conflicto de merge resuelto correctamente
- Code review cruzado con comentarios reales
- Uso correcto de git revert