--- 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 no guardados, así se pueden aplicar otros cambios en el repositorio sin mezclarlos. - `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