Repositorios de código

El repositorio de código es el que tiene la finalidad de; almacenar, detectar y registrar cambios, y proveer el mismo cuando sea solicitado.

Hay tanto plataformas de terceros, como plataformas que podemos configurar y levantar nosotros en nuestros servidores.

Suelen haber dos protocolos soportados; Git y Mercurial. Y las plataformas que los usan traen todo integrado.

Versionado de cambios

Tanto si tenemos nuestra documentación en markdown, como si estamos programando, debemos tener un registro confiable de los cambios que fueron realizados en el archivo modificado.

Los datos que se almacenan en el registro de cambios son;

  1. Timestamp de la modificación; años, mes, día, hora, minutos, segundos y zona horaria.
  2. Ruta del archivo modificado.
  3. Nombre del archivo modificado.
  4. Linea de código / texto añadido.
  5. Linea de código / texto eliminado.
  6. Nombre de usuario que realizo las modificaciones.
  7. Email de usuario que realizo las modificaciones.

Recordar lo que puse arriba; ‘Linea de código / texto añadido / eliminado’. Por ende si tenes (como suele pasar muchas veces) archivos JavaScript que están en una sola linea, al ser modificado no va a poder detectar el cambio en un carácter, si no que va mostrar como que cambio todo, haciendo inútil tal registro.

Por ende; se ordenado, pon comentarios de código en tu programa y usa la sintaxis apropiada según el lenguaje de programación que uses. Acá te dejo un ejemplo con Rust de como debe programarse para que si cambiamos un método, no se detecte un cambio en toda la linea, si no en el método que cambiamos en concreto;

#![allow(unused)] fn main() { let mut rune_history = match OpenOptions::new() .create(true) .append(true) .open( io_mods::get_user_home() + "/.ravnos/rune_history" ) { Ok(d) => d, Err(e) => { eprintln!("Error writting / creating .ravnos/rune_history file. \n History will be located in /tmp {e}"); OpenOptions::new() .create(true) .append(true) .open("/tmp/.ravnos/rune_history" ). expect("Fail creating temporary file") }, }; }

El código de ejemplo viene de mi sistema operativo RavnOS. Fíjate como esta separado por nuevas lineas.

Git

Git es un software y protocolo diseñado y creado por Linus Torvalds (el mismo creador del kernel Linux). Fue creado cuando el desarrollo del kernel se torno muy grande para continuar sin estos mecanismos de registro.

Apareció inicialmente en el 2005 y tiene por objetivo la eficiencia para el mantenimiento de grandes versiones de uno o varios archivos en un código fuente o proyecto.

Tiene soporte para plugins / extensiones, una de las casi obligatorias a tener es “git-lfs”. “LFS” viene de “Large File System” y permite que los repositorios puedan almacenar archivos de un peso mayor a 50MB.

Arquitectura

Git tiene una estructura de árbol en el que se parte de una base (llamada “main” o “master”) y de ahí se realizan cambios (los “commits”). Pero los cambios no son sobre la base obligatoriamente, si no que pueden ser también en ramificaciones (llamadas; “branch”) sobre esa base.

Ejemplo de flujo de trabajo
Copyright; https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow

A su vez, esas ramificaciones pueden ser luego fusionadas con la base.

Esto te permite trabajar en equipo sobre el mismo repositorio; todos parten de la misma base (“main” o “master”), cada uno crea una ramificación (“branch”), realizan sus modificaciones en el código base y luego lo fusionan con la base (o no, depende).

Uso

Como vimos en el capitulo anterior según la distribución que estés usando, instala tanto “git” como “git-lfs”.

Luego debemos elegir que carpeta / directorio (y lo que contenga) queremos que haga seguimiento git. Una vez en la misma (recuerda, el comando “cd”) vamos a iniciar todo;

git init

Luego añadimos que directorios y archivos son los que queremos que haga un seguimiento;

git add [directorio] [archivo]

Registramos este cambio con un mensaje indicando el mismo;

git commit -m “[mensaje]”

Luego creamos una ramificación;

git branch [nombre_de_la_rama]

Nos movemos a esa rama, u otra que necesitemos;

git checkout [nombre_de_la_rama]

Podemos verificar que ramas existen y en cual estamos (estará resaltada en color);

git branch

Hacemos las modificaciones que necesitemos en los archivos y luego los registramos con “git add” y el “git commit”.

Podemos verificar el registro de cambio, que sera independiente de la rama en donde estemos con;

git log

El retorno de “git log” tiene esta estructura;

commit [commit_ID] ([branchs_afectados]) Author: [Author: Nombre, Apellido y email] Date: [Timestamp] [comentario/mensaje_del_cambio]

