Uso básico de Git

Crear un proyecto

Crearemos un proyecto sencillo con Opendylan para practicar Git:

$ dylan new application curso-de-git

La herramienta dylan nos ayudará a crear una estructura de directorios para el proyecto:

$ tree curso-de-git

La salida es parecida a lo siguiente (lo he simplificado):

curso-de-git/
├── curso-de-git-app.dylan
├── curso-de-git-app-library.dylan
├── curso-de-git-app.lid
├── curso-de-git.dylan
├── curso-de-git.lid
├── dylan-package.json
├── library.dylan
├── _packages
│   ├── command-line-parser
│   │   ├── 3.1.1
│   │   └── current -> /home/fraya/Dylan/curso-de-git/_packages/command-line-parser/3.1.1
│   ├── json
│   │   ├── 1.0.0
│   │   └── current -> /home/fraya/Dylan/curso-de-git/_packages/json/1.0.0
│   ├── strings
│   │   ├── 1.1.0
│   │   └── current -> /home/fraya/Dylan/curso-de-git/_packages/strings/1.1.0
│   └── testworks
│       ├── 3.2.0
│       └── current -> /home/fraya/Dylan/curso-de-git/_packages/testworks/3.2.0
├── registry
│   └── x86_64-linux
└── tests
        ├── curso-de-git-test-suite.dylan
        ├── curso-de-git-test-suite.lid
        └── library.dylan

Entraremos en el directorio `curso-de-git

$ cd curso-de-git

Crear el repositorio

Para crear un nuevo repositorio se usa la orden git init.

Inicialización del repositorio
$ git init
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint:        git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint:        git branch -m <name>
Initialized empty Git repository in /home/fraya/Dylan/curso-de-git/.git/

Esto creará un directorio .git para almacenar nuestro repositorio local.

Nota

Tradicionalmente a la rama inicial de un proyecto se la llamaba master, pero esta palabra en inglés tiene connotaciones esclavistas ya que también significa amo, en la relación amo/esclavo.

Las nuevas tendencias en USA pretenden cambiar las palabras sin cambiar sus actos, pensando que al desaparecer la palabra «amo» desaparecen los esclavos. Por ello git nos da una pista por si queremos cambiar la rama inicial y poner main de nombre.

Podemos ser politicamente correctos y cambiarlo para todos los nuevos repositorios, evitando este mensaje de advertencia

git config --global init.defaultBranch main

siempre que seamos conscientes de que sigue habiendo esclavitud.

Añadir la aplicación

Ahora almacenaríamos todos los archivos creados en el repositorio para trabajar, pero antes miremos lo que tenemos (líneas resaltadas):

Estado de nuestro repositorio
$ git status

On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        _packages/
        curso-de-git-app-library.dylan
        curso-de-git-app.dylan
        curso-de-git-app.lid
        curso-de-git.dylan
        curso-de-git.lid
        dylan-package.json
        library.dylan
        registry/
        tests/

nothing added to commit but untracked files present (use "git add" to track)

Estamos en la rama (branch) main y no tenemos ninguna confirmación (commits) aún. Tenemos una lista de ficheros sin seguimiento (untracked) y podemos añadirlos usando git add.

Advertencia

No añadas los ficheros todavía y sigue leyendo.

.gitignore

Sin embargo dentro de los ficheros untracked hay ficheros que no necesitamos en nuestro repositorio.

Directorios que no queremos incluir en el repositorio
$ git status

On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        _packages/
        curso-de-git-app-library.dylan
        curso-de-git-app.dylan
        curso-de-git-app.lid
        curso-de-git.dylan
        curso-de-git.lid
        dylan-package.json
        library.dylan
        registry/
        tests/

Los directorios _packages y registry son creados por la herramienta dylan. Estos datos pueden ser creados de nuevo usando el comando dylan update en este directorio cuando lo necesitemos. Por lo tanto no deben estar dentro de nuestro repositorio. Para evitar que estos ficheros entren dentro por error, crearemos un fichero llamado .gitignore con una lista de ficheros y directorios que no queremos que git incluya ni que aparezcan en los comandos.

Pondremos el siguiente contenido en .gitignore

.gitignore
# directorios de desarrollo

/_packages/
/registry/

# ficheros de copia de seguridad

*~
*.bak

Después de grabar el fichero `.gitignore volvemos a ver el estado del repositorio:

