Git

Este es un primer acercamiento al VCS distribuido Git . Para l@s recién iniciad@s, recomiendo encarecidamente leer al menos los 3 primeros capítulos del libro Pro Git donde podrás aprender algo más sobre Git, sus comandos básicos y una aproximación al manejo de ramas.

Cuando empecé con Git, encontré estas herramientas interactivas que me ayudaron a entender mejor cómo fucionaba: http://learngitbranching.js.org/ y http://ndpsoftware.com/git-cheatsheet.html

Por otro lado, Atlassian tiene publicado un tutorial muy visual sobre Git, con ejemplos gráficos de los comandos y opciones más importantes: https://www.atlassian.com/git/tutorials

Por supuesto, para cualquier duda sobre algún comando y sus opciones lo mejor es consultar la documentación oficial aquí. Si tu duda es algo más compleja, siempre puedes acudir a la web preferida de cualquier programador (espero que lo sea y tod@s la conozcáis), Stack Overflow.

Cómo empezar… Preparando nuestro repositorio

Es muy posible que el primer comando que tengas que usar sea para clonar un repositorio remoto en tu máquina local con git clone:

$ git clone https://github.com/google/guava

Este comando creará un repositorio local que estará conectado (que no quiere decir sincronizado, ya que esto será tarea tuya) al repositorio remoto que acabamos de clonar. Este repositorio remoto se podría alojar en sitios como GitHub, GitLab o Bitbucket por ejmeplo. Para ver los repositorios remotos que tenemos configurados usa git remote:

$ git remote -v

La salida de este comando mostrará el nombre que le tengáis puesto a cada repositorio y con la opción -v además veréis la url remota, por ejemplo:

origin https://github.com/google/guava (fetch)
origin https://github.com/google/guava (push)

Estos nombres se pueden modificar de forma sencilla con la opción rename:

$ git remote rename origin new_name

Imagina que queremos empezar a desarrollar alguna funcionalidad nueva. Normalmente, al clonar un repositorio, estaremos posicionados por defecto en la rama master. Es aconsejable, por lo menos desde mi punto de vista, trabajar sobre una rama diferente, luego ya habrá tiempo de integrarla en la rama master o en cualquier otra. Para crear una rama local, puedes usar git checkout:

$ git checkout -b branch_name

Gracias a la opción -b, habremos creado una nueva rama y nos habremos posicionado directamente en ella. Realmente, este último comando es equivalente a primero crear la rama y luego posicionarte en ella:

$ git branch branch_name
$ git checkout branch_name

De hecho, usarás git checkout branch_name para ir cambiando de rama siempre que lo necesites.

Para el manejo de ramas usamos el comando git branch. Si queremos ver todas las ramas locales que hemos creado, simplemente ejecuta:

$ git branch

Si incluimos la opción -a, además de ver las ramas locales, veremos las ramas remotas:

$ git branch -a

Al igual que hemos creado ramas locales, también podemos borrarlas:

$ git branch -D branch_name

Usa la opción -D si tienes seguro que la rama está “mergeada“, ya que usando esta opción, lo que se ejecuta realmente es un --delete --force,  que es un borrado forzado y podrías perder cambios. Quizás sea mejor práctica usar -d or --delete (ambas opciones hacen lo mismo).

Si además de borrarla localmente, queremos borrarla remotamente,  tenemos que hacerlo con el comando git push y la opción --delete.

$ git push origin --delete branch_name

donde origin es el nombre que le tenemos dado al repositorio remoto (usa git remote para ver los repositorios configurados). Después de borrar una rama remota, es aconsejable comunicarlo al resto de personas que trabajen con ese repositorio y que ejecuten el siguiente comando para la propagación de los cambios, ya que podrían estar haciendo un seguimiento obsoleto de la rama:

$ git fetch --all --prune

Comiteando cambios

Bien, supongamos que ya hemos creado o modificado archivos de nuestro repositorio local y queremos comitear (aquí leerás muchas palabras que solo existen en el mundo del programador) esos cambios al repositorio remoto. Primero, podríamos ver todos los cambios que hemos hecho con git status:

$ git status

Este comando nos da información sobre qué archivos hemos modificado. Si ejecutamos este comando y no existen cambios, la salida sería algo como esto:

On branch master
Your branch is up-to-date with 'origin/master'.
nothing to commit, working directory clean

