De mayor quiero ser programador

Leo que harán falta urgentemente 900000 desarrolladores para el 2020 -vamos que no queda nada- en una especie de suerte por salvar el mundo estilo «Stranger things».

Claro que para lograr esa alza en matriculaciones habría que acabar con muchos tópicos como que tipo de carrera es informática y quiénes sus estudiantes, si realmente sirve para algo o es mejor estudiar industriales o alguna otra carrera «de verdad», a donde nos lleva inventar formaciones no regladas sacadas de la manga… aunque de todo esto ya hablaré en otro momento.

Ahora con la vista del tiempo repaso un poco las empresas que he conocido y lo junto con lo que conozco de este mundillo para recopilar algunos de los grandes «dogmas» de la consultoría TIC -principalmente en España- tal que:

«Si quieres llegar a algo hazte jefe de proyecto, ya sabes, traje, Excel y PowerPoint» o «ya tienes una edad para seguir dándole a la tecla» etc…

Más de uno ha caído ante estos argumentos creyendo que la única vía para ser algo en esta vida pasaba por desarrollar… su cargo en la tarjeta de visita, en algunos casos es una buena opción, puede que a esa persona ya no le guste desarrollar -igual nunca le gustó- o crea que se va a hacer rico y va a trabajar lo justo -lo cual no pasa ni siempre, ni pronto- o que tener gente a su cargo es un símbolo de estatus -en plan logro desbloqueable-

A partir de aquí hay varias opciones, que sea esta una buena elección para esa persona y desempeñe su cargo con eficacia, que sea una mala elección y no hayas ganado un buen jefe de proyecto -más bien uno malo- y encima has perdido un buen desarrollador, o aún peor, ahora tienes un desarrollador que no puede dejar de serlo -porque en el fondo es lo que es y lo que le gusta- así que no hará bien su trabajo de jefe de proyecto y encima se meterá en todos los «saraos» técnicos dando su opinión -cada vez más desfasada- mientras el resto de los desarrolladores se preguntan… «¿Este de que palo va?»

Traje o no traje
¿Traje o no traje?

Ahora en plena -según algunos- «burbuja de contratación» más de un jefe de proyecto se estará preguntando aquello de a ver si pasa esto y dejan de cobrar tanto, por un lado porque es verdad que las tarifas se encarecen y los márgenes se estrechan demasiado rápido y por otro -porque no decirlo- por aquello de está gente no puede cobrar cerca del que hace las cosas importantes como yo que para eso me puse un traje, -vaya, quizás no fuera una decisión tan buena eso de ponerse un traje…-

Reconozco que de un tiempo a esta parte «huelo el miedo», hay escasez y alta rotación, resulta complicado llevar proyectos a cabo no solo porque falte gente -que ya es malo de por sí- sino porque a veces hay que resignarse a lo que quede en el mercado -que es aún peor para todas las partes-

Así que ahora todo es para hacer feliz al desarrollador, que si un futbolín, que si un puf, que si el día del orgullo friki hago un vídeo molón,… A ver si parece que soy lo que no soy y por cuatro euros engaño a algún despistado -que pasa más de lo que parece-

Vuelvo al pasado y recuerdo por ejemplo que en otros países no es así, la idea base -que es lo importante- es otra, los técnicos están bien valorados y se reconoce su importancia en el equipo como pieza clave para llevar los proyectos a buen término, de hecho se considera necesario que haya siempre alguno en las reuniones –con criterio– para que todo lo que se diga tenga una viabilidad técnica y evitar el «si a todo» -en este punto más de uno se estará emocionando-

Es por esto entre otras cosas por lo que suelo recomendar tener experiencia internacional -pero de la buena, de la de hablar en inglés y conocer gente de otros países- obviamente no todas son así pero el porcentaje de error respecto a una nacional es menor y la experiencia merece la pena.

En el ámbito nacional una empresa de base técnica, es decir, creada por, gestionada por, con mentalidad de,… «técnica» será casi siempre mejor que una gran TIC tradicional, a no ser que se quiera poner un traje en cuyo caso hay para elegir.

Me acuerdo también ahora de metodologías ágiles donde el peso de un traje queda más limitado y es el propio equipo el que se gestiona, igual por eso ahora muchas empresas prefieren invertir salarios más altos en técnicos que además de desarrollar sepan metodologías ágiles -a las empresas siempre les gusta esto de matar dos pájaros de un tiro- lo cual sutilmente provoca que el traje quede limitado a cargos más altos -alguien tiene que contar las monedas- o temas un poco más políticos -vete a comer con el cliente que yo me voy a casa-

Pongo en valor el cargo de un traje como alguna vez he defendido, pero aunque las cosas estén cambiando aunque sea por necesidad, solo el hecho de que al final un buen técnico pueda decir que lo es y que se entienda entre el gran público que es un cargo con valor equiparable en responsabilidad y estatus a prácticamente cualquier cargo de gestión es algo que hará que mucha gente quiera subirse a este barco y no bajarse y esto es lo importante, es lo que hará que realmente cambie el escenario por encima de noticias de empleo y ascensos aspiracionales que quizás realmente no están solucionando el problema sino que lo están agravando.

Reconoceré que no me gusta que ser técnico sea un cargo de transición hacia algo mejor, que tenga que haber limitaciones en responsabilidad o sueldo, que tenga que elegir entre vocación o carrera y mucho menos que tenga que escuchar con gesto de sorpresa -¿Pero no quieres hacer otra cosa?- entiéndase por cosa llevar traje, cobrar más, hacer algo cool y bobadas varias.

Puede que incluso se me diera bien llevar un traje -mas de una vez me lo han dejado caer- pero para mi al contrario de lo que pueda parecer sería un retroceso, sería reconocer que ya no se me da bien lo que hago, que no puedo aportar más y que en cierta forma me estoy jubilando y reconociendo que solo me quedará la nostalgia y las frases de antes lo hacíamos así y acabábamos antes, me gusta resignarme a esta idea aunque al final tenga que claudicar porque la tecnología me supere y seguir pensando que, de mayor quiero ser programador.

