Git

Esto no es un reemplazo de las guías de Git que ya existen en Internet. Es mas bien un resumen que contiene dos temas:
 * Enlaces que probaron ser útiles para conocer Git
 * Convenciones de como usar Git

=Git checklist= Esta es una lista de temas que debería de saber como programador al usar Git. El objetivo es puntear cada uno de los elementos y ver en donde debo hacer fuerza. El resto de la página tiene documentación que puede ayudar en este proceso.

Lo mínimo

 * 1) Tener una cuenta de GitHub
 * 2) Poder clonar proyectos
 * 3) Configurar mi e-mail para los commit.
 * 4) Agregar/Eliminar archivos
 * 5) Hacer commit
 * 6) PUSH y PULL
 * 7) Trabajar con remotos

Lo deseable (si quiero colaborar en un proyecto)

 * 1) Crear branches
 * 2) Merge
 * 3) Conocer el workflow del proyecto
 * 4) Escribir buenos mensajes de commit
 * 5) Participar en un pull request
 * 6) Rebase y squash.

=Git startup=
 * 1) Lo primero que debemos hacer para arrancar con Git es tener una cuenta en GitHub.
 * 2) Lo siguiente es seguir el tutorial propio de GitHub.
 * 3) Practicar. Todo el tutorial del punto 2 hay que efectivamente hacerlo con un usuario.
 * 4) La referencia fundamental es Pro Git Book. Capítulos 1,2,3 bien leídos.
 * 5) Dentro de este canal de youtube hay varios videos. El promedio será de 3min, es muy bueno para verlos de a poco e ir incorporando las prácticas.

=GIT Workflow= Utilizamos el Git Workflow con las siguientes ramas:
 * master: Contiene solamente la versión estable del código
 * develop: Funciona como un integrador de diferentes features
 * (feature-branch): Es el branch en el que se está trabajando para un nuevo feature.

Los desarrolladores deben hacer un pull de "develop", luego un branch para el feature/corrección y trabajar en este. Nunca directamente en develop y menos en master. El branch en el que trabajan es el que se conoce como un "feature-branch" (topic dentro del siguiente gráfico).

Un feature-branch nunca puede ser mergeado directamente a master, tiene que ir primero a develop. Idealmente el proceso de ir a develop será a través de un "Pull Request" (mas adelante se verá esto).

Obs.: Mayor información en el libro de Pro Git

Links interesantes con un workflow similar.
 * 1) https://www.atlassian.com/git/workflows#!workflow-gitflow
 * 2) http://nvie.com/posts/a-successful-git-branching-model/

Crear un nuevo repositorio
Una opción para crear un nuevo repositorio es utilizar la linea de comando. touch README.md git init git add README.md git commit -m "first commit" git remote add origin git@github.com:alefq/prueba-remote.git git push -u origin master OBS: El URL del remote, debe ser reemplazado con el que corresponda al remote que se quiere utilizar.

Clonar un repositorio
Si vamos a trabajar en un proyecto que ya existe, entonces clonar es el primer paso. Clonar crea una copia local del repositorio Git. git clone URL

Obs.: Clonar un repositorio de mucho tamaño

A medida que se incrementan las versiones o la cantidad de datos históricos en un repositorio, puede resultar en una gran cantidad de metadatos, en estos casos se recomienda hacer un shallow clone, sin el histórico, cuando el objetivo es sólo probar algo o no se necesita versiones anteriores. Ejemplo: git clone --depth 1 URL

Actualizar copia local
Los desarrolladores hacen un pull de develop para actualizar la copia local con los últimos datos del repositorio remoto. git checkout develop git pull

Crear branch
Se hace un branch de develop para trabajar en un nuevo feature. git checkout -b mi_nuevo_killer_feature

Ciclo de commits y push
Acá se entra dentro de un ciclo de commit y push al repositorio remoto. A continuación se muestra un ciclo sencillo.

git add file git commit -m "Modifica un typo en la documentacion" git push origin mi_nuevo_killer_feature Obs.: "commit -m" no se recomienda. Para ver como escribir buenos commits, es importante mirar la sección de commits.

