Git – Reference Guide

English version of this post.
Git es un sistema de control de código fuente flexible y poderoso que permite versionar el código de manera sencilla y rápida, Git fue creado por Linus Torvarlds con la idea de reemplazar el software de versión de código que se utilizaba para mantener el código del kernel de Linux el cual se convirtió en software cerrado ocasionando el nacimiento de Git.

Git utiliza un paradigma de repositorios distribuidos, esto significa que cada cliente contiene una copia completa del repositorio de código, ésto hace que trabajar sobre un repositorio de código no requiera de ningún tipo de conexión, a diferencia de los sistemas centralizados como subversion que requieren marcar un archivo en modo edición en el servidor para después poder empezar a modificarlo.

Esta guía no pretende para nada ser una referencia completa de comandos y descripciones, ésa ya existe en la web, más bien esta guía pretende ayudar al cualquier persona que intenta empezar a utilizar Git como repositorio de código y quiere obtener una imagen rápida de como funciona y de que manera empezar a ser productivo rápidamente sin tener que invertir tanto tiempo en la curva de aprendizaje.

Configuración

Básicamente la primera cosa que hago cuando instalo git por primera vez es configurar las opciones globales que estaré utilizando al momento de usar git, por ejemplo, aumentar el buffer para postear, esto generalmente se hace debido a que muchas veces quieres hacer push de archivos más grandes del tamaño default que git permite

 
# increment git post buffer to 500MB 
git config --global http.postBuffer 524288000 

A continuación establecer un cache de 10 horas para que git no esté preguntando constantemente por las credenciales de los repositorios

# cache credentials for 10 hours
git config --global credential.helper cache
git config --global credential.helper "cache --timeout=36000"

Después se configura algo de color en el output de git para hacerlo mas atractivo 🙂

# and give some love to the git output
git config --global color.ui auto
git config --global color.branch auto
git config --global color.diff auto
git config --global color.status auto

A continuación establecer el nombre de usuario y el email los cuales aparecerán al momento de hacer algún movimiento en el repositorio

# add commit global information
git config --global user.name "Yohan Jasdid"
git config --global user.email yohan.jasdid@gmail.com
git config --global push.default simple

Después, configurar la mergetool, ésta será la herramienta a utilizar al momento de resolver algún conflicto en git, los conflictos pueden darse al momento de modificar algún código que alguna otra persona ha modificado también, las herramientas de merge sirven para mostrar las diferencias visualmente entre la versión local y la remota del código que se esté integrando para de esta manera resolver fácilmente los conflictos, en este caso yo utilizo kdiff pero hay muchas otras herramientas muy buenas

# configure merge tool global setting
git config --global --add merge.tool kdiff3
git config --global --add mergetool.kdiff3.path "/usr/bin/kdiff3"
git config --global --add mergetool.kdiff3.trustExitCode false

A continuación se configura la herramienta de diff, esta es la herramienta que se utilizara al momento de querer ver las diferencias de archivos entre revisiones, para este caso también utilizo kdiff

# configure diff tool global setting
git config --global --add diff.guitool kdiff3
git config --global --add difftool.kdiff3.path "/usr/bin/kdiff3"
git config --global --add difftool.kdiff3.trustExitCode false

Por último he deshabilitado el chequeo del certificado SSL, cabe mencionar que en mi configuración de servidor git utilizo un gitlab el cual está configurado con un certificado self signed creado por mi, probablemente necesite actualizar este certificado para que tenga un CA el cual git pueda verificar y deshacerme de esta opción, por el momento seguirá de esta manera

# disable ssl checking (not recommended for global)
git config --global http.sslVerify false

Clonando Repos

Clonar un repositorio en git significa obtener una copia completa del repositorio remoto en la máquina local y preparar el repositorio para poder agregar cambios, para clonar un repositorio se utiliza el siguiente comando

# clone a repository
git clone https://my_repo.git

Branching

Git está basado en el concepto de branching, los branches son básicamente derivados del branch principal o ramificaciones, es por eso su nombre, un branch está basado en un snapshot del repositorio al momento de crear el branch y éste puede contener cambios de código que se han hecho y que se pueden integrar de nuevo al branch principal o a cualquier otro branch, para crear un branch se pueden utilizar los siguiente comandos

# create branch
git branch [branch_name]

# create and move to branch
git checkout -d [branch_name]

Los branches existen localmente y remotamente, con los comandos anteriores se ha creado un branch localmente, una vez creado el branch es bueno hacerle push a dicho branch y crear un branch remoto que esté mapeado con el branch local, para hacer esto debemos hacer push al branch de la siguiente forma

# send local branch to origin in case the branch is just local
git push origin [branch_name]

Y para enviar el branch a origin y mapear el branch con el branch remoto, utilizamos el siguiente comando