De mayor quiero ser programador

Configurando SourceLink

Cuando se crean aplicaciones es muy habitual usar paquetes NuGet, muchas veces estos paquetes son de código libre y están albergados en GitHub o puede que sean privados y estén depositados en servidores NuGet privados.

Cuando se está desarrollando una aplicación a veces es cómodo poder depurar paquetes NuGet para obtener información como si fuera código local, para esto sirve SourceLink para permitir que el desarrollador pueda acceder al código fuente de un paquete NuGet.

Para que esto funcione el paquete debe soportar SourceLink así que es posible que no todos los paquetes que existan actualmente puedan usar este método.

También hará falta una versión de Visual Studio 15.3 y se recomienda una 15.7 para obtener toda la funcionalidad.

En este ejemplo se va a utilizar .Net Core que viene preparado para SourceLink, aunque se soportan otros formatos como .Net Framework así como varios formatos de PDB (Portable, Embedded y Windows PDBs).

Configuración

Es recomendable que la información de depuración de los proyectos sea portable, otros formatos son dependientes de Windows y no siempre funcionan bien.

Editamos el archivo .csproj y añadimos la siguiente información que es la recomendada por SourceLink.

<Project Sdk="Microsoft.NET.Sdk">
 <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
 
    <!-- Optional: Publish the repository URL in the built .nupkg (in the NuSpec <Repository> element) -->
    <PublishRepositoryUrl>true</PublishRepositoryUrl>
 
    <!-- Optional: Embed source files that are not tracked by the source control manager in the PDB -->
    <EmbedUntrackedSources>true</EmbedUntrackedSources>
  
    <!-- Optional: Build symbol package (.snupkg) to distribute the PDB containing Source Link -->
    <IncludeSymbols>true</IncludeSymbols>
    <SymbolPackageFormat>snupkg</SymbolPackageFormat>
  </PropertyGroup>
  <ItemGroup>
    <!-- Add PackageReference specific for your source control provider (see below) --> 
  </ItemGroup>
</Project>
  • PublishRepositoryUrl: Indicamos que queremos que en la información del paquete aparezca la Url del repositorio.
  • EmbedUntrackedSources: Permite añadir cómo código fuente a usar durante la depuración código que no esté asociado al repositorio de este paquete o que esté fuera del control del código fuente (como un AssemblyAttribute.cs), hay que tener cuidado porque puede añadir mucho código que igual no nos interesa.
  • IncludeSymbols: Construye el paquete .snupkg asociado que contiene la información necesaria para la depuración, otra forma sería añadirlo al paquete principal pero esto aumentaría el tamaño.
  • SymbolPackageFormat: Formato del paquete de símbolos.

Si tenemos problemas a la hora de depurar por ejemplo al tener las librerías en un repositorio privado podemos probar a usar está configuración que nos incluirá los símbolos dentro de la librería en lugar de un paquete de símbolos aparte, para Nuget.org se recomienda crear un paquete aparte (*.snupkg) como se indica aparte, en ese caso se puede omitir la directiva SymbolPackageFormat.

<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>

La configuración que hemos establecido hasta ahora lo que hace es crear una etiqueta <repository> con la dirección del repositorio así como el commit asociado a esta versión de código.
A través de Nuget Package Explorer se vería así.

<repository>

Varios paquetes por proyecto

En el caso de que queramos usar el fichero .nuspec, por ejemplo si tenemos varios proyectos y queremos crear un solo paquete con varios, tenemos que añadir esta misma etiqueta manualmente y parametrizarla ya que dotnet pack no funciona igual cuando se usa .nuspec, así que dentro del .nuspec añadimos:

<repository type="$RepositoryType$" url="$RepositoryUrl$" branch="$RepositoryBranch$" commit="$SourceRevisionId$"/>

Y los parámetros los tenemos que referenciar a través del .csproj añadiendo esta etiqueta.

<NuspecProperties>version=$(PackageVersion);SourceRevisionId=$(SourceRevisionId);RepositoryUrl=$(RepositoryUrl);RepositoryType=$(RepositoryType);RepositoryBranch=$(RepositoryBranch)</NuspecProperties>

Si usasemos un comando como el siguiente se rellenarían los valores correctos dentro de la etiqueta <repository>, más adelante se indicará como hacerlo a través de Azure DevOps.

dotnet pack .\Folder\Project.csproj -p:NuspecFile=C:\Projects\Project\Folder\File.nuspec -p:PackageVersion=4.9 -p:SourceRevisionId=7aab58c9134r2146dfr595a335e1474f4861649c -p:RepositoryUrl=https://github.com/acme/coyote -p:RepositoryType=git -p:RepositoryBranch=develop -o C:\folder\packages

También hay que tener en cuenta que si se quieren añadir los símbolos de depuración dentro del paquete hay que usar la siguiente etiquetas en lugar de <IncludeSymbols> ya que si existen ambas no se guardarán los .pdb dentro del paquete.

<DebugSymbols>true</DebugSymbols>

Ahora vamos a añadir los paquetes que harán falta para habilitar la depuración, este paquete lo marcamos como PrivateAssets=»All» (normalmente no hará falta hacerlo), esto evita que los proyectos que usen este paquete intente también usar el propio paquete SourceLink,

Los paquetes a instalar dependen del repositorio donde esté el código, unos ejemplos serían:

GitHub

<ItemGroup>
  <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0-beta2-18618-05" PrivateAssets="All"/>
</ItemGroup>

Aquí incluimos la referencia a paquete que antes hemos dejado vacía, actualmente la librería se encuentra en prerelease así que tendremos que marcar el check de NuGet Include prerelease para poder descargarla.

Azure DevOps

En este caso la referencia a añadir sería esta, al igual que para GitHub esta librería está en modo prerelease.

<ItemGroup>
  <PackageReference Include="Microsoft.SourceLink.Vsts.Git" Version="1.0.0-beta2-18618-05" PrivateAssets="All"/>
