Fundamentos de Git (parte 2)

Clonar, Inicializar y Guardar Cambios

Obtener un repositorio Git

Puedes obtener un proyecto Git de 2 maneras. La primera es tomar un proyecto o directorio existente e inicializarlo con Git. La segunda es clonar un repositorio existente en Git desde otro servidor.


Inicializar un directorio existente

git init

Esto crea un subdirectorio .git/, el cual contiene todos los archivos necesarios del repositorio. Todavia no hay nada en tu proyecto que este bajo seguimiento.

Para empezar a controlar versiones de archivos existentes, tenemos que comenzar el seguimiento de esos archivos y hace una confirmacion inicial. Esto se consigue con unos pocos comandos git add para especificar que archivos queremos controlar, seguidos de un git commit para confirmar los cambios:

git add *.c
git add LICENSE
git commit -m 'initial project version'

Veremos lo que hacen estos comandos mas adelante. Por ahora, tenemos un repositorio de Git con archivos bajo seguimiento y una confirmacion inicial.


Clonando un repositorio existente

Para obtener una copia de un repositorio Git existente, el comando que necesitas es git clone. Al ejecutar el comando, cada version de cada archivo de la historia del proyecto es descargada por defecto. De hecho, si el disco de tu servidor se corrompe, puedes usar cualquiera de los clones en cualquiera de los clientes para devolver el servidor al estado en el que estaba cuando fue clonado.


Podemos clonar un repositorio con git clone [url]. Por ejemplo si quisieramos clonar la libreria de Git llamada libgit2 podemos hacer algo asi:

git clone https://github.com/libgit2/libgit2

Esto crea un directorio llamado libgit2/, inicializa un directorio .git/ en su interior, descarga toda la informacion de ese repositorio y saca una copia de trabajo de la ultima version.

Si quisieramos clonarlo con otro nombre que no sea libgit2, podemos especificarlo asi:

git clone https://github.com/libgit2/libgit2 nombre

Guardar cambios en el repositorio

Ya tenemos un repositorio Git y un checkout o copia de trabajo de los archivos de dicho proyecto. El siguiente paso es realizar algunos cambios y confirmar instantaneas (snapshots) de esos cambios en el repositorio cada vez que el proyecto alcance un estado que queramos conservar.

Estado de los archivos

Recordemos que los archivos que tienen seguimiento en Git pueden tener 3 estados: committed (Confirmado), modified (Modificado) y staged (Preparado).


Ademas de archivos con seguimiento podemos tener archivos que son nuevos para Git y que no tienen seguimiento, aqui es donde se dividen en 2 estados mas:

  • tracked: Rastreado, son todos aquellos archivos que estaban en la ultima snapshot del proyecto, pueden ser archivos sin modificar, modificados o preparados.
  • untracked: Sin rastrear, son todos los demas archivos que Git no ha considerado en snapshots anteriores.

Cuando clonas por primera vez un repositorio, todos los archivos van a estar rastreados (tracked) y sin modificar (unmodified).


Ciclo de vida de los archivos

Mientras editas archivos, Git los ve como modificados, porque fueron cambiados desde su ultimo commit. Luego preparas estos archivos modificados y finalmente confirmas todos los cambios preparados, y repites el ciclo.

Ciclo de vida de los archivos en Git

Aqui Unmodified y Modified son archivos rasteados (tracked)


Revisar el estado de los archivos

La herramienta principal para determinar que archivos estan en que estado es el comando git status. Si ejecutas este comando inmediatamente despues de clonar un repositorio, deberias ver algo como esto:

Output de git status

Esto significa que tienes un directorio de trabajo limpio, osea que no hay archivos rastreados y modificados. Ademas, Git no encuentra archivos sin rastrear, de lo contrario aparecerian listados ahi. Finalmente, el comando te indica en cual rama estas y te informa que no ha variado respecto a la misma rama en el servidor.


Supongamos que añades un nuevo archivo a tu proyecto, un simple README. Si el archivo no existia antes y ejecutar git status, veras el archivo sin rastrear de la siguiente manera:

Output de git status (untracked)

Git no incluira en tu proximo commit a los archivos sin rastrear a menos que se lo indiques explicitamente.


Rastrear Archivos Nuevos

Para comenzar a rastrear un archivo debes usar el commando git add. Para comenzar a rastrar el archivo README, puede ejecutar lo siguiente:

git add README

Ahora si vuelves a ver el estado del proyecto, veras que el archivo README esta siendo rastreado y esta preparado para ser confirmado:

Output de git status (tracked)

