Saltar al contenido

Git en la práctica

Los comandos que se preguntan: crear y clonar un repositorio, el día a día, las ramas, merge frente a rebase, la sincronización con el remoto y las tres formas de deshacer.

Crear el repositorio y configurarlo

Un repositorio local se puede obtener de dos formas: creándolo desde cero sobre un directorio propio, o clonando uno que ya exista. Clonar trae el proyecto entero con toda su historia, no solo la última versión.

bash
# Opción A: crear un repositorio nuevo sobre un directorio propio
cd llegandoalcorte-temario
git init

# Opción B: clonar uno que ya existe (trae toda la historia)
git clone https://github.com/llegandoalcorte/temario-tai.git

# Quién firma los commits de esta máquina
git config --global user.name "Diego"
git config --global user.email "admin@llegandoalcorte.com"

git init crea un directorio oculto llamado .git dentro del proyecto. Ahí vive todo: la metainformación, los objetos (commits, árboles y ficheros), las referencias a ramas y etiquetas y el índice o área de preparación. Si se borra ese directorio, se pierde la historia y queda solo la copia de ficheros del disco.

Las ramas de Git no son directorios en el disco: son referencias internas dentro de .git que apuntan a un commit. Por eso crear una rama es instantáneo, mientras que en Subversion equivale a copiar un directorio entero.

El fichero .gitignore enumera lo que Git debe ignorar y no subir nunca: resultados de compilación, dependencias descargables, ficheros temporales del editor y, sobre todo, credenciales. En un proyecto Java se ignoran los .class porque se pueden regenerar a partir de los .java; en uno de Node, el directorio de dependencias.

Para el examen

  • Qué es una rama: una referencia dentro de .git que apunta a un commit

  • Lo que NO es: un directorio

  • Consecuencia: crearlas es instantáneo

Los comandos del día a día

El ciclo básico se repite decenas de veces al día: mirar cómo está el directorio de trabajo, revisar qué se ha cambiado, seleccionar lo que entra en el commit, confirmar y consultar la historia.

bash
git status                 # en qué rama estoy y qué falta por añadir o confirmar
git diff                   # qué he cambiado y todavía no he preparado
git add tema-9.ts          # preparo un fichero concreto
git add .                  # preparo todo lo del directorio actual
git commit -m "Añade el test del subtema de pruebas"
git log                    # la historia completa, del más reciente al primero
git log --graph --oneline --all   # la misma historia, resumida y con las ramas dibujadas
git blame tema-9.ts        # quién escribió cada línea y en qué commit

git log recorre la historia hacia atrás partiendo de HEAD. HEAD es un puntero al commit en el que estás situado ahora mismo, normalmente el último de tu rama actual; desde él se llega al anterior, y así sucesivamente hasta el primer commit del proyecto.

Cuidado con la errata que circula por muchos apuntes: la opción para ver cada commit en una sola línea es --oneline, no «--online». No existe ninguna opción de Git que se llame así.

git tag crea etiquetas: nombres estables para un commit concreto, que se usan para marcar una versión publicada. A diferencia de una rama, una etiqueta no se mueve: siempre señalará al mismo commit.

Para el examen

  • Ver cada commit en una línea: --oneline

  • Errata que circula: «--online», que no existe

Ramas

Una rama es una línea de desarrollo paralela. Se crea para trabajar en algo (un correctivo urgente, una funcionalidad nueva) sin molestar al resto del equipo ni arriesgar la línea principal, y cuando el trabajo está listo se incorpora de vuelta.

bash
git branch                     # lista las ramas; la actual sale marcada
git branch preguntas-falladas  # crea la rama, pero NO se mueve a ella
git checkout preguntas-falladas # se mueve a la rama ya creada
git checkout -b calendario-repasos  # crea la rama y se mueve en un solo paso

# Desde Git 2.23 existe un comando específico para cambiar de rama
git switch calendario-repasos
git switch -c calendario-repasos    # equivale a checkout -b

Todo repositorio arranca con una rama inicial. Históricamente se llamaba master, y sigue siendo el nombre por defecto en muchas instalaciones, pero GitHub y otras plataformas usan hoy main; en Subversion el equivalente se llama trunk. Es una convención, no una obligación: el nombre se puede cambiar con git branch -m nombre_viejo nombre_nuevo.