La primera línea, On branch master, nos muestra la rama sobre la que estamos posicionados. La siguiente línea nos indicaría cuantos cambios por delante estamos de nuestro repositorio remoto, pero en este caso está todo al día (up-to-date), pues no hay cambios.

Si hemos hecho cambios que queremos comitear, antes tendremos que preparar ese commit. Este paso es muy útil cuando estamos trabajando en algo que afecta a varios archivos pero no queremos comitear todos, solo algunos de ellos. Podemos hacerlo usando el comando git add:

$ git add file_path/file_name

Con este comando vamos añadiendo archivos al stage (es como se le conoce a este área donde se encuentra temporalmente los archivos añadidos) para luego comitearlos. Podemos añadir de uno en uno o en el mismo comando pasarle varios separados por un espacio. Normalmente, si quieres añadirlos todos, es más productivo usar “.” como opción que especificarlos explícitamente uno a uno:

$ git add .

Los archivos preparados para comitear, se les conoce como staged files, mientras que los restantes, los que aún no hemos añadido al stage, se les llama unstaged files.

El siguiente paso sería comitear los archivos que hemos añadido anteriormente. El comando para grabar los cambios en el repositorio es git commit:

$ git commit -m"Description"

Puedes añadir una descripción de los cambios que subes con la opción -m seguido de la descripción entre comillas, es muy recomendable y yo diría que hasta obligatorio. Cada descripción que pongas usando esta opción en la misma línea de comandos, será almacenada como un párrafo, por si quieres explayarte.

Ahora tenemos los cambios guardados en nuestro repositorio local, pero lo deseable es subirlos al repositorio remoto para no perder el trabajo que hemos realizado en caso de catástrofe. Usamos git push para este fin:

$ git push origin branch_name

Estamos subiendo los cambios de la rama en la que estamos posicionados (recuerda que podemos verlo ejecutando git status) al repositorio remoto origin (usa git remote para ver los repositorios configurados), concretamente a una rama que se llama branch_name. Si la rama no existe, será creada la primera vez que hagamos ese push.

Podría ser que tu último commit no fuera del todo correcto, que subieras algo erróneo o te dejaras un archivo sin subir. La opción rápida sería subir los cambios que lo solucionan en un nuevo commit. En el historial de cambios verías dos entradas, una por cada commit, sin embargo, solo quieres ver un commit, ya que el segundo ha sido para arreglar al primero. Puedes conseguirlo usando la opción --amend:

$ git commit --amend

Este comando tiene sus peculiaridades. Por un lado, solo lo puedes usar para arreglar el último commit. Además, si no especificas la descripción con la opción -m, cogerá el mensaje del commit anterior. Por otro lado, el viejo commit se pierde, porque lo que hace realmente este comando es combinar los cambios del último commit con los cambios que tienes en el stage (lo que tienes preparado para comitear). Ahora imagina que estamos trabajando sobre un repositorio con mas gente y que alguien ha empezado a trabajar a partir de tu primer commit. Si después tú has realizado un git commit --amend, este sustituye totalmente al primero que desaparece, llegando a una situación confusa para la persona que basó su trabajo en tu primer commit. Por eso, es recomendable usarlo con pequeños errores, por ejemplo ortográficos, o despistes. Sobre este tema puedes encontrar más información aquí.

¿Pero y si lo que tengo que modificar es un commit de hace tiempo? ¿puedo hacerlo? Pues sí, y es que con Git se puede hacer casi todo lo que se te ocurra sobre un VCS. Para ello hay que introducir una nueva opción --fixup

$ git commit --fixup bbb2222

donde bbb2222 es un ejemplo de un identificador del commit que queremos solucionar y que podemos ver con git log. En este caso, no estamos combinando cambios, al menos todavía no, por lo que veremos una nueva entrada en nuestro historial con este último commit que ha quedado marcado como fix. A partir de aquí, se aconseja ejecutar git rebase --interactive --autosquash master, pero esto es otra historia que os contaré más tarde, ya que el comando git rebase es uno de los más complejos y por consiguiente, de los más potentes de Git. Por ahora, quedaros con la idea de que ese último comando, con esas opciones, te ayudará de forma visual (por la opción --interactive) a comprobar el orden en el que irán esos commits e incluso te dará opción de combinar algunos de ellos (aunque con la opción --autosquash ya habremos combinado automáticamente nuestro commit marcado como fix).