Cada [commit_ID] es calculado utilizando SHA.

Si queremos ver el estado actual del proyecto (archivos modificados pero sin registro del cambio, archivos sin seguimiento, etc) con;

git status

Por ultimo, debemos verificar cual repositorio remoto es al que vamos a subir este proyecto, sus archivos, su registro git y sus cambios;

git remote -v

El retorno de “git remote -v” tiene esta estructura;

[nombre_del_repositorio] [url_remota]

Una vez identificado donde tenemos que subir todo procedemos a indicar el mismo y el branch donde se va aplicar los cambios;

git push [nombre_del_repositorio] [branch]

Dependiendo de la autenticación configurada, nos pedirá nuestro usuario y contraseña/token o nuestra clave de la llave ssh.

Si tenemos un repositorio remoto ya existente y necesitamos sincronizar los cambios localmente (el argumento “–all” traerá todos los branch, no solamente en el que estas);

git pull –all

O si directamente queremos clonar ese repositorio de forma local lo podemos hacer con;

git clone [url_repositorio] [path_local_a_donde_guardar]

Por ultimo, si queremos unificar una rama con la base (u otra rama) lo podemos hacer. Primero tenemos que ir al branch que queremos que se unifique todo (recordar; “git checkout [branch]”) y luego tenemos dos posibles opciones;

  1. Si queremos que se unifiquen los cambios en una rama pero sin eliminarla y preservando todos los registros anteriores;

git merge [branch_a_unificar_en_la_que_estamos]

De esta manera si nos movemos (checkout) a la rama “Hotfix1” y queremos que esos cambios se apliquen en la master;

git checkout master

git merge Hotfix1

  1. Si queremos que se unifiquen los cambios en una rama, eliminando la luego y unificando los cambios;

git rebase [branch_a_unificar_en_la_que_estamos]

De esta manera si nos movemos (checkout) a la rama “Hotfix1” y queremos que esos cambios se apliquen en la master;

git checkout master

git rebase --continue Hotfix1

Personalmente no te recomiendo que uses NUNCA rebase, como te dije, elimina el branch que estas unificando en el que estas parado. Esa eliminación incluye el registro de cambios.

Recomendaciones / Buenas practicas

  1. No hagas cambios en “master” o “main”. Crea siempre tus branchs.

  2. Registra cambios chicos por archivo, trata de no hacer millones de cambios en uno o varios archivos y registrarlos en un solo commit.

  3. Cada commit debe corresponder a una acción atómica; una actualización, una solución de un problema, etc.

  4. No hagas “rebase” entre los branchs.

  5. Los mensajes de los cambios deben ser descriptivos y bien explicados.

  6. Seguí la estrategia de ramificaciones (“branching”) que mejor sea para el proyecto;

    • Feature branching

      Cada feature/característica que se añade al programa conlleva un nuevo branch. Nunca se fusionan a “main” o “master”.

    • GitFlow

      Aplica el “feature branching” pero con modificaciones considerables; se parte de un branch “develop” o “dev”. Ahí se crea un branch por cada feature creada, que luego se fusionan (con “merge” obviamente) al de “dev”. Cuando el programa y sus cambios son estables se aplican otro merge / fusión pero en un branch llamado “release”. Cuando el programa en el branch “release” pasa las pruebas de calidad (“QA”) ahí se hace otra fusión en “main” o “master”.

      La imagen que puse en “Arquitectura” corresponde a esta estrategia.

    • Personal branching

      Acá cada desarrollador usa su propio branch y cada uno fusiona a “main” o “master”.

    Mientras que algunos equipos usan un flujo de trabajo especifico, muchos otros adaptan los anteriores a sus necesidades.

    Personalmente he usado tanto “GitFlow” como “Personal branching” y hasta una combinación de ambos.

Plataformas / Implementaciones

  1. Open source - Self Hosted - Third Party; GitLab
  2. Closed source - Third Party; GitHub
  3. Open source - Self Hosted; CGit
  4. Open source - Self Hosted - Third Party; GiTea

Mercurial

Desarrollado Matt Mackall en el 2005, tiene la finalidad de ser fácil de usar al igual que tener un gran rendimiento y escalabilidad. A diferencia de “git” trae un servidor web integrado y no tiene la necesidad de un servidor central si no que puede funcionar a modo distribuido.

Uso

Como vimos en el capitulo anterior según la distribución que estés usando, instala “mercurial”.

Luego debemos elegir que carpeta / directorio (y lo que contenga) queremos que haga seguimiento mercurial. Una vez en la misma (recuerda, el comando “cd”) vamos a iniciar todo;

hg init

Luego añadimos que directorios y archivos son los que queremos que haga un seguimiento;

