summaryrefslogtreecommitdiff
path: root/es/1.md
blob: 3939d27db19adf91acb1da98fea3dab81c721fab (plain)
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
---
title: GIT
subtitle: Introducción y uso básico
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/1.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).

# Introducción: sistemas de control de versiones

## Sistemas de control de versiones

`Document_v3_FINAL.pdf`

---

![Centralizados](img/zentralizatuak.png){ height=180px }

---

![Descentralizados](img/banatuak.png){ height=300px }

# Git: Introducción

## Contexto

- 2005
- Para el desarrollo de Linux
    - Miles de desarrolladores
    - Código fuente muy extenso
- Es distribuído (*distributed*)
- Se basa en *snapshots*
- La mayoría de las operaciones ocurren en local y no son destructivas
- Control de integridad mediante SHA-1 (o SHA-256)
- Tres estados:\
  `ARCHIVO — STAGING AREA — REPOSITORIO`

## Instalación

Debian:

```
apt-get install git
```

En el resto de distros es similar.

## Sistema de configuración

- Sistema: `/etc/gitconfig`
- Configuración general: `~/.config/git/config` o `~/.gitconfig`
- Local por repositorio: `$REPOSITORIO/.git/config`

Funciona en modo cascada, de local a sistema. Para ver de dónde se toman los
valores:

```
git config --list --show-origin
```

## Gestión de configuración

Los comandos leen y escriben los archivos de configuración de forma ordenada,
aunque se puede hacer a mano:

```
git config [--global] <SECTION>.<KEY> [<VALUE>]
```

- El `<VALUE>` es opcional. Si se usa, se escribe la configuración con ese
  valor. Si no se usa, se devuelve el valor en esa línea de la configuración.

Realmente es más complejo que esto: ver documentación.

## Configuración inicial

Definir la identidad para que se registre el autor de los commits:

```
git config --global user.name "John Doe"
git config --global user.email johndoe@example.com
```

Seleccionar editor de texto por defecto:

```
git config --global core.editor vim
```

## Ayuda

- `git help`
- `man`




# Git basico

## Obtener un repositorio

Dos alternativas:

- Crear un repositorio nuevo: `git init`. Esta opción crea un directorio `.git`
  en el directorio actual, donde se almacenará la información interna de Git.

- Clonar un repositorio existente: `git clone <URL>`. Esta opción copia el
  repositorio en el directorio actual, incluyendo su `.git`. Puede usar varios
  protocolos diferentes


## Estados de un archivo

![Ciclo de vida de los archivos](img/lifecycle.png){ height=180px }


## Visualizar estado de un repositorio

- `git status`
- Para ignorar archivos, añadirlos a un archivo `.gitignore`.
  Utiliza *glob pattern*s.

## Para cambiar de estado

- Añadir archivos con: `git add`
- Para enviar los cambios al *staging area*: `git add`

## Para ver cambios

- `git diff`
- `git diff --cached|--staged`
- `git difftool` (si está configurada)

La salida de `git diff` puede guardarse a archivo y después aplicarse sobre el
repositorio mediante `git apply`.


## Para escribir los cambios

- `git commit`

Pide rellenar un mensaje mediante el `$EDITOR` o el `git config --global
core.editor`


## Borrar archivos

- `git rm`
- `git rm --cached`

## Renombrar

- `git mv`

Equivalente a `git rm` + `git add`.


## Para ver la historia

- `git log`

Es un comando muy complejo.

- `-<N>` Muestra los últimos N commits.
- `-p/--patch` muestra el *patch*.
- `--stat` muestra estadísticas.
- `--pretty` cambia el formato de salida, tiene muchas opciones.
- `--graph` modo gráfico.

Se pueden combinar:

`git log --graph --pretty=oneline --decorate --all`


## Filtrar la historia

La salida de `git log` puede filtrarse:

- `--since` desde cuando, por ejemplo: `--since=2weeks`
- `--author` filtrar por autor
- `--grep` búsqueda por palabras clave
- `-S/-G/...` *pickaxe function*s, buscan palabras en los cambios aplicados en
  el commit.