Si es un branch compartido (como el develop) es probable que existan cambios en el repositorio remoto que no están dentro de la copia local. Por lo tanto, habrá la necesidad de hacer un pull.

Merge al develop
Se debe hacer un merge de vuelta al develop para integrar los cambios. git checkout develop git merge mi_nuevo_killer_feature

Eliminar un branch
Una vez que el branch está juntado (merged) al develop puede ser borrado de manera segura. $ git branch -d mi_nuevo_killer_feature Obs.: Git avisa si es que no se hizo el merge del branch, así que no hay problema en ejecutar el comando.

=Comparar (diff)= Es común en el proceso de desarrollo tener que comparar archivos en distintas versiones o inclusive distintos branches. El comando para hacer esto es git diff, resumimos a continuación sus distintas versiones:

Comparaciones entre dos branches
Este comando te muestra los archivos modificados entre el branch develop y el branch nuevo-branch

$ git diff --stat --color develop..nuevo-branch Ejemplo de salida en la consola src/main/java/py/org/demo/asistente/business/UsuarioBC.java    |  4 ++-- src/main/resources/META-INF/persistence.xml                    |  1 + src/main/webapp/WEB-INF/web.xml                                | 70 ++++++++++++++++++++++++++++-- src/main/webapp/login.jsp                                      |  2 +- 4 files changed, 32 insertions(+), 45 deletions(-)

=Deshacer merge(reset)= Hay ocasiones en las que es necesario revertir algun cambio el cual ya fue mezclado con la rama principal o alguna otra rama. El comando que nos ayuda para esto es reset. A continuacion se muestra un resumen para deshacer el merge.

Deshacer merge en repositorio remoto
Supongamos que la rama principal es develop, y queremos revertir un merge hecho alli. Entonces nos posicionamos en la rama develop. $ git checkout develop Hacemos un git log para copiar el hash del commit a revertir commit ad9a52ef3b9f39afa4f315cdd5511dc3bf66149c Merge: c71612cc d0e7247b Date:  Wed Jun 26 10:43:47 2019 -0400

Merge branch 'develop' into LINC-583

commit d0e7247b16045ef6620efdeaf91b855fa9340410 Merge: 9ee590de 6301dc4f Date:  Tue Jun 25 20:02:00 2019 +0000

Merge branch 'LINC-612' of jvillagra/soft-backend into develop En nuestro caso sera: d0e7247b16045ef6620efdeaf91b855fa9340410. Luego ejecutamos el siguiente comando: $ git reset --hard d0e7247b16045ef6620efdeaf91b855fa9340410 Con esto de forma local ya se habra revertido. Para confirmar podemos hacer un git log donde veremos que los cambios quedaron de la manera esperada. Por ultimo hacemos un push al repositorio remoto para afectar con estos cambios. $ git push upstream develop --force

=Sincronización de cambios en repositorios a nivel Git Upstream=

Ajustar remotes
El fork del usuario debe ser el origin, y el repositorio del cual se hizo el fork debe ser el upstream. git remote add origin URL-AL-FORK git remote add upstream URL-AL-REPOSITORIO-FINAL

Sincronizar repositorios
Una vez que se realizaron los cambios, para sincronizar los cambios en el repositorio final, se debe hacer el fetch de los mismos. git fetch upstream

Al posicionarse en la rama que contiene los cambios realizados en el fork, se debe intentar realizar el rebase de los cambios hechos en el upstream

El rebase hace que git vaya al ancestro común de ambas ramas (donde estás actualmente y de donde se desea reorganizar), saque las diferencias introducidas por cada confirmación en la rama donde estás, guarde esas diferencias en archivos temporales, reinicie (reset) la rama actual hasta llevarla a la misma confirmación en la rama de donde quieres reorganizar, y, finalmente, vuelva a aplicar ordenadamente los cambios. git rebase upstream

Si este rebase no se realiza correctamente (surgen conflictos al intentar realizar la sincronización), se puede intentar hacer un merge.