hg add [directorio] [archivo]

Registramos este cambio con un mensaje indicando el mismo;

hg commit -m “[mensaje]”

Luego creamos una ramificación;

hg branch [nombre_de_la_rama]

Nos movemos a esa rama, u otra que necesitemos;

hg update [nombre_de_la_rama]

Podemos verificar que ramas existen y en cual estamos (estará resaltada en color);

hg branches

Hacemos las modificaciones que necesitemos en los archivos y luego los registramos con “git add” y el “git commit”.

Podemos verificar el registro de cambio, que sera independiente de la rama en donde estemos con;

hg log -G -v

El retorno de “hg log -G -v” tiene esta estructura;

changeset: [commit_ID] user: [Author: Nombre, Apellido y email] date: [Timestamp] summary: [comentario/mensaje_del_cambio]

Cada [commit_ID] es calculado utilizando SHA.

Si queremos ver el estado actual del proyecto (archivos modificados pero sin registro del cambio, archivos sin seguimiento, etc) con;

hg status

Por ultimo, debemos verificar cual repositorio remoto es al que vamos a subir este proyecto, sus archivos, su registro git y sus cambios;

hg paths

El retorno de “hg paths” tiene esta estructura;

[nombre_del_repositorio] [url_remota]

Una vez identificado donde tenemos que subir todo procedemos a indicar el mismo y el branch donde se va aplicar los cambios;

hg push [nombre_del_repositorio] -b [branch]

Dependiendo de la autenticación configurada, nos pedirá nuestro usuario y contraseña/token o nuestra clave de la llave ssh.

Si tenemos un repositorio remoto ya existente y necesitamos sincronizar los cambios localmente (el argumento “–all” traerá todos los branch, no solamente en el que estas);

hg pull –all && hg update

O si directamente queremos clonar ese repositorio de forma local lo podemos hacer con;

hg clone [url_repositorio] [path_local_a_donde_guardar]

Por ultimo, si queremos unificar una rama con la base (u otra rama) lo podemos hacer. Primero tenemos que ir al branch que queremos que se unifique todo (recordar; “git checkout [branch]”) y luego tenemos dos posibles opciones;

  1. Si queremos que se unifiquen los cambios en una rama pero sin eliminarla y preservando todos los registros anteriores;

hg merge [branch_a_unificar_en_la_que_estamos]

De esta manera si nos movemos (checkout) a la rama “Hotfix1” y queremos que esos cambios se apliquen en la master;

hg update master

hg merge Hotfix1

  1. Si queremos que se unifiquen los cambios en una rama, eliminando la luego y unificando los cambios;

hg rebase -s [origen] -d [destino]

De esta manera si queremos que los cambios de la rama “Hotfix1” se apliquen en la master;

hg rebase -s Hotfix1 -d master

Personalmente no te recomiendo que uses NUNCA rebase, como te dije, elimina el branch que estas unificando en el que estas parado. Esa eliminación incluye el registro de cambios.

Recomendaciones / Buenas practicas

Básicamente lo mismo que para git;

  1. No hagas cambios en “master” o “main”. Crea siempre tus branchs.

  2. Registra cambios chicos por archivo, trata de no hacer millones de cambios en uno o varios archivos y registrarlos en un solo commit.

  3. Cada commit debe corresponder a una acción atómica; una actualización, una solución de un problema, etc.

  4. No hagas “rebase” entre los branchs.

  5. Los mensajes de los cambios deben ser descriptivos y bien explicados.

  6. Seguí la estrategia de ramificaciones (“branching”) que mejor sea para el proyecto;

    • Feature branching

      Cada feature/característica que se añade al programa conlleva un nuevo branch. Nunca se fusionan a “main” o “master”.

    • GitFlow

      Aplica el “feature branching” pero con modificaciones considerables; se parte de un branch “develop” o “dev”. Ahí se crea un branch por cada feature creada, que luego se fusionan (con “merge” obviamente) al de “dev”. Cuando el programa y sus cambios son estables se aplican otro merge / fusión pero en un branch llamado “release”. Cuando el programa en el branch “release” pasa las pruebas de calidad (“QA”) ahí se hace otra fusión en “main” o “master”.

      La imagen que puse en “Arquitectura” de Git corresponde a esta estrategia.

    • Personal branching

      Acá cada desarrollador usa su propio branch y cada uno fusiona a “main” o “master”.

    Mientras que algunos equipos usan un flujo de trabajo especifico, muchos otros adaptan los anteriores a sus necesidades.

    Personalmente he usado tanto “GitFlow” como “Personal branching” y hasta una combinación de ambos.

Plataformas / Implementaciones

  1. Open source - Self Hosted - Third Party; Heptapod