Estado del repositorio con .gitignore
$ git status
On branch main

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        .gitignore
        curso-de-git-app-library.dylan
        curso-de-git-app.dylan
        curso-de-git-app.lid
        curso-de-git.dylan
        curso-de-git.lid
        dylan-package.json
        library.dylan
        tests/

nothing added to commit but untracked files present (use "git add" to track)

Vemos que los directorios _packages y registry ya no aparecen entre los ficheros sin seguimiento. De la misma manera desaparecerían cualquier fichero terminado en ~ (copia de seguridad en Unix) o .bak (copia de seguridad en Windows) en cualquier subdirectorio (ya que no empieza la línea por / en .gitignore).

Ahora sí, añadimos la aplicación

Ya estamos preparados para añadir la aplicación. Podemos añadir los ficheros uno a uno, git add .gitignore por ejemplo o añadirlos todos a la vez, git add .

Añadimos todos los ficheros al área de preparación o staged
$ git add .

Volvemos a ver el estado del repositorio:

Estado tras añadir los ficheros
$ git status
On branch main

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)
        new file:   .gitignore
        new file:   curso-de-git-app-library.dylan
        new file:   curso-de-git-app.dylan
        new file:   curso-de-git-app.lid
        new file:   curso-de-git.dylan
        new file:   curso-de-git.lid
        new file:   dylan-package.json
        new file:   library.dylan
        new file:   tests/curso-de-git-test-suite.dylan
        new file:   tests/curso-de-git-test-suite.lid
        new file:   tests/library.dylan

Nos muestra que tenemos una lista de cambios en seguimiento preparados para ser confirmados (committed). También nos muestra que podemos sacarlos del área de seguimento (unstage) mediante el comando git rm --cached <file>....

Primera confirmación

No se trata de tu primera comunión ni de la confirmación de la Iglesia sino de aceptar los ficheros en seguimiento y guardarlos en el repositorio. Sin más dilación:

$ git commit -m "Revisión inicial"

Confirmamos con commit y escribimos un mensaje de confirmación (-m) en la misma línea (más adelante veremos como hacerlo más correcto).

La salida del comando es la siguiente:

Salida de la orden de confirmación
$ git commit -m "Revisión inicial"
[main (root-commit) 81f67ea] Revisión inicial
11 files changed, 122 insertions(+)
create mode 100644 .gitignore
create mode 100644 curso-de-git-app-library.dylan
create mode 100644 curso-de-git-app.dylan
create mode 100644 curso-de-git-app.lid
create mode 100644 curso-de-git.dylan
create mode 100644 curso-de-git.lid
create mode 100644 dylan-package.json
create mode 100644 library.dylan
create mode 100644 tests/curso-de-git-test-suite.dylan
create mode 100644 tests/curso-de-git-test-suite.lid
create mode 100644 tests/library.dylan

Nuestro repositorio tiene el siguiente aspecto:

digraph G {
        rankdir="RL";
        splines=line;

        c0

        {
            rank=same;
            node [
                style=filled,
                color=red,
                fillcolor=red,
                shape=rectangle,
                fontname=monospace,
                fontcolor=white
            ]
            c0 -> "81f67ea" [dir=back]
            main -> c0
        }
}

Rama master tras commit 81f67ea

Primer cambio

Vamos a compilar el programa y ejecutar las pruebas:

$ dylan build --all

Saldrá mucho texto y unas advertencias que no tiene importancia. Una vez terminado saldrá algo parecido a esto:

...
Opened project curso-de-git (/home/fraya/Dylan/curso-de-git/curso-de-git.lid)
Build of 'curso-de-git' completed
[========================================] Building targets: dll within /hom...