</ItemGroup>

Otros

Existen otras alternativas (GitLab, TFS, Bitbucket, …) que se pueden consultar en la propia web.

Sino aparece el paquete exacto para el repositorio la opción es usar Microsoft.SourceLink.GitHub.

Puede suceder que un paquete referencie a otros paquetes de otros proveedores, por ejemplo un paquete de GitHub que referencia a uno de Azure DevOps, en este caso la aplicación debe incluir las referencias a los paquetes necesarios a todos ellos.

Verificando

Para estar seguro de que el paquete corresponde a la versión podemos verificar que el commit que aparece en el paquete es el adecuado usando el comando git log.

Para comprobar que todo está bien configurado podemos usar la herramienta SourceLink, ejecutando este comando desde una consola de desarrollador o desde el propio VS podremos instalarla de forma global.

dotnet tool install --global SourceLink --version 3.0.0 
Instalando SourceLink como herramienta global

Ahora podemos lanzar comandos, por ejemplo el mapeo del código para ver si el .json está dentro de los .pdb con:

sourcelink print-json paquete\nCubed.EFCore.pdb
print-json

O comprobar que los ficheros se pueden descargar con:

sourcelink test paquete\nCubed.EFCore.pdb

O para imprimir el código SHA para ver los ficheros que contiene el .pdb:

sourcelink print-documents paquete\nCubed.EFCore.pdb
print-documents

O incluso las URL donde se ubicarán los ficheros de código fuente, estas direcciones son reales y se debería poder ver el contenido del código:

sourcelink print-urls paquete\nCubed.EFCore.pdb
print-urls

Publicando

Si queremos crear el paquete usando el pipeline de compilación de Azure a través de una tarea dotnet tendremos que usar el comando personalizado y no el pack (la opción de especificar un fichero .nuspec en lugar de .csproj on parece funcionar), dentro de los parámetros podemos usar variables de entorno para rellenar los valores necesarios para .nuspec. En este caso la versión es una variable que va dentro del pipeline, no es de entorno.

De cara a la publicación de los paquetes será necesario una versión del cliente NuGet al menos 4.9.0 y usar la URL v3 para poder publicar los símbolos, por ejemplo para NuGet.org https://api.nuget.org/v3/index.json

Puede pasar un tiempo desde que se publican los símbolos en GitHub hasta que los símbolos estén disponibles (máximo 1 hora pero suele ser menos).

Si tenemos un error como:

C:\Windows\ServiceProfiles\NetworkService\.nuget\packages\sourcelink.create.commandline\2.8.3\build\SourceLink.Create.CommandLine.targets(30,5): error : unable to convert OriginUrl: https://{private}.visualstudio.com/dotNET%20Project/_git/company.cqrslite [C:\agent\_work\34\s\company.CQRSlite\company.CQRSlite.csproj]

En ese caso tenemos que eliminar este paquete SourceLink.Create.CommandLine

Consejo DevOps, es mejor publicar de forma separada el paquete y los símbolos, o al menos tener la opción, ya que por ejemplo NuGet.org no permite publicar la misma versión de nuevo lo que impediría subir los símbolos al fallar la tarea anterior.

Visual Studio

Solo queda un detalle final, deshabilitar la opción Just My Code en las opciones de Visual Studio Tools->Options->Debugging esto permite poder acceder a otros orígenes de código y no solo al propio del desarrollador.

Deshabilitando Just My Code

Si se esta depurando código fuente que ya existe localmente en la máquina sera ese el que se use, pero si el código no existe en la máquina (que suele ser lo normal) se mostrará un mensaje como el siguiente.

Aviso de advertencia antes de descargar código

Eligiendo la primera opción se descargará el código en una ruta como:

C:\Users\user\AppData\Local\SourceServer\ 1c6a834f2006f59a0cb6dd7101918efae8ad05b614b5fc36d11bfc9c6d9bcb9b \src\proyecto\carpeta\codigo.cs

Este tipo de rutas permite que se puedan bajar diferentes versiones del código para diferentes versiones del mismo.

Referencias

https://stackoverflow.com/questions/53485362/pass-variable-to-nuspec-with-dotnet-pack
https://docs.microsoft.com/es-es/nuget/reference/nuspec#replacement-tokens
https://cezarypiatek.github.io/post/setting-assembly-and-package-metadata/
https://devblogs.microsoft.com/nuget/introducing-source-code-link-for-nuget-packages/
https://github.com/ctaggart/SourceLink/issues/256

Configurando SourceLink

Básicos Git (V): Amend

El propósito de Amend lo que hace es agrupar commits antes de hacer un push, si por ejemplo se está desarrollando una funcionalidad y nos damos cuenta que hemos olvidado añadir algo en lugar de crear dos commits separados podemos crear uno y después hacer un amend para agruparlos.

Imaginemos que tenemos un solo cambio en ese caso veríamos lo siguiente:

un solo cambio pendiente

Si hacemos esto varias veces tendremos cada vez más commits:

varios commits pendientes

Así que para evitar acumular commits a la hora de crear uno nuevo seleccionamos la opción ammend previous commit.

uso de ammend

Esto añadirá los cambios al anterior commit manteniendo solo uno con todas las modificaciones, el comentario de este nuevo commit será el mismo que tenía el commit anterior sino especificamos uno nuevo.

Básicos Git (V): Amend

Básicos Git (IV): Reset

Reset hace que el apuntador actual de la rama se desplace pudiendo apuntar a commits anteriores o posteriores en el historial.

Normalmente el apuntador de la rama apunta al último commit pero es posible moverlo en caso de que se quiera volver a una situación anterior de la rama y continuar a partir de ella.