git branch crea la rama pero deja donde estabas; git checkout cambia de rama. Es la pareja que más se pregunta, y por eso existe el atajo git checkout -b, que hace las dos cosas.

git checkout tiene un segundo uso que conviene no mezclar con el anterior: si se le da el nombre de un fichero en vez del de una rama, descarta los cambios hechos sobre ese fichero y lo devuelve al estado del último commit. Desde Git 2.23 esa función tiene su propio comando, git restore, precisamente para deshacer la ambigüedad.

Para el examen

  • git branch: crea la rama, pero no cambia de sitio

  • git checkout: cambia de rama

  • git checkout -b: hace las dos cosas

Merge frente a rebase

Los dos comandos sirven para lo mismo: incorporar a tu rama el trabajo hecho en otra. La diferencia está en qué le hacen a la historia, y esa diferencia es una pregunta clásica de examen.

git merge conserva la historia tal como ocurrió: crea un commit de fusión con dos padres, de modo que en el registro se ve que hubo dos líneas paralelas y en qué punto se juntaron. git rebase reescribe: coge los commits de tu rama y los vuelve a aplicar uno a uno encima de la punta de la otra rama, como si los hubieras escrito después. El resultado es una historia lineal, más fácil de leer, pero con commits nuevos y por tanto con identificadores distintos.

bash
# Fusionar: primero me sitúo en la rama que va a RECIBIR los cambios
git checkout main
git merge calendario-repasos

# Reescribir: me sitúo en la rama que va a MOVERSE
git checkout calendario-repasos
git rebase main

# Rebase interactivo: agrupar los 3 últimos commits en uno solo
git rebase -i HEAD~3
Aspectomergerebase
Efecto sobre la historiaLa conserva; añade un commit de fusiónLa reescribe; los commits se recrean con otro identificador
Aspecto del registroRamificado, se ve que hubo trabajo en paraleloLineal, como si todo se hubiera hecho en fila
Dónde te sitúas antesEn la rama que recibe los cambiosEn la rama que se va a mover
RiesgoNinguno; solo sumaNo debe usarse sobre commits ya publicados

La regla de oro: no se reescribe historia que ya han descargado otros. Rebase, amend y reset son seguros mientras el commit siga solo en tu máquina; después obligan a los demás a arreglar su copia.

Dentro del merge hay dos casos. Si la rama que recibe no ha avanzado desde que te separaste, Git resuelve la fusión moviendo simplemente el puntero hacia delante: es el fast forward, y no genera ningún commit de fusión. Con la opción --no-ff se fuerza a crear el commit de fusión igualmente, para que en el registro quede constancia de que existió una rama. Y si las dos ramas han tocado las mismas líneas del mismo fichero, Git no puede decidir por su cuenta: marca un conflicto y espera a que una persona elija qué versión vale.

El rebase interactivo, con la opción -i, se detiene y pide confirmación antes de aplicar cada commit, lo que permite reordenarlos, cambiar sus mensajes o fundir varios en uno. Se usa para limpiar una serie de commits pequeños antes de proponerlos al proyecto principal.

Para el examen

  • Merge: conserva la historia real

  • Rebase: la reescribe, dejándola lineal

  • Regla de oro: no se reescribe historia que ya han descargado otros

Sincronizar con el repositorio remoto

bash
git remote -v                  # a qué repositorios remotos estoy conectado
git fetch                      # descargo lo nuevo del remoto, sin tocar mi rama
git fetch --all                # lo mismo para todas las ramas del repositorio
git pull                       # fetch + merge: descargo y fusiono en mi rama actual
git pull origin main           # indicando remoto y rama de forma explícita
git push origin main           # subo mis commits locales al remoto

La diferencia entre fetch y pull es la pregunta habitual, y suele explicarse mal. Los dos descargan: git fetch trae al repositorio local todos los objetos nuevos que haya en el remoto, commits y ficheros incluidos, y los deja en las ramas de seguimiento (origin/main). Lo que fetch NO hace es tocar tu directorio de trabajo ni fusionar nada en tu rama. git pull hace las dos cosas: primero el fetch y después el merge sobre tu rama actual.