- `git log -- archivo` mostrar commits que afectaron a ese archivo.
- `--no-merges` descartar commits de tipo *merge*

Ver la ayuda: `git help log`


## Deshacer cambios

- `git commit --amend` reescribe el último commit
    - Cambiar el mensaje
    - Añadir o quitar archivos
    - ...

- `git reset` quita cambios de la *staging area*. CUIDADO con `--hard`.
- `git checkout` para deshacer los cambios. CUIDADO
- `git restore` comando nuevo (>2.23.0), parecido a los anteriores.

Los comandos `reset` y `checkout` son parte integral de Git y realizan más
acciones que las que se acaban de mencionar. Se estudian más adelante en
detalle.

## Remotos (*remote*)

Son copias de un repositorio. Se pueden actualizar enviándoles cambios locales
mediante *push* o se pueden descargar cambios desde ellos para actualizar el
repositorio local mediante *fetch* y *pull*.

Pueden ubicarse en otras máquinas y accederse mediante la red, en otras partes
del mismo disco duro, etc.

## Gestión de remotos

- `git remote`

Están escritos en `.git/config`. También pueden editarse a mano. No se
recomienda hacerlo.


- `git clone` añade el remoto llamado `origin` automáticamente, apuntando al
  repositorio del que se clonó.
- `git remote -v`
- `git remote add <nombre> <URL>`
- `git remote show <nombre>`
- `git remote rename <nombre> <nuevo nombre>`
- `git remote remove <nombre>`

## Intercambiar información con el remoto


- `git fetch [<remote>]` descarga la información del remoto sin alterar el
  repositorio local.

- `git pull` aplica un `fetch` seguido de un `merge` si las ramas están
  configuradas correctamente:\
  ```
  git pull = git fetch + git merge
  ```

- `git push [<remote> <branch>]` sube los cambios al remoto de forma segura. En
  caso de conflicto los rechaza.


## Etiquetas (*tag*)

Nombres que se les pueden asignar a los commits. Sirven para identificar los
commits con nombres fáciles de recordar, son útiles para señalar *releases*.

- `git tag -l` muestra las etiquetas

Hay dos tipos de etiquetas:

- **Lightweight**: son referencias, como una rama fija. Sólo son un
  identificador.
- **Annotated**: son un objeto más en Git, como un commit, y pueden tener
  autor, firma, mensaje, etc.

## Annotated tags

- `git tag -a <nombre> [<commit>]`

Se muestran en `git show`.

## Lightweight tags

- `git tag <nombre> [<commit>]`

`git show` no las muestra, sino que muestra el commit al que hacen referencia.

## Compartir tags

- `git push [<remote>] <tag>`
- `git push [<remote>] --tags` para enviar todos

## Eliminar tags

- `git tag -d <tag>`
- `git push [<remote>] --delete <tag>`


## Aliases

Permiten escribir nuevos comandos usando los que ya están disponibles en Git
como base. Son valores de la configuración:

- `git config --global alias.co checkout`
- `git co` => `git checkout`

Yo uso mucho:

```
git config --global alias.lg log --graph \
  --decorate --all --oneline
```



# Git básico: Ramas

## Rama (*branch*)

Las ramas permiten al desarrollo del repositorio tomar diferentes caminos sin
alterar el desarrollo principal.

En Git las ramas son fundamentales, ya que está diseñado alrededor de éstas.

En otros sistemas de control de versiones el uso de ramas es pesado, pero en
Git es muy ligero y no es raro ver repositorios con cientos de ramas distintas.

## Git por dentro

- Cuando los archivos se mandan al *staging area* Git guarda sus contenidos en
  un *blob* y calcula su *checksum* (SHA-1)
- Cuando se aplica un commit, calcula los checksums de todos los directorios y
  construye un objeto *tree* por cada uno de ellos. Los objetos *tree* apuntan
  a los objetos *blob* indicando su nombre, y formando la estructura de
  archivos. El *tree* principal se añade al commit, junto con el autor, fecha,
  mensaje y otros datos. Se almacena el objeto *commit* resultante.