Vamos a suponer que tenemos inicialmente una rama master y una develop, al crear la rama develop como es normal el apuntador apunta a HEAD, es decir al último commit, si añadiéramos un nuevo commit (Commit #3) el apuntador se desplazaría junto con el.

inicial

Ahora vamos a usar la opción reset para desplazarnos hacia atrás en el historial, para esto accederemos al historial de main y usaremos la opción reset estando posicionados en la rama en la que nos queremos mover, en este caso develop.

Keep changes (–mixed)

Esta opción provoca que si tenemos ficheros modificados se mantendrán a la par que nos desplazamos hacia atrás en el historial.

reset –mixed

Para verificar esto volvemos a la sección changes y veremos que los cambios siguen ahí y que a su vez los demás ficheros han sido cambiados a la versión concreta elegida.

Keep changes (–hard)

Esta opción provoca que si tenemos algún cambio se perderá a la par que nos desplazamos hacia atrás en el historial.

reset –hard

Para verificar esto volvemos a la sección changes y veremos que no hay ningún cambio pendiente y al igual que en el caso anterior los ficheros han sido cambiados a la versión elegida.

Bonus

Se puede deshacer el último commit usando reset en una rama local, simplemente habría que hacer un reset al commit anterior al que estamos.

Esto se debe a que como no hemos hecho push no hemos avanzado en el historial y al hacer reset podemos volver al punto de partida.

Por ejemplo tenemos un commit en una rama local:

historial de la rama

Ahora vamos a deshacer el último, tenemos que seleccionar el anterior (Commit Id e52d15a7).

detalle del commit

Solo tenemos que pulsar la opción de reset eligiendo –hard si queremos deshacer el commit y borrar el contenido o –mixed si queremos deshacer el commit pero manteniendo el contenido, en este caso veremos los contenido del commit en la sección de changes.

contenido del commit usando –mixed

Como detalle final se puede hacer también un reset al último commit de la rama local, en ese caso lo que haríamos sería ponerlo como commit pendiente de realizar un push, es decir seguiría siendo un commit y no aparecía en changes.

Esto nos puede servir para modificarlo a través de un amend si queremos mantener el contenido pero además agregar otros nuevos.

Básicos Git (IV): Reset

Básicos Git (III): Revert

VisualStudio proporciona una opción de deshacer cambios que permite deshacer los cambios a los ficheros antes de crear el commit, esta opción de deshacer no realiza ninguna acción en Git.

Por ejemplo si realizamos cambios en uno o varios ficheros veremos que en la sección changes del plugin nos indica los ficheros que hemos cambiado y tendremos la opción de realizar undo changes.

Esta imagen tiene un atributo ALT vacío; su nombre de archivo es image.png
undo changes

Esto eliminará los cambios pendientes y dejará los archivos en su estado original sin crear un commit.

Revert pertenece a ese grupo de comandos pensado para deshacer cambios, en este caso revert lo que hace es crear un nuevo commit que contiene los ficheros originales previos al cambio, en este caso Git si se ve afectado.

El historial se mantiene por lo que no hay problema de usarlo con el equipo de desarrollo, si alguien del equipo está trabajando con la rama o la fusiona con una existente solo tendré que añadir el nuevo commit.

En el ejemplo hemos creado una rama develop y queremos deshacer el commit marcado en rojo (#3).

situación inicial

A continuación para deshacer un commit solo tendremos que ir al historial de la rama, seleccionar el commit que queremos deshacer y seleccionar la opción revert.

creando un revert

Esto provocará un nuevo commit con el nombre por defecto Revert «comentario del commit a deshacer».

comentario de un revert

Este revert se queda en el historial, por lo tanto se podrá ver tanto el commit original como el commit que lo deshizo, este nuevo commit lo que contiene son los contenidos originales que había en el commit #2 que fueron modificados por el commit #3.

A su vez este contenido de la forma habitual se puede fusionar con la rama master por lo que nos quedaría un árbol así.

merge con master de un revert.
Básicos Git (III): Revert

Básicos Git (II): Rebase

Existe otra forma de hacer una fusión de ramas y es con un comando rebase. Rebase lo que hace es insertar una rama dentro de otra en el mismo punto donde se creo de tal forma que parezca una sola rama.

Situación inicial

Para poder entenderlo bien vamos a manejar dos ramas, master y develop, creamos dos commits en develop y otros dos en master, el orden no es importante (Figura 1).

Situación inicial

En este punto master y develop divergen y tienen commits diferentes, vamos a aplicar un rebase para crear una sola rama con todos los cambios.

Aplicando rebase

Para tener una sola rama rama vamos a hacer un rebase, nos posicionamos en la rama master con los parámetros que se ven en la imagen y pulsamos rebase.

comando rebase usando el plugin de VisualStudio

Conflictos

Esto provocará varios conflictos, el rebase irá aplicando primero cada cambio de master a develop, los conflictos que se aplicarán a develop son:

situación final
  1. El primer commit de develop (que sería el existente) con el primer commit de master (Commit C1).
  2. El resultado del commit anterior con el segundo commit de master (Commit C2).
  3. Así sería sucesivamente si hubiera más commits.

Estos nuevos commits tendrán como comentarios los correspondientes a su commit de master.

Ahora para terminar el rebase solo queda aplicar estos cambios de la rama develop en la rama master provocando un último conflicto (Commit R), en este caso si que habrá que indicar un comentario.

Resultado

En la imagen anterior si nos fijamos en la rama master vemos que están todos los commits de develop insertados en la rama master (recuadro rojo) justo en el punto que se creo la rama develop, por lo que en master queda un arbol límpio con todos los commits.

Este sistema se suele utilizar más para actualizar ramas locales que para actualizar la rama master ya que se está alterando el historial de una rama (master en este caso).

Esto también provoca que se altere el cronograma por lo que podemos ver commits más recientes que aparecen más tarde en el historial, ya que prima el orden de creación más que la fecha de los mismos.

alteración del cronograma de git por rebase

Este sistema también puede crear un problema para el resto de desarrolladores que hayan creado otras ramas de master a posteriori lo que genera conflictos a veces complicados de resolver.

Generalmente no se recomienda hacer un rebase sobre ramas remotas por el peligro que conlleva para otros desarrolladores, sin embargo en ramas locales suele haber más disparidad de criterio.

Básicos Git (II): Rebase

Básicos Git (I): Cherry pick

El uso básico de Git es bastante conocido sin embargo hay algunos conceptos que se suelen nombrar y quizás a veces inducen al error, en este caso comentaré los más habituales que se suelen ver en el plugin de VS2017 para Git.

Para este ejemplo se ha creado una rama master y una rama develop que hereda de master. En el historial de Git del plugin de VisualStudio se vería de la siguiente forma.

historial de Git

Aquí podemos ver que nos está indicando con la marca roja master que master ha tenido commits hasta el commit id 9e3acdb5, con la marca roja develop vemos que esta rama está avanzada y tiene más commits que master.

Para este ejemplo solo hemos añadido un fichero en master y después en la rama develop hemos continuado haciendo modificaciones sobre este fichero.

Cuando fusionemos las ramas las dos estarán sincronizadas por lo que veremos algo como esto, las dos marcas rojas alineadas.

master y develop sincronizado

Cherry pick

El concepto de cherry pick es poder coger uno o varios commits de una rama y copiarlos a otra rama distinta, este concepto lo vamos a aplicar a la fusión (merge) de ramas.

La idea de un merge es fusionar dos ramas entre sí, la forma más habitual es fusionar en una rama destino (en este caso master con los commits en blanco) una rama origen (develop con los commits en azul) incluyendo en la rama destino tantos commits y en el mismo orden como commits tiene la rama origen. (Figura 1).

Existe otra opción que consiste en fusionar todos los cambios de una rama origen en una rama destino en un solo commit, es decir todos los commits de la rama origen se agrupan en uno solo y es este el que se inserta en la rama destino. (Figura 2).

merge vs cherry pick

El comando merge por defecto no hace un cherry pick, el plugin de VisualStudio permite hacerlo de dos formas diferentes, en ambos casos el comentario que se verá en la rama destino será el del último commit.

Cherry pick de una rama

En el caso de una rama entera habría que posicionarse en la rama origen de los commits y después hacer click en la opción cherry pick en la rama que va a recibir los commits.

En este ejemplo la rama develop va a recibir todos los commits de master.

cherry pick de una rama

Esta acción genera un conflicto al contrario que un merge normal, esto se debe a que cherry pick solo transfiere commits no es un merge en el sentido estricto, los errores de conflicto se solucionan de la forma habitual.

Cherry pick de commits individuales

Otra opción es seleccionar específicamente uno o varios commits y transferirlos a otra rama, en este caso nos situamos en la rama destino y abrimos el historial de la rama origen, seleccionamos los commits que nos interesen y hacemos click en cherry pick.

En este caso solo habrá conflictos si seleccionamos varios commits o elegimos uno que no sea inmediatamente posterior tanto en la rama origen como destino, es decir que si hicieramos cherry pick de cada commit en el mismo orden en la rama destino no deberíamos tener conflictos, realmente esta última forma es lo mismo que emular un merge normal.

cherry pick de commits individuales
Básicos Git (I): Cherry pick

Cambiar nombres de propiedades de navegación en base de datos con EFCore

Es bastante común que la nomenclatura que se use en código, habitualmente Camel (CustomerId), no sea la misma que usemos en base de datos (CUSTOMER_ID) no voy a entrar a valorar cual es mejor o peor pero es conocido que es un hecho bastante habitual.

EFCore proporciona muchos mecanismos para realizar estos mapeos y poder elegir el nombre que se prefiera pero me he encontrado con el siguiente caso (muy habitual).

public class Project 
{
    public int ProjectId { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public DateTime? End { get; set; }
    public DateTime Start { get; set; }
    public Customer Customer { get; set; }
}

Soy fan de FluentAPI y no me gusta meter anotaciones dentro de las clases, tampoco voy a entrar a discutir esto, así que me creo una clase de mapeo tal que:

public class ProjectMapping : IEntityTypeConfiguration<Project>
{
    public void Configure(EntityTypeBuilder<Project> builder)
    {
        builder.ToTable("PROJECT");
        builder.HasKey(c => c.ProjectId);
        builder.Property(c => c.ProjectId).HasColumnName("PROJECT_ID");

        builder.Property(c => c.Name).IsRequired().HasColumnName("NAME");
        builder.Property(c => c.Description).HasColumnName("DESCRIPTION");
        builder.Property(c => c.End).HasColumnName("END_DATE");
        builder.Property(c => c.Start).IsRequired().HasColumnName("START_DATE");

        builder.HasOne(x => x.Customer)
         .WithMany(y => y.Projects)
         .OnDelete(DeleteBehavior.Cascade);
}

Haciendo las migraciones, etc… nos crearía un SQL como:

CREATE TABLE [PROJECT] (
    [PROJECT_ID] int NOT NULL IDENTITY,
    [NAME] nvarchar(max) NOT NULL,
    [DESCRIPTION] nvarchar(max) NULL,
    [END_DATE] datetime2 NULL,
    [START_DATE] datetime2 NOT NULL,
    [CustomerId] int NULL,
    CONSTRAINT [PK_PROJECT] PRIMARY KEY ([PROJECT_ID]),
    CONSTRAINT [FK_PROJECT_CUSTOMER_CustomerId] FOREIGN KEY ([CustomerId]) REFERENCES [CUSTOMER] ([CUSTOMER_ID]) ON DELETE CASCADE
);

Vemos que todos los nombres son acordes a nuestra nomenclatura menos CustomerId que ha sido creada con el nombre por defecto de las convenciones, si quisiéramos cambiarlo podríamos añadir una propiedad CustomerId a la entidad mapearla como campo poniendo entonces el nombre que queramos y luego haciendo que la foreign key apunte a esta propiedad:

builder.Property(c => c.CustomerId).HasColumnName("CUSTOMER_ID");
builder.HasOne(x => x.Customer)
                .WithMany(y => y.Projects)
                .HasForeignKey(z=>z.CustomerId)
                .OnDelete(DeleteBehavior.Cascade);

Y esto nos sacaría el SQL que queremos:

CREATE TABLE [PROJECT] (
    [PROJECT_ID] int NOT NULL IDENTITY,
    [NAME] nvarchar(max) NOT NULL,
    [DESCRIPTION] nvarchar(max) NULL,
    [END_DATE] datetime2 NULL,
    [START_DATE] datetime2 NOT NULL,
    [CUSTOMER_ID] int NOT NULL,
    CONSTRAINT [PK_PROJECT] PRIMARY KEY ([PROJECT_ID]),
    CONSTRAINT [FK_PROJECT_CUSTOMER_CUSTOMER_ID] FOREIGN KEY ([CUSTOMER_ID]) REFERENCES [CUSTOMER] ([CUSTOMER_ID]) ON DELETE CASCADE
);

Ahora CustomerId tiene el nombre correcto que buscamos y además la clave foránea apunta a dicho campo. La verdad es que hasta aquí nada nuevo, el problema o la duda que me surgía era ¿se puede omitir crear la propiedad CustomerId? siendo realistas aunque puedo darle algún uso solo la he creado para poder mapear con un nombre acorde al resto de los campos de la tabla.

EFCore da más opciones con los llamados backing fields así que ahora si se puede hacer, lo que se hace es crear un campo shadow con el nombre que queramos y después usar dicho nombre en el mapeo, así que borramos la propiedad que hemos creado antes y hacemos lo siguiente:

  builder.Property<int>("CUSTOMER_ID"); // Genero un shadow
  builder.HasOne(x => x.Customer)
                .WithMany(y => y.Projects)
                .HasForeignKey("CUSTOMER_ID") // Lo uso aquí :)
                .OnDelete(DeleteBehavior.Cascade);

Esto nos genera exactamente el mismo SQL que en el caso anterior.

CREATE TABLE [PROJECT] (
    [PROJECT_ID] int NOT NULL IDENTITY,
    [NAME] nvarchar(max) NOT NULL,
    [DESCRIPTION] nvarchar(max) NULL,
    [END_DATE] datetime2 NULL,
    [START_DATE] datetime2 NOT NULL,
    [CUSTOMER_ID] int NOT NULL,
    CONSTRAINT [PK_PROJECT] PRIMARY KEY ([PROJECT_ID]),
    CONSTRAINT [FK_PROJECT_CUSTOMER_CUSTOMER_ID] FOREIGN KEY ([CUSTOMER_ID]) REFERENCES [CUSTOMER] ([CUSTOMER_ID]) ON DELETE CASCADE
);

Yendo un poco más lejos, ¿se podría hacer esto mismo con las tablas intermedias? así tendría solo objectos como propiedades de navegación y no tendría que tener propiedades extras para los mapeos de los campos de identidad.

En este caso hay un problema adicional, la clave primaria de la tabla intermedia suele estar compuestas por las claves primarias de las tablas a las que hace referencia y no se puede usar un objeto como clave primaria en el mapeo por lo tanto al quitarlas y dejar solo objetos no podría crear la clave primaria.

Resulta que ambos problemas se solucionan de la misma forma, para una entidad tal que:

    public class ProjectResource
    {
        public Resource Resource { get; set; }
        public Project Project { get; set; }
    }

Hacemos lo siguiente:

    public class ProjectResourceMapping : IEntityTypeConfiguration<ProjectResource>
    {
       public void Configure(EntityTypeBuilder<ProjectResource> builder)
       {
            builder.ToTable("PROJECT_RESOURCE");

            builder.Property<int>("PROJECT_ID"); // Campo que apunta a la tabla PROJECTS
            builder.Property<int>("RESOURCE_ID"); // Campo que apunta a la tabla RESOURCES

            builder.HasKey("PROJECT_ID", "RESOURCE_ID"); // Los uso para crear la clave primaria

            builder.HasOne(c => c.Resource)
                   .WithMany(d => d.ProjectResources)
                   .HasForeignKey("RESOURCE_ID"); // Y como clave foránea de RESOURCES
            builder.HasOne(c => c.Project)
                   .WithMany(d => d.ProjectResources)
                   .HasForeignKey("PROJECT_ID"); // y de PROJECTS
        }
    }

Ahora hay que hacer lo mismo en las tablas referenciadas para que sepan cual es el campo al que tienen que apuntar en la tabla intermedia, de otra forma crearían referencias duplicadas, cogemos de ejemplo la tabla RESOURCES (la otra tabla sería igual).

public class ResourceMapping : IEntityTypeConfiguration<Resource>
{
    public void Configure(EntityTypeBuilder<Resource> builder)
    {
        builder.ToTable("RESOURCE");
        builder.HasKey(c => c.ResourceId);

        builder.HasMany(c => c.ProjectResources)
               .WithOne(d => d.Resource)
               .HasForeignKey("RESOURCE_ID"); // El mismo nombre que hemos elegido antes
    }
}

Con esto estaría resuelto, además tendríamos todas las ventajas de los campos shadow.

Un detalle importante, las propiedades shadow  tienen que aparecer antes que las propiedades que las hacen referencia, ya sea una clave foránea o una clave primaria, de otro modo dará un error, supongo que son cosas del directo.

¡Hasta otra!

Cambiar nombres de propiedades de navegación en base de datos con EFCore

El intermediario

Cuando vi esta escena de la grandísima serie que es Halt and Catch Fire tuve que sobreponerme a las ganas de empezar a aplaudir, esta serie que en muchos momentos recoge lo mejor del mundo tecnológico lo había vuelto a conseguir y si a eso le sumamos este artículo que leí hace poco (los programadores son más importantes para las empresas que los directores de TI) la cosa se pone aún mejor.

Esta escena muestra bastante bien lo que muchos desarrolladores piensan sobre sus jefes de proyecto y posiblemente los jefes de proyecto pensarán aquello de “si te digo todo lo que pasa que no te quiero contar…”, esta situación acaba llevando a un sutil enfrentamiento y sectarismo entre “gente con traje vs gente sin traje”.

Siempre ha existido un miedo irracional hacia lo desconocido que confunde y a menudo irrita al ser humano haciéndole tomar actitudes que generalmente no son muy correctas o sensatas, más o menos la actitud que toma un jefe de proyecto ante un grupo de desarrolladores cuando estos le intentan explicar algo, generalmente el jefe de proyecto no es técnico -ni intención que tiene- solo le interesa saber cuándo se va a entregar algo y que no le engañen diciendo cuatro meses cuando pueden ser tres.

De todas formas, ante la duda el jefe de proyecto siempre sacará alguna queja –puede que sin demasiado sentido- y de paso te quitará dos semanas, -que seguro que ya será menos el tiempo que hace falta-, lo que el jefe de proyecto no sabe es que ante esto la próxima vez el desarrollador le meterá un mes más, como el lazarillo de Tormes si tu coges dos uvas yo cojo tres, pero no nos decimos nada, -y la rueda del absurdo sigue girando-.

Tomando una actitud “zen” -que llegado cierto punto es la mejor opción- la respuesta fácil sería decir, las probabilidades de que un proyecto se cumpla en plazo es inversamente proporcional a su tamaño, es decir que si dices que entregas en una semana puede que sea verdad, pero si dices que entregas en un año, seguro que al final es año y tres meses.

En cualquier caso esta sensación de “no sé qué me han contado” del jefe de proyecto también la tiene el cliente, el jefe de proyecto que no se acaba de enterar muy bien le habla a su cliente sobre el proyecto -y le transmite esta inseguridad-, este acaba haciendo lo mismo, realmente no se está enterando muy bien de que le están hablando pero tampoco va a parecerlo así que plantea dudas, quejas y plazos que igual no tienen nada que ver con el proyecto, y en particular con lo que el cliente realmente necesita -pero llegado ese punto el jefe de proyecto hace un «si a todo»-, lo cual se traslada finalmente al equipo de desarrollo que se queda pensando aquello de “hay días que no se para que me molesto”, -el grado de frustración pasa de lineal a exponencial-.

Algunos dirían que esto se arregla con un jefe de proyecto con antecedentes tecnológicos, el problema es que la tecnología avanza a veces demasiado rápido y es fácil quedarse atrás y acabar volviendo al punto anterior encima con la idea de “yo que fui desarrollador…” el desenlace final suele ser aún peor.

Sin embargo hay matices, depende de la empresa, del cliente, con quien trabajes… es posible que los plazos se puedan mantener y todo vaya bien, pero si la empresa no es muy grande y no tiene capacidad negociadora, o el cliente es demasiado importante o el presupuesto escaso… entonces los plazos se piden por cortesía pero es probable que acaben siendo otros –y volvemos al primer punto-.

Hace algún tiempo desde una empresa me contactaron para un proceso de selección que necesitaba a una persona que sirviera de enlace entre el equipo de desarrollo y el de gestión, que fuera capaz de “entender” a los programadores y luego “traducir” todo eso en plazos y tiempos de forma razonada, algo así como “no sé si los desarrolladores me engañan o es verdad, pero tengo que dar una fecha y plazos que tengan sentido y se cumplan».

Yo he de reconocer que me alegré, no porque me hubieran contactado –que también- sino que por fin después de años alguien se había dado cuenta que hace falta “un intermediario” que sea capaz de hacer ese trabajo, que por un lado sea técnico –generalmente de buen nivel- y ejerza como tal pero que también entienda que los proyectos se hacen para entregarlos y sea capaz tanto de trasmitir esa idea al equipo, así como a su vez transmitir a gestión por parte del equipo aquella gran frase de “nueve mujeres no paren en un mes”.

De hecho, yendo más allá en los últimos tiempos he tenido ocasión de observar empresas creadas solamente por desarrolladores y aunque es verdad que hace falta gente de todo tipo en una empresa- la verdad es que les está yendo muy bien.

Quizás lo que algunas empresas necesitan es un perfil así, no solo mejorarían su productividad, sino que la empatía entre “gente con traje vs gente sin traje” aumentaría, lo que posiblemente es el mayor beneficio.

 

 

 

El intermediario

Torrent en Raspberry Pi (Deluge & Transmission) con Apache

Actualizado a 3 de Septiembre de 2020

Instrucciones para instalar torrent manualmente en un servidor ya existente para proporcionarle más funcionalidad, en este caso estas son las instrucciones para los dos más populares, Deluge y Transmission.

Primero actualizar la Raspberry pi

sudo rpi-update
sudo apt-get update
sudo apt-get upgrade
sudo apt-get dist-upgrade

Vamos a utilizar el cliente Deluge así que instalamos los paquetes:

sudo apt-get install deluged
sudo apt-get deluge-console

Ahora hay que arrancar el demonio en Linux y después lo paramos, esto es para que cree la configuración por defecto.

sudo deluged 
sudo pkill deluged

Primero un backup de la configuración original y editamos el archivo, al haber instalado con sudo los ficheros estarán en la carpeta /root.

sudo cp /root/.config/deluge/auth /root/.config/deluge/auth.old.nano
sudo nano /root/config/deluge/auth

Ahora hay que cambiar la línea que aparece en fichero con este formato user:password:level, esta cuenta sirve para administrar raspberry, en este caso usamos los valores por defecto de raspberry como ejemplo, donde 10 indica máximo nivel de permisos, pi:raspberry:10 y ahora arrancamos el demonio y arrancamos la consola.

sudo deluged 
sudo deluge-console

En esta consola arrancamos los siguientes comandos: config -s allow_remote True, esto habilita las conexiones remotas, para verificar el valor se puede usar el comando config allow_remote, para salir basta con usar exit.

Ahora reiniciar para hacer efectivos los cambios.

sudo pkill deluged
sudo deluged

En web de Deluge existen clientes de escritorio para instalar, pero en este caso vamos a usarlo vía web.

sudo apt-get install python-mako
sudo apt-get install deluge-web
sudo deluge-web &

El puerto por defecto es 8112, si se quiere cambiar se puede hacer con el siguiente comando, en el fichero cambiar la línea port por el puerto (superior a 1000), en este mismo fichero completamos la línea: «default_daemon»:»localhost:58846″

sudo pkill deluge-web
sudo nano /root/.config/deluge/web.conf

Ahora se puede acceder a la dirección web http://raspberry:8112 la cual abrirá una web con una contraseña que por defecto es deluge.

Después de esto es necesario reiniciar y arrancar de nuevo.

sudo pkill deluged
sudo deluged

Ahora solo queda configurar Deluge para que arranque en el inicio, para esto editamos el archivo /etc/rc.local y agregamos las siguientes líneas al final del archivo (antes de la línea exit 0)

sudo -u pi /usr/bin/python /usr/bin/deluged
sudo -u pi /usr/bin/python /usr/bin/deluge-web

Ahora vamos a instalar transmission:

Primero instalar los paquetes:

sudo apt-get update
sudo apt-get install transmission transmission-daemon

Ahora paramos el servicio para poder editar los ficheros de configuración

sudo service transmission-daemon stop

Ahora editamos el fichero:

sudo nano /etc/transmission-daemon/settings.json

Y modificamos los siguientes parámetros, en este caso los parámetros «download-dir» y «incomplete-dir» apuntan a la carpeta donde se guardará el torrent y la carpeta temporal donde se ubican mientras se van descargando. La carpeta «watch-dir» es la carpeta donde se dejan los torrent que se descargarán automaticamente.

"blocklist-enabled": true,
"blocklist-url": "http://john.bitsurge.net/public/biglist.p2p.gz",
"download-dir": "/mnt/samba/hdd/download", 
"incomplete-dir": "/mnt/samba/hdd/temp", 
"incomplete-dir-enabled": true, 
"rpc-password": "TuPassWord", 
"rpc-username": "transmission", 
"rpc-whitelist-enabled": false,
"watch-dir":"/mnt,
"rpc-host-whitelist-enabled": false,

Ahora tenemos que cambiar el usuario con el que se ejecuta para poder hacerlo con el usuario pi, esto es especialmente útil sobre todo si hemos usado el usuario pi para mapear una unidad de red o una unidad USB.

Primero detenemos el demonio.

service transmission-daemon stop

Cambiamos el usuario

sudo nano /etc/init.d/transmission-daemon

y cambiamos:

USER=debian-transmission por USER=pi

También tenemos que editar el siguiente fichero:

nano /etc/systemd/system/multi-user.target.wants/transmission-daemon.service

Y ponemos esto:

[Unit]
Description=Transmission BitTorrent Daemon
After=network.target
[Service]
User=pi
Type=forking
PIDFile=/var/lib/transmission-daemon/.config/transmission-daemon/trans.PID 
ExecStart=/usr/bin/transmission-daemon --pid-file /var/lib/transmission-daemon/.config/transmission-daemon/trans.PID --config-dir /var/lib/transmission-daemon/.config/transmission-daemon/ 
[Install] 
WantedBy=multi-user.target

Ahora creamos el archivo pid y configuramos los permisos:

touch /var/lib/transmission-daemon/.config/transmission-daemon/trans.PID 
chown pi.pi /var/lib/transmission-daemon/.config/transmission-daemon/trans.PID

Ahora cambiamos el propietario de los ficheros:

chown -R pi.pi /var/lib/transmission-daemon 
chown -R pi.pi /etc/transmission-daemon/settings.json 
chown -h pi.pi /var/lib/transmission-daemon/info/settings.json

La dirección por defecto para acceder es:

http://127.0.0.1:9091/transmission

Hay que tener en cuenta que el fichero settings.json se tiene que editar cuando el servicio esté parado de lo contrario al pararse sobreescribe con los cambios actuales.

Ahora vamos a ver como configurar estas rutas por algo más fácil de recordar, para ello en Apache creamos un virtual host que nos haga de proxy con el servidor de torrent, creamos un fichero en la carpeta /etc/apache2/sites-available/004-torrent.conf con el siguiente contenido, obviamente deberemos tener un sistema DNS que nos resuelva correctamente el nombre torrent, vamos a suponer que el servidor está en la dirección 192.168.1.4

 <VirtualHost torrent:80>
   ServerName torrent.domain
   ServerAlias torrent
   ProxyPreserveHost Off
   ProxyRequests On
   ProxyPass / http://192.168.1.4:9091/
   ProxyPassReverse / http://192.168.1.4:9091/
   CustomLog /var/log/apache2/torrent.log combined
   ErrorLog /var/log/apache2/torrent.error.log
 </VirtualHost>

Ahora habilitamos el sitio usando el archivo de configuración que hemos creado

sudo a2ensite 004-torrent.conf

Después de reiniciar el servidor podremos acceder al servidor torrent usando la dirección http://torrent

Finalmente es posible que haya problemas con las carpetas watch de torrent si están en un disco externo ya que es posible que no detecte los cambios, así que vamos a configurar una carpeta por samba para poder dejar los ficheros ahí, así que editamos el fichero /etc/samba/smb.conf e incluimos el siguiente contenido:

[torrent]
comment = Carpeta para dejar torrents que se descargarán automaticamente
path = /media/torrent
read only = No
force user = pi
force group = pi
create mask = 0660

En este caso estamos suponiendo que la carpeta donde se colocarán los torrent está en /media/torrent

En caso de reinstalar la aplicación si no aparecen todos los ficheros que hemos descargado previamente es porque tenemos que rellenar la carpeta torrents con todos los torrents que nos hayamos descargado, podemos copiar el contenido de la antigua carpeta torrents en la nueva.

Enlaces:

https://www.alvaroreig.com/como-configurar-un-proxy-inverso-con-apache/

https://www.vichaunter.org/como-se-hace/cambiar-nombre-usuario-transmission-linux

https://www.vichaunter.org/como-se-hace/instalar-configurar-transmission-raspbian-raspberry-pi

Torrent en Raspberry Pi (Deluge & Transmission) con Apache