# send local branch to origin and map with remote branch and start tracking changes
git push --set-upstream origin [branch_name]

Una vez que tenemos creados branches para hacer cambios, probablemente queramos por alguna razón borrar alguno de ellos, a continuación agrego los comandos para borrar branches locales de manera sencilla o de manera forzada, también se agrega el comando para borrar branches remotos

# delete local branch (safely)
git branch -d [branch_name]

# delete local branch (forced)
git branch -D [branch_name]

# delete remote branch
git push origin --delete [branch_name]

Si queremos listar los branches locales o remotos podemos hacerlo con los siguientes comandos respectivamente

# show local branches
git branch

# show all branches (local and remote)
git branch -a

Después de utilizar o borrar branches es necesario hacer una limpieza y actualizar las referencias a los branches actuales, eso lo podemos hacer con el siguiente comando

# Update references to remote branches, if remote branch is deleted then with this command the branch list gets updated
git remote prune origin

Con el comando anterior actualizamos las referencias de los branches remotos que ya fueron borrados. Para ver la lista actualizada de los branches remotos podemos usar el siguiente comando

# Show remote branches list
git branch -r

Una vez que comprobamos que la liste de branches remotos esta actualizada y ya no se muestran los branches borrados tenemos otro problema. Los branches locales que estaban mapeados a los branches remotos siguen existiendo en nuestra maquina local. Por cuestiones de mantenimiento lo mas seguro es que querramos borrar los branches que ya no tienen su contraparte remota. Es posible que en algun caso por alguna razon querramos conservar un branch que no esta mapeado con uno remoto pero por lo general lo que queremos es borrar los branches locales de los que su branch remoto fue borrado. Para ver una lista de los branches locales, su mapeo con el branch remoto y el estatus del branch remoto utilizamos el siguiente comando

# Show the list of branches with their remote tracking branch
git branch -vv

Una vez identificados los branches locales no deseados podemos proceder a borrarlos con los comandos mencionados anteriormente.

Una vez que hemos trabajado lo suficiente con branches, probablemente surja la necesidad de hacer cambios en branches que no han sido creados por nosotros pero que existen en el repositorio remoto de código, para hacer esto necesitamos primero adquirir el branch localmente para después hacer los cambios deseados, existen varias formas de conseguir esto, las 2 formas que utilizo se listan a continuación, el primer comando adquiere un branch el cual se mapea con un branch local de diferente nombre al remoto, el segundo comando mapea el branch local y remoto utilizando el mismo nombre de branch

# Checkout a remote branch and track it locally (it is possible to use different names for local and remote branches)
git checkout -b [local_branch] origin/[remote_branch]
# Will create [local_branch] and track origin/[remote_branch]

# Checkout a remote branch and track it locally (it is NOT possible to use different names for local and remote branches)
git checkout --track origin/[remote_branch]
# Will only create '[remote_branch]', not a branch with a different name

Existen ocasiones en que algo va mal en el branch local y queremos simplemente reiniciar el branch obteniendo los últimos cambios del branch que están en el servidor, para eso hacemos un reset hard como se muestra

# Force replace local branch with remote branch
git reset --hard origin/[remote_branch]

También, en ciertos casos es útil simplemente borrar el branch local y regenerarlo con el branch remoto, para ésto utilizamos los siguientes comandos

# Recreate branch locally
git checkout master
git branch -D [local_branch]
git checkout --track origin/[remote_branch]

Otro comando de utilidad en cuanto a branches se refiere es status. En casi de que querramos saber si nuestro branch local ha tenido nuevos commits en el tiempo que lo hemos tenido en nuestra maquina y debido a que alguien mas haya utilizado nuestro branch para hacer algunos cambios, podemos ver el status del branch y tener informacion de los commits remotos y locales

# will tell you whether the branch you are tracking is ahead, behind or has diverged. 
git status -uno

Add

Una vez que hemos dominado y entendido como funcionan los branches, es tiempo de hacer algún cambio de código o simplemente desarrollar una nueva funcionalidad en el sistema que estemos trabajando, git es inteligente, al momento de hacer cambios locales en un branch que hemos creado git inmediatamente detecta los cambios, para desplegar los cambios que git ha detectado automáticamente utilizamos el siguiente comando

# show status of modified files detected by git
git status

En git hay varios estados de los archivos modificados, el primero es untracked dicho estado significa que el archivo es completamente nuevo y nunca ha sido trackeado por git, el siguiente estado es trackeado, lo que significa que git ya tiene registro del archivo el cual puede estar en un estado modificado. De cualquier estado en el cual se encuentre el archivo modificado si se quiere agregare cambio al repositorio primeramente debemos agregar el archivo o archivos modificados al área de staging, ésta área es la que identifica los cambios de los archivos que se quieren agregar al repositorio, para agregar un archivo a staging se utiliza el comando add de la siguiente forma

