diff options
author | Ekaitz Zarraga <ekaitz@elenq.tech> | 2023-11-04 00:14:56 +0100 |
---|---|---|
committer | Ekaitz Zarraga <ekaitz@elenq.tech> | 2023-11-04 00:14:56 +0100 |
commit | 5f7b91af68a3967e2072d3afc3a4d8c3ce58146c (patch) | |
tree | e204211c460a08071f0cc1d3a664cdb2b62bd551 | |
parent | a75bf7c6bd6e801d7b83501596026793f66d0b95 (diff) |
es: finish second part
-rw-r--r-- | es/2.md | 420 |
1 files changed, 420 insertions, 0 deletions
@@ -558,3 +558,423 @@ Las estrategias no son lo mismo que las opciones. Las estrategias *seleccionan* qué algoritmo de merge utilizar. Las opciones *configuran* el algoritmo. ## Subtree + +Los subtrees son un mecanismo para introducir otro proyecto dentro del actual. +Este sub-proyecto funciona como una rama independiente: se añade como un remoto +independiente en una rama suelta, que no tiene relación con las otras. + +- `git read-tree` para leer el subproyecto e inicializarlo en una rama + independiente +- `git merge -Xsubtree` para combinar (*merge*) la rama del subproyecto en el + proyecto actual sin problemas +- `git diff-tree` para analizar cambios en el subproyecto, un `diff` normal no + funcionaría + +*No se utiliza mucho* porque hoy en día se usan los *submodules* en su lugar. +Pero es interesante para ver lo flexible de los métodos de merge. + +## Rerere: Reuse Recorded Resolution — I + +El rerere es un mecanismo para guardar soluciones a conflictos, y enseñar a Git +a resolverlos cuando vuelvan a darse. Por ejemplo: + +1. Al mergear una rama de test aparecen conflictos +2. Se arreglan los conflictos +3. Los tests van mal +4. Se deshace el merge +5. Se arreglan los tests +6. Se vuelve a hacer el merge => los mismos conflictos reaparecen + +Con el **rerere** se podía haber guardado la resolución en el paso 1, de esta +manera al aplicar el paso 6 Git podía haber recordado la solución del paso 1 y +volver a aplicarla sin necesitar interacción humana. + +## Rerere: Reuse Recorded Resolution — II + +- `git config --global rerere.enabled true` +- `git rerere status` +- `git rerere diff` para ver los cambios que aplicaría rerere +- `git add` + `git commit` guarda la resolución del conflicto:\ + `"Recorded resolution"` +- `git reset --hard HEAD^` volver atrás el en el tiempo, y volver a hacer el + merge resuelve el conflicto automáticamente:\ + `"Resolved with previous resolution"` +- `git checkout --conflict=merge <file>` recupera el conflicto, desactivando el + rerere +- `git rerere` se puede aplicar después el rerere automáticamente + +## Debugging con Git + +- `git blame|annotate` muestra qué commit introdujo las líneas del archivo + indicado. Las líneas que empiezan con `^` fueron introducidas con la creación + del archivo. Usar `-C` intenta buscar los bloques que han sido movidos[^move]. + +[^move]: Git no guarda record de los movimientos pero aplica algoritmos de + búsqueda que pueden calcular este tipo de cambios. + +- `git bisect` aplica el [método de la bisección][bisect] para buscar commits + que introdujeron errores en el repositorio. Método: + - Tomar dos commits: uno con error y otro sin él + - Toma un commit en el centro de los dos: si tiene error, el commit que + introdujo el error en el repositorio estará entre éste commit y el que no + tenía error del paso anterior. Si no tiene error, será al revés. + - Con el nuevo rango, repite el algoritmo tomando nuevos commits cada vez, + hasta encontrar el commit que introdujo el fallo. + +[bisect]: https://en.wikipedia.org/wiki/Bisection_method + +## Debugging con Git: `git bisect`o + +Pasos a aplicar: + +1. `git bisect start` empezar +2. `git bisect bad` indicar commit actual como erróneo +3. `git bisect good <commit-id>` indicar último commit que se tiene constancia + de que era correcto +4. `git bisect good/bad` Git elegirá commits y saltará a ellos para que los + marquemos como `good` o `bad`:\ + `"Bisecting, N revisions left to test"` +5. `git bisect reset` volver al inicio + +Se puede automatizar con `git bisect run <command>`. El comando debe retornar +`0` en casos de acierto y cualquier otro valor cuando falle. + +## Submodule + +Son un modo interesante para gestionar subproyectos en el repositorio. + +- `git submodule add <URL>` crea un nuevo subproyecto e inicializa el archivo + `.gitmodules` +- `.gitmodules` es un archivo que guarda los la información de los + subproyectos: la URL, el nombre y el path. +- `git submodule add ...` seguido de `git diff --cached` muestra que el + contenido de un submodulo no se gestiona como un archivo cualquiera:\ + `"Subproject commit ---"` +- `git diff --submodule --cached` muestra mejor el contenido +- `git push` añade el submódulo pero como una referencia a otro repositorio, no + como archivo + +## Clonar submódulos + +- `git clone` **no** clona los submódulos automáticamente +- `git submodule init` inicializa el control de submódulos +- `git submodule update` para actualizar los archivos de los submódulos +- `git clone --recurse-submodule <URL>` descarga automáticamente todos los + submódulos (recursivamente) al hacer `clone`. Si no se hace, debe lanzarse + `git submodule update --init` o `git submodule update --init --recursive` más + tarde. + +## Trabajar con submodulos + +- `git config --global diff.submodule log` para no tener que añadir cada vez + `--submodule` que se hace `git diff` +- `git submodule update --remote <submodule>` para actualizar los contenidos + del submódulo. También se puede hacer `fetch` + `merge` dentro del submódulo. +- `git config -f .gitmodules submodule.<submodule>.branch <adarra>` para + cambiar el submódulo de rama. Equivalente a editar el archivo `.gitmodules` +- `git config status.submodulesummary 1` para que `git status` muestre el + estado de los submódulos. +- `git log --submodule` para mostrar el listado de cambios de los submódulos + +## Publicar cambios de los submódulos + +- `git push` dentro del submódulo publica sus cambios (son un repositorio + normal) +- `git push --recurse-submodules=check|on-demand` hay dos estrategias. `check` + comprueba si deben pushearse cambios, mientras que `on-demand` pushea + automáticamente los submódulos si hay cambios en ellos. + - `git config push.recurseSubmodules check|on-demand` para configurarlo por + defecto + +## Trucos con submódulos + +- `git submodule foreach <command>` lanza un comando en cada submódulo. + Ejemplo: `git submodule foreach 'git stash'` + + +## Bundle + +Son archivos que guardan conjuntos de commits o repositorios completos de modo +que son fáciles de compartir. + +- `git bundle create <file> [<commit selector>]` guarda los commits + seleccionados en el archivo `<file>`. Normalmente es interesante introducir + `HEAD` para que luego el bundle sepa dónde estaba el repositorio. + +- `git clone <bundle-file>` para clonar desde un bundle. Necesita que el bundle + contenga todos los commits del repositorio. + +- `git bundle verify <bundle-file>` para comprobar si un bundle parcial se + puede aplicar en el el repositorio actual + +- `git bundle list-heads <bundle-file>` para ver las ramas en el archivo + +- `git fetch/pull <bundle-file>` permite aplicar commits desde el archivo + bundle + +## Replace + +La base de datos de Git no puede alterarse, pero con `git replace` puede +hacerse que parezca que ha cambiado. + +- `git replace <object> <replacement>` hace que cuando se use `<object>` + funcione como si se hubiese usado `<replacement>` en su lugar. + +*No es común, investigar en el libro posibles casos de uso* + +## Sistemas de credenciales + +Al usar credenciales sobre HTTP Git necesita que todas las operaciones con el +servidor usen usuario y contraseña. Este comportamiento puede configurarse: + +- `git config --global credential.helper <modo>` + 1. Pedir siempre (lo hace por defecto)) + 2. `cache` mantener las credenciales en memoria 15 minutos + 3. `store` guardar las credenciales + +Hay más opciones (`--timeout`, sistemas de credenciales a medida, etc.) + + +# Configuración avanzada + +## Configuración + +Ver documentación sobre la configuración `git help git-config` + +- Editor y paginador: `core.editor` y `core.pager` +- Plantillas para commits: `commit.template` +- Herramientas de merge y diff: `merge.tool` y `diff.tool` +- Control de espacios en blanco: `core.autocrlf` y `core.whitespace` + +## Git attributes — I + +Dentro del proyecto, a nivel de archivo, se puede cambiar el comportamiento de +Git mediante los atributos (*attribute*). + +- `.gitattributes` o `.git/info/attributes`, el primero se puede compartir en + el repositorio, el segundo no. + +El archivo `.gitattributes` es similar a `.gitignore`, además de las plantillas +que seleccionan los archivos se definen unos atributos para ellos. Muchas veces +se requiere de configuración adicional para los atributos mediante *drivers*. +Ver `git help attributes` + +## Git attributes — II + +Hay muchos atributos diferentes, los más interesantes: + +- `binary` para tratar un archivo como binario +- `diff=<filtro>` pasa el archivo por el filtro indicado antes de hacer diff. + Necesita configuración adicional: + - `git config diff.<filtro>.textconv <programa>` + - Por ejemplo: `*.png diff=exif` + - `git config diff.exif.textconv exiftool`\ + para comparar los resultados de ejecutar `exiftool` sobre el archivo, en + lugar de sus contenidos +- `ident` añade identificación. Sustituye el texto en `$Id$` por el SHA-1 del + commit actual. + +## Git attributes — III + +- `filter` pasa los archivos por un filtro + - `smudge` aplica el filtro cuando el archivo pasa del *index* al *working + directory* + - `clean` en el camino opuesto + - Ejemplo: `*.c filter=ident` + - `git config filter.ident.clean indent`\ + `git config filter.ident.smudge cat`\ + indenta los archivos automáticamente al hacer `git add` (a la vuelta + no aplica nada) +- `export-ignore` ignora archivos al hacer un archivado del repositorio (`git + archive`) +- `export-subst` aplica las expansiones que `git log` aplicaría y sustituye + patrones en los archivos +- `merge` selecciona estrategias de merge por archivo + +## Git hooks + +Los hooks son programas que se ejecutan en momentos importantes del uso del +repositorio. Se usan muy a menudo. + +- `.git/hooks` es el directorio donde se guardan, deben usar un nombre en + particular y tener permisos de ejecución + +Por defecto, se instalan varios hooks al hacer `git init` pero están +desactivados con `.sample` al final de su nombre. Se instalan para servir de +muestra. + +## Hooks del lado del cliente + +Para uso privado. **No se copian junto al repositorio**[^compartir-hooks]. Son +interesantes como alertas para quien use el repositorio: que los tests no +pasan, que hay un problema en los formatos, etc. Algunos interesantes: + +- `pre-commit` se ejecuta antes de escribir el mensaje del commit. Se suele + utilizar para comprobar si se ha olvidado algo. +- `prepare-commit-msg` procesa el mensaje de commit por defecto antes de + mostrárselo al usuario. Sirve para añadir información adicional en el + mensaje, cambiar la plantilla, etc. +- `commit-msg` procesa el contenido el mensaje del commit. Si el mensaje del + commit no es correcto puede rechazarse. Se utiliza para asegurar que el + mensaje de commit usa la forma adecuada. + +[^compartir-hooks]: Hay herramientas que facilitan el intercambio de hooks, + pero no están incluidas en Git. + +## Hooks del lado del servidor + +Son interesantes para imponer políticas de desarrollo, ya que pueden rechazar +`push`es si estas políticas no se cumplen. Algunos interesantes: + +- `pre-receive` se lanza al recibir un `push`, recibe todas las referencias y + si da error no se actualizarán los datos del servidor +- `update` similar al anterior, pero se ejecuta una vez por cada rama +- `post-receive` se ejecuta después de haber recibido todo bien, puede usarse + como notificación + + +# Git por dentro + +## Git por dentro: introducción + +Git es una herramienta de bajo nivel y tiene comandos para trabajar a este +nivel conocidos como *plumbing*. Los que se han utilizado hasta ahora se +conocen como *porcelain*[^baño]. + +Git es una base de datos *content-addressed*. Sus elementos se guardan por +clave y valor (*key-value*) donde la clave se obtiene del valor. Hasta ahora se +ha visto: se usan los hashes SHA-1 de los contenidos del commit para +identificarlos. + +El contenido de la base de datos se almacena en el directorio `.git`. Donde se +almacenan elementos de diferentes tipos. + +[^baño]: En un baño, la porcelana está por fuera, las tuberías por dentro + +## Tipos de datos + +Hay dos tipos de dato principales en la base de datos de Git: + +- Objetos: que tienen contenidos: commits, blobs, árboles, etc. +- Referencias: son elementos que apuntan a otros: ramas, etiquetas ligeras, + etc. + +## Objetos + +Se almacenan en `.git/objects` usando sus hashes como identificador. Los +primeros dos caracteres como nombre de directorio.\ +Por ejemplo: `.git/objects/cf/6cbb8a400c7cad0f7f93610366c3672f598cdd` + +- `git hash-object -w` para escribir nuevos contenidos, devuelve el hash del + objeto escrito +- `git cat-file` para leerlos, `-p` para mostrarlo bonito. + +## Tipos de objetos + +`git cat-file -t` muestra el tipo + +- `blob` para guardar los contenidos de los archivos u otro tipo de datos +- `tree` para guardar directorios o los nombres de los archivos\ + Contienen entradas que apuntan a otros `tree`s o `blob`s identificándolos por + su hash. +- `commit` para guardar los datos de los commits. Contiene: un `tree` con el + snapshot, el mensaje, la autoría, etc. + +## Tipos de objetos: tree + +![](img/data-model-1.png) + +## Tipos de objetos: commit + +![](img/data-model-3.png) + +## Referencias + +Sólo contienen un identificador + +- `git update-ref` puede actualizar su contenido. También se puede realizar + manualmente + +Hay diferentes tipos de referencias: + +- Tag: Se guardan en `.git/refs/tag`. Cualquier objeto puede etiquetarse + (normalmente sólo se hace con los commits). Hay dos tipos: + - Lightweight tag: sólo son una referencia + - Annotated tag: son un **objeto** y una referencia a éste +- Ramas: en `.git/refs/heads`. Hacen referencia a un commit +- Remotos: en `.git/refs/remotes` similares a las ramas pero no se pueden + cambiar. +- HEAD: `.git/HEAD`. Apunta a una rama, pero puede apuntar a un commit + (*detached HEAD*). + +## Referencias: ramas + +![](img/data-model-4.png) + +## Ejercicio: notas + +- `git notes` gestiona las notas + +*Ver qué son, y cómo funcionan: `git help git-notes`* + +## Packfiles + +Para que el almacenamiento de archivos sea más eficiente Git utiliza sistemas +de compresión de archivos, pero no es suficiente. + +Git se basa en snapshots, pero no guarda los archivos una y otra vez en cada +commit, sino que guarda sólo aquellos que han cambiado, y por partes. + +Además, agrupa los archivos en *packfile*s para agilizar el proceso. + +- `.git/objects/pack` +- `git gc` (*garbage colector*) agrupa los archivos y los comprime (git lo + ejecuta automáticamente) +- `git verify-pack` comprueba el contenido de un packfile + +## Mantenimiento y recuperación de datos + +- `git gc --auto` reorganiza los packfiles +- `git reflog` almacena los cambios realizados en HEAD haciendo que los commits + perdidos puedan recuperarse desde éste. +- `git fsck --full` muestra los elementos perdidos, permitiendo también la + recuperación. + +*EJERCICIO: si se añade un archivo grande por accidente, `git rm` no lo +elimina, por lo que se convierte en un problema. ¿Cómo se borra? ¿Por qué?* + +## El refspec + +Para gestionar el mapeo entre las ramas remotas y locales. Éste es el contenido +de `.git/config` tras hacer +`git remote add origin https://github.com/schacon/simplegit-progit`: + +``` +[remote "origin"] + url = https://github.com/schacon/simplegit-progit + fetch = +refs/heads/*:refs/remotes/origin/* +``` + +El bloque `fetch` indica mediante el refspec cómo se deben gestionar las ramas. +Puede haber muchas entradas, con diferentes reglas. Formato: + +- Un `+` indica (opcional) si deben actualizarse incluso cuando no es + *fast-forward* +- `<fuente>:<destino>` indica cómo mapear las ramas. Ambos son patrones. Puede + usarse `*` para indicar «todo» + +## El refspec manualmente + +Pueden usarse patrones al hacer `fetch` o `push` para indicar este aquí y este +allá. + +Git aplica expansiones basándose en el refspec: + +- `git log origin/master`\ + => `git log remotes/origin/master`\ + => `git log refs/remotes/origin/master` + +Antes, para borrar las ramas había que usar el refspec: + +- `git push :<rama>` al añadir la fuente vacía, en el remoto se borra la rama +- Ahora puede usarse `git push --delete <rama>` |