fetch descarga y no cambia tu trabajo; pull descarga y además fusiona. Por eso fetch es seguro en cualquier momento y pull conviene hacerlo con el directorio de trabajo limpio.

El aviso de que «tu rama está adelantada» significa que tienes commits en local que el remoto todavía no tiene, y se resuelve con push. Si Git rechaza el push es al revés: el remoto ha avanzado por su cuenta y hay que traerse antes esos cambios con pull o fetch.

Para el examen

  • fetch: descarga y no toca tu trabajo

  • pull: descarga y además fusiona

  • Cuándo usar cada uno: fetch siempre es seguro; pull, con el directorio limpio

Deshacer: reset, revert y amend

Hay dos filosofías para deshacer. Una borra el commit de la historia y hace como si nunca hubiera existido; la otra deja el commit donde está y añade otro que lo anula. La primera solo es aceptable si nadie más ha visto ese commit.

git reset retrocede el puntero de la rama hasta un commit anterior, indicado con HEAD, con HEAD~2 o directamente con su identificador. Lo que cambia entre sus tres variantes es hasta qué zona arrastra los cambios que quedan huérfanos.

VarianteQué deshaceDónde quedan los cambios
git reset --softSolo el commitEn el área de preparación, listos para volver a confirmarse
git reset --mixed (por defecto)El commit y la preparación (el add)En el directorio de trabajo, sin preparar
git reset --hardEl commit, la preparación y los ficherosEn ninguna parte: se pierden

--hard es la única variante destructiva y no avisa. Las otras dos solo mueven los cambios hacia atrás por las zonas de trabajo, así que siempre se pueden recuperar.

bash
git reset --soft HEAD~1    # deshago el último commit, conservo el trabajo preparado
git reset --mixed HEAD~1   # deshago el commit y el add; el trabajo sigue en disco
git reset --hard HEAD~1    # deshago el commit y BORRO el trabajo

git commit --amend         # rehago el último commit (mensaje o contenido)
git revert 8f3c21a         # creo un commit nuevo que anula lo que hizo ese commit

git revert es la alternativa segura para lo ya publicado: no borra nada de la historia, sino que introduce un commit nuevo cuyo contenido es exactamente el inverso del que se quiere anular. Al final la historia es más larga, pero nadie tiene que arreglar su copia.

git commit --amend sirve para corregir el último commit: cambiar su mensaje o añadirle algo que se olvidó, en vez de dejar en la historia un commit adicional que no aporta nada. Cuidado con una imprecisión frecuente: amend no modifica el commit anterior, construye uno nuevo y lo pone en su lugar, así que el identificador cambia y también cuenta como reescribir historia.

Para el examen

  • reset --hard: la única variante destructiva, y no avisa

  • revert: crea un commit que deshace

  • amend: rehace el último commit

Flujos de trabajo y herramientas gráficas

Git no impone cómo organizar las ramas, así que los equipos adoptan convenios. El más citado es Gitflow, que define una rama permanente para lo publicado y otra para el desarrollo en curso, y ramas temporales para cada funcionalidad, cada preparación de versión y cada arreglo urgente.

Rama de GitflowPara qué sirve
main o masterRefleja siempre lo que está en producción
developIntegra el trabajo terminado a la espera de la próxima versión
feature/…Una por cada funcionalidad nueva; nace de develop y vuelve a develop
release/…Preparación de una versión: solo correcciones y ajustes finales
hotfix/…Arreglo urgente que sale de main y se incorpora a main y a develop

Frente a Gitflow, muchos equipos prefieren hoy flujos más simples, con una sola rama principal y ramas cortas de funcionalidad que se integran mediante pull request, precisamente porque encaja mejor con la integración continua: cuanto más viva una rama separada, más doloroso es fusionarla.

Sobre las herramientas gráficas: Sourcetree es un cliente gratuito de Atlassian, la misma empresa de Jira y Bitbucket, y sirve para trabajar con Git sin escribir comandos. No aporta nada que la línea de comandos no tenga, pero ayuda a visualizar el grafo de ramas.

Para el examen

  • main: lo publicado

  • develop: lo integrado

  • Ramas temporales de Gitflow: feature, release y hotfix