Los programas ejecutables se encuentran en _build/bin. Ejecutemos las pruebas:

$ _build/bin/curso-de-git-test-suite
Suite curso-de-git-test-suite:
Test test-greeting:
Test test-greeting PASSED in 0.000048s and 8KiB
Test test-$greeting:
Test test-$greeting PASSED in 0.000032s and 2KiB
Suite curso-de-git-test-suite PASSED in 0.000080s and 10KiB
Ran 2 assertions
Ran 2 tests
PASSED in 0.000080 seconds

Ejecutemos ahora el programa:

$ _build/bin/curso-de-git-app
Hello world!

Nos gustaría que el mensaje fuera distinto de Hello world! y que dijese ¡Hola mundo! u ¡Hola mamá!. Vamos a cambiar el código para que al pasarle un parámetro nos devuelva un Hola personalizado.

Primero voy a cambiar las pruebas con lo que quiero que salga:

tests/curso-de-git-test-suite.dylan
 1Module: curso-de-git-test-suite
 2
 3define test test-$greeting ()
 4  assert-equal("Hola", $greeting);
 5end test;
 6
 7define test test-greeting ()
 8  assert-equal("¡Hola mundo!", greeting("mundo"));
 9end test;
10
11// Use `_build/bin/curso-de-git-test-suite --help` to see options.
12run-test-application()

En la línea 4, quiero asegurarme que la constante $greeting que contenía Hello World! se vaya a cambiar por Hola.

En la línea 8, quiero que cuando le pase el parámetro mundo a la función greeting me devuelva ¡Hola mundo!.

Realizo los cambios, compilo con dylan build -a o dylan build --all.

$ dylan build -a
Open Dylan 2023.1

Opened project curso-de-git-app (/home/fraya/Dylan/curso-de-git/curso-de-git-app.lid)
Build of 'curso-de-git-app' completed
[====================================    ] Building targets: exe within /hom...Open Dylan 2023.1

Opened project curso-de-git-test-suite (/home/fraya/Dylan/curso-de-git/tests/curso-de-git-test-suite.lid)

/home/fraya/Dylan/curso-de-git/tests/curso-de-git-test-suite.dylan:8.33-50:
Serious warning - Too many arguments in call to method greeting () => (s :: <string>) - 1 supplied, 0 expected.
                                   -----------------
     assert-equal("¡Hola mundo!", greeting("mundo"));
                                   -----------------
Build of 'curso-de-git-test-suite' completed
[=================================       ] Building targets: exe within /hom...Build of "curso-de-git-test-suite" failed with exit status 3.

El compilador se pone serio y nos avisa con un Serious warning de que tenemos demasiados argumentos para llamar al método greeting, que esperaba 0 y le hemos pasado 1. En la siguiente línea nos muestra donde ha visto el código sospechoso.

Como un día que vas a cenar y se presenta alguien sin avisar, el método greeting tiene un invitado que no esperaba. ¿Qué pasará si ejecutamos las pruebas?

 1$ _build/bin/curso-de-git-test-suite
 2Suite curso-de-git-test-suite:
 3  Test test-$greeting:
 4    FAILED: "Hola" = $greeting
 5      want: "Hola"
 6      got:  "Hello world!"
 7      detail: sizes differ (4 and 12); element 1 is the first mismatch
 8  Test test-$greeting FAILED in 0.000245s and 23KiB
 9  Test test-greeting:
10    CRASHED: "\<C2>\<A1>Hola mundo!" = greeting("mundo")
11      Error evaluating assert-equal expressions: Attempted to call {<simple-method>: ??? () => (<string>)} with 1 arguments
12  Test test-greeting FAILED in 0.000230s and 25KiB
13Suite curso-de-git-test-suite FAILED in 0.000475s and 47KiB
14  FAILED: curso-de-git-test-suite
15  FAILED: test-$greeting
16  FAILED: "Hola" = $greeting
17     want: "Hola"
18     got:  "Hello world!"
19     detail: sizes differ (4 and 12); element 1 is the first mismatch
20  FAILED: test-greeting
21  CRASHED: "\<C2>\<A1>Hola mundo!" = greeting("mundo")
22     Error evaluating assert-equal expressions: Attempted to call {<simple-method>: ??? () => (<string>)} with 1 arguments
23
24Ran 2 assertions
25Ran 2 tests: 2 failed
26FAILED in 0.000475 seconds