# adds a file to staging
git add [file_name]

Es probable que necesitemos agregar una gran cantidad de cambios a staging de una sola vez, para evitar estar agregando estos cambios uno por uno, podemos utilizar pattern matching y agregar un montón de cambios de un jalón como se muestra a continuación

# add all files with pattern match to staging
git add *

# add all files with pattern match to staging
git add .

Y por último, para agregar los cambios a staging de solamente los archivos nuevos, es decir, los que no están trackeados por git, utilizamos el siguiente comando

# add all files that are untracked to staging
git add -u

Commits

Una vez que agregamos un cambio o un grupo de cambios a staging, se puede proceder a hacer commits, un commit es básicamente eso, un cambio o un grupo de cambios encapsulados en una bolsa con un código de barras hahah. para generar un commit “nuestra bolsa de cambios” se puede generar con los siguientes comandos

# Standard commit with simple message
git commit -m "Message of commit"

# Commit with message and description - this commit is intended for when having a title and a long description
git commit -m "Title of commit" -m "Detail message of commit"

Pushing

Una vez que tenemos un commit o varios commits de los cambios que fueron agregados a staging en nuestro branch local, lo que queremos ahora es enviar estos cambios al branch remoto para que los cambios sean públicos y todo mundo los pueda ver, esto es lo que hace precisamente el comando push. Cabe mencionar que se puede hacer push de un branch que no ha sido publicado en el repositorio remoto. Para hacer push de los commits locales o el branch y sus commits utilizamos los siguientes comandos respectivamente

# Send all changes that are already in commits to remote repository that is tracked
git push

# Send local branch to origin in case the branch is just local and start tracking remote branch with local branch
git push origin [branch_name]

Revert

Hay casos en que por alguna razón decidimos mejor deshacer todos los cambios locales que hemos hecho a algún archivo o grupo de archivos o definitivamente deshacer todos los cambios y volver al estado de un determinado commit, para ese efecto utilizamos los siguientes comandos

# revert a file to a commit state
git checkout [commit_hash] path/to/file

# discard file changes
git checkout path/to/file

# discard file changes with pattern matching
git checkout .

Merging

Hacer merge significa incorporar cambios de un determinado commit o grupo de commits o branch en el branch actual. Por ejemplo digamos que tenemos un branch llamado branch_a y queremos integrar los cambios de branch_a en master, esto se hacer de la siguiente manera.

# We are currently at [branch_a]
git checkout master # move to master
git merge [branch_a] 
# incorporate [branch_a]

# In case of merge conflicts we need to launch conflict solver tool
git mergetool
git commit -m "merge branch_a into testing" # After merge we need to commit the merge
git push # and push

# Cherry pick - to choose a commit from one branch and apply it to the current branch
git cherry-pick [commit-hash] # Apply commit from other branch to this one
git push  # push changes

En caso de que se haya hecho merge y se tengan conflictos y se quiera cancelar el merge, se puede utilizar el comando reset, el cual hace como lo dice un reseat del estado actual a uno que se le especifique. En este caso para cancelar un merge con conflictos se utiliza lo siguiente

git reset --hard HEAD

Stash y Clean

Hay veces en las que por las prisas empiezas a hacer cambios y a introducir nueva funcionalidad en el sistema, llega un punto en el que decides agregar el set de cambios y seguir con las actividades de desarrollo, al momento te das cuenta de que todo este tiempo estuviste haciendo los cambios en un branch que no era el adecuado para lo que estas haciendo, ¿Y ahora, quién podrá ayudarme? ¡git stash! hahah. el comando stash de git permite guardar los cambios que hiciste en un branch en un espacio temporal lo que te permite cambiar de branch y reestablecer todos tus cambios, ¡que elegancia!. Se utiliza de la siguiente forma

# when having changes in different branch and want them to move to another branch use git stash
# git stash saves changes to a temporary location and applies them with a command
# sample workflow to move changes from one branch to another
git checkout [branch_name]       # move to a branch
[make some changes]
git stash                                       # stash the changes in temp location
git checkout [another_branch]      # move to a different branch
git stash pop                                # apply the last stash changes to [another_branch] and drops the stash from the stack

Git stash puede almacenar sets de cambios de manera acumulativa, para ver la lista de todos los stash podemos utilizar el siguiente comando

# shows a list of stashes. there can be more than one stash in form of a list
git stash list

Si se ocupa hacer stash tambien de los untracked files podemos usar el siguiente comando

git stash --include-untracked

Hay veces en la que los branches locales y remotos divergen, en ese caso lo que se quiere es regenerar el branch local, lo podemos hacer con el comando reset como se muestra

