CLASE 11
Herramientas de Git
BuscandoCon casi cualquier tamaño de base de codigo, a menudo necesitaras encontrar en donde se llama o define una funcion, o encontrar el historial de un metodo. Git proporciona un par de herramientas utiles para examinar el codigo y hacer commit a las snapshots almacenadas en su base de datos de forma rapida y facil.
Git posee un comando llamado grep que te permite buscar facilmente a traves de cualquier arbol de directorio de trabajo con commit por una cadena o expresion regular.
Para estos ejemplos, veremos el codigo fuente de Git.
Por defecto, mirara a traves de los archivos en su directorio de trabajo. Puedes pasar -n para imprimir los numeros de linea donde Git ha encontrado coincidencias:
git grep -n gtime_r
compat/gmtime.c:3:#undef gtime_r
compat/gmtime.c:8: return git_gmtime_r(timep, &result);
compat/gmtime.c:11:struct tm *git_gmtime_r(const time_t *timep, struct tm *result)
...
Hay una serie de opciones interesantes que puede proporcionar el comando grep.
Por ejemplo, en lugar de la llamada anterior, puedes hacer que Git resuma el resultado simplemente mostrandote que archivos coinciden y cuantas coincidencias hay en cada archivo con la opcion --count:
git grep --count gtime_r
compat/gmtime.c:4
compat/xmmap.c:1
date.c:3
git-compat-util.h:2
Si quieres ver en que metodo o funcion piensa Git que ha encontrado una coincidencia, puedes pasar -p:
git grep -p gtime_r *.c
date.c=static int match_multi_number(...)
date.c: if (gtime_r(&now, &now_tm))
...
Tambien puedes buscar combinaciones complejas de cadenas con el indicador --and, que asegura que multiples coincidencias esten en la misma linea. Por ejemplo, busquemos cualquier linea que defina una constante con las cadenas LINK o BUF_MAX en ellas en la base del codigo de Git en una version 1.8.0 anterior, usando tambien las opciones --break y --heading que ayudan a dividir el resultado en un formato mas legible:
git grep --break --heading \
-n -e '#define' --and \( -e LINK -e BUF_MAX \) v1.8.0
v1.0:/builtin/index-pack.c
62:#define FLAG_LINK (1u<<20)
v1.8.0:cache.h
73:#define S_IFGITLINK 0160000
76:#define S_ISGITLINK(m) (((m) & S_IFMT) == S_IFGITLINK)
...
El comando git grep tiene algunas ventajas sobre los comandos de busqueda normales como grep y ack: es realmente rapido,
y puedes buscar a traves de cualquier arbol de Git, no solo en el directorio de trabajo.
Quizas no estas buscando donde existe un termino, sino cuando existio o se introdujo. El comando git log tiene varias herramientas potentes para encontrar commits especificos por el contenido de sus mensajes o incluso el contenido de las diferencias que introducen.
Si queremos saber, por ejemplo, cuando se introdujo originalmente la constante ZLIB_BUF_MAX, podemos decirle a Git que solo nos muestre los commits que agregaron o eliminaron esa cadena con la opcion -S:
git log -SZLIB_BUF_MAX --oneline
e01503b zlib: allow feeding more than 4GB in one go
ef49a7a zlib: zlib can only process 4GB at a time
Si miramos la diferencia de esos commits, podemos ver que en ef49a7a se introdujo la constante y en e01503b se modifico.
Si necesitas ser mas especifico, puedes proporcionar una expresion regular para buscar con la opcion -G.
Otra busqueda de registro bastante avanzada que es increiblemente util es la busqueda del historial de linea. Se llama con la opcion -L a git log y te mostrara el historial de una funcion o linea de codigo en tu base de codigo.
Por ejemplo, si quisieras ver cada cambio realizado en la funcion git_deflate_bound en el archivo zlib.c, podrias ejecutar git log -L :git_deflate_bound:zlib.c. Esto intentara descubrir cuales son los limites de esa funcion y luego examinara el historial y nos mostrara cada cambio que se hizo a la funcion como una serie de parches desde cuando se creo la funcion por primera vez:
git log -L :git_deflate_bound:zlib.c
commit ef49a7a0126d64359c974b4b3b71d7ad42ee3bca
Author: Junio C Hamano <gitster@pobox.com>
Date: Fri Jun 10 11:52:15 2011 -0700
zlib: zlib can only process 4GB at a time
diff --git a/zlib.c b/zlib.c
--- a/zlib.c
+++ b/zlib.c
@@ -85,3 +130,3 @@
-unsigned long git_deflate_bound(z_streamp strm, unsigned long size)
+unsigned long git_deflate_bound(z_stream *strm, unsigned long size)
...
Si Git no puede encontrar la forma de relacionar una funcion o metodo en tu lenguaje de programacion, tambien puedes proporcionarle una expresion regular. Por ejemplo, esto habria hecho lo mismo: git log -L '/unsigned long git_deflate_bound/',/^}/:zlib.c. Tambien podrias darle un rango de lineas o un unico numero de linea y obtendrias el mismo tipo de salida.
tar -xzf 14-busqueda-historial.tar.gzcd 14-busqueda-historial/code .) y lee el README.mdHerramientas de Git
Reescribiendo la HistoriaMuchas veces, cuando trabajas con Git, puede que quieras modificar tu historial de commits.
Una de las grandes virtudes de Git es que te permite tomar decisiones en el ultimo momento posible.
Puedes decidir que archivos van en que commits justo antes de confirmar desde el area de preparacion,
puedes decidir que no quieres trabajar en algo todavia con git stash,
y puedes reescribir commits que ya han ocurrido para que parezcan haber sido hechas de una manera diferente.
Nunca reescribas commits que ya hayas enviado a un repositorio publico.
Modificar tu ultimo commit es probablemente la reescritura de la historia que haras mas frecuentemente. A menudo quieres hacer dos cosas: cambiar el mensaje del commit, o cambiar la snapshot que acabas de registrar añadiendo, modificando o eliminando archivos.
Para modificar solo el mensaje del ultimo commit:
git commit --amend
Esto abre tu editor con tu ultimo mensaje de commit en el, listo para que lo modifiques. Cuando guardas y cierras el editor, el editor escribe un nuevo commit que contiene ese mensaje y la hace tu nuevo ultimo commit.
Si ya hiciste commit y quieres cambiar la snapshot añadiendo o modificando archivos, el proceso es basicamente el mismo: haz los cambios, prepara los archivos con git add y luego:
git commit --amend
Ten cuidado con esta tecnica: al enmendar cambia el SHA-1 del commit. Es como un rebase muy pequeño — no enmiendes tu ultimo commit si ya lo has enviado al servidor.
Para modificar un commit que esta mas atras en tu historia, debes usar el rebase interactivo.
Puedes ejecutar el rebase interactivamente añadiendo la opcion -i a git rebase.
Debes indicar que tan atras quieres ir especificando a que commit quieres rebasar.
Por ejemplo, si quieres cambiar los mensajes de los tres ultimos commits:
git rebase -i HEAD~3
El script que se abre tiene un aspecto similar a este:
pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
# Rebase 710f0f8..a5f4a0d onto 710f0f8
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like squash, but discard this commit's log message
# d, drop = remove commit
Nota que el orden es al reves del que muestra git log: el mas viejo aparece arriba.
Para cambiar el mensaje de un commit, cambia pick por reword (o r).
Para modificar el contenido de un commit, usa edit (o e).
Al guardar y salir, Git reproductira los commits y se detendra en cada una marcada con edit, donde podras enmendar los cambios y luego ejecutar:
git commit --amend
git rebase --continue
El rebase interactivo tambien te permite reordenar o eliminar commits completamente. Si quieres eliminar el commit “added cat-file” y cambiar el orden de las otras dos, puedes cambiar el script de esto:
pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
A esto:
pick 310154e updated README formatting and added blame
pick f7f3f6d changed my name a bit
Al guardar y salir, Git rebobina tu rama al padre de esos commits, aplica 310154e y luego f7f3f6d, y luego se detiene.
Has cambiado efectivamente el orden de esos commits y eliminado la de “added cat-file” completamente.
Con el rebase interactivo tambien es posible tomar una serie de commits y unirlos en uno solo. El script pone instrucciones utiles en el mensaje del rebase:
Para unir tres commits en uno, cambia el script de esto:
pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
A esto:
pick f7f3f6d changed my name a bit
squash 310154e updated README formatting and added blame
squash a5f4a0d added cat-file
Al guardar y salir, Git aplica los tres cambios y luego te devuelve al editor para que combines los tres mensajes de los commits en uno solo.
Dividir un commit lo deshace y luego lo organiza y confirma parcialmente tantas veces como commits quieras obtener de el. Supongamos que quieres dividir el del medio de tus tres commits. En lugar de “updated README formatting and added blame”, quieres dividirlo en dos: “updated README formatting” en el primero, y “added blame” en el segundo.
En el script de rebase -i, cambia pick por edit:
pick f7f3f6d changed my name a bit
edit 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
Cuando el script te deje en la linea de comando, deshaces ese commit con git reset HEAD^, que deshace el commit y deja los archivos modificados sin preparar.
Ahora puedes preparar y confirmar los archivos hasta que tengas varios commits, y luego ejecutar git rebase --continue cuando hayas terminado:
git reset HEAD^
git add README
git commit -m 'updated README formatting'
git add lib/simplegit.rb
git commit -m 'added blame'
git rebase --continue
tar -xzf 15-reescribiendo-historia.tar.gzcd 15-reescribiendo-historia/code .) y lee el README.mdExiste otra opcion para reescribir la historia que puedes usar si necesitas reescribir un gran numero de commits de forma programatica — por ejemplo, cambiar tu direccion de correo electronico globalmente, o eliminar un archivo de cada commit.
El comando es filter-branch y puede reescribir grandes franjas de tu historia.
No deberias usarlo a menos que tu proyecto aun no sea publico y nadie haya basado su trabajo en los commits que estas a punto de reescribir.
Para eliminar un archivo llamado passwords.txt de toda tu historia:
git filter-branch --tree-filter 'rm -f passwords.txt' HEAD
La opcion --tree-filter ejecuta el comando especificado despues de cada extraccion del proyecto y luego vuelve a confirmar los resultados.
En este caso, eliminas un archivo llamado passwords.txt de cada snapshot, tanto si existe como si no.
Para ejecutarlo en todas las ramas:
git filter-branch --tree-filter 'rm -f passwords.txt' --all
Supongamos que has importado desde otro sistema de control de versiones y tienes subdirectorios que no tienen sentido (trunk/, tags/, etc.).
Si quieres hacer del subdirectorio trunk/ la nueva raiz del proyecto:
git filter-branch --subdirectory-filter trunk HEAD
Para cambiar tu direccion de correo electronico en todos los commits:
git filter-branch --commit-filter '
if [ "$GIT_AUTHOR_EMAIL" = "schacon@localhost" ];
then
GIT_AUTHOR_NAME="Scott Chacon";
GIT_AUTHOR_EMAIL="schacon@example.com";
git commit-tree "$@";
else
git commit-tree "$@";
fi' HEAD
filter-branchreescribe todos los SHA-1 de cada commit en tu historia. Trata este comando como dinamita: usalo solo cuando sepas exactamente lo que estas haciendo.
7.5 — busqueda en el historial.7.6 — reescritura de la historia.