Si quieres aprender más sobre --fixup hay un buen artículo aquí para leer sobre cómo conseguir ramas con historiales de cambios limpios y legibles.

Sincronizando nuestro repositorio

En nuestro día a día, es muy aconsejable mantener nuestro repositorio sincronizado y actualizado con los últimos cambios del repositorio remoto. Hay varias formas de hacerlo, como en todo cuando trabajamos con Git. Yo suelo usar git pull. Por ejemplo:

$ git pull origin master

Este comando combinará automáticamente los cambios del repositorio remoto origin en la rama master de nuestro repositorio local. Es equivalente a ejecutar:

$ git fetch origin
$ git merge origin/master

Realmente no es exactamente lo mismo, pero las diferencias entre ambas formas no son demasiado significantes como para entrar en ellas en este punto.

A veces, si estoy trabajando sobre el master, me traigo los cambios directamente con git merge

git merge --ff-only origin/master

Histórico de cambios

Es interesante que una vez hayamos actualizado nuestro repositorio local, revisemos los cambios que se han bajado, podemos usar git log:

$ git log

Veremos una lista cuyo tamaño dependerá del tamaño de nuestro proyecto. Habrá casos en los que incluso la salida en consola será paginada debido al enorme número de commits. Podemos usar un parámetro para ver por ejemplos los 10 últimos:

$ git log -10

Ahora veremos una lista algo más acotada, aunque quizás el formato en el que se muestra no sea el más adecuado, depende del gusto, ya que por defecto veremos la información de forma estructurada:

commit e44f5927749a30c672a1e7f86ca66d2a758cc663
Author: mruiz 
Date:   Fri Mar 24 13:42:46 2017 +0100

    Commit description here.

Es customizable la forma en la que Git nos muestra esta información, tan sólo necesitamos incluir un par de opciones, yo solo usar estas:

$ git log --oneline --decorate

Con --oneline tendremos la información en una sola línea (solo el id del commit y el mensaje), mientras que con --decorate se mostrará además sobre qué commits se encuentran los índices, por ejemplo de nuestra rama en local o la rama en remoto.

Comprobando las diferencias

Ahora que sabemos cómo listar los cambios de nuestro repositorio, podemos profundizar algo más en la comparación de commits para encontrar los cambios que se han realizado en cada uno. El comando git diff será el que usemos para esto. Por ejemplo, podemos empezar viendo las diferencias que hay entre la staging area y la working copy (esto son los archivos locales modificados que aún no hemos añadido para preparar el commit), tan simple como:

$ git diff

O podemos ver la diferencia entre la staging area y el último commit:

$ git diff --staged

La diferencia entre la working copy y un commit en concreto:

$ git diff 4ac0a6733

Entre un commit en concreto y el último:

$ git diff 4ac0a6733 HEAD

Dos commits cualquiera del historial:

$ git diff 4ac0a6733 826793951

Guardando cambios para luego…stash

Esta característica que ofrece Git es una de mis favoritas. Podemos imaginar un contexto para que lo entiendas fácilmente: estás trabajando en algo, y ya tienes varios archivos y cambios en tu working copy. Por algún motivo, el último commit que subiste tiene errores y urge solucionarlo. Con git stash podemos guardar el estado actual de lo que llevamos desarrollado en ese momento, volver al estado que había antes de empezar para solucionar ese commit erróneo y recuperar el estado de nuestro desarrollo en cualquier momento, y mejor aún, sobre cualquier rama local. Vamos a ver cómo:

$ git stash save "stash_name"

Con este comando habremos grabado el estado actual en un stash con el nombre stash_name. Podemos recuperarlo y aplicarlo en la rama en la que estemos posicionados con:

$ git stash pop

Además de aplicar el último stash almacenado, lo estaremos borrando de la pila de stashes con la opción pop. Si no nos interesa borrarlo, porque por ejemplo queremos aplicarlo en otra rama, usamos:

$ git stash apply

Pero hay más, como he comentado antes, tenemos una pila de stashes que podemos gestionar. Para ver la pila de todos los que tenemos guardados:

$ git stash list

Ahora por ejemplo podríamos aplicar uno en concreto (igual para la opción pop):

$ git stash apply stash_name

Nivel leyenda: git rebase

//TODO
git rebase

Anuncios
Publicado en Uncategorized | Etiquetado , | Deja un comentario