- Los *commit* hacen referencia a sus *commit* padre. Los commits "normales"
  tienen un único padre, los commits de tipo *merge* tienen varios padres y el
  commit inicial no tiene padre.

Las ramas son referencias móviles que apuntan a los *commits*.

## Git por dentro

![Datos internos](img/commit-and-tree.png)

## Git por dentro

![Estructura de commits](img/commits-and-parents.png)

## Crear ramas

- `git branch <branchname>`

Por defecto hay una rama llamada `master` (puede configurarse), y siempre debe
haber al menos una rama, donde se irán añadiendo los commits nuevos.

Git guarda una referencia que apunta a la rama actual: `HEAD`

## Cambiar de rama

- `git checkout <branchname>`
- `git checkout -b <branchname>` crear rama y saltar a ella

Como `checkout` es un comando complejo se ha añadido el comando `switch` que
cumple esta tarea de forma más clara:

- `git switch <branchname>`
- `git switch -c <branchname>` crear rama y saltar a ella
- `git switch -` saltar a la rama anterior


## Ramas y cambios — I

![`git branch testing`](img/head-to-master.png)

## Ramas y cambios — II

![`git checkout testing`](img/head-to-testing.png)

## Ramas y cambios — III

![`git commit ...`](img/advance-testing.png)

## Ramas y cambios — IV

![`git checkout master`](img/checkout-master.png)

## Ramas y cambios — V

![`git commit ...`](img/advance-master.png)

## Ramas y cambios — VI

Visto desde la interfaz de Git:

```
$ git log --oneline --decorate --graph --all
* c2b9e (HEAD, master) Made other changes
| * 87ab2 (testing) Made a change
|/
* f30ab Add feature #32
* 34ac2 Fix bug #1328
* 98ca9 initial commit
```

> CUIDADO: Si no se añade `--all` no se muestran todas las ramas al hacer `git
> log`, sino que sólo se muestra la rama actual.


## Ramas y merges — I

![](img/basic-branching-4.png){ height=180px }

## Ramas y merges — II

Caso **fast forward**, una rama contiene la otra

![`git checkout master`\
`git merge hotfix`](img/basic-branching-5.png){ height=200px }

## Ramas y merges — III

Siempre no es así de fácil:

![](img/basic-branching-6.png){ height=180px }

## Ramas y merges — IV

Se busca el ancestro común (*common ancestor*) y se aplica un *three-way
merge*.

![`git checkout master`\
`git merge iss53`](img/basic-merging-1.png){ height=180px }

## Ramas y merges — V

Crea un commit nuevo que combina ambas ramas.

Es un *Merge*: tiene al menos dos padres.

![`git checkout master`\
`git merge iss53`](img/basic-merging-2.png){ height=180px }


## Conflictos — I

El caso de antes no siempre sale bien. Es posible que haya un conflicto si se
han editado los mismos archivos en las dos ramas.

```
$ git merge iss53
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then 
commit the result.
```

## Conflictos — II

```
$ git status
On branch master
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

    both modified:      index.html

no changes added to commit (use "git add" and/or
"git commit -a")
```

## Conflictos — III

El archivo se ve así:

```
<<<<<<< HEAD:index.html
<div id="footer">contact : 
    email.support@github.com</div>
=======
<div id="footer">
 please contact us at support@github.com
</div>
>>>>>>> iss53:index.html
```

Aparecen unos separadores: `<<<<<<<`, `=======` y `>>>>>>>`.

En el primer trozo aparece el contenido del `HEAD` y en el segundo `iss53`.

Si está configurada una herramienta, puede usarse `git mergetool`


## Gestión de ramas

- `git branch` muestra las ramas
- `git branch --merged | --no-merged` muestra las ramas mergeadas o las no
  mergeadas con la actual.
- `git branch -d <adarra>` borra ramas. Las no-mergeadas no las borra, hay que
  hacer `-D` para eso (diferencia mayúsculas y minúsculas).