Trabajando con otros remote
Para pasarse a la rama deseada sin tener un remote al que apuntar (detached). git checkout nombre-repositorio-remoto/nombre-rama En caso de que se deseen realizar cambios en la rama es necesario crear una rama local que apunte al repositorio remoto. Esto es posible con este código: git checkout --track nombre-repositorio-remoto/nombre-rama

Como hacer merge de dos branches
La idea central es la siguiente: Si tengo que mergear dos branches, se sugiere crear un tercer branch, y realizar ahí la mezcla. Entonces si algo sale mal, simplemente se deshecha ese tercer branch creado. En este ejemplo "develop" tiene los cambios que quiero integrar al "branch-con-cambios", y asumimos que branch-con-cambios nacio de develop git checkout develop git checkout -b branch-temporal-para-el-merge git merge branch-con-cambios .... revisar nuevo código y resolver conflictos.... git commit git checkout develop git merge branch-temporal-para-el-merge Al pasar los cambios a develop se realizará un "fast forward" que es la estrategia que nos permite juntar los cambios sin tener que editar los archivos a mano.

=Utilidades del día a día= Durante el proceso de desarrollo nos dimos cuenta que los siguientes procesos son repetitivos, así que decidimos documentarlos.

Volver atrás
Vuelve el archivo a la última versión que se hizo commit $ git checkout HEAD -- nombre_archivo

Corregir el último commit
Esto puede hacerse para agregar un nuevo archivo que falto agregar al stagging area o simplemente para corregir un mensaje. git add file git commit --amend Obs.: Si no hay nada que agregar, simplemente nos podemos saltar el git add file

Descartar o mover cambios
Se pueden descartar temporalmente cambios y commits hechos en un branch, realizar otras acciones (ej. cambiarse de rama, hacer otros commits, hacer releases, etc.) y luego recuperar esos cambios descartados.

Para descartar los cambios: git stash

Para recuperar esos cambios (en cualquier rama): git stash pop

Eliminar commits duplicados
Fuente: https://stackoverflow.com/a/17577876/195904

Si te encuentras con la situación:

...
 * | | | * cc9ee142 2018-03-12 | Release Internal test 3.0.70. Arreglos post review " (tag: internal-test-v3.0.70) [Hans Solo]
 * | | * | ba403a44 2018-03-12 | Release Internal test 3.0.70. Arreglos post review " [Hans Solo]
 * | * | | 96d4d732 2018-03-12 | Release Internal test 3.0.70. Arreglos post review " [Hans Solo]
 * | |  badc4249 2018-03-23 | Merge branch 'JOKO-45' of c3po/falcon-movil into develop (upstream/develop) [Hans Solo]

Una posible causa; es que la historia del branch original y de tu branch se han desfazado. Si estás seguro de que todos los cambios están en tu "upstream", puedes eliminar los commits duplicados con

git rebase -i  En el ejemplo: git rebase -i badc4249

Entonces tendrás de elegir los commits que deseas mantener, el GIT te proporcionará ayuda contextual al momento de realizar el rebase.

Una vez escogidos los commits que deseas eliminar y mantener. Debes forzar el push.

git push -f origin develop

''WARNING: You are rewriting history doing this. Doing this with changes that have been pushed to a remote repo will cause issues. I recommend only doing this with commits that are local.''

=Convención para commit= La base de convenciones para commit están expuestas en este blog de Chris Beams. A continuación se presenta un resumen de los mandamientos y una breve justificación, el blog lo trata de manera extensa.


 * 1) Separar el asunto del commit con una línea en blanco: Al escribir el mensaje del commit en el editor de texto, la primera línea se considera el asunto del commit, una línea en blanco a continuación se considera un separador, y el texto restante se considera el cuerpo del commit.
 * 2) Limitar la línea del asunto a 50 caracteres: esto ayuda a que el asunto sea breve y vaya directo al grano.
 * 3) Mayúscula inicial en el asunto: hace presentable el asunto
 * 4) No agregar un punto al final del asunto: ahorra un caracter ;)
 * 5) Usar modo imperativo en el asunto: básicamente es indicar qué ocurriría al aplicar el commit. Un ejercicio interesante es completar la oración "si es aplicado, este commit ", al poner un asunto en modo imperativo la oración tiene sentido. Si la oración no tiene sentido no estamos usando el modo imperativo. Ejemplo bueno: "si es aplicado, este commit ". Ejemplo malo: "si es aplicado, este commit <Íconos para transaction tips>".
 * 6) Limitar el cuerpo del commit a líneas de 72 caracteres: la extensión del cuerpo puede contener tantas líneas como sea necesario, varios párrafos, pero siempre que cada línea tenga hasta 72 caracteres de largo.
 * 7) Usar el contenido del cuerpo para explicar los "qués" y "porqués" y no los "cómos": En el mensaje del cuerpo tenemos la posiblidad de justificar nuestros cámbios. Cómo se llevo a cabo el cambio se puede ver directamente en el código.

