lunes, 25 de julio de 2011

Branch remotos en git, dos pasos. ¿Y despues?

Anteriormente, en gitevangelism, hice un post que explica como crear un branch remoto en dos pasos, aca un resumen:

Crear un branch de manera local
git checkout -b nuevo_branch
Publicar el branch, y trackearlo local (al especificar -u)
git push -u origin nuevo_branch

Despues, hay que trabajar con el branch, hay cosas que no resultaran tan triviales al principio

Trabajar en el branch, en los branches

Si se ejecutaron los comandos para creacion de branch explicados, habra quedado activo el nuevo_branch, sino se esta seguro se puede utilizar git branch sin argumentos, asi:
git branch
Devuelvera la lista de branch, indicando con un asterisco el branch activo en el momento

Si el branch en el que queremos trabajar no es el activo, entonces ejecutamos el comando checkout
git checkout nuevo_branch
A partir de entonces, todos los commits seran dirigidos al branch activo, actualizando el branch para que apunte a cada commit que se hace.

Si por alguna razon, es necesario dejar el trabajo que se esta realizando en este branch para trabajar en otro (por ejemplo, en master), se utiliza el comando checkout para reactivar master

git checkout master

Si hay cambios no commiteados, seguramente el comando se negara a cambiar el branch activo, si ese fuera el caso hay que commitearlos o stashearlos (stash es un commit temporal que se usa para guardar el indice y los cambios no commiteados y que despues se puede recuperar):

git stash

Para volver, otra vez hay que ejecutar
git checkout nuevo_branch
Y si se tuvo que salvar los cambios mediante stash, hay que recuperarlo haciendo
git stash pop

Por ultimo, se debe ejecutar el comando git push, este sube cada branch trackeado a su correspondiente branch remoto (en este caso subiria el master local al master remoto y el nuevo_branch local al nuevo_branch remoto)

Colaboracion

Si se necesita que otra persona contribuya al branch, desde otra maquina, tiene que trackearlo
git fetch
git checkout --track -b nuevo_branch origin/nuevo_branch

Despues, trabajar de esa manera es lo mismo que trabajar al master, con la diferencia (claro esta) que es el nuevo_branch el que recibe los cambios que se pushean

Otra opcion, para mirar el branch es directamente usar las ref remotas

git fetch
git checkout origin/nuevo_branch


Esto es mas practico si solo se necesita "ver" el contenido del branch, pero para trabajar con el es conveniente trackearlo, de otra manera se tendria que explicitar el branch cada vez que se hace push:


git push origin HEAD:refs/heads/nuevo_branch


Integrar los cambios

Esta es la manera mas practica de integrar, que utiliza el 3-way-merge (otro dia hago un articulo explicando eso), no hay mas que hacer que lo siguiente:
git checkout master
git merge nuevo_branch

Puede ser que master y nuevo_branch no sea divergentes (esto es que una, generalmente master, apuntara a un commit ancestro del otro), en ese caso no habra ningun problema y simplemente master cambiara para igualar a nuevo_branch.
Cuando master y nuevo_branch no son divergentes, se utilizara el algoritmo de merge que
puede no dar conflicto en cuyo caso efectuara un commit automaticamente, ese commit sera un commit de merge (es lo mismo que cuando se hace git pull despues de que un git push fue rechazado)
O puede dar conflicto dependiendo de los cambios que se hayan hecho, para resolver esos conflictos hay que ejecutar git status, ver cuales son los archivos marcados como en conflicto y resolverlos (estan marcados claramente dentro del archivo de texto)
Despues de resolver todos los conflictos, se puede hacer un add de update al indice
git add -u
Y entonces commitear
git commit -m 'merged nuevo_branch into master'

Eliminar el branch cuando ya no se use

Para eliminar el branch local
git branch -d nuevo_branch

NOTA: no funciona si el branch que se quiere eliminar es el activo en ese momento, para evitar eso hay que cambiar a otro branch o commit
NOTA2: si el branch no esta integrado a nada, se negara a borarrlo a no ser que se use la opcion -D en lugar de -d

Para eliminar el branch remoto
git push origin :refs/heads/nuevo_branch

Links

Branch remotos en git, dos pasos

Actualizacion: ¿Y despues?, sigue en este post

En dos pasos se puede crear un branch remoto


Crear branch de manera local

git checkout -b nuevo_branch


Publicar el branch

git push -u origin nuevo_branch

Despues, el branch queda traqueado, con lo que cada push sin parametros envia los commits de nuestro branch local a ese branch remoto sin necesidad de explicitarlo

Tambien se puede trabajar en el branch local antes de publicarlo y por supuesto despues.

Actualizacion:
¿Y despues?, sigue en este post

sábado, 23 de julio de 2011

Publicar el repo y el historial, pero sin contraseñas harcodeadas

El historial, la secuencia de commits detallando todo lo que fue cada "snapshot" es muy util, pero ¿Que pasaria si quisiesemos publicar branches en cuyo historial hay contraseñas que no queremos que el publico las vea ?

Para todo "gitero" el historial es importante, pero tambien lo es evitar la fuga de informacion y mas cuando somos concientes de que si hacemos ese "push", esa contraseña o codigo de acceso sera publico

La respuesta a esto es el comando filter-branch ¿ Que hace filter-branch ? crea un commit inicial aparte (que es como el commit inicial del repositorio, no tiene parent) y va copiando los commits del branch que donde estemos parados al momento de ejecutar el comando, pero esto lo hace aplicandole un filtro que se define como argumento del comando

