From 49f0b89fba02e582ad866245ff1d6d1af43e1afe Mon Sep 17 00:00:00 2001 From: Ekaitz Zarraga Date: Fri, 3 Nov 2023 14:02:17 +0100 Subject: es: continue with es translation, chapter 2 --- es/2.md | 561 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 561 insertions(+) diff --git a/es/2.md b/es/2.md index e69de29..4fc1c71 100644 --- a/es/2.md +++ b/es/2.md @@ -0,0 +1,561 @@ +--- +title: GIT +subtitle: Uso avanzado +license: CC-BY-NC-SA 4.0 +author: Ekaitz Zarraga - ElenQ Technology +links-as-notes: true +lang: spanish +polyglossia-lang: + name: spanish +how-to: pandoc -f markdown+smart -t beamer % -o pdf/2.pdf --pdf-engine=xelatex --template=./template.tex +... + +## Licencia + +- CC-BY-SA 4.0 +- El contenido del documento ha sido adaptado de [**Pro Git (Scott Chacon, Ben + Straub)**](https://git-scm.com/book/en/v2). + +# Git en el servidor + +## Git en el servidor: introducción + +Para compartir el trabajo con el resto del equipo de desarrollo suele +utilizarse un servidor: siempre está disponible, facilita la gestión, etc. + +En los servidores suelen utilizarse lo que se conoce como *bare repository*. +Éstos no tienen *working directory*: sólo tienen directorio `.git`. + +- `git init --bare` construye un *bare repository* + +> Los *bare repository* suelen nombrarse `loquesea.git` porque sólo contienen +> la propia carpeta `.git`, pero siguen siendo directorios, a pesar de su +> extensión. + +## Protocolos + +- **Local**: un repositorio que está en otro lugar del sistema de archivos de + la máquina: `git clone /path/to/file` +- **HTTP**: tiene modos *smart* o *dumb*, el primero siendo capaz de gestionar + permisos. +- **SSH**: es uno de los modos más utilizados. Necesita unas credenciales:\ + `git clone ssh://[@]/.git`\ + También se puede usar el modo corto de SCP: `[@]:.git` +- **Git**: Similar a SSH pero sin autenticación + +## Protocolos — Un ejemplo + +Mi [servidor](https://git.elenq.tech/): + +- Está accesible desde la Web mediante `cgit`. Hay otras interfaces web como + `gitweb`, que viene con el propio Git. +- Los proyectos públicos se pueden clonar sin problema con el protocolo Git. +- Para escribir, aporto acceso mediante SSH para tener control de permisos. + +*En el libro se cuenta cómo montarlos* + +## Workflows con Git + +Git es un sistema distribuido, lo que permite muchos workflows distintos. Sin +embargo, hoy en día se usa sobre todo el workflow *centralizado*, debido al +éxito de las plataformas web que integran muchas partes del proceso de +desarrollo de software (incidencias, releases, etc) junto al control de +versiones: GitHub, GitLab, Gitea... + +![Los clientes se sincronizan con el servidor](img/centralized_workflow.png){ height=140px } + +## Mediante email + +Muchos proyectos de software libre se basan en el email: + +- `git format-patch ` crea parches a partir de los commits + seleccionados +- `git send-email ` envía los parches por email (requiere de + configuración adicional) +- `git am ` aplica los parches en el proyectos mediante commits +- `git apply ` aplica los cambios de los parches sin commitear + + +# Herramientas avanzadas de Git + +## Selectores de commits + +Se pueden aplicar de forma generalizada en muchísimos comandos de Git. + +- `git show ` para mostrar los objetos seleccionados. + +> Ver la ayuda de `git show` + +> Ver la ayuda de `git rev-parse` para leer la sintaxis de los selectores + +## Selectores de commits — I + +- **Hash SHA-1** el propio identificador de un commit es un selector válido. + Como son muy largos pueden abreviarse (ver `git log --abbrev-commit`) + +- **Cabezas de Ramas** (también llamadas *head*), son el nombre de la rama y se + resuelven al commit al que la rama apunta. Ejemplo `git show `. Para + ver cuál es el commit subyacente `git rev-parse `. *Cuidado, a veces + funcionan como cabeza y otras como la rama completa* + +- **El Reflog** es un registro histórico de los cambios aplicados en Git + (similar al undo/redo de cualquier programa). Las entradas del Reflog pueden + usarse como selectores. Es interesante porque pueden usar el tiempo: + `HEAD@{yesterday}` + + - `git reflog` para listar las entradas del *reflog*. + + +## Selectores de commits — II + +- **Ancestros** de los commits: + + - El acento circunflejo (`^`) indica el padre del selector utilizado. Por + ejemplo: `git show HEAD^`. Los *merge commits* tienen muchos padres, y se + puede elegir cuál de ellos con un número después del símbolo `^`. + + - La vírgula (`~`) sirve para obtener los ancestros del selector. Es similar + al anterior, pero al añadir un número se elige la generación, no cuál de + sus padres. `git show HEAD~2` elige el padre del padre mientras que + `git show HEAD^2` elige el segundo padre. + +> Usados por separado, son equivalentes + +## Selectores de commits — III + +- **Rango de commits** indicados mediante `..` seleccionan una lista de commits + entre los dos selectores indicados en los extremos. + ![](img/double-dot.png){width=350px}\ + - `git log master..experiment` => D C\ + - `git log experiment..master` => F E + +## Selectores de commits — IV + +- Los **puntos triples** actúan como un rango pero seleccionan los commits a + los que se puede llegar desde las dos cabezas indicadas pero que las dos + ramas no tienen en común. + ![](img/double-dot.png){width=350px}\ + - `git log master...experiment` => F E D C\ + - Para ver de dónde vienen, `--left-right`: + ``` + git log master...experiment --left-right + < F + < E + > D + > C + ``` + +## Selectores de commits — V + +- **Puntos múltiples** pueden seleccionarse mediante `--not` o `^` (por + delante) para retirar commits de un selector: + - Los siguientes comandos son equivalentes: + ``` + git log refA..refB + git log ^refA refB + git log refB --not refA + ``` + - Los siguientes comandos obtienen los commits de `refA` y `refB` pero + descartan los de `refC`: + ``` + git log refA refB ^refC + git log refA refB --not refC + ``` + +## Staging interactivo + +- `git add --interactive|-i` para gestionar el *stash* de forma interactiva +- `git add --patch|-p` para seleccionar los cambios manualmente + +> Se pueden aplicar en muchos comandos diferentes. Son muy útiles. + + +## El stash + +El *stash* es un saco donde guardar cambios. Los cambios no guardados en el +repositorio pueden guardarse ahí para poder realizar cambios en el repositorio +y luego recuperarlos, para que no se pierdan en el proceso. + +- `git stash [push]` introduce los cambios en el stash y limpia el directorio + de trabajo +- `git stash list` muestra las entradas del stash +- `git stash apply []` aplica los cambios de la entrada del + stash seleccionada (por defecto la última) en el *working directory*. +- `git stash drop []` borra la entrada del stash + seleccionada +- `git stash pop` = `git stash apply` + `git stash drop` (sólo aplica el `drop` + si el `apply` ha ido bien) + +Se usan mensajes *push* y *pop* porque las entradas del stash funcionan como +una pila (*stack*) + +## El stash — opciones interesantes + +- `git stash apply --index` introduce los cambios en la *staging area* (también + se llama *index*). Por defecto no lo hace. +- `git stash --keep-index` los archivos indexados (en el *staging area*) no se + mandan al stash, se mantienen en el *index* +- `git stash --include-untracked|-u` los archivos no-trackeados también se + mandan al *stash*. +- `git stash --all|-a` los archivos ignorados (`.gitignore`) también se envían + al *stash* +- `git stash --patch|-p` para seleccionar los cambios a enviar al *stash* de + forma interactiva +- `git stash branch []` crea una rama nueva y aplica + el *stash* en ella. Si todo va bien borra la entrada del *stash* más tarde. + +## Para limpiar el directorio + +- `git stash --all` limpia el directorio enviando todo al *stash*. Luego se + puede eliminar haciendo `drop` + +- `git clean` hace esto mismo, sin pasar por el *stash*. + - `-f` (*force*) es necesario porque si no sólo aplica un *dry-run* (una + muestra de lo que pasaría sin llegar a aplicarlo) + +> CUIDADO: aplicar siempre el `--dry-run|-n` en `git clean` primero por si +> acaso. + +## Firmas + +Para evitar commits falsos, se pueden firmar por GPG. + +> GPG (Gnu Privacy Guard), es una implementación de PGP (Pretty Good Privacy) + +Hay que configurar `user.signkey` + +- `git commit -S` para firmar el commit +- `git tag -s` para firmar etiquetas +- `git merge --verify-signatures` comprueba las firmas en el merge +- `git tag -v` comprueba las firmas en una etiqueta + +Si se utilizan firmas, todo el equipo de desarrollo debe utilizarlas. + +## Búsquedas + +- `git grep` para hacer búsquedas mediante expresiones regulares. Similar al + comando `grep`, pero es más rápido y busca en el index y el histórico +- `git log` es muy potente para buscar en el histórico + - `-S` funciones *pickaxe* + - `-L` para ver la evolución de una línea de código o una función + +## Reescribir la historia + +- `git commit --amend` es como un mini-rebase +- `git rebase -i|--interactive ` cambia los commits necesarios para + llegar al selector.\ + Abre un editor de texto con los commits seleccionados y permite manipularlos + con diversos comandos (los explica en el propio editor) que sirven para + fusionar commits, cambiar los mensajes, editar los contenidos... En algunos + casos se pausa para editar el contexto, permite continuar mediante + `git rebase --continue` o cancelar con `--abort`. El propio proceso es + bastante explicativo. + +- `git filter-branch` es mucho más poderoso pero menos utilizado. Permite + cambiar todos los commits de una rama + +## Reset y checkout en profundidad — I + +Para entenderlos en detalle es necesario comprender los estados de los archivos +en profundidad. Hay tres secciones disponibles: + +1. HEAD: el snapshot del último commit +2. Index: el *staging area*, la propuesta del próximo commit +3. Working directory: el lugar donde realizamos los cambios + +![Egoren arteko trantsizioak](img/reset-workflow.png){height=150px} + +## Reset y checkout en profundidad — II + +- `git reset` tiene tres modos: + - `--soft`: Mover la rama apuntada por HEAD + - `--mixed`: `--soft` + pasar los cambios al index + - `--hard`: `--mixed` + aplica los cambios también en tu *working directory* + +## Reset y checkout en profundidad — III + +Empezando con un repositorio vacío: + +![ ](img/reset-ex1.png) + +## Reset y checkout en profundidad — IV + +Pasar un archivo al *index*: + +![ ](img/reset-ex2.png) + +## Reset y checkout en profundidad — V + +Escribirlo en el repositorio: + +![ ](img/reset-ex3.png) + +## Reset y checkout en profundidad — VI + +Cambiar un archivo: + +![ ](img/reset-ex4.png) + +## Reset y checkout en profundidad — VII + +Mandar los cambios al *index* + +![ ](img/reset-ex5.png) + +## Reset y checkout en profundidad — VIII + +Añadir un commit nuevo: + +![ ](img/reset-ex6.png) + +## Reset y checkout en profundidad — IX + +Añadir otro commit + +![ ](img/reset-start.png) + +## Reset y checkout en profundidad — X + +`--soft`: + +![ ](img/reset-soft.png) + +## Reset y checkout en profundidad — XI + +`--mixed` (es el que se aplica por defecto): + +![ ](img/reset-mixed.png) + +## Reset y checkout en profundidad — XII + +`--hard`: + +![ ](img/reset-hard.png) + +## Reset y checkout en profundidad — XIII + +Además de las tres opciones, se le pueden entregar archivos a `git reset`. + +En este caso, el primer paso (mover el HEAD) no puede aplicarse del todo[^head] +pero el resto pueden aplicarse sin problemas. Esto habilita funcionamientos +interesantes: + +- `git reset ` realmente hace\ + `git reset --mixed HEAD `\ + 1. ~~Mueve el HEAD~~ + 2. Coloca el estado del archivo en el index +\ + Esto es: **quita el archivo del staging area** + +[^head]: El HEAD no se puede mover a medias, o se mueve el repositorio completo + o no se mueve. + +## Reset y checkout en profundidad — XIV + +A nivel gráfico: + +![ ](img/reset-path1.png) + +## Reset y checkout en profundidad — XV + +En un ejemplo más complejo: `git reset -- ` + +1. El HEAD no se puede mover +2. Se mueve el estado que `` tenía en `` al index (`--mixed`). + +![ ](img/reset-path3.png){height=220px} + +## Reset y checkout en profundidad — XVI + +Puede usarse este concepto para hacer un *squash* (combinar muchos commits en +uno) fácilmente [^rebase-interactive]. + +- `git reset --soft HEAD~` lleva HEAD hacia atrás, manteniendo los cambios + en el *index*. Después, se puede hacer `git commit` para aplicar los cambios + que quedaron en el *index*, todos de una vez. En **un solo commit**. + +[^rebase-interactive]: También se puede hacer con `git rebase --interactive|-i` + +## Reset y checkout en profundidad — XVII + +Gráficamente: + +![ ](img/reset-squash-r1.png) + +## Reset y checkout en profundidad — XVIII + +Mover el HEAD manteniendo los cambios en el index: + +![ ](img/reset-squash-r2.png) + +## Reset y checkout en profundidad — XIX + +Añadir el commit con los cambios que quedaron en el index + +![ ](img/reset-squash-r3.png) + +## Reset y checkout en profundidad — XX + +`checkout` y `reset` son similares pero no son iguales: + +- `git checkout` no manipula el HEAD, sino que lo reasigna a la referencia que + se le indique, sin transformar la rama subyacente. + +- `git checkout ` y `git reset --hard ` son casi iguales, pero + el checkout no pisa el directorio de trabajo directamente. + +- `git checkout ` **pisa el estado del archivo** en el *working + directory*, parecido a `git reset --hard`. CUIDADO + +A ambos se les puede enviar `--patch` para realizarlos por partes. + +## Reset y checkout en profundidad — XXI + +Efecto en el HEAD: + +![ ](img/reset-checkout.png) + + +## Merges avanzados, gestión de conflictos — I + +Antes de hacer un *merge* es interesante limpiar el *working directory* (`git +stash`), de este modo, si algo falla es fácil volver atrás. + +- `git merge --abort` deshace el merge en caso de conflicto. + + > CUIDADO: Si hay cambios en el directorio de trabajo, no sabrá abortar. + +## Merges avanzados, gestión de conflictos — II + +En los conflictos, Git aporta 3 archivos: + +1. Stage 1: el *common ancestor* (*base*), el commit al que se puede acceder + subiendo por ambas ramas. Para verlo puede usarse `git merge-base` +2. Stage 2: Nuestra (*ours*) versión, lo que está en nuestra rama +3. Stage 3: Su (*theirs*) versión, lo que está en la rama que estamos + mergeando. + +- `git show ::` para ver los archivos + - `::` obtiene el hash del blob señalado + +- `git diff` muestra los cambios: + - `git diff -1|--base` + - `git diff -2|--ours` + - `git diff -3|--theirs` + +## Merges avanzados, gestión de conflictos — III + +- `git show ::` se puede utilizar para obtener el contenido de + los diferentes archivos y aplicarlos directamente. + +- `git merge-file` puede utilizarse para aplicar el merge a mano. Aplica + algoritmos de merging. + +## Merges avanzados, gestión de conflictos — IV + +Git tiene herramientas mejores: + +- `git checkout --conflict` te devuelve al estado del conflicto, con los + marcadores, para deshacer los cambios que se hayan podido realizar por error + al tratar de corregir el conflicto + +- `git checkout --ours/--theirs` para ver uno o otro estado + +- `git log --oneline --left-right --merge` facilita mostrar el contexto + +- `git diff` muestra un *combined diff* cuando se aplica en un conflicto + +- `git show -p|--patch` junto con `--cc` muestra el *combined diff* + +## Merges avanzados, gestión de conflictos — V + +Este es el aspecto de un *combined diff*: + +``` +diff --cc hello.rb +index 0399cd5,59727f0..0000000 +--- a/hello.rb ++++ b/hello.rb +@@@ -1,7 -1,7 +1,7 @@@ + #! /usr/bin/env ruby + + def hello +- puts 'hola world' + - puts 'hello mundo' +++ puts 'hola mundo' + end +``` + +Muestra dos columnas con símbolos `+` y `-`, mostrando qué ha ocurrido en cada +lado del merge. + +## Merges avanzados, gestión de conflictos — VI + +Puede ser interesante usar `diff3` para los conflictos. Por defecto se usa +`merge`. `diff3` muestra una parte adicional en el conflicto, que muestra la +*base*. + +``` +<<<<<<< ours + puts 'hola world' +||||||| base + puts 'hello world' +======= + puts 'hello mundo' +>>>>>>> theirs +``` + +- `git checkout --conflict=diff3` para mostrar el conflicto en 3 partes + +- `git config --global merge.conflictstyle diff3` recomiendo configurarlo para + que funcione así siempre + + +## Deshaciendo merges — I + +Dos modos: + +1. `git rest --hard HEAD~` (sobreescribe el histórico) +2. `git revert -m 1 HEAD` añade un commit que deshace los cambios. Esto da + problemas a la hora de volver a combinar la rama. + + +## Deshaciendo merges — II + +`git revert -m 1 HEAD` añade un commit que deshace los cambios. `^M` y `C6` +tienen los mismos contenidos, pero los cambios en `topic` pueden accederse +desde `master`, para Git, ambas están mergeadas. CUIDADO + +![](img/undomerge-revert.png) + +## Deshaciendo merges — III + +Lo que es peor, si se siguen añadiendo cambios en `topic` y se intenta hacer un +merge nuevo, Git no añadirá los cambios previos, pensando que ya fueron +combinados (y lo fueron) pero sin saber que se deshicieron. CUIDADO + +![](img/undomerge-revert2.png) + +## Deshaciendo merges — IV + +Para evitar el problema, hay que deshacer de nuevo el commit que se deshizo. +Esto puede hacerse con un nuevo `revert`. + +![](img/undomerge-revert3.png) + +Ahora todos los cambios de `topic` se verán en `master`. + +## Preferencias en los merges + +`git merge` tiene muchas opciones y estrategias: + +- `-X` añade opciones. Por ejemplo `-Xours` resuelve los cambios a nuestro + favor. + +- `-s` selecciona estrategias. Por ejemplo `-s ours` en lugar de hacer un merge + sólo se queda con nuestros cambios. Es interesante para engañar a Git. + +Las estrategias no son lo mismo que las opciones. Las estrategias *seleccionan* +qué algoritmo de merge utilizar. Las opciones *configuran* el algoritmo. + +## Subtree -- cgit v1.2.3