1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
|
---
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://[<user>@]<host>/<project>.git`\
También se puede usar el modo corto de SCP: `[<user>@]<host>:<project>.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 <selector-de-commits>` crea parches a partir de los commits
seleccionados
- `git send-email <parches>` envía los parches por email (requiere de
configuración adicional)
- `git am <parches>` aplica los parches en el proyectos mediante commits
- `git apply <parches>` 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 <selector de commits>` 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 <rama>`. Para
ver cuál es el commit subyacente `git rev-parse <rama>`. *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 [<entrada del stash>]` aplica los cambios de la entrada del
stash seleccionada (por defecto la última) en el *working directory*.
- `git stash drop [<entrada del stash>]` 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 <rama> [<entrada del stash>]` 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 <selector>` 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 <archivo>` realmente hace\
`git reset --mixed HEAD <archivo>`\
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 <commit> -- <file>`
1. El HEAD no se puede mover
2. Se mueve el estado que `<file>` tenía en `<commit>` 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~<N>` 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 <branch>` y `git reset --hard <branch>` son casi iguales, pero
el checkout no pisa el directorio de trabajo directamente.
- `git checkout <archivo>` **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 :<stageN>:<file>` para ver los archivos
- `:<stageN>:<file>` 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 :<stageN>:<file>` 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
|