filter-branch tiene muchas opciones muy interesantes, pero la que mas sirve para el caso que describo en este post, es la opcion --tree-filter, que ejecuta una secuencia de comandos a cada tree de cada commit, esa secuencia podria agregar, eliminar, mover o crear archivos. Asi, el siguiente comando eliminaria un archivo Web.config de todo el historial haciendo que no se pueda recuperar ninguna version de el de ningun commit en el historial:

git filter-branch --tree-filter 'rm Web.config'


NOTA: no olvidar poner la ruta completa si corresponde, el script se ejecutara en el root directory del proyecto

A algunos puristas podria no gustarle esa opcion, ya que eliminar ese archivo vital dejaria invalido y "unbuildeable" esos commits pasados. En lugar de eso se podria usar algo mas especifico y recurrir al amigo "sed" para editar ese archivo y remover la contraseña

git filter-branch --tree-filter "sed 's/mipasswordloco/insert your password here/g' Web.config > Web.config.bkp; mv Web.config.bkp Web.config"

En este caso, en lugar de simplemente eliminar el archivo, se lo esta procesando con sed, una "navaja suiza" de la shell para editar archivos de texto desde la linea de comandos de manera no interactiva.

Hay dos comandos en el script separados por punto y coma, el primero es sed que reemplaza el texto "mipasswordloco" por "insert your password here" y usa el parametro g para indicar que debe reemplazar todas las ocurrencias en cada linea del archivo de texto (ver la man page de sed para mas informacion), el segundo parametro escribe en Web.config los cambios realizados con un move, recalco que esto es asi porque no se debe dejar ningun archivo extra o formara parte del tree tambien (que no es lo que se esta tratando de hacer en este caso)

Aplicando scripts mas complejos como este, despues de ejecutar filter-branch es conveniente revisar el historial para verificar que efectivamente se obtuvo el resultado buscado

Si por casualidad hicieron un git-filter-branch y despues se arrepintieron, se restaura el branch con la penultima entrada del reflog, asi:

git reset --hard HEAD@{1}


Links

Ahorrando espacio con git gc

Pregunta frecuente ¿Cuanto espacio ocupa un repositorio de git?
Respuesta: Depende, no solamente de los contenidos sino tambien de si los objetos estan empaquetados o no.
Si estan empaquetados pueden llegar a ocupar hasta 10 veces menos cantidad de espacio que si no lo estuvieran.

Para empaquetar los objetos, hay que ejecutar el comando

git gc

NOTA: Despues de que gc limpie y comprima los objetos el repositorio se puede seguir usando de manera transparente. No es necesario ejecutar ningun comando especifico ni nada ya que git trabaja con los objetos empaquetados
NOTA2: por default git viene configurado para correr este comando automaticamente cuando sea necesario, pero puede ser util correrlo a mano algunas veces


Si despues de esto todavia se necesita liberar todavia mas espacio, se pueden eliminar los logs y hacer git gc de nuevo

rm -fr .git/logs


Pero un advertencia, esos logs almacenan los registros que se acceden mediante git reflog y los stashes, con lo que seguramente se perderan cambios que no hayan sido commiteados a alguna rama existente

Como Funciona

Este comando, lo que hace como su nombre lo indica es actuar de "garbage collector", es decir que elimina todos aquellos objetos que son inalcazables, esto es analago a un entorno gestionado como .NET, Java, Ruby, etc... donde el gc borra de la memoria objetos que no estan siendo referenciados por nadie, en este caso las referencias vendrian a ser los branches, los tags, las entradas de logs y los propios objetos que hacen referencia a parent commits (si son commits), blobs y trees. Es decir que git gc no elimina nada que este a nuestro alcance como la informacion referenciada por los branches en los que estamos trabajando e incluso los commits referenciados por los logs.

Pero la accion de git gc no solo se limita a su papel como garbage collector, sino que tambien se encarga de realizar el packing de los objetos, lo cual implica reorganizarlos todos en un solo archivo y comprimirlo usando delta compression, un metodo de compresion optimizado para comprimir snapshots que tiene ligeras diferencias uno con el otro

Para dar un ejemplo, en un repo relativamente nuevo (200 commits aproximadamente) que pesa 1.8 Megabytes, si se examina el directorio .git/objects se podria ver como git almacena los objetos unpacked:
dario@dario-laptop:~/projects/imageruby-devil/.git/objects$ find | head -n 15
.
./71
./71/a0a195d6b9192f3a2ed92afce74600a18d5463
./5e
./5e/d0b14304e16bb8404fc1106923e47b9e8efc65
./11
./11/4db5a061a6a3d9c010c132cc90890bbfe5c8a9
./34
./34/66e89a2fe1078cbd96bccdce9f0525e1039f2c
./06
./06/21559a3294f6d11c7fa80727d8f9dbc8594ad8
./0f
./0f/f89b8482e83ee109dbf2061ea77b436eaa91c8
./0f/ccf20d3f9fc5f92d56ea4f5959218d6a3109eb
./be
...
...
# sigue un monton
...
...

Pero despues de ejecutar git gc, el repositorio pasa a pesar 232 Kilobytes y los objetos ya no estan almacenados de esa forma, ya que se empaquetaron y comprimieron en solo dos archivos:

dario@dario-laptop:~/projects/imageruby-devil/.git/objects$ find
.
./info
./info/packs
./pack
./pack/pack-eaadc1cff53d9b6d8f5a5ba7eb52fe733c61aac6.pack
./pack/pack-eaadc1cff53d9b6d8f5a5ba7eb52fe733c61aac6.idx

NOTA: Para los que no esten enterados, el comando find busca todos los archivos en el directorio y subdirectorios y muestra las rutas de los archivos que encontro por por salida estandar

Links