En el blog se muestra el siguiente commit como un ejemplo. Es una copia de un commit del core de Bitcoin.

commit eb0b56b19017ab5c16c745e6da39c53126924ed6 Author: Pieter Wuille  Date:  Fri Aug 1 22:57:55 2014 +0200

Simplify serialize.h's exception handling

Remove the 'state' and 'exceptmask' from serialize.h's stream implementations, as well as related methods.

As exceptmask always included 'failbit', and setstate was always called with bits = failbit, all it did was immediately raise an  exception. Get rid of those variables, and replace the setstate with direct exception throwing (which also removes some dead  code).

As a result, good is never reached after a failure (there are  only 2 calls, one of which is in tests), and can just be replaced by !eof.

fail, clear(n) and exceptions are just never called. Delete them.

=Convención para Modelo de ramas=

Las ramas principales
El repositorio central tiene dos ramas principales con una vida infinita:

master develop

La rama master en el origen debe ser familiar para todos los usuarios de Git. Paralela a la rama master, existe otra rama llamada develop.

Consideramos que origin/master es la rama principal donde el código fuente de HEAD siempre refleja un estado listo para producción.

Consideramos que origen/develop es la rama principal donde el código fuente de HEAD siempre refleja un estado con los últimos cambios de desarrollo entregados para la próxima versión. Algunos llamarían a esto la rama de integración.

Cuando el código fuente en la rama develop alcanza un punto estable y está listo para ser lanzado, todos los cambios deben fusionarse nuevamente en master de alguna manera y luego etiquetarse con un número de release. Por lo tanto, cada vez que los cambios se fusionan nuevamente en la rama master, esta es una nueva versión de producción por definición.

Ramas de apoyo
Además de las ramas principales, nuestro modelo de desarrollo utiliza una variedad de ramas de apoyo para ayudar al desarrollo paralelo entre los miembros del equipo, facilitar el seguimiento de las nuevas funcionalidades, prepararse para lanzamientos de producción y ayudar a solucionar rápidamente problemas de producción en vivo. A diferencia de las ramas principales, estas ramas siempre tienen un tiempo de vida limitado, ya que eventualmente se eliminarán.

Los diferentes tipos de ramas que podemos usar son: Feature branches Hotfix branches

Cada una de estas ramas tiene un propósito específico y están sujetas a reglas estrictas sobre qué ramas pueden ser su rama de origen y qué ramas deben ser sus objetivos de fusión.

Ramas Feature
Pueden ramificarse de: develop

Deben fusionarse nuevamente en: develop

Convención de nomenclatura de rama: feature/[cualquier nombre según convención excepto master, develop, release* o hotfix*]

Las ramas de tipo feature se utilizan para desarrollar nuevas características/funcionalidades para el próximo lanzamiento o un futuro lejano. Al comenzar el desarrollo de una característica, la versión de destino en la que se incorporará esta característica puede ser desconocida en ese momento. La esencia de una rama feature es que existe mientras la característica/funcionalidad esté en desarrollo, pero finalmente se fusionará nuevamente a la rama develop (para agregar definitivamente la nueva característica a la próxima versión) o se descartará (en caso de ser una rama experimental).

Ramas Hotfix
Pueden ramificarse de: master

Deben fusionarse nuevamente en: develop y master

Convención de nomenclatura de rama: hotfix/[cualquier nombre según convención excepto master, develop, release*]

