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
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
|
---
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
![Transiciones entre estados](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=<style> <file>` 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 <file>` 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 <file>` 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 reset --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
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=<style> <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`
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>`
|