- `git branch --move <adarra> <rama_nueva>` cambiar nombre de rama
- `git push -u|--set-upstream <remote> <adarra>` asigna una rama a un remoto
- `git push <remote> --delete <adarra>` borra una rama en un remoto

## Workflows habituales

- *Long-running branches*:  `master`, `testing` y `development`
- *Topic branches*: por cada issue o feature se trabaja en una rama
  independiente.

## Ramas en los remotos

Git guarda referencias a los remotos para poder ver su estado (`git ls-remote
<remote>` o `git remote show` para verlo).

Las ramas en los remotos pueden verse con el nombre `<remoto>/<rama>`. No son
ramas normales, sino que se muestran como tal. La diferencia es que estas ramas
no se pueden alterar de forma directa. La forma de manipularlas es hacer
cambios en el remoto, para que éstos se reflejen en la rama.

![Ejemplo: el repositorio local tiene dos commits nuevos](img/remote-branches.png)

## Hacer push

- `git push <remote> <branch>` envía la rama al remoto. Hay que hacerlo
  manualmente para no subir más que la rama que se quería. Facilitando el uso
  de ramas locales.

- `<rama-local>:<rama-remota>` para usar diferente nombre en local que en el
  remoto.

## Tracking branches

Es una forma de relacionar una rama remota (*upstream*) y una local
(*tracking*):

- `git checkout -b <rama> <remote>/<rama>` relaciona `<remote>/<rama>` con
  `<rama>`
- `git checkout --track <remote>/<rama>` relaciona la rama actual con la
  remota.
- `git checkout <rama>` si la rama local `<rama>` no existe, se crea
  relacionada con `<remoto>/<rama>` automáticamente.
- `git clone` crea una rama local relacionada con la remota automáticamente.
- `git branch -u|--set-upstream-to <remote>/<adarra>` también relaciona las
  ramas. Ver `git push`

> Una vez relacionadas, para referirse a la rama remota puede usarse
> `@{upstream}` o `@{u}`

## Hacer pull

Si hay una rama de tracking configurada, hacer `git pull` es equivalente a
hacer `git fetch` seguido de un `git merge`.

Cuidado: a veces es difícil ver qué ocurre con el `merge`. Para evitar
problemas, hacer `fetch`, ver el estado del repositorio y hacer el `merge`
manualmente.


## Ramas y rebases — I

![](img/basic-rebase-1.png)

## Ramas y rebases — II

![Al hacer `git merge` pasa esto](img/basic-rebase-2.png)

## Ramas y rebases — II

![`git checkout experiment`\
`git rebase master`](img/basic-rebase-3.png)

Ahora el merge es *fast-forward* y no añade commit de merge.


## Un rebase más complejo — I

![](img/interesting-rebase-1.png)

## Un rebase más complejo — II

![`git rebase --onto master server client`](img/interesting-rebase-2.png)

Pasa los cambios de `client` a `master` sin incluir los de `server`.

Ahora se puede hacer un rebase de `server` en `master` y después hacer un
`merge` *fast-forward*.

## Cuidado con los rebase

Al hacer rebase, se crean commits nuevos con contenidos similares a los que
había anteriormente, pero con diferente valor. Al enviarlos a un remoto
(`push`) se fuerza al resto de nuestro equipo a hacer rebases:

- `git push --force` reescribe la historia en el remoto. Es peligroso.
- `git pull --rebase` puede ayudar a la hora de hacer pull de un repositorio en
  el que hayan ocurrido rebases. Se puede configurar Git para que siempre se
  comporte así en el pull.

## Rebase vs Merge

En función de la filosofía con la que se trabaje es interesante usar uno u
otro.

- Si se entiende el repositorio como un histórico de cambios el *rebase* no
  tiene sentido, porque puede manipular el pasado. (*El sistema de control de
  versiones fossil usa esta filosofía, y no tiene forma de hacer rebases*)
- Si se entiende el repositorio como un *making-of*, tiene sentido aplicar
  rebases, porque limpian la historia haciéndola más fácil de leer.

Consejo: En local todo vale. En el servidor compartido cambiar la historia es
peligroso porque afecta al equipo de desarrollo, usuarios, etc.