# when remote and local branches had diverged you can use reset to reset your local branch
# and sync with the remote branch
git fetch
git reset --hard origin/[branch_name]

En otras ocasiones se da el caso en el que agregamos un montón de archivos nuevos al branch que en realidad no se requerían y queremos eliminarlos, en este caso usamos el comando clean de la siguiente forma

# delete untracked files - show untracked files to delete using the -n option:
git clean -f -n

# delete untracked files - beware: this will delete files
git clean -f

Y por último si se quieren descartar todos los cambios efectuados se utiliza el siguiente comando

# remove all changes in current branch and checkout branch changes
git checkout .

Log y Show

Una vez que tenemos branches, commits, push de los commits y un historial de cambios se nos vuelve una necesidad consultar el historial por los últimos cambios que se han introducido en el repositorio ya sea en la búsqueda de un cambio en específico o simplemente para saber quien realizó dicho cambio. Para esto, el comando log es muy útil, por ejemplo, con el comando log se muestra el historial de commits con descripciones como se muestra a continuación

# show commits history with descriptions
git log

También, para ver el historial de commits y los cambios introducidos en cada commit se utiliza el siguiente comando

# show commits history with changes
git log -p

Y si queremos buscar los commits por autor de dicho commit

# show commits history with descriptions of a certain author
git log --author=bob

Para mostrar el historial en una sola linea o con decoración y en formato más gráfico se puede hacer como se muestra

# to see a very compressed log where each commit is one line
git log --pretty=oneline

# to see an ASCII art tree of all the branches, decorated with the names of tags and branches
git log --graph --oneline --decorate --all

Para mostrar solamente los archivos que han cambiado

# see only which files have changed
git log --name-status

Para desplegar la ayuda sobre el comando

# show help for log
git log --help

Para mostrar las diferencias de commits entre branches

# show commit difference between branches
git log master..[branch_name]

Y para mostrar los detalles de un commit determinado

# show commit diff details
git show [commit_hash]

Para mostrar los commits que contienen una palabra clave utilizamos el siguiente comando

# find all commits where commit message contains given word
git log --grep=word

Diff

Existe otro comando de extrema utilidad que nos permite mostrar las diferencias entre branches o commits, es el comando diff, a continuación se muestran 3 ejemplos, el primero muestra las diferencias entre 2 branches, el segundo las diferencias entre 2 commits los cuales se identifican por medio del hash del commit y el tercero que muestra solamente archivos modificados entre branches

# show changes between source and target branches
git diff [source_branch] [target_branch]

# show changes between commit's ancestor and commit
git diff [commit_hash] ~ [commit_hash]

# show modified files between branches
git diff --name-only [source_branch] [target_branch]

# show file differences between branches
git diff --name-status [branch1] .. [branch2]

# show file content differences between branches
git diff [branch1] [branch2] -- [file_path_name]

Tags

# it's recommended to create tags for software releases. this is a known concept, create a new tag named 1.0.0 with this command
# the 558688a2968362a6826f581a03872e3ec836ad04 stands for the commit id you want to reference with your tag.
git tag 1.0.0 558688a2968362a6826f581a03872e3ec836ad04

# By default, the git push command doesn't transfer tags to remote servers. You will have to explicitly push tags to a shared server after you have created them.
git push origin 1.0.0

# Show tag list
git tag

# Create a branch based on tag
git checkout -b [branch_name] [tag_name]

Escenarios

# Usually at home in my server I'll do the following 
git checkout development  # Move to dev branch

# Do some programming.
git status                              # to see what files I changed.
git diff [file]                           # to see exactly what I modified.
git add .                               # to stage all changes
git commit -m [message]     # to commit.
git push                               # push changes to branch

# Steps to fast forward merge from development branch into master and return to development to continue working
git checkout master 
git pull 
git merge development 
git push 
git checkout development

Merge Parcial

En el supuesto de que querramos hacer un merge parcial a de ciertos commits a otro branch, podriamos hacerlo de esta manera. Para mas contexto, imagina que hay un branch que otro equipo el cual queremos consumir hasta cierto punto e ignorar los ultimos commits, este escenario se me ha presentado y uno de los aproaches a seguir fue el siguiente

# change to branch which contains the desired commits to pull and merge
git checkout [branch_name]
# fetch all commits but don't integrate into branch yet
git fetch
# compare local branch with remote branch commits and determine the commit hash point to retrieve commits
git log --oneline [branch_name]..origin/[branch_name]
# pull all commit changes until commit hash 05a7b1055
git merge 05a7b1055
# push commits that were pulled
git push

# once we got the commits that we want from remote we can proceed to merge them to another branch
git checkout [other_branch_name]
git merge [branch_name]
git push

Referencias

¡Saludos!
-Yohan

Leave a Reply

Your email address will not be published. Required fields are marked *