Las ramas hotfixsurgen de la necesidad de actuar inmediatamente sobre un estado no deseado de una versión de producción en vivo. Cuando un error crítico en una versión de producción debe resolverse de inmediato, una rama de revisión puede separarse del tag correspondiente en la rama master que marca la versión de producción.

La esencia es que el trabajo de los miembros del equipo (en la rama de desarrollo) puede continuar, mientras que otra persona está preparando una solución rápida de producción.

Otras Ramas de apoyo
Se pueden crear otras ramas de apoyo dependiendo de la necesidad que no necesariamente entran en la categoría de features o hotfixes. Ej. ramas de refactor de código o bug fixes menores sobre la rama de desarrollo.

Nombres de Ramas
Deben:


 * 1) Incluir un breve resumen descriptivo. En caso de verbos usar tiempo presente imperativo.
 * 2) Incluir el tipo de trabajo: feature, refactor, bugfix, hotfix, etc.
 * 3) Usar slashes para separar las partes de la rama
 * 4) Usar guiones para separar palabras

Opcional:
 * 1) Se puede incluir la identificación del ticket / historia correspondiente (por ejemplo, de Jira, GitHub issue, etc.)

Formato:

{tipo de trabajo} / {resumen de 2-3 palabras} / {historia o id del ticket}

Ejemplos:

git checkout -b feature/menu-lateral git checkout -b feature/spring-migration/TICKET-244

=Pull request= Cada usuario debe trabajar en un fork propio del proyecto. Pre-requisitos para solicitar el PR:
 * Evaluar con el sonarqube y eliminar los issues BCM
 * Los test unitarios y de integración deben quedar funcionando

Testear PR envíados desde Forks
En muchos casos surge la necesidad de probar un PR enviado por alguien que no tiene acceso push al repositorio, por lo que lo envió desde un fork(bifurcación) del repositorio principal. Cuando alguien envía un PR, se crea una referencia de Git (ref) que almacena la referencia a los commits en el repositorio. Podemos ver esto ejecutando el siguiente comando:

git ls-remote --refs origin

Verás todas las referencias en el remoto con nombre origin, incluido cierto número de referencias en el formato refs/pull/77/head. En el caso de refs /pull/77/head, es una referencia al encabezado de la rama para el PR #77 en el repositorio. Sabiendo que el repositorio almacena esa información de esta manera, podemos buscar esa referencia en el repositorio local y verificar la rama con el siguiente comando:

git fetch origin pull/77/head:pr/77 && git checkout pr/77 Ahora nos encontraremos en una rama local llamada pr/77 que contiene el código del PR #77.

=Configuraciones=

Configuración de usuario y mail
Este es el usuario con el que se realizarán los commits y push. $ git config --global user.name "John Doe" $ git config --global user.email johndoe@example.com

Para confirmar las configuraciones git config --list

Configuración de colores
$ git config --global color.status auto $ git config --global color.branch auto $ git config --global color.interactive auto $ git config --global color.diff auto

Configuracion de contraseñas/credenciales
Para guardar de forma definitiva las credenciales de un server https git config credential.helper store

Git y https (ssl)
Para aceptar certificados self-signed git config --global http.sslVerify false Para guardar la contraseña por 1 hora git config --global credential.helper "cache --timeout=3600" OBSERVACIÓN: Por recomendaciones de seguridad, no se debe hacer que la contraseña quede guardada permanentemente a menos que se tenga el sistema de archivos encriptado.

Creando un archivo de configuración local
Es posible crear un archivo de configuración donde almacenar las configuración personalizada para un repositorio local. Ej: colores, editor de mensajes, shorcuts para comandos de git. etc. Se debe tener en cuenta que no es posible incluir automáticamente un archivo de configuración personalizada solo a través de git, porque crea una vulnerabilidad de seguridad. Si este archivo será compartido con otras personas se debe tener cuidado de no almacenar datos personales en el mismo como user.*, Es mejor mantener estos datos en la configuración global.

Para tener una configuración local se debe crear manualmente un archivo llamado .gitconfig en la raíz del proyecto y luego se debe ejecutar el siguiente comando para cargarlo.

git config --local include.path ../.gitconfig

