{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Encontrar en qué momento se rompió\n",
    "\n",
    "Bisect, que busca por la mitad y resuelve 180 commits en ocho pruebas\n",
    "\n",
    "Cuaderno de práctica del capítulo 26 de **Git desde cero**, de Miss Yera.\n",
    "\n",
    "Corre de arriba abajo. Si lo abres en Google Colab no necesitas instalar nada.\n",
    "\n",
    "Capítulo completo: https://missyera.com/guias/git-desde-cero/encontrar-el-culpable/\n",
    "\n",
    "Los ejercicios están al final y traen una celda vacía debajo de cada uno. Las\n",
    "respuestas viven en el cuaderno de soluciones, y merece la pena pelearse un\n",
    "rato antes de abrirlo 💛"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Antes de empezar\n",
    "\n",
    "Este capítulo son comandos de terminal, no Python. La celda de abajo baja el\n",
    "ayudante que los ejecuta y que **recuerda en qué carpeta quedaste**, que es lo\n",
    "que hace falta para que un `cd` de una celda siga valiendo en la siguiente.\n",
    "\n",
    "A partir de ahí, cada celda de comandos empieza por `%%consola`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import urllib.request\n",
    "\n",
    "# El ayudante de los cuadernos. Trae la corrección de los ejercicios y, en los\n",
    "# capítulos de consola, la celda mágica que ejecuta los comandos. Se baja en\n",
    "# vez de venir pegado aquí para que siempre sea el último.\n",
    "urllib.request.urlretrieve(\n",
    "    \"https://missyera.com/static/cuadernos/revisa.py\", \"revisa.py\")\n",
    "import revisa\n",
    "revisa.carga({\n",
    "    1: \"ZThiN2QxOCBFbnRyYSBlbCBzZWd1bmRvIG1pbmltYXJrZXQKODVhMmEzZiBFbnRyYSBsYSBib2RlZ2EgZGVsIGNlbnRybwo0M2I0N2VhIEVudHJhIGVsIG1heW9yaXN0YSBkZWwgbm9ydGUKMmI1YWJmMyBFbnRyYSBsYSBib2RlZ2EgZGUgQXJlcXVpcGEKNGNiNTE5YSBFbnRyYSBlbCBtaW5pbWFya2V0IGRlIFRydWppbGxvCmE5ODc0YWUgQ2F0YWxvZ28gaW5pY2lhbCBkZSBib2RlZ2Fz\",\n",
    "    2: \"cmVzdWx0YWRvOiAxLCBlc3RhIHJvdG8=\",\n",
    "    3: \"c3RhdHVzOiB3YWl0aW5nIGZvciBib3RoIGdvb2QgYW5kIGJhZCBjb21taXRzCnN0YXR1czogd2FpdGluZyBmb3IgZ29vZCBjb21taXQocyksIGJhZCBjb21taXQga25vd24KQmlzZWN0aW5nOiAyIHJldmlzaW9ucyBsZWZ0IHRvIHRlc3QgYWZ0ZXIgdGhpcyAocm91Z2hseSAxIHN0ZXApClsyYjVhYmYzZTc3YzY3Mzk5NTJmZTYwNjU0NTc4YzE0ZTQxNTQ1NmQ0XSBFbnRyYSBsYSBib2RlZ2EgZGUgQXJlcXVpcGE=\",\n",
    "    4: \"cmVzdWx0YWRvOiAwLCBlc3RhIGJpZW4KQmlzZWN0aW5nOiAwIHJldmlzaW9ucyBsZWZ0IHRvIHRlc3QgYWZ0ZXIgdGhpcyAocm91Z2hseSAxIHN0ZXApCls4NWEyYTNmNDA4YWZiY2VlY2Y5MmY5OWJkMTU0Mjg3Y2I2NWU5YTU1XSBFbnRyYSBsYSBib2RlZ2EgZGVsIGNlbnRybw==\",\n",
    "    5: \"cmVzdWx0YWRvOiAxLCBlc3RhIHJvdG8KQmlzZWN0aW5nOiAwIHJldmlzaW9ucyBsZWZ0IHRvIHRlc3QgYWZ0ZXIgdGhpcyAocm91Z2hseSAwIHN0ZXBzKQpbNDNiNDdlYTJkZDViMjUyMjIzOWI5ZGI5MDUxMzRiNDJhMmNjMGMxY10gRW50cmEgZWwgbWF5b3Jpc3RhIGRlbCBub3J0ZQ==\",\n",
    "    6: \"UHJldmlvdXMgSEVBRCBwb3NpdGlvbiB3YXMgNDNiNDdlYSBFbnRyYSBlbCBtYXlvcmlzdGEgZGVsIG5vcnRlClN3aXRjaGVkIHRvIGJyYW5jaCAnbWFpbicKQmlzZWN0aW5nOiAyIHJldmlzaW9ucyBsZWZ0IHRvIHRlc3QgYWZ0ZXIgdGhpcyAocm91Z2hseSAxIHN0ZXApClsyYjVhYmYzZTc3YzY3Mzk5NTJmZTYwNjU0NTc4YzE0ZTQxNTQ1NmQ0XSBFbnRyYSBsYSBib2RlZ2EgZGUgQXJlcXVpcGEKcnVubmluZyAnYmFzaCcgJ3JldmlzYS5zaCcKQmlzZWN0aW5nOiAwIHJldmlzaW9ucyBsZWZ0IHRvIHRlc3QgYWZ0ZXIgdGhpcyAocm91Z2hseSAxIHN0ZXApCls4NWEyYTNmNDA4YWZiY2VlY2Y5MmY5OWJkMTU0Mjg3Y2I2NWU5YTU1XSBFbnRyYSBsYSBib2RlZ2EgZGVsIGNlbnRybwpydW5uaW5nICdiYXNoJyAncmV2aXNhLnNoJwpCaXNlY3Rpbmc6IDAgcmV2aXNpb25zIGxlZnQgdG8gdGVzdCBhZnRlciB0aGlzIChyb3VnaGx5IDAgc3RlcHMpCls0M2I0N2VhMmRkNWIyNTIyMjM5YjlkYjkwNTEzNGI0MmEyY2MwYzFjXSBFbnRyYSBlbCBtYXlvcmlzdGEgZGVsIG5vcnRlCnJ1bm5pbmcgJ2Jhc2gnICdyZXZpc2Euc2gnCjQzYjQ3ZWEyZGQ1YjI1MjIyMzliOWRiOTA1MTM0YjQyYTJjYzBjMWMgaXMgdGhlIGZpcnN0IGJhZCBjb21taXQKY29tbWl0IDQzYjQ3ZWEyZGQ1YjI1MjIyMzliOWRiOTA1MTM0YjQyYTJjYzBjMWMKQXV0aG9yOiBNaXNzIFllcmEgPGhvbGFAbWlzc3llcmEuY29tPgpEYXRlOiAgIFRodSBKYW4gMTUgMDk6MDA6MDAgMjAyNiAtMDUwMAoKICAgIEVudHJhIGVsIG1heW9yaXN0YSBkZWwgbm9ydGUKCiBib2RlZ2FzLmNzdiB8IDEgKwogMSBmaWxlIGNoYW5nZWQsIDEgaW5zZXJ0aW9uKCspCmJpc2VjdCBmb3VuZCBmaXJzdCBiYWQgY29tbWl0ClByZXZpb3VzIEhFQUQgcG9zaXRpb24gd2FzIDQzYjQ3ZWEgRW50cmEgZWwgbWF5b3Jpc3RhIGRlbCBub3J0ZQpTd2l0Y2hlZCB0byBicmFuY2ggJ21haW4nCmU4YjdkMTggRW50cmEgZWwgc2VndW5kbyBtaW5pbWFya2V0\",\n",
    "    7: \"WW91IG5lZWQgdG8gc3RhcnQgYnkgImdpdCBiaXNlY3Qgc3RhcnQi\",\n",
    "}, lenguaje=\"bash\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\"El reporte de ventas está dando mal el total y no sé desde cuándo.\" Esa\n",
    "frase, en un proyecto con meses de historia, suena a tarde perdida 😩\n",
    "\n",
    "Y se resuelve en cinco minutos, porque Git trae una herramienta que casi\n",
    "nadie usa y que es de las más ingeniosas que tiene."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## La idea, con el diccionario"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Para buscar \"mercadería\" en un diccionario no empiezas por la A. Abres por\n",
    "el medio, ves si estás antes o después, y repites. Cada apertura descarta la\n",
    "mitad de lo que queda.\n",
    "\n",
    "Eso es `bisect`. Con 180 commits, la primera prueba descarta 90,\n",
    "la segunda 45, y en ocho pruebas ya no queda ninguno. Con mil commits serían\n",
    "diez 🎯"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Armar el escenario"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Un proyecto donde el total de ventas se rompe en algún punto:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%consola\n",
    "mkdir reporte-ventas\n",
    "cd reporte-ventas\n",
    "git init -q\n",
    "printf 'ciudad,monto\\nLima,1200\\n' > ventas.csv\n",
    "git add ventas.csv\n",
    "git commit -q -m \"Primeras ventas de Lima\"\n",
    "printf 'Arequipa,890\\n' >> ventas.csv\n",
    "git add ventas.csv\n",
    "git commit -q -m \"Entran las ventas de Arequipa\"\n",
    "printf 'Cusco,760\\n' >> ventas.csv\n",
    "git add ventas.csv\n",
    "git commit -q -m \"Entran las ventas de Cusco\"\n",
    "printf 'Trujillo,mil cuatrocientos\\n' >> ventas.csv\n",
    "git add ventas.csv\n",
    "git commit -q -m \"Entran las ventas de Trujillo\"\n",
    "printf 'Piura,980\\n' >> ventas.csv\n",
    "git add ventas.csv\n",
    "git commit -q -m \"Entran las ventas de Piura\"\n",
    "printf 'Chiclayo,1120\\n' >> ventas.csv\n",
    "git add ventas.csv\n",
    "git commit -q -m \"Entran las ventas de Chiclayo\"\n",
    "git log --oneline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí dentro hay un monto escrito con letras, que es de los errores más\n",
    "comunes cuando el CSV se llena a mano. La prueba de \"está bien\" va a ser que\n",
    "todos los montos sean números:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%consola\n",
    "cat > revisa.sh <<'FIN'\n",
    "tail -n +2 ventas.csv | cut -d, -f2 | grep -qv '^[0-9]*$' && exit 1\n",
    "exit 0\n",
    "FIN\n",
    "chmod +x revisa.sh\n",
    "if bash revisa.sh; then echo \"resultado: 0, esta bien\"; else echo \"resultado: 1, esta roto\"; fi"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Devuelve 1, o sea que hoy está roto. Y en el primer commit estaba bien."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Bisect a mano"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%consola\n",
    "git bisect start\n",
    "git bisect bad\n",
    "git bisect good HEAD~5"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Léelo: le dijiste \"aquí está mal\" y \"cinco atrás estaba bien\". Git te dejó\n",
    "parada en el commit del medio y te avisa cuántos pasos faltan 🧭"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%consola\n",
    "if bash revisa.sh; then echo \"resultado: 0, esta bien\"; else echo \"resultado: 1, esta roto\"; fi\n",
    "git bisect good"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%consola\n",
    "if bash revisa.sh; then echo \"resultado: 0, esta bien\"; else echo \"resultado: 1, esta roto\"; fi\n",
    "git bisect bad"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ahí está: **el primer commit malo**, con su hash, su mensaje y\n",
    "el cambio exacto que lo causó. Tres pruebas 🎉"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Salir de bisect"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Esto no se puede olvidar, porque mientras bisect está activo estás parada en\n",
    "un commit del pasado:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%consola\n",
    "git bisect reset\n",
    "git log --oneline -1\n",
    "git status --short --branch"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "De vuelta donde estabas. `reset` aquí no borra nada, solo cierra\n",
    "la búsqueda 🔚"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Y la versión que se hace sola"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Si la prueba se puede escribir como un comando que devuelve 0 cuando está\n",
    "bien, no hace falta que estés delante:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%consola\n",
    "git bisect start HEAD HEAD~5\n",
    "git bisect run bash revisa.sh"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%consola\n",
    "git bisect reset\n",
    "git log --oneline -1"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Eso es lo que hace que bisect valga la pena de verdad: con una prueba\n",
    "automática, encontrar el culpable entre mil commits es escribir dos líneas e ir\n",
    "por un café ☕"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### La trampa\n",
    "\n",
    "El reporte de ventas empezó a dar mal el total y nadie sabe desde cuándo. Hay 180 commits desde la última vez que estuvo bien.\n",
    "\n",
    "```\n",
    "$ git log --oneline | wc -l\n",
    "180\n",
    "\n",
    "# el plan: revisar uno por uno hacia atras\n",
    "$ git switch --detach HEAD~1 && python3 reporte.py\n",
    "$ git switch --detach HEAD~2 && python3 reporte.py\n",
    "$ git switch --detach HEAD~3 && python3 reporte.py\n",
    "```\n",
    "\n",
    "**¿Qué está mal?** La respuesta está en el cuaderno de soluciones. Míralo tú primero."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Comprueba que se entendió"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comprueba que lo tienes\n",
    "\n",
    "Hay 1.000 commits entre \"funcionaba\" y \"está roto\". ¿Cuántas pruebas necesita bisect?\n",
    "\n",
    "a) Unas 10\n",
    "\n",
    "b) 1.000, una por commit\n",
    "\n",
    "c) 500, la mitad\n",
    "\n",
    "d) Depende de dónde esté el commit malo"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Ejercicios"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 1. Arma un proyecto con un error escondido\n",
    "\n",
    "Seis commits del catálogo de bodegas, y en uno se cuela\n",
    "una ciudad vacía."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 1\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 2. Escribe la prueba\n",
    "\n",
    "Un script que falla si alguna fila tiene la ciudad\n",
    "vacía."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 2\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3. Arranca la búsqueda\n",
    "\n",
    "Marca dónde está mal y dónde estaba bien."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 3\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 4. Prueba y responde\n",
    "\n",
    "Ejecuta la prueba en el commit donde estás y marca el\n",
    "resultado."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 4\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 5. Termina la búsqueda\n",
    "\n",
    "Una prueba más y ya está."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 5\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 6. Hazlo automático\n",
    "\n",
    "Sal de la búsqueda y vuelve a hacerla sin estar delante."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 6\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 7. Marca sin haber arrancado\n",
    "\n",
    "Prueba a decir \"bad\" cuando no hay búsqueda abierta."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%revisa 7\n",
    "# tu turno"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Lo que te llevas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Bisect parte por la mitad: mil commits son diez pruebas. Y con un\n",
    "script, ninguna.**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "\n",
    "Ese era el capítulo 26 de **Git desde cero**. El texto completo, con las salidas de cada bloque, está en https://missyera.com/guias/git-desde-cero/encontrar-el-culpable/\n",
    "\n",
    "Que tengas lindo día! 🌸"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
