Git y GitHub: convierte cambios en una historia verificable
Aprende a registrar decisiones con Git y colaborar en GitHub mediante un proyecto práctico. Crea commits claros, trabaja con ramas y pull requests, automatiza pruebas, resuelve conflictos y recupera versiones. Completa el …
Contenido de la guía ⌄
- De guardar archivos a explicar decisiones
- Preparar un laboratorio que puedas repetir
- Herramientas y requisitos
- Una terminal y nombres consistentes
- El mismo proyecto durante todo el recorrido
- Git y GitHub: dónde ocurre cada cosa
- Relaciona cada acción con su efecto
- Un commit local y su publicación son pasos distintos
- Una rama y un pull request también cumplen funciones distintas
- Qué conserva Git cuando creas un commit
- Preparar un archivo captura su estado en ese momento
- Una instantánea y sus relaciones
- Preparar el repositorio y sus primeros archivos
- Elegir qué entra en el primer commit
- Excluir lo local y conservar lo necesario
- Leer las diferencias correctas
- Probar, inspeccionar y registrar
- Un commit responde a una sola intención
- Prepara dos ediciones concretas
- Preparar fragmentos con git add -p
- Mensajes que permiten reconstruir el motivo
- El asunto presenta la decisión; el cuerpo conserva su motivo
- Una convención que puedes adoptar
- Abrir la historia y contrastar el mensaje
- Ramas: conservar una base y desarrollar otra decisión
- HEAD indica desde dónde estás trabajando
- Integrar decisiones con merge
- Qué cambia cuando se utiliza rebase
- Conectar GitHub sin confundir identidad y acceso
- Crear un remoto vacío
- Tres preguntas distintas
- Traer información no es integrar cambios
- Preparar un avance remoto independiente
- Un pull request cuenta la decisión completa
- Revisar el cambio y responder observaciones
- Preparar las comprobaciones automáticas
Git y GitHub · Guía práctica
De guardar archivos a explicar decisiones
Un proyecto se vuelve difícil de mantener cuando dejas de entender cómo llegó a su estado actual. Un historial útil permite reconstruir decisiones y comprobar sus consecuencias.
Piensa en una carpeta con final.py, final2.py y final_ahora_si.py. Los nombres sugieren un orden, pero dejan preguntas abiertas: ¿qué diferencia hay entre esas versiones?, ¿cuál se probó?, ¿por qué cambió una regla que antes funcionaba?
Una función acepta un título vacío. Después de una mejora, lo rechaza. Queremos conservar el cambio, el motivo de esa decisión y una prueba que permita comprobar el nuevo comportamiento.
Construiremos una historia capaz de responder cinco preguntas:
- ¿Qué cambió? La diferencia concreta entre dos versiones.
- ¿Por qué cambió? El problema o la decisión que motivó la modificación.
- ¿Desde qué versión? El punto de partida y la revisión que introdujo el comportamiento.
- ¿Cómo se comprobó? Las pruebas y el resultado asociado a una revisión identificada.
- ¿Cómo se corrige? Una forma de recuperar o deshacer el cambio conservando el contexto necesario.
Durante el recorrido crearás commits pequeños, trabajarás con ramas, prepararás un pull request y ejecutarás pruebas automáticas. También resolverás un conflicto, localizarás una regresión y comprobarás una entrega firmada.
Cada afirmación debe poder contrastarse con una evidencia: un cambio inspeccionable, una prueba sobre una revisión concreta, una revisión documentada o una firma comprobada frente a una clave confiable. Cada evidencia responde a una pregunta distinta.
El laboratorio será pequeño para que puedas seguir cada decisión. Al terminar, tendrás un recorrido que podrás aplicar a un script, una página web o un proyecto compartido.
Preparar un laboratorio que puedas repetir
Trabajaremos con bitacora-git, un proyecto que normaliza títulos escritos por una persona. Comenzará quitando espacios laterales y después incorporará una regla para rechazar títulos vacíos. Las pruebas nos ayudarán a comprobar cada comportamiento.
Herramientas y requisitos
| Herramienta | Para qué la usaremos | Comprobación |
|---|---|---|
| Git 2.34 o superior | Registrar cambios, trabajar con ramas y completar la sección de firma SSH. | git --version debe mostrar la versión instalada. |
| Python 3 | Ejecutar el ejemplo y sus pruebas con la biblioteca estándar. | python --version debe mostrar una versión de Python 3. |
| Un editor de texto | Modificar archivos y examinar diferencias. | Debes poder abrir una carpeta y guardar archivos de texto en UTF-8. |
| Una cuenta de GitHub | Publicar el repositorio, abrir pull requests y ejecutar comprobaciones automáticas. | La utilizaremos cuando lleguemos a la colaboración con el remoto. |
Si necesitas instalar las herramientas, utiliza las páginas oficiales de Git y Python. El ejemplo utilizará unittest, incluido en Python, y no requerirá paquetes adicionales.
Una terminal y nombres consistentes
La ruta principal usará PowerShell en Windows. Los comandos se presentarán por líneas y sin prefijos como PS C:\..., de modo que puedas copiar únicamente la orden que debes ejecutar. Cuando haga falta editar un archivo, lo indicaremos como un paso separado.
En algunas instalaciones Python se ejecuta con py o python3. Comprueba cuál responde con una versión de Python 3 y utiliza ese mismo nombre en las órdenes de Python de toda la guía. Las órdenes que comienzan por git conservan su nombre.
El mismo proyecto durante todo el recorrido
En la práctica crearemos una carpeta nueva llamada bitacora-git. Sus primeros archivos serán:
bitacora.py: la función cuyo comportamiento iremos modificando.tests/test_bitacora.py: las pruebas de ese comportamiento..gitignore: las reglas para excluir archivos locales del seguimiento.
Comenzaremos trabajando en local. Más adelante añadiremos un remoto y un segundo clon en una carpeta hermana. Llamaremos A al repositorio de trabajo inicial y B al segundo clon; cada sesión indicará en cuál debes ejecutar los comandos.
Los dos clones permiten observar cómo se comparten y reciben cambios. Cuando hablemos de una aprobación independiente, esa revisión corresponderá a otra persona. En el recorrido individual practicarás la autoevaluación del mismo cambio.
Antes de continuar, comprueba que Git y Python responden en tu terminal y elige la carpeta donde crearás el laboratorio. La conexión a internet será necesaria para instalar herramientas y trabajar con GitHub; el historial local y las pruebas podrán practicarse sin conexión una vez preparado el entorno.
Git y GitHub: dónde ocurre cada cosa
Para interpretar una historia de cambios conviene saber dónde sucede cada operación. El repositorio de tu equipo y el repositorio alojado en GitHub se actualizan mediante acciones concretas.
Git · El historial de tu proyecto
Git registra versiones, relaciona commits y permite inspeccionar diferencias y trabajar con ramas. Crear un commit es una operación local: puedes realizarla sin conexión a internet.
GitHub · El espacio de colaboración
GitHub aloja repositorios Git y añade herramientas para proponer cambios, discutirlos, revisarlos y ejecutar comprobaciones automáticas.
Relaciona cada acción con su efecto
| Acción | Dónde ocurre | Qué produce |
|---|---|---|
| Editar un archivo | Tu carpeta de trabajo. | Una modificación que todavía debes revisar y registrar. |
| Crear un commit | Tu repositorio local. | Un nuevo punto de la historia con el contenido preparado para ese commit. |
| Crear una rama con Git | Tu repositorio local. | Una referencia desde la que puedes desarrollar una línea de trabajo. |
Enviar cambios con push |
Entre tu repositorio y el remoto. | El envío de los objetos necesarios y la actualización de las referencias seleccionadas, si el remoto lo permite. |
| Abrir un pull request | GitHub, en este recorrido. | Una propuesta de integración donde se pueden revisar el cambio, su contexto y sus comprobaciones. |
Un commit local y su publicación son pasos distintos
Imagina que terminas una mejora durante un viaje sin conexión. Puedes revisar los archivos, ejecutar las pruebas y crear un commit. Ese commit queda en tu repositorio local. Cuando recuperes la conexión y envíes la rama correspondiente, podrá estar disponible en el remoto.
Hacer commit conserva una decisión en tu repositorio. Comprueba después qué rama enviaste y qué revisión recibió el remoto: así podrás identificar la versión disponible para otras personas.
Una rama y un pull request también cumplen funciones distintas
La rama identifica una línea de desarrollo en Git. El pull request propone integrar sus cambios en otra rama y reúne la conversación y las comprobaciones relacionadas. Al revisar el proyecto, podrás conectar el código de los commits con el motivo explicado en esa propuesta.
Si creas un commit nuevo sin conexión, existe en tu repositorio local. Para compartirlo mediante GitHub tendrás que enviar la referencia correspondiente al remoto. Abrir un pull request será otro paso, cuando quieras proponer su integración.
Para profundizar: cómo funciona Git y qué reúne un pull request en GitHub.
Ahora vamos a distinguir tres lugares que intervienen al registrar un cambio: la carpeta de trabajo, el área de preparación y la historia confirmada.
Qué conserva Git cuando creas un commit
La figura anterior distingue tres lugares: los archivos que editas, el contenido que preparas y la historia que confirmas. Entender esa separación te permite decidir exactamente qué registrar.
| Área | Qué contiene | Pregunta que responde |
|---|---|---|
| Carpeta de trabajo | Los archivos que estás editando. | ¿Qué tengo ahora en mi equipo? |
| Índice o staging | El estado preparado para el siguiente commit. | ¿Qué versión voy a registrar? |
| Historia confirmada | Commits que relacionan instantáneas del proyecto. | ¿Qué decisiones ya quedaron registradas? |
Preparar un archivo captura su estado en ese momento
Al ejecutar git add, Git prepara el contenido actual del archivo. Si después lo vuelves a editar, el índice conserva la versión preparada. Un commit posterior incluirá esa versión; la edición más reciente seguirá en la carpeta de trabajo hasta que decidas prepararla.
Puede contener cambios preparados y, además, cambios posteriores sin preparar. Por eso inspeccionaremos tanto git diff como git diff --cached: comparan estados distintos.
Un archivo nuevo comienza como no rastreado. Al añadirlo al índice, Git empieza a incluirlo en el estado que registrarás. Los archivos no rastreados que no prepares, incluidos los nuevos archivos ignorados, quedan fuera de esa instantánea.
Una instantánea y sus relaciones
Un commit enlaza un árbol que describe el estado versionado del proyecto y registra metadatos, un mensaje y sus padres, cuando los tiene. Los archivos que conservan su contenido pueden reutilizar objetos existentes. Git obtiene una diferencia al comparar estados; el commit no es simplemente un archivo con líneas añadidas y borradas.
Cada objeto tiene un identificador derivado de su contenido. En la práctica usarás esos identificadores para localizar revisiones. El nombre, el correo y las fechas del commit son metadatos configurables; más adelante estudiaremos qué evidencia adicional aporta una firma.
Preparar el repositorio y sus primeros archivos
- Ejecuta el siguiente bloque en PowerShell desde la carpeta donde quieras crear
bitacora-git. Ese será nuestro repositorio A. - Abre la carpeta creada en tu editor. Guarda el bloque Python de la función como
bitacora.py. - Guarda el bloque de pruebas como
tests/test_bitacora.pyy las exclusiones como.gitignore. - Utiliza UTF-8. Tras guardar los tres archivos, continuaremos con las pruebas y el primer commit.
La configuración de autor se guardará únicamente en este repositorio. Introduce el nombre que quieras mostrar y tu correo de GitHub verificado, o el correo de privacidad que GitHub te proporcione.
# Terminal: PowerShell. Punto de partida: carpeta padre del laboratorio.
& {
Get-Command -Name git, python -ErrorAction Stop | Out-Null
git --version
if ($LASTEXITCODE -ne 0) { throw "Git no esta disponible." }
python --version
if ($LASTEXITCODE -ne 0) { throw "Python no esta disponible." }
$rutaLaboratorio = Join-Path (Get-Location).Path "bitacora-git"
if (Test-Path -LiteralPath $rutaLaboratorio) {
throw "La carpeta bitacora-git ya existe. Elige otra carpeta padre."
}
$nombreAutor = (Read-Host "Nombre para tus commits").Trim()
$correoAutor = (Read-Host "Correo verificado de GitHub o correo de privacidad").Trim()
if ([string]::IsNullOrWhiteSpace($nombreAutor) -or
[string]::IsNullOrWhiteSpace($correoAutor)) {
throw "El nombre y el correo deben tener contenido."
}
New-Item -ItemType Directory -Path $rutaLaboratorio -ErrorAction Stop | Out-Null
Set-Location -LiteralPath $rutaLaboratorio -ErrorAction Stop
git init -b main
if ($LASTEXITCODE -ne 0) { throw "No se pudo iniciar el repositorio." }
git config --local user.name $nombreAutor
if ($LASTEXITCODE -ne 0) { throw "No se pudo guardar el nombre." }
git config --local user.email $correoAutor
if ($LASTEXITCODE -ne 0) { throw "No se pudo guardar el correo." }
New-Item -ItemType Directory -Path "tests" -ErrorAction Stop | Out-Null
git config --local --get user.name
git config --local --get user.email
git status --short --branch
}
# Archivo: bitacora.py
def normalizar_titulo(titulo: str) -> str:
"""Quita espacios en blanco de los extremos."""
return titulo.strip()
# Archivo: tests/test_bitacora.py
import unittest
from bitacora import normalizar_titulo
class NormalizarTituloTests(unittest.TestCase):
def test_titulo_sin_espacios(self):
self.assertEqual(normalizar_titulo("Hola"), "Hola")
def test_recorta_espacios_laterales(self):
self.assertEqual(normalizar_titulo(" Hola "), "Hola")
def test_conserva_espacios_interiores(self):
self.assertEqual(normalizar_titulo(" Hola mundo "), "Hola mundo")
def test_conserva_unicode(self):
self.assertEqual(normalizar_titulo(" Café "), "Café")
if __name__ == "__main__":
unittest.main()
# Archivo de destino: .gitignore
# Caches y archivos compilados de Python
__pycache__/
*.py[cod]
# Entornos virtuales locales
.venv/
venv/
# Configuracion local y posibles secretos
.env
.env.*
!.env.example
Elegir qué entra en el primer commit
Ya tienes una función pequeña y cuatro pruebas. El comportamiento inicial consiste en recortar espacios laterales y conservar el texto interior. La regla que rechazará títulos vacíos llegará en una mejora posterior.
Excluir lo local y conservar lo necesario
Guarda las exclusiones con el nombre exacto .gitignore. Las pruebas y el módulo forman parte del proyecto; los cachés y entornos virtuales pueden reconstruirse. La excepción !.env.example permite versionar una plantilla de configuración con valores ficticios, si más adelante decides crearla.
Las reglas de .gitignore actúan sobre archivos no rastreados. Un archivo ya versionado seguirá bajo seguimiento. Si una credencial real llegó al historial, revócala o rótala; eliminarla en un commit posterior no borra las versiones anteriores.
Leer las diferencias correctas
| Comando | Qué permite comprobar |
|---|---|
git status --short |
Qué archivos son nuevos, están modificados o tienen cambios preparados. |
git diff |
Las diferencias entre el índice y la carpeta de trabajo en archivos rastreados. |
git diff --cached |
Lo preparado frente al último commit; antes del primero, muestra el contenido preparado para iniciar la historia. |
Antes de añadir los archivos nuevos, git status los señalará con ??. En ese momento, git diff puede no mostrar nada: todavía no está comparando el contenido de esos archivos no rastreados.
Probar, inspeccionar y registrar
Ejecuta la siguiente secuencia en PowerShell, dentro de bitacora-git. Primero correrá las pruebas; si fallan, la secuencia se detendrá. Después preparará únicamente los tres archivos previstos y mostrará su diferencia.
Revisa que aparezcan bitacora.py, tests/test_bitacora.py y .gitignore. El bloque hará una pausa antes de crear el commit para que puedas leer el contenido preparado. La opción --no-pager muestra los resultados directamente en la terminal.
Las cuatro pruebas pasan. El primer commit conserva los tres archivos y el estado final muestra la rama main sin archivos pendientes. El identificador que obtengas será propio de tu ejecución.
# Terminal: PowerShell. Repositorio A, dentro de bitacora-git.
& {
git status --short --branch
git --no-pager diff
python -m unittest discover -s tests -v
if ($LASTEXITCODE -ne 0) {
throw "Corrige las pruebas antes de continuar."
}
git add -- bitacora.py tests/test_bitacora.py .gitignore
if ($LASTEXITCODE -ne 0) {
throw "No se pudieron preparar los archivos."
}
git diff --cached --check
if ($LASTEXITCODE -ne 0) {
throw "Revisa los espacios y conflictos indicados."
}
git --no-pager diff --cached
[void](Read-Host "Revisa el contenido preparado. Pulsa Enter para crear el commit")
git commit -m "Crea el normalizador de titulos y sus pruebas"
if ($LASTEXITCODE -ne 0) {
throw "El commit no se completo."
}
git --no-pager show --stat --oneline HEAD
git status --short --branch
}
Un commit responde a una sola intención
El siguiente ejercicio empieza después del primer commit, con main limpio. Haremos dos ediciones pequeñas que se pueden explicar y revisar de forma independiente.
Prepara dos ediciones concretas
- En
bitacora.py, sustituye la docstring"""Quita espacios en blanco de los extremos."""por"""Quita espacios en blanco de los extremos y conserva el interior.""". Conserva la implementación de la función. - En
tests/test_bitacora.py, dentro detest_conserva_unicode, añade debajo de la aserción existenteself.assertEqual(normalizar_titulo(" Acción "), "Acción"), con la misma sangría de ocho espacios. - Guarda ambos archivos. La documentación será una decisión; ampliar la comprobación de Unicode será otra.
Seguiremos teniendo cuatro métodos de prueba, aunque uno incluirá una aserción adicional. El programa conserva su comportamiento.
Preparar fragmentos con git add -p
Un fragmento o hunk agrupa líneas de una diferencia. git add -p permite decidir qué fragmentos preparar, en lugar de incluir todas las ediciones de una vez.
| Respuesta | Acción |
|---|---|
y |
Preparar el fragmento que estás viendo. |
n |
Dejar ese fragmento sin preparar. |
q |
Terminar la selección; lo ya preparado se conserva. |
s |
Dividir el fragmento, cuando Git ofrezca esa posibilidad. |
? |
Consultar la ayuda de las respuestas disponibles. |
En la primera selección, responde y al cambio de bitacora.py y n al de tests/test_bitacora.py. Aquí habrá un fragmento por archivo; no necesitarás dividirlos. Confirma cada respuesta con Enter si la terminal lo requiere.
El diff preparado contiene únicamente la docstring. La nueva aserción sigue como una modificación sin preparar. Así puedes verificar qué decisión estás registrando.
Después del commit de documentación, el bloque abrirá una segunda selección solo para el archivo de pruebas. Responde y a su fragmento y revisa la diferencia antes de registrar la segunda decisión.
# Terminal: PowerShell. Repositorio A, rama main.
# Antes de ejecutar: guarda las dos ediciones indicadas en el texto anterior.
& {
python -m unittest discover -s tests -v
if ($LASTEXITCODE -ne 0) {
throw "Corrige las pruebas antes de continuar."
}
git --no-pager diff -- bitacora.py tests/test_bitacora.py
# Primera seleccion: y para bitacora.py; n para tests/test_bitacora.py.
git add -p -- bitacora.py tests/test_bitacora.py
if ($LASTEXITCODE -ne 0) {
throw "La seleccion no se completo."
}
$archivosPreparados = @(git diff --cached --name-only)
if ($archivosPreparados.Count -ne 1 -or
$archivosPreparados[0] -ne "bitacora.py") {
throw "El primer commit debe preparar solo bitacora.py. Revisa el staging."
}
git status --short
git --no-pager diff --cached
[void](Read-Host "Revisa la docstring preparada. Pulsa Enter para registrarla")
git commit -m "Aclara el alcance del recorte de titulos"
if ($LASTEXITCODE -ne 0) {
throw "El commit de documentacion no se completo."
}
# Segunda seleccion: y para la asercion de Unicode pendiente.
git add -p -- tests/test_bitacora.py
if ($LASTEXITCODE -ne 0) {
throw "La segunda seleccion no se completo."
}
$archivosPreparados = @(git diff --cached --name-only)
if ($archivosPreparados.Count -ne 1 -or
$archivosPreparados[0] -ne "tests/test_bitacora.py") {
throw "El segundo commit debe preparar solo el archivo de pruebas."
}
git --no-pager diff --cached
[void](Read-Host "Revisa la nueva asercion. Pulsa Enter para registrarla")
git commit -m "Comprueba otro titulo con caracteres Unicode"
if ($LASTEXITCODE -ne 0) {
throw "El commit de pruebas no se completo."
}
git --no-pager log -3 --oneline
git status --short --branch
}
Mensajes que permiten reconstruir el motivo
Ahora la historia contiene tres commits: el proyecto inicial, una aclaración de documentación y una ampliación de las pruebas. Cada mensaje permite anticipar la intención de su cambio.
| Mensaje poco informativo | Mensaje ligado a una decisión |
|---|---|
| Primeros archivos | Crea el normalizador de titulos y sus pruebas |
| Actualiza cosas | Aclara el alcance del recorte de titulos |
| Mas cambios | Comprueba otro titulo con caracteres Unicode |
El asunto presenta la decisión; el cuerpo conserva su motivo
Escribe un asunto breve y específico. Cuando el motivo necesite contexto, añade un cuerpo que explique el problema, la decisión tomada y sus límites. El mensaje debe corresponder al diff que realmente incluye el commit.
Asunto: Rechaza los títulos vacíos después de normalizarlos.
Motivo: Una entrada formada solo por espacios produce una cadena vacía. La nueva regla permitirá detectar ese caso mediante un error explícito y pruebas dedicadas.
Cuando exista un issue relacionado, incluye su referencia para conectar la explicación con la conversación del proyecto. Utiliza referencias reales y conserva en el mensaje la información necesaria para entender el cambio.
Una convención que puedes adoptar
Conventional Commits propone asuntos estructurados, como docs: aclara el recorte de títulos o test: amplía los casos Unicode. Es una convención opcional que puede facilitar la lectura y la automatización del historial. Los mensajes que ya creamos son válidos en Git.
Abrir la historia y contrastar el mensaje
En el siguiente bloque leerás el grafo, el diff de un commit anterior y los datos del commit actual. HEAD permite referirse a la revisión actual; HEAD~1 sigue un enlace al primer padre y HEAD~2 sigue dos. En nuestra historia lineal corresponden a las dos decisiones anteriores.
En la salida de git cat-file -p HEAD, tree enlaza el árbol del proyecto y parent enlaza el commit anterior. También verás autor, committer y mensaje. Un cambio en los datos de un objeto modifica el contenido sobre el que se calcula su identificador.
Localiza la aclaración de la docstring y comprueba que su commit modifica únicamente bitacora.py. Después confirma que el commit más reciente amplía únicamente las pruebas. Los identificadores serán los de tu repositorio.
# Terminal: PowerShell. Repositorio A, dentro de bitacora-git.
# Estas ordenes inspeccionan el historial sin modificarlo.
git --no-pager log --oneline --graph --decorate --all
# Identificador completo de la revision actual.
git rev-parse HEAD
# Datos del commit actual.
git --no-pager show --format=fuller --no-patch HEAD
git cat-file -p HEAD
# El commit anterior debe mostrar la aclaracion de la docstring.
git --no-pager show "HEAD~1" -- bitacora.py
# Diferencias acumuladas desde el primer commit hasta el actual.
git --no-pager diff "HEAD~2" HEAD -- bitacora.py tests/test_bitacora.py
git status --short --branch
Ramas: conservar una base y desarrollar otra decisión
Una rama es una referencia móvil a un commit. Crear una rama añade esa referencia; no duplica todos los archivos del proyecto.
Si dos ramas apuntan al mismo commit, comparten ese punto de partida. Al crear un commit en una de ellas, esa rama avanza. La otra conserva su referencia anterior.
HEAD indica desde dónde estás trabajando
Normalmente, HEAD es una referencia simbólica a la rama activa. En nuestro laboratorio señala main, que identifica el último de los tres commits creados.
También puedes abrir directamente un commit, sin activar una rama. Ese estado se llama detached HEAD. Permite inspeccionar revisiones e incluso crear commits; para conservar claramente una nueva línea de trabajo puedes crear una rama que los referencie. Practicaremos esa situación en la sección de recuperación.
Integrar decisiones con merge
Cuando la rama de destino es antecesora de la otra y la configuración lo permite, un merge puede resolverse mediante fast-forward: basta con avanzar la referencia. Cuando las historias han divergido, una integración con merge puede crear un commit con dos padres que relaciona ambos recorridos.
En esta guía elegiremos una integración que conserve un merge commit para las mejoras revisadas. Así podremos reconocer los commits de la mejora y el punto en que se incorporaron a la rama principal.
Qué cambia cuando se utiliza rebase
Rebase permite reaplicar cambios sobre otra base. Al volver a crear commits con padres diferentes, genera nuevas identidades para esas decisiones. La historia resultante suele presentarse como una secuencia lineal.
| Operación | Qué observarás en la historia |
|---|---|
| Merge con commit de integración | Conserva los commits existentes y añade un punto con dos padres para relacionar las líneas integradas. |
| Rebase sobre una base distinta | Reaplica los cambios como nuevos commits, con otros enlaces y otros identificadores. |
Usaremos ramas para desarrollar mejoras y merge para integrarlas. Si un equipo decide reescribir una historia ya compartida, deberá coordinar cómo actualizarán sus referencias las personas que trabajan con ella.
Si has completado la práctica, tienes tres commits en main y una carpeta de trabajo limpia. A continuación veremos cómo representar una historia que se bifurca y se integra; después crearemos mejora/validar-titulos para implementar la siguiente regla.
# Repositorio A, dentro de bitacora-git.
# Punto de partida: main limpio.
# Despues, guarda los dos bloques Python siguientes
# en bitacora.py y tests/test_bitacora.py.
& {
if (@(git status --porcelain).Count -ne 0) {
throw "Conserva o registra los cambios pendientes antes de crear la rama."
}
git switch main
if ($LASTEXITCODE -ne 0) {
throw "No se pudo activar main."
}
git switch -c mejora/validar-titulos
if ($LASTEXITCODE -ne 0) {
throw "No se pudo crear la rama de mejora."
}
git branch --show-current
git status --short --branch
}
# Archivo: bitacora.py
def normalizar_titulo(titulo: str) -> str:
"""Recorta los extremos y rechaza titulos vacios."""
normalizado = titulo.strip()
if not normalizado:
raise ValueError("El titulo no puede estar vacio.")
return normalizado
# Archivo: tests/test_bitacora.py
# Los dos casos nuevos expresan el requisito incorporado.
import unittest
from bitacora import normalizar_titulo
class NormalizarTituloTests(unittest.TestCase):
def test_titulo_sin_espacios(self):
self.assertEqual(normalizar_titulo("Hola"), "Hola")
def test_recorta_espacios_laterales(self):
self.assertEqual(normalizar_titulo(" Hola "), "Hola")
def test_conserva_espacios_interiores(self):
self.assertEqual(
normalizar_titulo(" Hola mundo "),
"Hola mundo",
)
def test_conserva_unicode(self):
self.assertEqual(normalizar_titulo(" Café "), "Café")
self.assertEqual(normalizar_titulo(" Acción "), "Acción")
def test_rechaza_cadena_vacia(self):
with self.assertRaises(ValueError):
normalizar_titulo("")
def test_rechaza_solo_espacios(self):
with self.assertRaises(ValueError):
normalizar_titulo(" ")
if __name__ == "__main__":
unittest.main()
# Repositorio A.
# Guarda primero los archivos Python de los dos bloques anteriores.
# Se esperan seis pruebas.
& {
if ((git branch --show-current).Trim() -ne "mejora/validar-titulos") {
throw "Activa la rama mejora/validar-titulos antes de registrar la mejora."
}
python -m unittest discover -s tests -v
if ($LASTEXITCODE -ne 0) {
throw "Corrige las pruebas antes de registrar el cambio."
}
git --no-pager diff -- bitacora.py tests/test_bitacora.py
git add -- bitacora.py tests/test_bitacora.py
if ($LASTEXITCODE -ne 0) {
throw "No se pudieron preparar los dos archivos."
}
git --no-pager diff --cached
[void](Read-Host "Revisa implementacion y pruebas. Pulsa Enter para registrar la mejora")
git commit -m "Rechaza titulos vacios tras normalizarlos"
if ($LASTEXITCODE -ne 0) {
throw "No se pudo crear el commit de mejora."
}
git --no-pager diff main...HEAD -- bitacora.py tests/test_bitacora.py
git status --short --branch
}
Conectar GitHub sin confundir identidad y acceso
La mejora ya tiene código, seis pruebas y un commit propio. main conserva el estado anterior. Ahora compartiremos ambas referencias para poder comparar la propuesta con su base.
Crear un remoto vacío
- En GitHub, crea un repositorio llamado
bitacora-gitdentro de tu cuenta. - Elige su visibilidad según el contenido que publicarás. Un laboratorio público permite consultar sus pruebas y revisiones; la disponibilidad de protecciones depende del plan y la visibilidad.
- Para este recorrido, deja desmarcada la creación inicial de README, licencia y archivo de exclusiones. Ya tenemos una historia local que enviaremos.
- Copia la URL HTTPS del repositorio. Después del primer envío, confirma que
mainsea su rama predeterminada.
Tres preguntas distintas
| Elemento | Qué resuelve |
|---|---|
user.name y user.email |
Los metadatos de autor de tus commits locales. |
| Autenticación HTTPS | Con qué cuenta y permisos accedes al remoto. |
| Firma de un commit | Qué clave firma ese objeto y cómo se verifica. |
Usaremos HTTPS con Git Credential Manager. Cuando se solicite acceso, completa el inicio de sesión de la cuenta propietaria del laboratorio. Conserva las credenciales fuera de los archivos y de la URL del remoto.
SSH es otra ruta de autenticación válida, pero mantendremos una sola ruta principal. Más adelante utilizaremos una clave SSH dedicada para firmar; ese uso es independiente del acceso HTTPS que estamos preparando.
Debes tener un remoto vacío y su URL HTTPS. El siguiente bloque pedirá esa URL y enviará explícitamente main y mejora/validar-titulos.
# Repositorio A.
# Utiliza la URL HTTPS de TU repositorio vacio.
& {
if (@(git remote) -contains "origin") {
throw "origin ya existe. Comprueba su URL antes de continuar."
}
$urlRepositorio = (Read-Host "URL HTTPS de tu repositorio de GitHub").Trim()
$uriRepositorio = [uri]$urlRepositorio
if ($uriRepositorio.Scheme -ne "https" -or
$uriRepositorio.Host -ne "github.com" -or
$uriRepositorio.UserInfo -ne "" -or
$uriRepositorio.AbsolutePath.Trim("/").Split("/").Count -ne 2) {
throw "Utiliza una URL HTTPS de GitHub sin credenciales dentro de ella."
}
git remote add origin $urlRepositorio
if ($LASTEXITCODE -ne 0) {
throw "No se pudo registrar origin."
}
git remote -v
[void](Read-Host "Confirma que origin es tu repositorio. Pulsa Enter para enviar las ramas")
git push -u origin main
if ($LASTEXITCODE -ne 0) {
throw "No se pudo enviar main. Revisa el acceso al remoto."
}
git push -u origin mejora/validar-titulos
if ($LASTEXITCODE -ne 0) {
throw "No se pudo enviar la mejora."
}
git branch -vv
}
# Comienza en el repositorio A.
# Al finalizar quedaras dentro del clon B.
& {
$rutaA = (git rev-parse --show-toplevel).Trim()
if ($LASTEXITCODE -ne 0) {
throw "Abre primero el repositorio A."
}
$urlOrigen = (git remote get-url origin).Trim()
if ($LASTEXITCODE -ne 0) {
throw "No se pudo consultar origin."
}
$nombreAutor = (git config --local --get user.name).Trim()
$correoAutor = (git config --local --get user.email).Trim()
$rutaB = Join-Path (Split-Path -Parent $rutaA) "bitacora-git-colaborador"
if (Test-Path -LiteralPath $rutaB) {
throw "La carpeta del segundo clon ya existe. Inspeccionala antes de continuar."
}
git clone --branch main $urlOrigen $rutaB
if ($LASTEXITCODE -ne 0) {
throw "No se pudo crear el segundo clon."
}
git -C $rutaB config --local user.name $nombreAutor
git -C $rutaB config --local user.email $correoAutor
Set-Location -LiteralPath $rutaB -ErrorAction Stop
git remote -v
git branch -vv
git status --short --branch
}
Traer información no es integrar cambios
Los clones A y B contienen copias del mismo repositorio remoto. Ambos usan tu identidad en este laboratorio: nos permiten observar estados independientes sin simular una revisión de otra persona.
Preparar un avance remoto independiente
Abre .gitignore en la carpeta bitacora-git-colaborador, el clon B. Añade al final una línea con .DS_Store y guarda el archivo. Conserva las reglas existentes.
Este cambio no toca la función. Lo enviaremos a main desde B y después observaremos cómo lo descubre A. Esta demostración sucede antes de configurar las protecciones de la rama.
| Operación | Efecto |
|---|---|
git fetch origin |
Trae objetos y actualiza referencias remotas de seguimiento, como origin/main, según la configuración del remoto. |
git pull --ff-only |
Trae información e intenta avanzar la rama actual sin crear un merge. Se detiene si la historia local ha divergido. |
origin/main es una referencia local al estado remoto observado. Entre dos consultas, el servidor podría recibir nuevos commits que tu clon todavía no conoce.
Después de fetch, A podrá comparar main con origin/main antes de integrar el avance. El siguiente bloque termina de nuevo en A, con su rama de mejora activa.
# Comienza en B: bitacora-git-colaborador.
# Guarda antes la linea .DS_Store al final de su .gitignore.
& {
$rutaB = (git rev-parse --show-toplevel).Trim()
if ((Split-Path -Leaf $rutaB) -ne "bitacora-git-colaborador") {
throw "Este bloque comienza en el clon B."
}
$rutaA = Join-Path (Split-Path -Parent $rutaB) "bitacora-git"
git switch main
if ($LASTEXITCODE -ne 0) {
throw "No se pudo activar main en B."
}
git --no-pager diff -- .gitignore
git add -- .gitignore
git commit -m "Excluye metadatos locales de macOS"
if ($LASTEXITCODE -ne 0) {
throw "No se pudo registrar el cambio independiente."
}
git push origin main
if ($LASTEXITCODE -ne 0) {
throw "No se pudo enviar el avance desde B."
}
Set-Location -LiteralPath $rutaA -ErrorAction Stop
git switch main
if ($LASTEXITCODE -ne 0) {
throw "No se pudo activar main en A."
}
git fetch origin
if ($LASTEXITCODE -ne 0) {
throw "No se pudo actualizar la informacion del remoto."
}
git --no-pager log --oneline main..origin/main
git --no-pager diff main origin/main -- .gitignore
git pull --ff-only
if ($LASTEXITCODE -ne 0) {
throw "No hubo fast-forward. Inspecciona la divergencia."
}
git switch mejora/validar-titulos
if ($LASTEXITCODE -ne 0) {
throw "No se pudo volver a la rama de mejora."
}
git status --short --branch
}
Un pull request cuenta la decisión completa
Ahora main tiene un avance independiente y la rama de mejora conserva su propuesta. Abriremos una conversación que conecte el problema con el código y sus pruebas.
- En Issues, crea un issue titulado «Rechazar títulos vacíos tras normalizarlos». Explica que una cadena de espacios produce un resultado vacío.
- En Pull requests, crea un PR con base: main y compare: mejora/validar-titulos.
- Revisa Files changed para confirmar que la propuesta modifica la función y sus pruebas.
- Añade la referencia real del issue en la descripción y publica el PR para revisarlo.
Problema: los títulos vacíos, o formados solo por espacios, producen una cadena vacía después del recorte.
Cambio: normalizar_titulo conserva el recorte y lanza ValueError cuando el resultado queda vacío.
Pruebas: seis métodos de unittest cubren comportamiento previo y los dos casos de rechazo.
Alcance: la función recibe texto y conserva los espacios interiores.
Si el issue tiene, por ejemplo, el número 12, puedes añadir Closes #12. Sustituye ese número por el real. El cierre automático mediante estas palabras se interpreta cuando el PR apunta a la rama predeterminada; confirma que sea main.
La descripción debe permitir que otra persona evalúe el cambio sin reconstruir toda la conversación. Las pruebas indicadas serán las que realmente hayas ejecutado en tu revisión.
Revisar el cambio y responder observaciones
Revisa la implementación, los casos límite y el alcance antes de integrar. Una observación útil describe un comportamiento comprobable.
«La documentación promete conservar el interior del título. ¿Podemos comprobar también un título con dos espacios interiores?»
En test_conserva_espacios_interiores, añade una segunda aserción, con la misma sangría: self.assertEqual(normalizar_titulo(" Hola mundo "), "Hola mundo").
- En A, mantén activa
mejora/validar-titulos, guarda la prueba y ejecutapython -m unittest discover -s tests -v. Siguen siendo seis métodos de prueba. - Revisa
git diff -- tests/test_bitacora.pyy prepara ese archivo congit add -- tests/test_bitacora.py. - Registra la decisión con
git commit -m "Comprueba dos espacios interiores"y envíala congit push. - Responde en el PR indicando el comportamiento añadido, el commit correspondiente y el resultado de las pruebas.
En modo individual, esta actividad es una autoevaluación. Una aprobación independiente requiere otra persona con los permisos correspondientes; el autor no puede aprobar su propio PR.
Si recibes una observación real, conserva su contexto y resuelve la conversación cuando el cambio esté comprobado. Cada nuevo commit debe revisarse dentro de la propuesta final.
Preparar las comprobaciones automáticas
Crea las carpetas .github/workflows en A y guarda el siguiente bloque como .github/workflows/ci.yml. Usará Python 3.12 y el mismo comando local. Las acciones están fijadas a revisiones completas verificadas para esta guía; al actualizarlas, revisa sus cambios y sustituye el identificador de forma deliberada.
# Archivo: .github/workflows/ci.yml
name: Pruebas de bitacora
on:
push:
pull_request:
permissions:
contents: read
jobs:
unittest-python:
name: unittest-python
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Obtener la revision
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
persist-credentials: false
- name: Preparar Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
with:
python-version: "3.12"
- name: Identificar el entorno probado
run: |
git rev-parse HEAD
python --version
- name: Ejecutar las pruebas
run: python -m unittest discover -s tests -v
Comentarios y valoraciones
No hay comentarios aún. ¡Sé el primero en opinar!