Si confirmamos en este punto, se guardara en el historial la version del archivo correspondiente al instante en que ejecutaste git add. Anteriormente cuando ejecutaste git init, ejecutaste luego git add [files], lo cual indico el rastreo de archivos en tu directorio. El comando git add puede recibir tanto una ruta de archivo como de un directorio. Si es un directorio, el comando añade recursivamente los archivos que estan dentro de el.


Preparar Archivos Modificados

Vamos a cambiar un archivo que este rastreado. Si cambias el archivo rastrado CONTRIBUTING.md y luego ejecutas el commando git status, veras algo como esto:

Output de git status (modified)

La cabecera “Changes not staged for commit”, significa que existe un archivo rastreado que ha sido modificado en el directorio de trabajo pero que aun no esta preparado. Para prepararlo, ejecutas el comando git add:

Output de git status (staged)

Ambos archivos estan preparados y formaran parte de tu proxima confirmacion.


Cambios de ultima hora

Ahora supongamos que recuerdas que debes hacer un pequeño cambio en CONTRIBUTING.md antes de confirmarlo. Modificas el archivo y ahora estas listo para confirmar. Sin embargo, ejecutemos git status una vez mas:

Output de git status (last-change)

¿¿Comoooo??…


Ahora CONTRIBUTING.md aparece como preparado y como no preparado Resulta que Git prepara un archivo de acuerdo al estaod que tenia cuando ejecutas el comando git add. Si confirmas ahora, se confirmara la version de CONTRIBUTING.md que tenias la ultima vez que ejecutaste git add y no la version que ves ahora en tu directorio de trbaajo al ejecutar git status.


Si modificas un archivo luego de ejecutar git add, deberas ejecutar git add de nuevo para preparar la ultima version del archivo:

Output de git status (last-change-add)

Estado Abreviado

Si bien es cierto que la salida de git status es bastante explicita, tambien es verdad que es muy extensa. Existe un comando para una version mas corta del estado de los cambios. Ejecutando git status -s o git status --short obtendremos algo como esto:

Output de git status (s)

Las letras que indican el estado de los archivos se compone de 2 columnas:

  • Columna Izquierda (en verde): Indica el estado preparado (staged), el estado que tienen los archivos en el area de staging.
  • Columna Derecha (en rojo): Indica el estado sin preparar (unstaged), el estado que tienen los archivos en el directorio de trabajo (Working Directory).

Cada letra tiene su significado, la M indica Modificado, la A indica Nuevo Archivo, y los ?? significa No Rastreado.


Ignorar Archivos

A veces, trendras algun tipo de archivo que no quieres que Git añada automaticamente o mas aun, que ni siquiera quieras que aparezca como no rastreado. En estos casos, puedes crear un archivo llamado .gitignore que liste patrones a considerar. Aqui un ejemplo:

Output de git ignore

Se pueden definir archivos o carpetas o patrones de archivos y carpetas utilizando expresiones regulares.


Ver cambios preparados y no preparados

Cambios no preparados

Si el comando git status te resulta muy impreciso. Puedes ver exactamente que esta pasando con el comando git diff. Un ejemplo de git status vs git diff:

Output de git status (s)
Output de git diff

Cambios preparados

Tambien podemos ver que hemos preparado hasta ahora, con git diff --staged o git diff --cached (son el mismo comando):

Output de git status (s)
Output de git diff --staged

Confirmar Cambios

Ahora que el area de preparacion (staging) esta como queremos, podemos confirmar los cambios. La forma sencilla de confirmar es:

git commit

Al ejecutar, arrancara el editor que configuramos usando git config --global core.editor <editor>:


# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
# Changes to be committed:
#	modified:   CONTRIBUTING.md
#	new file:   docs.txt
#	modified:   main.c
#
# Changes not staged for commit:
#	modified:   CONTRIBUTING.md
#	modified:   README
#
# Untracked files:
#	LICENSE
#
# ------------------------ >8 ------------------------
# Do not modify or remove the line above.
# Everything below it will be ignored.

La forma rapida

Tambien podemos indicar el mensaje de confirmacion directamente en el comando, esta es la forma convencional de realizar commits ya que es mas rapida:

git commit -m "feat: nueva funcionalidad!"

Tambien podemos agregarle la opcion -a para que Git prepare automaticamente todos los archivos rastreados antes de confirmarlos, ahorrandote el paso de git add:

git commit -am "fix: terrible bug de aliasing"

Eliminar Archivos

Para eliminar archivos de Git, debes eliminarlos de los archivos rastreados (del area de preparacion) y luego confirmar. Para eso existe el comando git rm, que ademas elimina el archivo de tu directorio de trabajo de manera que no aparezca la proxima vez como un archivo no rastreado.

Output de git rm
Output ls despues de git rm

Tambien es posible solo quitarlos del area de preparacion (staging) sin eliminarlos del disco, usando:

git rm --cached <archivo>

En general los comandos de git que aceptan nombres de archivo, tambien aceptan patrones glob, ej: git rm log/\*.txt


Cambiar el nombre de los archivos

A diferencia de muchos VCSs, Git no rastrea explicitamente los cambios de nombre en archivos. Si renombras un archivo en Git, no se guardara ningun metadato que indique que renombraste el archivo. Sin embargo, Git es bastante listo como para detectar estos cambios luego que los has hehco. Por esto, resulta confuso que Git tenga un comando mv. Si quieres renombrar un archivo en Git, puedes ejecutar algo como:

git mv file_old file_new

Tambien si luego ejecutas git status, veras que Git lo considera como un renombramiento, ejemplo git mv README.md README:

git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    renamed:    README.md -> README

Fundamentos de Git (parte 3)

Historial, Deshacer, Remotos y Alias

Ver Historial de Confirmaciones

La herramienta mas basica y potente para ver el historial es git log. Para los siguientes ejemplos se usara el repositorio simplegit.

git clone https://github.com/schacon/simplegit-progit

Cuando ejecutes git log sobre este repositorio, deberias ver una salida similar a esta:

Output de git log

El comando git log proporciona una gran cantidad de opcoines para mostrarte exactamente lo que buscas.


Opciones -p -2

Una de las opciones mas utiles es -p, que muestra las diferencias en cada confirmacion. Tambien se puede usar -2 para limitar a las ultimas 2 entradas del historial.

Output de git log -p -2

Opcion --stat

Si quieres ver estadisticas de cada confirmacion puedes usar la opcion --stat.

Output de git log --stat

Opcion --pretty

Esta opcion es realmente util, porque modifica el formato de la salida. Tienes unos cuantos estilos disponibles, la sub-opcion oneline imprime cada confirmacion en una unica linea.

Output de git log --pretty=oneline

Opcion --pretty=format

La opcion mas interesante es format, que te permite especificar tu propio formato. Por ejemplo usando %h, %an, %ar y %s:

Output de git log --pretty=format

Tabla de opciones utiles para git log --pretty=format

OpciónDescripción de la salida
%HHash de la confirmación
%hHash de la confirmación abreviado
%THash del árbol
%tHash del árbol abreviado
%PHashes de las confirmaciones padre
%pHashes de las confirmaciones padre abreviados
%anNombre del autor
%aeDirección de correo del autor
%adFecha de autoría (el formato respeta la opción --date)
%arFecha de autoría, relativa
%cnNombre del confirmador
%ceDirección de correo del confirmador
%cdFecha de confirmación
%crFecha de confirmación, relativa
%sAsunto

Opcion --graph

Las sub-opciones oneline y format son especialmente utiles combinadas con la opcion --graph. Esta añade un mini grafico ASCII mostrando tu historial de ramificaciones y uniones (branches & merges)

Output de git log --pretty=format.. --graph

Tabla de opciones tipicas de git log

OpciónDescripción
-pMuestra el parche introducido en cada confirmación.
--statMuestra estadísticas sobre los archivos modificados en cada confirmación.
--shortstatMuestra solamente la línea de resumen de la opción --stat.
--name-onlyMuestra la lista de archivos afectados.
--name-statusMuestra la lista de archivos afectados, indicando además si fueron añadidos, modificados o eliminados.
--abbrev-commitMuestra solamente los primeros caracteres de la suma SHA-1, en vez de los 40 caracteres de que se compone.
--relative-dateMuestra la fecha en formato relativo (por ejemplo, “2 weeks ago” (“hace 2 semanas”)) en lugar del formato completo.
--graphMuestra un gráfico ASCII con la historia de ramificaciones y uniones.
--prettyMuestra las confirmaciones usando un formato alternativo. Posibles opciones son oneline, short, full, fuller y format (mediante el cual puedes especificar tu propio formato).

Limitar la Salida del Historial

Ademas de formatear la salida, existen una seria de opciones para limitarla. Por ejemplo ya vimos la opcion -2 que proviene de la opcion -<n> que limita la salida a n entradas.


Las opciones temporales --since (desde) y --until (hasta) son muy utiles. Por ejemplo, este comando lista todas las confirmaciones hechas durante las ultimas 2 semanas:

git log --since=2.weeks

Este comando acepta muchos formatos. Puedes indicar exactamente "2008-01-15" o de manera relativa como "2 years 1 day 3 minutes ago"

Opcion -S

La ultima opcion realmente util para filtrar la salida de git log es -S, la cual recibe un string y solo muestra confirmaciones que cambiaron el codigo añadiendo o eliminando el string indicado. Por ejemplo si queremos encontrar la ultima confirmacion que modifico una referencia a una funcion especifica, ejecutamos:

git log -Sfunction_name

Tabla de opciones para limitar la salida de git log

OpciónDescripción
-(n)Muestra solamente las últimas n confirmaciones
--since, --afterMuestra aquellas confirmaciones hechas después de la fecha especificada.
--until, --beforeMuestra aquellas confirmaciones hechas antes de la fecha especificada.
--authorMuestra sólo aquellas confirmaciones cuyo autor coincide con la cadena especificada.
--committerMuestra sólo aquellas confirmaciones cuyo confirmador coincide con la cadena especificada.
-SMuestra sólo aquellas confirmaciones que añaden o eliminen código que corresponda con la cadena especificada.

Deshacer Cosas

En cualquier momento puede que quieras deshacer algo. Una de las acciones mas comunes a deshacer es cuando confirmas un cambio antes de tiempo y olvidas agregar algun archivo, Si quieres rehacer la confirmacion, puedes reconfirmar con la opcion --amend.

git commit --amend

Este comando utiliza tu area de preparacion (staging) para la confirmacion. Si no has hecho cambios desde tu ultima confirmacion, entonces la instantanea (snapshot) lucira exactamente igual y lo unico que cambiaras sera el mensaje de confirmacion.


Se lanzara el mismo editor de confirmacion, pero veras que ya incluye el mensaje de tu confirmacion anterior. Edita el mensaje y se sobreecribira tu confirmacion anterior.


Por ejemplo, si confirmas y luego te das cuenta que olvidaste preparar los cambios de un archivo que querias incluir en esta confirmacion, puedes hacer lo siguiente:

git commit -m 'initial commit'
git add forgotten_file
git commit --amend

Al final terminaras con una sola confimarcion, la segunda confirmacion reemplaza el resultado de la primera.


Deshacer un Archivo Preparado

Supongamos que has cambiado 2 archivos y que quieres confirmarlos como dos cambios separados, pero accidentalmente has escrito git add * y has preparado ambos. ¿Como puedes sacar del area de preparacion uno de ellos? Veamos que dice git status:

Output de git status (staged)

El propio git status te indica como hacerlo:

git restore --staged <archivo>

El archivo vuelve al estado modified pero ya no esta preparado.


Deshacer un Archivo Modificado

¿Que pasa si te das cuenta que no quieres mantener los cambios del archivo CONTRIBUTING.md? ¿Como puedes revertirlo facilmente a como estaba en la ultima confirmacion? git status tambien te lo indica:

Output de git status (staged)
git restore CONTRIBUTING.md

Importante: git restore <archivo> es un comando destructivo. Cualquier cambio que hayas hecho al archivo desaparecera — se sobreescribira con la ultima version confirmada. Nunca uses este comando a menos que estes absolutamente seguro de que no quieres los cambios.


Trabajar con Remotos

Para colaborar necesitas saber como gestionar repositorios remotos. Un remoto es una version de tu proyecto hospedada en internet o en otra red.

Ver remotos configurados

git remote       # lista los nombres
git remote -v    # lista nombres + URLs

Agregar un remoto

git remote add origin https://github.com/usuario/repo.git
Output de git remote

Obtener datos de un remoto

git fetch origin   # descarga cambios sin integrarlos a tu rama
git pull           # descarga + integra automaticamente (fetch + merge)

La diferencia entre fetch y pull es importante: fetch solo descarga, tu decides cuando integrar. pull lo hace automaticamente.

Enviar datos a un remoto

git push origin main

Si alguien mas hizo push antes que tu, deberas integrar su trabajo primero con git pull.


Nota: En la clase 7 veremos remotos en profundidad: tracking branches, git remote rename, git remote remove, push --force-with-lease y mas.


Alias de Git

Git no deduce automaticamente tu comando si lo tecleas parcialmente. Puedes establecer facilmente un alias para cada comando mediante git config.

git config --global alias.co checkout
git config --global alias.br branch
git config --global alias.ci commit
git config --global alias.st status

Esto significa que, por ejemplo, en lugar de teclear git commit, solo necesitas teclear git ci.

Alias utiles para el taller

# Ver el historial como grafo visual
git config --global alias.lg "log --oneline --graph --all --decorate"

# Ver el ultimo commit
git config --global alias.last "log -1 HEAD"

# Sacar del staging (version moderna)
git config --global alias.unstage "restore --staged"

Git simplemente sustituye el nuevo comando por lo que hayas puesto en el alias. Usaremos git lg constantemente a partir de la clase 3 para visualizar el DAG.


Recursos

  • Pro Git - Cap. 2: Desde 2.1 a 2.8 (excepto 2.6, junto con un poco de 2.5 y 2.7)