Ejemplo

Crearemos una configuración personalizada para facilitar el trabajo con PR envíados desde forks. Para esto utilizaremos alias de modo a crear shorcuts para los comandos.

1) Creamos el archivo .gitconfig con el siguiente contenido:

[alias] ls-pr= ls-remote --refs origin co-pr = !sh -c 'git fetch origin pull/$1/head:pr/$1 && git checkout pr/$1' -

2) Cargamos la configuración utilizando el comando:

git config --local include.path ../.gitconfig

Ahora para actualizar las referencias podemos simplemente ejecutar: git ls-pr en lugar de git ls-remote --refs origin

Así mismo para crear una rama loca con el código de un PR y hacer checkout a la misma podemos ejecutar:

git co-pr 77 en lugar de git fetch origin pull/77/head:pr/77 && git checkout pr/77

=Trabajando con remotes= Los remotes son los destinos a donde el código será enviado. Resumimos los comandos mas utilizados en relación a esto.

Agregar repositorio remoto
Si queremos utilizar un repositorio remoto diferente al que ya tenemos, se debe agregar el repositorio remoto a la lista de repos locales.

git remote add nombre-nuevo-repo-local repository-url Para comprobar que el repositorio fue agregado podemos ejecutar: git remote -v

Una vez hecho esto, debemos agregar el contenido a nuestro repo local.

git fetch nombre-nuevo-repo-local

Cambiar url del origin
Para cambiar la url del repositorio al cual apunta un remote (e.j. origin), se debe ejecutar el siguiente comando:

git remote set-url origin urlNueva Ejemplo, se clonó originalmente con https, y se quiere cambiar a SSH. git remote set-url upstream git@github.com:jokoframework/security.git

=Tags= Las etiquetas se utilizan para congelar un punto importante. Se utiliza normalmente para marcar hasta que commit incluye que versión del software.

Existen dos tipos de tags, los ligeros que son como un branch que no se puede modificar y los tags anotados que son los que nos interesan.

Los tags anotados se almacenan como un objeto en la base de datos de Git y eso permite agregar información particular para el tag, como por ejemplo: un mensaje, nombre del autor, mail, etc.

Para más información sobre etiquetado puede consultar el capítulo 2.6 del libro oficial de GIT-SCM en este link.

Listar
$ git tag

Crear
A modo de ejemplo crearemos un tag con el nombre release-v1.2. Es importante notar que no se utilizan comillas para el nombre del tag.

$ git tag -a release-v1.2 -m "este es un mensaje con explicación de en que consiste mi tag."

Subir a un repositorio remoto
A modo de ejemplo subiremos el tag release-v1.2 al repositorio remoto origin.

$ git push origin release-v1.2

Crear un branch nuevo desde un tag
A modo de ejemplo crearemos un branch nuevo llamado release-v1.2-fix a partir del tag release-v1.2

$ git checkout release-v1.2
 * Nos movemos al tag. Recuerda que no puedes realizar modificaciones dentro de un tag.

$ git checkout -b release-v1.2-fix
 * Creamos un nuevo branch desde el tag.

Sincronizar tags
Para sincronizar todos los tags en un branch de una sola vez se debe ejecutar el siguiente comando: git push --tags

=Windows Workarounds=

Problemas al clonar proyectos (Git Bash - Windows)
Cuando queremos clonar algun proyecto, pero al intentarlo, nos pide una contraseña de usuario, podemos probar la siguiente solución:

- Ir al putty gen, leer la clave privada desde el directorio donde se encuentra y exportarla con la opción "Export OpenSSH key" que se encuentra dentro de la pestaña Conversions.

- Es importante verificar que las claves, pública y privada, estén guardadas dentro de la carpeta ".ssh" (normalmente en C:\Users\nombre_usuario).

Eliminar branch locales y remotos
Para eliminar un branch local

git branch -d the_local_branch Para eliminar el branch remoto git push origin :the_remote_branch Con git 1.7.0 o superior, se puede abreviar en un comando git push origin --delete the_remote_branch

Pushear rama local al repo git remoto:
(rama local que no exista aún en el repo remoto) git push -u origin 