Hoy en día cuando se trabaja en un proyecto es casi imprescindible utilizar un 'Sistema de Control de Versiones'. Si desarrollamos junto a otras personas es indispensable poder compartir los cambios y desarrollar distintas partes del proyecto en paralelo.
Y si somos unos desarrolladores solitorios, un sistema como Git nos permite ir guardando copias de seguridad automáticamente y volver a cualquier versión anterior del proyecto además de poder trabajar localmente y tener nuestro proyecto a buen recaudo en un servidor remoto (GitHub por ejemplo).
Lo malo de Git a mi entender es que requiere bastante formación antes de poder utilizarlo como se debe en un proyecto. Hay varios conceptos que hay que tener claros y es aconsejable probar una y otra vez todas las opciones que ofrece en algún proyecto de prueba. Con Git hay muchas maneras de 'cagarla' si no sabemos lo que hacemos.
En la red hay montones de tutoriales y cursos para poder aprender Git. Lo mejor que he encontrado es la documentación de su página oficial, que incluso pone a nuestra disposición y de manera gratuita el libro 'Pro Git' en el siguiente enlace:
https://git-scm.com/book/es/v1/
Otro buen enlace es la propia documentación de la página oficial:
https://git-scm.com/doc
En este artículo no pretendo explicar lo que es Git, ni GitHub (que no es lo mismo), ya que haría falta crear todo un tutorial para ello.
Pero creo que sí que sirve de utilidad resumir los comandos básicos y más comunes que se utilizan. Sobre todo con los que uno debe familiarizarse para comenzar a trabajar y utilizarlos en distintas situaciones para comprender bien su funcionamiento.
Iniciando Git:
git init
Ejecutando este comando dentro de la carpeta de nuestro proyecto inicializamos 'Git' en nuestro proyecto creando un repositorio local. Se crea la carpeta oculta '.git'.
git config --global user.name "UserName"
git config user.name "UserName"
Con estos comandos se configura el nombre de usuario para poder identificar quien hizo cada 'commit' del proyecto. Con el primero se configura el nombre de usuario para todos los repositorios o proyectos locales y con el segundo solo para el respositorio o proyecto en el cual ejecutemos el comando (dentro de la carpeta del proyecto).
git config --global user.email "user@email.com"
git config user.email "user@email.com"
Con estos comandos se configura el email de usuario para asociarlo a cada 'commit' del proyecto. Con el primero se configura el email de usuario para todos los repositorios o proyectos locales y con el segundo solo para el respositorio o proyecto en el cual ejecutamos el comando (dentro de la carpeta del proyecto).
git remote add origin https://github.com/user/repo.git
Con este comando establecemos la dirección del servidor Git remoto dentro de nuestro repositorio local, relacionando ambos para poder descargar (pull) y subir (push) los cambios realizados en nuestras ramas.
Ignorando archivos y carpetas de nuestro proyecto en nuestro repositorio:
Para ignorar archivos de nuestro proyecto y que no se incluyan en el repositorio hay que crear el archivo oculto '.gitignore'.
nano .gitignore
Una vez que estemos editando el archivo debemos indicar a git que carpetas y/o arhcivos queremos que ignore siguiendo este formato.
carpeta1/
archivo1.txt
archivo2.txt
carpeta2/
Puede ocurrir que queramos ignorar algún archivo o carpeta después de haberlo añadido al repositorio o incluso haber hecho algún commit con él.
En este caso podremos añadirlo al '.gitignore' pero como el archivo o carpeta ya pertenecen al repositorio, git lo seguirá utilizando y no lo ignorará.
Para que el .gitignore haga su trabajo hay que dejar de seguir el archivo o carpeta quitándolo del repositorio o control de versiones con el siguiente comando:
git rm --cache 'archivo'
git rm -r --cache 'directorio'
Después en el archivo '.gitignore' asegurarse de que existe la entrada correspondiente:
archivo
directorio/
Si lo que se quiere es borrar un archivo o carpeta del directorio de trabajo (fisicamente)
hay que utilizar:
git rm 'archivo'
git rm -r 'directorio'
Estado del repositorio:
git status
Este comando nos indica el estado actual del repositorio (los cambios que quedan por añadir o se han añadido antes de hacer el próximo 'commit')
git log
Muestra una lista con todos los 'commits' o nodos de la rama actual en la que estamos trabajando. Es importante fijarse que cada 'commit' tiene asociado un código SHA1 que se usará con otros comandos.
git log --oneline
Muestra una lista con todos los 'commits' o nodos de la rama actual en la que estamos trabajando pero con la información resumida en una única línea por 'commit'.
También se muestra el código SHA1 de cada 'commit' de forma abreviada, pero que sigue siendo útil para utilizar con otros comandos.
git log --oneline -n
Muestra una lista con todos los 'commits' o nodos de la rama actual en la que estamos trabajando mostrando la información resumida en una línea por 'commit' pero únicamente de los 'n' últimos commits realizados.
git show codigoSHA1Commit
Muestra información ampliada del 'commit' identificado por el código SHA1 'codigoSHA1Commit'.
Empezar a desarrollar a partir de un proyecto remoto:
En este apartado se describen los comandos a utilizar en caso de que ya exista un proyecto en un servidor remoto y queramos descargalo para trabajar con él.
git clone https://github.com/user/repo.git
Con este comando se crea una carpeta con el nombre del proyecto y se descargan en su interior todas las ramas del proyecto indicado en el repositorio remoto (servidor git).
git pull origin master
Con este comando descargamos del proyecto remoto la rama indicada, en este caso la rama 'master'. Hay que tener cuidado porque por defecto (si la rama no está trackeada), el contenido de la rama que se descarga del servidor lo hará en la rama local en la que estemos en ese momento (HEAD).
Es bueno asegurarse que en nuestro proyecto local realmente estamos trabajando en la rama con el mismo nombre que la rama remota que queremos descargar.
En el caso de que queramos descargar una rama remota que no existe en nuestro proyecto local, hay que hacer un 'tracking' de una rama remota con el siguiente comando.
git branch --track ramaLocal origin/ramaRemote
Así se creará la rama 'ramaLocal' en nuestro proyecto local que será una copia de la rama remota 'ramaRemote' y además la rama estará 'trackeada'. Pero seguimos trabajando en la misma rama que estábamos.
Para todas las ramas que están traqueadas con otra rama en el 'origin', cuando se hace un
'git push' o 'git pull' (en vez de 'git push origin nombreRama' o 'git pull origin nombreRama') se hace el 'push' o el 'pull' de la rama actual (HEAD) con su rama trackeada en el servidor, sin tener que escribir el nombre de la rama remota cada vez.
git checkout -b ramaLocal origin/ramaRemote
Este comando hace lo mismo que el anterior pero cambiamos de rama local de trabajo.
Crea una nueva rama local con el nombre 'ramaLocal' que es una copia de la rama remota 'ramaRemote', trackeando las ramas entre ellas.
Además cambiamos de rama y pasamos a trabajar sobre la nueva rama creada.
Empezar a desarrollar a partir de un proyecto local:
En este apartado se explican los comandos a utilizar en caso de que empecemos a desarrollar un proyecto en local y en un momento posterior queramos crearlo en un repositorio remoto y subirlo a él.
git remote add origin https://github.com/user/repo.git
Con este comando establecemos la dirección del servidor Git remoto dentro de nuestro repositorio local, relacionando ambos para poder descargar (pull) y subir (push) los cambios realizados en nuestras ramas.
git add .
Con este comando añadimos todos los cambios que hemos realizado en nuestro proyecto local para que se incluyan en el siguiente 'commit'.
git add nombreArchivo
git add nombreCarpeta
Con este comando añadimos un archivo o carpeta en concreto para que sus cambios se incluyan en el suiguiente 'commit'.
git commit -m "mensaje de este commit"
Con este comando realizamos un 'commit' de todos los cambios que hemos añadido previamente mediante el comando 'git add'.
Un 'commit' representa un nodo o momento del proyecto. Es aconsejable hacer 'commit' cada vez que se han realizado cambios importantes en el proyecto, ya que cada 'commit' representa una copia exacta del proyecto en ese momento permitiéndonos volver a él si es necesario.
git push origin master
Con este comando enviamos una copia de la rama local en la que estemos trabajando (HEAD) hacia la rama 'master' del servidor.
Trabajando con ramas:
git branch
Muestra todas las ramas locales y con un '*' indica en la rama que estamos trabajando actualmente, se dice que el 'HEAD' de git está posicionado en dicha rama.
git branch -r
Muestra todas las ramas remotas de este proyecto que existen en el servidor remoto.
git branch -a
Muestra todas las ramas locales y remotas del proyecto.
git branch nombreRama
Crea un rama con el nombre 'nombreRama' que es una copia de la rama local en la que estamos trabajando actualmente. Seguimos trabajando en la misma rama que estábamos.
ls -laF (en Linux) dir (en Windows)
Estos comandos no son propios de Git sino del Sistema Operativo, pero sirven para comprobar en cada momento el contenido de la carpeta de trabajo. Lógicamente se puede comprobar desde la interfaz gráfica de usuario pero los pongo aquí para que quede claro que cada vez que se cambia de rama, el directorio de trabajo se carga con los archivos y carpetas que tiene dicha rama y con su correspondiente contenido.
git checkout nombreRama
Cambiamos la rama de trabajo. Pasamos de estar trabajando en una rama a otra.
Al cambiar de rama el contenido de la carpeta del proyecto cambia al estado del último 'commit' de la nueva rama de trabajo. Es decir, solo aparecen los archivos y carpetas del último commit de dicha rama y en el estado en el que quedaron.
Es aconsejable realizar un 'commit' en la rama actual antes de cambiar a otra, ya que los cambios que no se hayan guardado se arrastran a la nueva rama sin que nos demos cuenta.
git checkout -b nombreRama
Se crea la rama 'nombreRama' que es una copia de la rama local en la que estamos trabajando actualmente y además nos cambiamos a dicha rama. Es igual que hacer un
'git branch nombreRama' y después un 'git checkout nombreRama'.
git branch --track ramaLocal origin/ramaRemote
Así se creará la rama 'ramaLocal' en nuestro proyecto local que será una copia de la rama remota 'ramaRemote' y además la rama estará 'trackeada'. Pero seguimos trabajando en la misma rama que estábamos.
Para todas las ramas que están traqueadas con otra rama en el 'origin', cuando se hace un
'git push' o 'git pull' (en vez de 'git push origin nombreRama' o 'git pull origin nombreRama') se hace el 'push' o el 'pull' de la rama actual (HEAD) con su rama trackeada en el servidor, sin tener que escribir el nombre de la rama remota cada vez.
git checkout -b ramaLocal origin/ramaRemote
Este comando hace lo mismo que el anterior pero cambiamos de rama local de trabajo.
Crea una nueva rama local con el nombre 'rramaLocal' que es una copia de la rama remota 'ramaRemote', trackeando las ramas entre ellas.
Además cambiamos de rama y pasamos a trabajar sobre la nueva rama creada.
git branch -d nombreRama
Se elimina la rama local 'nombreRama'.
git push origin :nombreRamaRemota
Se elimina la rama remota 'nombreRamaRemota'
Tracking o seguimiento de ramas remotas:
Que una rama local esté trackeada con una rama remota nos facilita el uso de los comandos 'push' y 'pull'. En ambos casos, como git sabe a que rama remota está siguiendo nuestra rama local, al utilizar dichos comandos no hace falta indicar el nombre 'origin' ni la 'rama remota'.
Por lo tanto, si nuestra rama de trabajo está trackeada con su correspondiente rama remota podemos utilizar:
git push
Se suben los cambios del último commit de nuestra rama local en la que estemos trabajando a la rama remota que está trackeada con ella.
git pull
Se bajan los cambios del último commit de la rama remota que está trackeada con la rama local en la que estamos trabajando.
Las distintas formas que tenemos de trackear una rama local con otra remota son las siguientes:
git push -u origin nombreRamaRemota
Subimos el último comit de la rama local en la que estemos trabajando a la 'ramaRemota' del servidor. Además las ramas quedan trackeadas.
git pull -u origin nombreRamaRemota
Descargamos el último commit de la 'ramaRemota' en la rama local que estemos trabajando actualmente. Además las ramas quedan trackeadas.
git branch --track ramaLocal origin/ramaRemota
Se crea una nueva rama local que es una copia de la 'ramaRemota' pero seguimos estando posicionados en la rama en la que estábamos trabajando. Además ambas quedan trackeadas.
git checkout -b ramaLocal origin/ramaRemota
Se crea una nueva rama local que es una copia de la 'ramaRemota' y pasamos a trabajar automáticamente sobre la nueva rama local creada. Además ambas quedan trackeadas
Mezclando ramas:
git merge nombreRamaLocal
Se mezcla la rama 'nombreRamaLocal' con la rama en la que estamos trabajando actualmente, añadiendo un nuevo commit con los cambios recibidos.
Resolviendo conflictos:
Cada vez que fusionamos o mezclamos ramas con los comandos 'merge' o 'pull' pueden producirse conflictos. Se producen conflictos si para algún archivo de nuestra rama local en la que estamos trabajando hemos realizado cambios y en la rama que queremos fusionar con la nuestra, para el mismo archivo, se han realizado cambios que no existen en nuestra rama de trabajo.
Recordar que el proceso de mezclado de ramas se realiza mezclando una rama determinada sobre la rama actual en la que estamos trabajando, no al revés.
Cuando se producen conflictos al ejectuar los comandos 'merge' o 'pull' es recomendable ejecutar un 'git status' para comprobar exactamente que archivos son los afectados.
A continuación hay que editar cada uno de los archivos. Podemos comprobar como Git ha cambiado el texto de las líneas conflictivas indicándonos que parte corresponde a nuestra rama actual de trabajo (HEAD) y que parte a la rama que queríamos fusionar en la nuestra.
Debemos modificar el archivo de manera que su contenido sea el correcto, guardar los cambios y a continuación realizar un 'git add .' y un 'git commit' para guardar los cambios en el repositorio y finalizar el mezclado de ramas.
Una buena práctica es siempre crear una rama local 'develop' a partir de la rama 'master'.
Y desarrollar siempre las nuevas funcionalidades en la rama 'develop'. Antes de fusionar la rama 'develop' con la rama 'master', se hace en la rama 'master' un 'git pull origin master' para actualizarla respecto al servidor remoto. Una vez actualizada nuestra rama 'master' local, fusionar en ella nuestra rama 'develop' con 'git merge develop' (estando posicionados en la rama 'master').
Así, en caso de producirse conflictos en el mezclado de las ramas podremos solucionarlos tranquilamente sabiendo que tenemos nuestra rama 'develop' con las nuevas funcionalidades intactas y además que la última versión de la rama 'master' sigue estando en el servidor remoto.
Recuperando versiones anteriores del proyecto:
Cada 'commit' de nuestra rama de trabajo es como una versión de nuestro proyecto.
En cualquier momento podemos decidir volver a un momento o 'commit' anterior y seguir desarrollando a partir de ahí. Nuestro directorio de trabajo se cargará con los mismos archivos y el mismo contenido que tenía en el momento en el que se realizó el correspondiente 'commit'.
git log --oneline -n
Muestra una lista con todos los 'commits' o nodos de la rama actual en la que estamos trabajando mostrando la información resumida en una línea por 'commit' pero únicamente de los 'n' últimos commits realizados.
(Utilizamos este comando para recuperar el código SHA1 del 'commit' al que queremos regresar.)
git checkout codSHA1Commit
Este comando crea una nueva rama 'temporal' y nos posiciona sobre ella para poder comprobar el estado que tenía el proyecto en el 'commit' identificado por el 'codSHA1Commit'. La rama desaparecé tan pronto como cambiemos a otra rama con el comando 'git checkout nombreRama'.
git reset --hard codSHA1Commit
Se regresa el estado de la rama actual al estado que tenía cuando se realizó el 'commit' con código SHA1 'codSHA1Commit'. Se pierden todos los cambios realizados en los 'commit' posteriores a él.
Es recomendable anotar el SHA1 del último 'commit' antes de hacer el 'git reset' ya que si después de hacer un 'git reset' ejecutamos un 'git log' comprobaremos que el último 'commit' de nuestra rama es al que hemos vuelto y no podremos ver los commit que le seguían. Esta información es útil porque en caso de querer deshacer el 'git reset', podremos volver al 'commit' en el estábamos si anotamos su SHA1 ejecutando:
git reset --hard codSHA1CommitInicial
git reset --soft codSHA1Commit
Con este tipo de 'reset' se devuelve el 'HEAD' de la rama actual al 'commit' con SHA1 'codSHA1Commit'. Pero a diferencia de un 'git reset --hard', directorio de trabajo sigue igual que estaba antes de hacer el 'git reset' y todos los cambios que existen entre el último 'commit' que hicimos y el 'commit' al que hemos regresado están añadidos (staged) como si hubiésemos realizado un 'git add .' No hemos perdido los cambios.
Todo está listo para que si hacemos un 'git commit' se actualice el 'commit' al que hemos regresado con el mismo estado que teníamos antes de realizar el 'git reset --soft'.
git reset --mixed codSHA1Commit
Con este tipo de 'reset' se devuelve el 'HEAD' de la rama actual al 'commit' con SHA1 'codSHA1Commit'. Igual que con el 'reset --soft' no perdemos los cambios que teníamos en nuestro último 'commit', pero ahora estos cambios solo están presentes en el directorio de trabajo y no están añadidos (staged) para realizar el próximo 'commit'.
Si queremos que el proyecto quede igual a como lo teníamos antes de hacer el 'git reset --mixed' lo único que tenemos que hacer es un 'git add .' y a continuación un 'git commit'.
git revert codSHA1Commit
Se regresa el estado de la rama actual al estado que tenía cuando se realizó el 'commit' con código SHA1 'codSHA1Commit'. Pero a diferencia del 'reset' no se pierden los 'commit' posteriores, lo que ocurre es que se crea un nuevo 'commit' que es una copia del 'commit' con SHA1 'codSHA1Commit' pero se conservan todos los 'commit' realizados sobre la rama, pudiendo así volver al 'commit' desde el que realizamos el 'revert' si así lo deseamos.
git checkout -- .
git checkout -- ./nombreArchivo
Este comando es muy útil. Sirve para deshacer los ultimos cambios sobre el proyecto o sobre un archivo en concreto desde que se hizo el último cambio.
Por ejemplo, si acabamos de hacer un commit, seguimos trabajando y sin querer eliminamos un archivo o modificamos algo que no deberíamos, con este comando se deshacen los cambios y volvemos al mismo estado justo después de hacer el commit.
Creando Tags:
Los tags sirven para identificar las distintas versiones de nuestro proyecto.
Cuando después de realizar un commit decidimos que se puede realizar una entrega o versión del proyecto, lo adecuado es crear primero un tag con el nº de versión y/o descripción de dicho commit para posteriormente realizar una release con el contenido del repositorio en ese punto del desarrollo.
Los tags pueden verse como fotografías o instantáneas de un commit al que se le da una descripción y se diferencia del resto de commits.
git tag
Muestra los tags creados.
git tag -a v1.0.0 -m "Mensaje del Tag"
Se crea un tag sobre el último commit de la rama actual con el nombre 'v1.0.0' y con el texto descriptivo 'Mensaje del Tag'.
git push --tags
Se suben al repositorio remoto los tags creados.
Desde nuestro repositorio remoto, por ejemplo GitHub, seleccionando un tag creado se puede generar una versión release.