En la línea 3 vemos que la primera prueba ha fallado, queríamos (want) que saliera Hola, pero tuvimos (got) Hello world!.

En la línea 9 nuestro segunda prueba también ha fallado, esta vez con el programa roto (crashed). El programa ha preferido suicidarse antes que ejecutar algo que podría haber estropeado el ordenador.

Nota

Cuando un programa hace un crash, se mata el mismo y suele escribir un volcado de la memoria en el momento de la ejecución para que el programador haga la autopsia y pueda saber que pasó. Este volcado se conoce como core dump. El volcado del fallo y el lugar donde se guarda dependen de tu sistema operativo. ¡Ya tienes deberes, averígualo!.

Vamos a arreglarlo, primero cambiaremos la constante $greeting para que contenga Hola en lugar de Hello world!.

curso-de-git.dylan
Module: curso-de-git-impl

// Internal
define constant $greeting = "Hola";

// Exported
define function greeting () => (s :: <string>)
  $greeting
end function;

Grabamos, compilamos (dylan build -a) y ejecutamos las pruebas (_build/bin/curso-de-git-test-suite`).

Suite curso-de-git-test-suite:
  Test test-$greeting:
  Test test-$greeting PASSED in 0.000050s and 8KiB
  Test test-greeting:
    CRASHED: "\<C2>\<A1>Hola mundo!" = greeting("mundo")
      Error evaluating assert-equal expressions: Attempted to call {<simple-method>: ??? () => (<string>)} with 1 arguments
  Test test-greeting FAILED in 0.000266s and 30KiB
Suite curso-de-git-test-suite FAILED in 0.000316s and 38KiB
 FAILED: curso-de-git-test-suite
 FAILED: test-greeting
    CRASHED: "\<C2>\<A1>Hola mundo!" = greeting("mundo")
      Error evaluating assert-equal expressions: Attempted to call {<simple-method>: ??? () => (<string>)} with 1 arguments

Ran 2 assertions
Ran 2 tests: 1 failed
FAILED in 0.000316 seconds

Bueno, la primera prueba ya ha pasado con éxito, pero aún nos queda arreglar la segunda. Esta será más complicada.

curso-de-git.dylan
1Module: curso-de-git-impl
2
3// Internal
4define constant $greeting = "Hola";
5
6// Exported
7define function greeting (mensaje) => (s :: <string>)
8  concatenate("¡", $greeting, " ", mensaje, "!")
9end function;

Primero en la línea 7 pasamos a greeting el parámetro mensaje, que pasa de no esperar a ninguno () a esperar uno (mensaje).

En la línea 8 unimos todas las cadenas para conseguir el ¡Hola <mensaje>!.

Grabamos y compilamos el código:

$ dylan build -a
Open Dylan 2023.1

Opened project curso-de-git-app (/home/fraya/Dylan/curso-de-git/curso-de-git-app.lid)

/home/fraya/Dylan/curso-de-git/curso-de-git-app.dylan:5.22-32:
Serious warning - Too few arguments in call to method
greeting (mensaje :: <object>) => (s :: <string>) -
0 supplied, 1 expected.
                        ----------
     format-out("%s\n", greeting());
                        ----------
Build of 'curso-de-git-app' completed
[====================================    ] Building targets: exe within /hom

Claro, hemos arreglado las pruebas pero no el programa principal, que sigue esperando una función greeting sin parámetros.

curso-de-git-app.dylan
Module: curso-de-git-app

define function main
    (name :: <string>, arguments :: <vector>)
  format-out("%s\n", greeting(arguments[0]));
  exit-application(0);
end function;

// Calling our main function (which could have any name) should be the last
// thing we do
main(application-name(), application-arguments());

Le pasamos a greeting el primer argumento que le pase el usuario al programa ejecutable (arguments[0]).

Grabamos, compilamos (ya no se muestran errores) y probamos.

$ _build/bin/curso-de-git-test-suite
Suite curso-de-git-test-suite:
 Test test-greeting:
 Test test-greeting PASSED in 0.000066s and 8KiB
 Test test-$greeting:
 Test test-$greeting PASSED in 0.000009s and 2KiB
Suite curso-de-git-test-suite PASSED in 0.000075s and 10KiB
Ran 2 assertions
Ran 2 tests
PASSED in 0.000075 seconds

¡Conseguido! Ahora probamos el programa:

$ _build/bin/curso-de-git-app mundo
¡Hola mundo!
$ _build/bin/curso-de-git-app Luisa
¡Hola Luisa!

Dan ganas de saludar a todo el mundo. Tranquila. Vamos a ver el repositorio.

$ git status
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   curso-de-git-app.dylan
        modified:   curso-de-git.dylan
        modified:   tests/curso-de-git-test-suite.dylan

 Untracked files:
   (use "git add <file>..." to include in what will be committed)
        _build/

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

Por un lado vemos que tenemos ficheros modificados (modified) que deberíamos añadir al stage. Por otro que el compilador ha creado el directorio _build/ y que no tiene seguimiento.

Añadimos a nuestro fichero .gitignore la línea:

# directorios de desarrollo

/_packages/
/registry/
/_build/

# ficheros de copia de seguridad

*~
*.bak

Una vez quitado este directorio que no necesitamos, añadimos todos los cambios.

$ git add .
$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
     modified:   .gitignore
     modified:   curso-de-git-app.dylan
     modified:   curso-de-git.dylan
     modified:   tests/curso-de-git-test-suite.dylan

Confirmamos los cambios

$ git commit -m "Parametrizar el mensaje de saludo"
[master 73c695b] Parametrizar el mensaje de saludo
4 files changed, 8 insertions(+), 7 deletions(-)

Ahora nuestro repositorio tiene este aspecto:

digraph G {
        rankdir="RL";
        splines=line;

        c1 -> c0

        {
            rank=same;
            node [
                style=filled,
                color=red,
                fillcolor=red,
                shape=rectangle,
                fontname=monospace,
                fontcolor=white
            ]

            c1 -> "73c695b" [dir=back]
            main -> c1
        }
}

Rama main tras commit 73c695b

Diferencias entre workdir y staging

Aún hay un problema si llamamos a nuestro programa sin argumentos de entrada.

$ _build/bin/curso-de-git-app
ELEMENT outside of range: 0
Backtrace:
  invoke-debugger:internal:dylan##1 + 0x29
  default-handler:dylan:dylan##1 + 0x12
  default-last-handler:common-dylan-internals:common-dylan##0 + 0x2f1
  error:dylan:dylan##0 + 0x27
  0x7f2fe2051a15
  0x7f2fe2062017
  0x7f2fe23e728f
  0x5571c9bf1159
  0x7f2fe1a7f14a
  __libc_start_main + 0x8b
  0x5571c9bf1075

El programa falla porque no hay un elemento 0 en los argumentos. Modifiquemos la aplicación.

curso-de-git-app.dylan
Module: curso-de-git-app

define function main
    (name :: <string>, arguments :: <vector>)
  let mensaje = if (arguments.size < 1)
                  "mundo"
                else
                  arguments[0]
                end;
  format-out("%s\n", greeting(mensaje));
  exit-application(0);
end function;

// Calling our main function (which could have any name) should be the last
// thing we do
main(application-name(), application-arguments());

Esta vez añadimos los cambios a la fase de staging, pero sin confirmarlos (commit).

$ git add curso-de-git-app.dylan

Volvemos a modificar el programa para indicar con un comentario lo que hemos hecho:

curso-de-git-app.dylan
Module: curso-de-git-app

define function main
    (name :: <string>, arguments :: <vector>)
  // Si no hay argumentos poner mensaje por defecto
  let mensaje = if (arguments.size < 1)
                  "mundo"
                else
                  arguments[0]
                end;
  format-out("%s\n", greeting(mensaje));
  exit-application(0);
end function;

// Calling our main function (which could have any name) should be the last
// thing we do
main(application-name(), application-arguments());

Veamos el estado del repositorio:

Estado del repositorio con un fichero en stage y otro en workdir
$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   curso-de-git-app.dylan

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   curso-de-git-app.dylan

Podemos ver como aparecen el archivo curso-de-git-app.dylan dos veces. El primero está preparado para ser confirmado y está almacenado en la zona de staging. El segundo indica que el archivo curso-de-git-app.dylan está modificado otra vez en la zona de trabajo (workdir).

Advertencia

Si volvieramos a hacer un git add curso-de-git-app.dylan sobreescribiríamos los cambios previos que había en la zona de staging.

Almacenamos los cambios por separado:

Añadir el parámetro por defecto
$ git commit -m "Se añade un parámetro por defecto"
[master 47e9e6f] Se añade un parámetro por defecto
1 file changed, 5 insertions(+)

$ git status
On branch main
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   curso-de-git-app.dylan

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

$ git add .
$ git status
On branch main
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   curso-de-git-app.dylan
digraph G {
         rankdir="RL";
         splines=line;

         c2 -> c1 -> c0

         {
         rank=same;
             node [
                 style=filled,
                 color=red,
                 fillcolor=red,
                 shape=rectangle,
                 fontname=monospace,
                 fontcolor=white
             ]

             c2 -> "47e9e6f" [dir=back]
             main -> c2
         }
}

Rama main tras commit 47e9e6f

Añadir el comentario
$ git commit -m "Añade comentario para parámetro por defecto"
[main 2ee18ff] Añade comentario para parámetro por defecto
1 file changed, 1 insertion(+)
digraph G {
         rankdir="RL";
         splines=line;

         c3 -> c2 -> c1 -> c0

         {
           rank=same;
           node [
                 style=filled,
                 color=red,
                 fillcolor=red,
                 shape=rectangle,
                 fontname=monospace,
                 fontcolor=white
           ]

             c3 -> "2ee18ff" [dir=back]
             main -> c3
         }
}

Rama main tras commit 2ee18ff

Trabajando con el historial

Con la orden git log podemos ver todos los cambios que hemos hecho:

Salida de git log
Author: Fernando Raya <f..@gmail.com>
Date:   Sat May 18 17:02:01 2024 +0200

    Añade comentario para parámetro por defecto

commit 47e9e6f5ba7aa85e63d17c31e244f020a93ec8bb
Author: Fernando Raya <f..@gmail.com>
Date:   Sat May 18 17:00:59 2024 +0200

    Se añade un parámetro por defecto

commit 73c695b8c026eb0f7c5ff3bf0ad6215dee359953
Author: Fernando Raya <f..@gmail.com>
Date:   Sat May 18 14:01:28 2024 +0200

    Parametrizar el mensaje de saludo

commit 81f67ea62cc92abc6e39a9daaae9d207165def31
Author: Fernando Raya <f..@gmail.com>
Date:   Sat May 18 11:02:01 2024 +0200

    Revisión inicial

También es posible ver versiones abreviadas o limitadas, dependiendo de los parámetros:

Salida de git log --oneline
$ git log --oneline
2ee18ff (HEAD -> main) Añade comentario para parámetro por defecto
47e9e6f Se añade un parámetro por defecto
73c695b Parametrizar el mensaje de saludo
81f67ea Revisión inicial

Una versión muy útil de git log es la siguiente, pues nos permite ver en que lugares está main y HEAD, entre otras cosas:

git log --pretty=format:'%h %ad | %s%d [%an]' --grap--date=short
* 2ee18ff 2024-05-18 | Añade comentario para parámetro por defecto (HEAD -> main) [Fernando Raya]
* 47e9e6f 2024-05-18 | Se añade un parámetro por defecto [Fernando Raya]
* 73c695b 2024-05-18 | Parametrizar el mensaje de saludo [Fernando Raya]
* 81f67ea 2024-05-18 | Revisión inicial [Fernando Raya]

Recuperando versiones anteriores

Cada cambio es etiquetado por un hash, para poder regresar a ese momento del estado del proyecto se usa la orden git checkout.

$ git checkout 81f67ea
Note: checking out '81f67ea'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

  git checkout -b new_branch_name

HEAD is now at 81f67ea... Revisión inicial

El aviso que nos sale nos indica que estamos en un estado donde no trabajamos en ninguna rama concreta. Eso significa que los cambios que hagamos podrían «perderse» porque si no son guardados en una nueva rama, en principio no podríamos volver a recuperarlos. Hay que pensar que Git es como un árbol donde un nodo tiene información de su nodo padre, no de sus nodos hijos, con lo que siempre necesitaríamos información de dónde se encuentran los nodos finales o de otra manera no podríamos acceder a ellos.

Volver a la última versión de la rama main

Usamos git checkout indicando el nombre de la rama:

$ git checkout main

Previous HEAD position was 81f67ea... Revisión inicial

Etiquetando versiones

Para poder recuperar versiones concretas en la historia del repositorio, podemos etiquetarlas, lo cual es más facil que usar un hash. Para eso usaremos la orden git tag.

$ git tag v1

Ahora vamos a etiquetar la versión inmediatamente anterior como v1-beta. Para ello podemos usar los modificadores ^ o ~ que nos llevarán a un ancestro determinado. Las siguientes dos órdenes son equivalentes:

$ git checkout v1^
$ git checkout v1~1

Si ejecutamos la orden sin parámetros nos mostrará todas las etiquetas existentes.

$ git tag
v1

Y para verlas en el historial:

$ git log --oneline
b804719 (HEAD -> main, tag: v1) Añade comentario para parámetro por defecto
9f41f0b Se añade un parámetro por defecto
cf2b658 Parametrizar el mensaje de saludo
56b0ec5 Revisión inicial

Ignorando archivos

Ya hemos visto antes que podemos ignorar ciertos ficheros para que no entren en nuestro repositorio. La orden git add . o git add nombre_directorio es muy cómoda, ya que nos permite añadir todos los archivos del proyecto o todos los contenidos en un directorio y sus subdirectorios. Es mucho más rápido que tener que ir añadiéndolos uno por uno. El problema es que, si no se tiene cuidado, se puede terminar por añadir archivos innecesarios o con información sensible.

Por lo general se debe evitar añadir archivos que se hayan generado como producto de la compilación del proyecto, los que generen los entornos de desarrollo (archivos de configuración y temporales) y aquellos que contentan información sensible, como contraseñas o tokens de autenticación. Por ejemplo, en un proyecto de C/C++, los archivos objeto no deben incluirse, solo los que contengan código fuente y los make que los generen.

Para indicarle a git que debe ignorar un archivo, se puede crear un fichero llamado .gitignore, bien en la raíz del proyecto o en los subdirectorios que queramos. Dicho fichero puede contener patrones, uno en cada línea, que especiquen qué archivos deben ignorarse. El formato es el siguiente:

dir1/           # ignora todo lo que contenga el directorio dir1
!dir1/info.txt  # El operador ! excluye del ignore a dir1/info.txt (sí se guardaría)
dir2/*.txt      # ignora todos los archivos txt que hay en el directorio dir2
dir3/**/*.txt   # ignora todos los archivos txt que hay en el dir3 y sus subdirectorios
*.o             # ignora todos los archivos con extensión .o en todos los directorio

Cada tipo de proyecto genera sus ficheros temporales, así que para cada proyecto hay un .gitignore apropiado. Existen repositorios que ya tienen creadas plantillas. Podéis encontrar uno en https://github.com/github/gitignore