Fundamentos GIT y GitHub - Documentos de Google
Fundamentos GIT y GitHub - Documentos de Google
Fundamentos GIT y GitHub - Documentos de Google
Si eres diseador grfico o web, y quieres mantener cada versin de una imagen o diseo (algo
que sin duda quieres), un sistema de control de versiones (Version Control System o VCS en
ingls) es una eleccin muy sabia. Te permite revertir archivos a un estado anterior, revertir el
proyecto entero a un estado anterior, comparar cambios a lo largo del tiempo, ver quin modific
por ltima vez algo que puede estar causando un problema, quin introdujo un error y cundo, y
mucho ms. Usar un VCS tambin significa generalmente que si fastidias o pierdes archivos,
puedes recuperarlos fcilmente. Adems, obtienes todos estos beneficios a un coste muy bajo.
Un mtodo de control de versiones usado por mucha gente es copiar los archivos a otro directorio
(quizs indicando la fecha y hora en que lo hicieron, si son avispados). Este enfoque es muy
comn porque es muy simple, pero tambin tremendamente propenso a errores. Es fcil olvidar
en qu directorio te encuentras, y guardar accidentalmente en el archivo equivocado o
sobrescribir archivos que no queras.
Para hacer frente a este problema, los programadores desarrollaron hace tiempo VCSs locales
que contenan una simple base de datos en la que se llevaba registro de todos los cambios
realizados sobre los archivos (vase Figura 1-1).
Figura 1-1. Diagrama de control de versiones local.
Una de las herramientas de control de versiones ms popular fue un sistema llamado rcs, que
todava podemos encontrar en muchos de los ordenadores actuales. Hasta el famoso sistema
operativo Mac OS X incluye el comando rcs cuando instalas las herramientas de desarrollo. Esta
herramienta funciona bsicamente guardando conjuntos de parches (es decir, las diferencias
entre archivos) de una versin a otra en un formato especial en disco; puede entonces recrear
cmo era un archivo en cualquier momento sumando los distintos parches.
El siguiente gran problema que se encuentra la gente es que necesitan colaborar con
desarrolladores en otros sistemas. Para solventar este problema, se desarrollaron los sistemas de
control de versiones centralizados (Centralized Version Control Systems o CVCSs en ingls).
Estos sistemas, como CVS, Subversion, y Perforce, tienen un nico servidor que contiene todos
los archivos versionados, y varios clientes que descargan los archivos desde ese lugar central.
Durante muchos aos ste ha sido el estndar para el control de versiones (vase Figura 1-2).
Figura 1-2. Diagrama de control de versiones centralizado.
Esta configuracin ofrece muchas ventajas, especialmente frente a VCSs locales. Por ejemplo,
todo el mundo puede saber (hasta cierto punto) en qu estn trabajando los otros colaboradores
del proyecto. Los administradores tienen control detallado de qu puede hacer cada uno; y es
mucho ms fcil administrar un CVCS que tener que lidiar con bases de datos locales en cada
cliente.
Sin embargo, esta configuracin tambin tiene serias desventajas. La ms obvia es el punto nico
de fallo que representa el servidor centralizado. Si ese servidor se cae durante una hora, entonces
durante esa hora nadie puede colaborar o guardar cambios versionados de aquello en que estn
trabajando. Si el disco duro en el que se encuentra la base de datos central se corrompe, y no se
han llevado copias de seguridad adecuadamente, pierdes absolutamente todo toda la historia
del proyecto salvo aquellas instantneas que la gente pueda tener en sus mquinas locales. Los
VCSs locales sufren de este mismo problema cuando tienes toda la historia del proyecto en un
nico lugar, te arriesgas a perderlo todo.
Es aqu donde entran los sistemas de control de versiones distribuidos (Distributed Version
Control Systems o DVCSs en ingls). En un DVCS (como Git, Mercurial, Bazaar o Darcs), los
clientes no slo descargan la ltima instantnea de los archivos: replican completamente el
repositorio. As, si un servidor muere, y estos sistemas estaban colaborando a travs de l,
cualquiera de los repositorios de los clientes puede copiarse en el servidor para restaurarlo. Cada
vez que se descarga una instantnea, en realidad se hace una copia de seguridad completa de
todos los datos (vase Figura 1-3).
Como muchas de las grandes cosas en esta vida, Git comenz con un poco de destruccin creativa
y encendida polmica. El ncleo de Linux es un proyecto de software de cdigo abierto con un
alcance bastante grande. Durante la mayor parte del mantenimiento del ncleo de Linux
(1991-2002), los cambios en el software se pasaron en forma de parches y archivos. En 2002, el
proyecto del ncleo de Linux empez a usar un DVCS propietario llamado BitKeeper.
En 2005, la relacin entre la comunidad que desarrollaba el ncleo de Linux y la compaa que
desarrollaba BitKeeper se vino abajo, y la herramienta dej de ser ofrecida gratuitamente. Esto
impuls a la comunidad de desarrollo de Linux (y en particular a Linus Torvalds, el creador de
Linux) a desarrollar su propia herramienta basada en algunas de las lecciones que aprendieron
durante el uso de BitKeeper. Algunos de los objetivos del nuevo sistema fueron los siguientes:
Velocidad
Diseo sencillo
Fuerte apoyo al desarrollo no lineal (miles de ramas paralelas)
Completamente distribuido
Capaz de manejar grandes proyectos (como el ncleo de Linux) de manera eficiente
(velocidad y tamao de los datos)
Desde su nacimiento en 2005, Git ha evolucionado y madurado para ser fcil de usar y an
conservar estas cualidades iniciales. Es tremendamente rpido, muy eficiente con grandes
proyectos, y tiene un increble sistema de ramificacin (branching) para desarrollo no lineal
(vase el Captulo 3).
1.3 Empezando - Fundamentos de Git
Fundamentos de Git
Entonces, qu es Git en pocas palabras? Es muy importante asimilar esta seccin, porque si
entiendes lo que es Git y los fundamentos de cmo funciona, probablemente te sea mucho ms
fcil usar Git de manera eficaz. A medida que aprendas Git, intenta olvidar todo lo que puedas
saber sobre otros VCSs, como Subversion y Perforce; hacerlo te ayudar a evitar confusiones
sutiles a la hora de utilizar la herramienta. Git almacena y modela la informacin de forma muy
diferente a esos otros sistemas, a pesar de que su interfaz sea bastante similar; comprender esas
diferencias evitar que te confundas a la hora de usarlo.
Instantneas, no diferencias
La principal diferencia entre Git y cualquier otro VCS (Subversion y compaa incluidos) es cmo
Git modela sus datos. Conceptualmente, la mayora de los dems sistemas almacenan la
informacin como una lista de cambios en los archivos. Estos sistemas (CVS, Subversion,
Perforce, Bazaar, etc.) modelan la informacin que almacenan como un conjunto de archivos y
las modificaciones hechas sobre cada uno de ellos a lo largo del tiempo, como ilustra la Figura
1-4.
Figura 1-4. Otros sistemas tienden a almacenar los datos como cambios de cada archivo respecto
a una versin base.
Git no modela ni almacena sus datos de este modo. En cambio, Git modela sus datos ms como
un conjunto de instantneas de un mini sistema de archivos. Cada vez que confirmas un cambio,
o guardas el estado de tu proyecto en Git, l bsicamente hace una foto del aspecto de todos tus
archivos en ese momento, y guarda una referencia a esa instantnea. Para ser eficiente, si los
archivos no se han modificado, Git no almacena el archivo de nuevo, slo un enlace al archivo
anterior idntico que ya tiene almacenado. Git modela sus datos ms como en la Figura 1-5.
Figura 1-5. Git almacena la informacin como instantneas del proyecto a lo largo del tiempo.
Esta es una distincin importante entre Git y prcticamente todos los dems VCSs. Hace que Git
reconsidere casi todos los aspectos del control de versiones que muchos de los dems sistemas
copiaron de la generacin anterior. Esto hace que Git se parezca ms a un mini sistema de
archivos con algunas herramientas tremendamente potentes construidas sobre l, que a un VCS.
Exploraremos algunos de los beneficios que obtienes al modelar tus datos de esta manera cuando
veamos ramificaciones (branching) en Git en el Captulo 3.
La mayora de las operaciones en Git slo necesitan archivos y recursos locales para operar. Por
lo general no se necesita informacin de ningn otro ordenador de tu red. Si ests acostumbrado
a un CVCS donde la mayora de las operaciones tienen esa sobrecarga del retardo de la red, este
aspecto de Git te va a hacer pensar que los dioses de la velocidad han bendecido Git con poderes
sobrenaturales. Como tienes toda la historia del proyecto ah mismo, en tu disco local, la mayora
de las operaciones parecen prcticamente inmediatas.
Por ejemplo, para navegar por la historia del proyecto, Git no necesita salir al servidor para
obtener la historia y mostrrtela, simplemente la lee directamente de tu base de datos local. Esto
significa que ves la historia del proyecto casi al instante. Si quieres ver los cambios introducidos
en un archivo entre la versin actual y la de hace un mes, Git puede buscar el archivo hace un mes
y hacer un clculo de diferencias localmente, en lugar de tener que pedirle a un servidor remoto
que lo haga, u obtener una versin antigua desde la red y hacerlo de manera local.
Esto tambin significa que hay muy poco que no puedas hacer si ests desconectado o sin VPN. Si
te subes a un avin o a un tren y quieres trabajar un poco, puedes confirmar tus cambios
felizmente hasta que consigas una conexin de red para subirlos. Si te vas a casa y no consigues
que tu cliente VPN funcione correctamente, puedes seguir trabajando. En muchos otros sistemas,
esto es imposible o muy doloroso. En Perforce, por ejemplo, no puedes hacer mucho cuando no
ests conectado al servidor; y en Subversion y CVS, puedes editar archivos, pero no puedes
confirmar los cambios a tu base de datos (porque tu base de datos no tiene conexin). Esto puede
no parecer gran cosa, pero te sorprendera la diferencia que puede suponer.
Todo en Git es verificado mediante una suma de comprobacin (checksum en ingls) antes de ser
almacenado, y es identificado a partir de ese momento mediante dicha suma. Esto significa que
es imposible cambiar los contenidos de cualquier archivo o directorio sin que Git lo sepa. Esta
funcionalidad est integrada en Git al ms bajo nivel y es parte integral de su filosofa. No puedes
perder informacin durante su transmisin o sufrir corrupcin de archivos sin que Git lo detecte.
El mecanismo que usa Git para generar esta suma de comprobacin se conoce como hash SHA-1.
Se trata de una cadena de 40 caracteres hexadecimales (0-9 y a-f), y se calcula en base a los
contenidos del archivo o estructura de directorios. Un hash SHA-1 tiene esta pinta:
24b9da6552252987aa493b52f8696cd6d3b00373
Vers estos valores hash por todos lados en Git, ya que los usa con mucha frecuencia. De hecho,
Git guarda todo no por nombre de archivo, sino por el valor hash de sus contenidos.
Cuando realizas acciones en Git, casi todas ellas slo aaden informacin a la base de datos de
Git. Es muy difcil conseguir que el sistema haga algo que no se pueda deshacer, o que de algn
modo borre informacin. Como en cualquier VCS, puedes perder o estropear cambios que no has
confirmado todava; pero despus de confirmar una instantnea en Git, es muy difcil de perder,
especialmente si envas (push) tu base de datos a otro repositorio con regularidad.
Esto hace que usar Git sea un placer, porque sabemos que podemos experimentar sin peligro de
fastidiar gravemente las cosas. Para un anlisis ms exhaustivo de cmo almacena Git su
informacin y cmo puedes recuperar datos aparentemente perdidos, ver Captulo 9.
Ahora presta atencin. Esto es lo ms importante a recordar acerca de Git si quieres que el resto
de tu proceso de aprendizaje prosiga sin problemas. Git tiene tres estados principales en los que
se pueden encontrar tus archivos: confirmado (committed), modificado (modified), y preparado
(staged). Confirmado significa que los datos estn almacenados de manera segura en tu base de
datos local. Modificado significa que has modificado el archivo pero todava no lo has confirmado
a tu base de datos. Preparado significa que has marcado un archivo modificado en su versin
actual para que vaya en tu prxima confirmacin.
Esto nos lleva a las tres secciones principales de un proyecto de Git: el directorio de Git (Git
directory), el directorio de trabajo (working directory), y el rea de preparacin (staging area).
El directorio de trabajo es una copia de una versin del proyecto. Estos archivos se sacan de la
base de datos comprimida en el directorio de Git, y se colocan en disco para que los puedas usar o
modificar.
El rea de preparacin es un sencillo archivo, generalmente contenido en tu directorio de Git, que
almacena informacin acerca de lo que va a ir en tu prxima confirmacin. A veces se le
denomina ndice, pero se est convirtiendo en estndar el referirse a ella como el rea de
preparacin.
Ahora que tienes Git en tu sistema, querrs hacer algunas cosas para personalizar tu entorno de
Git. Slo es necesario hacer estas cosas una vez; se mantendrn entre actualizaciones. Tambin
puedes cambiarlas en cualquier momento volviendo a ejecutar los comandos correspondientes.
Git trae una herramienta llamada git config que te permite obtener y establecer variables de
configuracin, que controlan el aspecto y funcionamiento de Git. Estas variables pueden
almacenarse en tres sitios distintos:
Archivo /etc/gitconfig: Contiene valores para todos los usuarios del sistema y todos
sus repositorios. Si pasas la opcin --system a git config, lee y escribe
especficamente en este archivo.
Archivo ~/.gitconfig file: Especfico a tu usuario. Puedes hacer que Git lea y escriba
especficamente en este archivo pasando la opcin --global.
Archivo config en el directorio de Git (es decir, .git/config) del repositorio que ests
utilizando actualmente: Especfico a ese repositorio. Cada nivel sobrescribe los
valores del nivel anterior, por lo que los valores de .git/config tienen preferencia
sobre los de /etc/gitconfig.
Tu identidad
Lo primero que deberas hacer cuando instalas Git es establecer tu nombre de usuario y direccin
de correo electrnico. Esto es importante porque las confirmaciones de cambios (commits) en Git
usan esta informacin, y es introducida de manera inmutable en los commits que envas:
$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com
De nuevo, slo necesitas hacer esto una vez si especificas la opcin --global, ya que Git siempre
usar esta informacin para todo lo que hagas en ese sistema. Si quieres sobrescribir esta
informacin con otro nombre o direccin de correo para proyectos especficos, puedes ejecutar el
comando sin la opcin --global cuando ests en ese proyecto.
Tu editor
Ahora que tu identidad est configurada, puedes elegir el editor de texto por defecto que se
utilizar cuando Git necesite que introduzcas un mensaje. Si no indicas nada, Git usa el editor por
defecto de tu sistema, que generalmente es Vi o Vim. Si quieres usar otro editor de texto, como
Emacs, puedes hacer lo siguiente:
Tu herramienta de diferencias
Otra opcin til que puede que quieras configurar es la herramienta de diferencias por defecto,
usada para resolver conflictos de unin (merge). Digamos que quieres usar vimdiff:
Git acepta kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, y opendiff como
herramientas vlidas. Tambin puedes configurar la herramienta que t quieras; vase el
Captulo 7 para ms informacin sobre cmo hacerlo.
Comprobando tu configuracin
Si quieres comprobar tu configuracin, puedes usar el comando git config --list para listar
todas las propiedades que Git ha configurado:
Puede que veas claves repetidas, porque Git lee la misma clave de distintos archivos
(/etc/gitconfig y ~/.gitconfig, por ejemplo). En ese caso, Git usa el ltimo valor para cada clave
nica que ve.
Tambin puedes comprobar qu valor cree Git que tiene una clave especfica ejecutando git
config {clave}:
Si alguna vez necesitas ayuda usando Git, hay tres formas de ver la pgina del manual (manpage)
para cualquier comando de Git:
Por ejemplo, puedes ver la pgina del manual para el comando config ejecutando:
Estos comandos estn bien porque puedes acceder a ellos desde cualquier sitio, incluso sin
conexin. Si las pginas del manual y este libro no son suficientes y necesitas que te ayude una
persona, puedes probar en los canales #git o #github del servidor de IRC Freenode
(irc.freenode.net). Estos canales estn llenos de cientos de personas muy entendidas en Git, y
suelen estar dispuestos a ayudar.
Chapter 2
Fundamentos de Git
Si slo puedes leer un captulo para empezar a trabajar con Git, es ste. Este captulo cubre todos
los comandos bsicos que necesitas para hacer la gran mayora de las cosas a las que vas a
dedicar tu tiempo en Git. Al final del captulo, deberas ser capaz de configurar e inicializar un
repositorio, comenzar y detener el seguimiento de archivos, y preparar (stage) y confirmar
(commit) cambios. Tambin te ensearemos a configurar Git para que ignore ciertos archivos y
patrones, cmo deshacer errores rpida y fcilmente, cmo navegar por la historia de tu proyecto
y ver cambios entre confirmaciones, y cmo enviar (push) y recibir (pull) de repositorios remotos.
Puedes obtener un proyecto Git de dos maneras. La primera toma un proyecto o directorio
existente y lo importa en Git. La segunda clona un repositorio Git existente desde otro servidor.
$ git init
Esto crea un nuevo subdirectorio llamado .git que contiene todos los archivos necesarios del
repositorio un esqueleto de un repositorio Git. Todava no hay nada en tu proyecto que est
bajo seguimiento. (Vase el Captulo 9 para obtener ms informacin sobre qu archivos estn
contenidos en el directorio .gitque acabas de crear.)
Si deseas empezar a controlar versiones de archivos existentes (a diferencia de un directorio
vaco), probablemente deberas comenzar el seguimiento de esos archivos y hacer una
confirmacin inicial. Puedes conseguirlo con unos pocos comandos git add para especificar qu
archivos quieres controlar, seguidos de un commit para confirmar los cambios:
Veremos lo que hacen estos comandos dentro de un minuto. En este momento, tienes un
repositorio Git con archivos bajo seguimiento, y una confirmacin inicial.
Si deseas obtener una copia de un repositorio Git existente por ejemplo, un proyecto en el que
te gustara contribuir el comando que necesitas es git clone. Si ests familizarizado con otros
sistemas de control de versiones como Subversion, vers que el comando es clone y no
checkout. Es una distincin importante, ya que Git recibe una copia de casi todos los datos que
tiene el servidor. Cada versin de cada archivo de la historia del proyecto es descargado cuando
ejecutas git clone. De hecho, si el disco de tu servidor se corrompe, puedes usar cualquiera de los
clones en cualquiera de los clientes para devolver al servidor al estado en el que estaba cuando
fue clonado (puede que pierdas algunos hooks del lado del servidor y dems, pero toda la
informacin versionada estara ah vase el Captulo 4 para ms detalles).
Puedes clonar un repositorio con git clone [url]. Por ejemplo, si quieres clonar la librera Ruby
llamada Grit, haras algo as:
Esto crea un directorio llamado "grit", inicializa un directorio .git en su interior, descarga toda la
informacin de ese repositorio, y saca una copia de trabajo de la ltima versin. Si te metes en el
nuevo directorio grit, vers que estn los archivos del proyecto, listos para ser utilizados. Si
quieres clonar el repositorio a un directorio con otro nombre que no sea grit, puedes especificarlo
con la siguiente opcin de lnea de comandos:
Ese comando hace lo mismo que el anterior, pero el directorio de destino se llamar mygrit.
Git te permite usar distintos protocolos de transferencia. El ejemplo anterior usa el protocolo
git://, pero tambin te puedes encontrar con http(s):// o usuario@servidor:/ruta.git, que
utiliza el protocolo de transferencia SSH. En el Captulo 4 se introducirn todas las opciones
disponibles a la hora de configurar el acceso a tu repositorio Git, y las ventajas e inconvenientes
de cada una.
2.2 Fundamentos de Git - Guardando
cambios en el repositorio
Guardando cambios en el repositorio
Tienes un repositorio Git completo, y una copia de trabajo de los archivos de ese proyecto.
Necesitas hacer algunos cambios, y confirmar instantneas de esos cambios a tu repositorio cada
vez que el proyecto alcance un estado que desees grabar.
Recuerda que cada archivo de tu directorio de trabajo puede estar en uno de estos dos estados:
bajo seguimiento (tracked), o sin seguimiento (untracked). Los archivos bajo seguimiento son
aquellos que existan en la ltima instantnea; pueden estar sin modificaciones, modificados, o
preparados. Los archivos sin seguimiento son todos los dems cualquier archivo de tu
directorio que no estuviese en tu ltima instantnea ni est en tu rea de preparacin. La
primera vez que clonas un repositorio, todos tus archivos estarn bajo seguimiento y sin
modificaciones, ya que los acabas de copiar y no has modificado nada.
A medida que editas archivos, Git los ve como modificados, porque los has cambiado desde tu
ltima confirmacin. Preparas estos archivos modificados y luego confirmas todos los cambios
que hayas preparado, y el ciclo se repite. Este proceso queda ilustrado en la Figura 2-1.
Figura 2-1. El ciclo de vida del estado de tus archivos.
$ git status
# On branch master
nothing to commit, working directory clean
Esto significa que tienes un directorio de trabajo limpio en otras palabras, no tienes archivos
bajo seguimiento y modificados. Git tampoco ve ningn archivo que no est bajo seguimiento, o
estara listado ah. Por ltimo, el comando te dice en qu rama ests. Por ahora, esa rama
siempre es "master", que es la predeterminada. No te preocupes de eso por ahora, el siguiente
captulo tratar los temas de las ramas y las referencias en detalle.
Digamos que aades un nuevo archivo a tu proyecto, un sencillo archivo README. Si el archivo
no exista y ejecutas git status, vers tus archivos sin seguimiento as:
$ vim README
$ git status
# On branch master
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# README
nothing added to commit but untracked files present (use "git add" to track)
Puedes ver que tu nuevo archivo README aparece bajo la cabecera Archivos sin seguimiento
(Untracked files) de la salida del comando. Sin seguimiento significa bsicamente que Git ve un
archivo que no estaba en la instantnea anterior; Git no empezar a incluirlo en las
confirmaciones de tus instantneas hasta que se lo indiques explcitamente. Lo hace para que no
incluyas accidentalmente archivos binarios generados u otros archivos que no tenas intencin de
incluir. S que quieres incluir el README, as que vamos a iniciar el seguimiento del archivo.
Para empezar el seguimiento de un nuevo archivo se usa el comando git add. Iniciaremos el
seguimiento del archivo README ejecutando esto:
$ git add README
Si vuelves a ejecutar el comando git status, vers que tu README est ahora bajo seguimiento y
preparado:
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: README
#
Puedes ver que est preparado porque aparece bajo la cabecera Cambios a confirmar (Changes
to be committed). Si confirmas ahora, la versin del archivo en el momento de ejecutar git add
ser la que se incluya en la instantnea. Recordars que cuando antes ejecutaste git init,
seguidamente ejecutaste git add (archivos). Esto era para iniciar el seguimiento de los archivos
de tu directorio. El comando git add recibe la ruta de un archivo o de un directorio; si es un
directorio, aade todos los archivos que contenga de manera recursiva.
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: README
#
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
#
# modified: benchmarks.rb
#
El archivo benchmarks.rb aparece bajo la cabecera Modificados pero no actualizados (Changes
not staged for commit) esto significa que un archivo bajo seguimiento ha sido modificado en el
directorio de trabajo, pero no ha sido preparado todava. Para prepararlo, ejecuta el comando
git add (es un comando multiuso puedes utilizarlo para empezar el seguimiento de archivos
nuevos, para preparar archivos, y para otras cosas como marcar como resueltos archivos con
conflictos de unin). Ejecutamos git add para preparar el archivo benchmarks.rb, y volvemos a
ejecutar git status:
Ambos archivos estn ahora preparados y se incluirn en tu prxima confirmacin. Supn que en
este momento recuerdas que tenas que hacer una pequea modificacin en benchmarks.rb antes
de confirmarlo. Lo vuelves abrir, haces ese pequeo cambio, y ya ests listo para confirmar. Sin
embargo, si vuelves a ejecutar git status vers lo siguiente:
$ vim benchmarks.rb
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: README
# modified: benchmarks.rb
#
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
#
# modified: benchmarks.rb
#
Pero qu...? Ahora benchmarks.rb aparece listado como preparado y como no preparado.
Cmo es posible? Resulta que Git prepara un archivo tal y como era en el momento de ejecutar
el comando git add. Si haces git commit ahora, la versin de benchmarks.rb que se incluir en
la confirmacin ser la que fuese cuando ejecutaste el comando git add, no la versin que ests
viendo ahora en tu directorio de trabajo. Si modificas un archivo despus de haber ejecutado git
add, tendrs que volver a ejecutar git add para preparar la ltima versin del archivo:
Ignorando archivos
A menudo tendrs un tipo de archivos que no quieras que Git aada automticamente o te
muestre como no versionado. Suelen ser archivos generados automticamente, como archivos de
log, o archivos generados por tu compilador. Para estos casos puedes crear un archivo llamado
.gitignore, en el que listas los patrones de nombres que deseas que sean ignorados. He aqu un
archivo .gitignore de ejemplo:
$ cat .gitignore
*.[oa]
*~
La primera lnea le dice a Git que ignore cualquier archivo cuyo nombre termine en .o o .a
archivos objeto que suelen ser producto de la compilacin de cdigo. La segunda lnea le dice
a Git que ignore todos los archivos que terminan en tilde (~), usada por muchos editores de
texto, como Emacs, para marcar archivos temporales. Tambin puedes incluir directorios de log,
temporales, documentacin generada automticamente, etc. Configurar un archivo .gitignore
antes de empezar a trabajar suele ser una buena idea, para as no confirmar archivos que no
quieres en tu repositorio Git.
Las reglas para los patrones que pueden ser incluidos en el archivo .gitignore son:
Las lneas en blanco, o que comienzan por #, son ignoradas.
Puedes usar patrones glob estndar.
Puedes indicar un directorio aadiendo una barra hacia delante (/) al final.
Puedes negar un patrn aadiendo una exclamacin (!) al principio.
Los patrones glob son expresiones regulares simplificadas que pueden ser usadas por las shells.
Un asterisco (*) reconoce cero o ms caracteres; [abc] reconoce cualquier carcter de los
especificados entre corchetes (en este caso, a, b o c); una interrogacin (?) reconoce un nico
carcter; y caracteres entre corchetes separados por un guin ([0-9]) reconoce cualquier carcter
entre ellos (en este caso, de 0 a 9).
Si el comando git status es demasiado impreciso para ti quieres saber exactamente lo que ha
cambiado, no slo qu archivos fueron modificados puedes usar el comando git diff. Veremos
git diff en ms detalle despus; pero probablemente lo usars para responder estas dos
preguntas: qu has cambiado pero an no has preparado?, y qu has preparado y ests a punto
de confirmar? Aunque git status responde esas preguntas de manera general, git diff te
muestra exactamente las lneas aadidas y eliminadas el parche, como si dijsemos.
Supongamos que quieres editar y preparar el archivo README otra vez, y luego editar el archivo
benchmarks.rb sin prepararlo. Si ejecutas el comando status, de nuevo vers algo as:
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: README
#
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
#
# modified: benchmarks.rb
#
Para ver lo que has modificado pero an no has preparado, escribe git diff:
$ git diff
diff --git a/benchmarks.rb b/benchmarks.rb
index 3cb747f..da65585 100644
--- a/benchmarks.rb
+++ b/benchmarks.rb
@@ -36,6 +36,10 @@ def main
@commit.parents[0].parents[0].parents[0]
end
Ese comando compara lo que hay en tu directorio de trabajo con lo que hay en tu rea de
preparacin. El resultado te indica los cambios que has hecho y que todava no has preparado.
Si quieres ver los cambios que has preparado y que irn en tu prxima confirmacin, puedes usar
git diff -cached. (A partir de la versin 1.6.1 de Git, tambin puedes usar git diff -staged,
que puede resultar ms fcil de recordar.) Este comando compara tus cambios preparados con tu
ltima confirmacin:
$ git diff --cached
diff --git a/README b/README
new file mode 100644
index 0000000..03902a1
--- /dev/null
+++ b/README2
@@ -0,0 +1,5 @@
+grit
+ by Tom Preston-Werner, Chris Wanstrath
+ http://github.com/mojombo/grit
+
+Grit is a Ruby library for extracting information from a Git repository
Es importante indicar que git diff por s solo no muestra todos los cambios hechos desde tu
ltima confirmacin slo los cambios que todava no estn preparados. Esto puede resultar
desconcertante, porque si has preparado todos tus cambios, git diff no mostrar nada.
Por poner otro ejemplo, si preparas el archivo benchmarks.rb y despus lo editas, puedes usar git
diffpara ver las modificaciones del archivo que estn preparadas, y las que no lo estn:
Ahora puedes usar git diff para ver qu es lo que an no est preparado:
$ git diff
diff --git a/benchmarks.rb b/benchmarks.rb
index e445e28..86b2f7c 100644
--- a/benchmarks.rb
+++ b/benchmarks.rb
@@ -127,3 +127,4 @@ end
main()
##pp Grit::GitRuby.cache_client.stats
+# test line
Y git diff --cached para ver los cambios que llevas preparados hasta ahora:
Ahora que el rea de preparacin est como t quieres, puedes confirmar los cambios. Recuerda
que cualquier cosa que todava est sin preparar cualquier archivo que hayas creado o
modificado, y sobre el que no hayas ejecutado git add desde su ltima edicin no se incluir en
esta confirmacin. Se mantendrn como modificados en tu disco.
En este caso, la ltima vez que ejecutaste git status viste que estaba todo preparado, por lo que
ests listo para confirmar tus cambios. La forma ms fcil de confirmar es escribiendo git
commit:
$ git commit
Al hacerlo, se ejecutar tu editor de texto. (Esto se configura a travs de la variable de entorno
$EDITORde tu shell normalmente vim o emacs, aunque puedes configurarlo usando el
comando git config --global core.editor como vimos en el Captulo 1.)
# 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:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: README
# modified: benchmarks.rb
~
~
~
".git/COMMIT_EDITMSG" 10L, 283C
Puedes ver que el mensaje de confirmacin predeterminado contiene la salida del comando git
statuscomentada, y una lnea vaca arriba del todo. Puedes eliminar estos comentarios y escribir
tu mensaje de confirmacin, o puedes dejarlos para ayudarte a recordar las modificaciones que
ests confirmando. (Para un recordatorio todava ms explcito de lo que has modificado, puedes
pasar la opcin -v a git commit. Esto provoca que se aadan tambin las diferencias de tus
cambios, para que veas exactamente lo que hiciste.) Cuando sales del editor, Git crea tu
confirmacin con el mensaje que hayas especificado (omitiendo los comentarios y las
diferencias).
Como alternativa, puedes escribir tu mensaje de confirmacin desde la propia lnea de comandos
mediante la opcin -m:
Acabas de crear tu primera confirmacin! Puedes ver que el comando commit ha dado cierta
informacin sobre la confirmacin: a qu rama has confirmado (master), cul es su suma de
comprobacin SHA-1 de la confirmacin (463dc4f), cuntos archivos se modificaron, y
estadsticas acerca de cuntas lneas se han aadido y cuntas se han eliminado.
Aunque puede ser extremadamente til para elaborar confirmaciones exactamente a tu gusto, el
rea de preparacin es en ocasiones demasiado compleja para las necesidades de tu flujo de
trabajo. Si quieres saltarte el rea de preparacin, Git proporciona un atajo. Pasar la opcin -a al
comando git commithace que Git prepare todo archivo que estuviese en seguimiento antes de la
confirmacin, permitindote obviar toda la parte de git add:
$ git status
# On branch master
#
# Changes not staged for commit:
#
# modified: benchmarks.rb
#
$ git commit -a -m 'added new benchmarks'
[master 83e38c7] added new benchmarks
1 files changed, 5 insertions(+), 0 deletions(-)
Fjate que no has tenido que ejecutar git add sobre el archivo benchmarks.rb antes de hacer la
confirmacin.
Eliminando archivos
Para eliminar un archivo de Git, debes eliminarlo de tus archivos bajo seguimiento (ms
concretamente, debes eliminarlo de tu rea de preparacin), y despus confirmar. El comando
git rm se encarga de eso, y tambin elimina el archivo de tu directorio de trabajo, para que no lo
veas entre los archivos sin seguimiento.
Si entonces ejecutas el comando git rm, preparas la eliminacin del archivo en cuestin:
$ git rm grit.gemspec
rm 'grit.gemspec'
$ git status
# On branch master
#
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# deleted: grit.gemspec
#
La prxima vez que confirmes, el archivo desaparecer y dejar de estar bajo seguimiento. Si ya
habas modificado el archivo y lo tenas en el rea de preparacin, debers forzar su eliminacin
con la opcin -f. sta es una medida de seguridad para evitar la eliminacin accidental de
informacin que no ha sido registrada en una instantnea, y que por tanto no podra ser
recuperada.
Otra cosa que puede que quieras hacer es mantener el archivo en tu directorio de trabajo, pero
eliminarlo de tu rea de preparacin. Dicho de otro modo, puede que quieras mantener el archivo
en tu disco duro, pero interrumpir su seguimiento por parte de Git. Esto resulta particularmente
til cuando olvidaste aadir algo a tu archivo .gitignore y lo aadiste accidentalmente, como un
archivo de log enorme, o un montn de archivos .a. Para hacer esto, usa la opcin --cached:
El comando git rm acepta archivos, directorios, y patrones glob. Es decir, que podras hacer algo
as:
$ git rm log/\*.log
Fjate en la barra hacia atrs (\) antes del *. Es necesaria debido a que Git hace su propia
expansin de rutas, adems de la expansin que hace tu shell. En la consola del sistema de
Windows, esta barra debe de ser omitida. Este comando elimina todos los archivos con la
extensin .log en el directorio log/. Tambin puedes hacer algo as:
$ git rm \*~
Moviendo archivos
A diferencia de muchos otros VCSs, Git no hace un seguimiento explicito del movimiento de
archivos. Si renombras un archivo, en Git no se almacena ningn metadato que indique que lo
has renombrado. Sin embargo, Git es suficientemente inteligente como para darse cuenta
trataremos el tema de la deteccin de movimiento de archivos un poco ms adelante.
Por tanto, es un poco desconcertante que Git tenga un comando mv. Si quieres renombrar un
archivo en Git, puedes ejecutar algo as:
Y funciona perfectamente. De hecho, cuando ejecutas algo as y miras la salida del comando
status, vers que Git lo considera un archivo renombrado:
Despus de haber hecho varias confirmaciones, o si has clonado un repositorio que ya tena un
histrico de confirmaciones, probablemente quieras mirar atrs para ver qu modificaciones se
han llevado a cabo. La herramienta ms bsica y potente para hacer esto es el comando git log.
Estos ejemplos usan un proyecto muy sencillo llamado simplegit que suelo usar para hacer
demostraciones. Para clonar el proyecto, ejecuta:
Cuando ejecutes git log sobre este proyecto, deberas ver una salida similar a esta:
$ git log
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date: Mon Mar 17 21:52:11 2008 -0700
commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sat Mar 15 16:40:33 2008 -0700
removed unnecessary test code
commit a11bef06a3f659402fe7563abf99ad00de2209e6
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sat Mar 15 10:31:28 2008 -0700
first commit
Por defecto, si no pasas ningn argumento, git log lista las confirmaciones hechas sobre ese
repositorio en orden cronolgico inverso. Es decir, las confirmaciones ms recientes se muestran
al principio. Como puedes ver, este comando lista cada confirmacin con su suma de
comprobacin SHA-1, el nombre y direccin de correo del autor, la fecha y el mensaje de
confirmacin.
El comando git log proporciona gran cantidad de opciones para mostrarte exactamente lo que
buscas. Aqu veremos algunas de las ms usadas.
Una de las opciones ms tiles es -p, que muestra las diferencias introducidas en cada
confirmacin. Tambin puedes usar la opcin -2, que hace que se muestren nicamente las dos
ltimas entradas del histrico:
$ git log p -2
commit ca82a6dff817ec66f44342007202690a93763949
Author: Scott Chacon <schacon@gee-mail.com>
Date: Mon Mar 17 21:52:11 2008 -0700
commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sat Mar 15 16:40:33 2008 -0700
removed unnecessary test code
end
-
-if $0 == __FILE__
- git = SimpleGit.new
- puts git.show
-end
\ No newline at end of file
Esta opcin muestra la misma informacin, pero aadiendo tras cada entrada las diferencias que
le corresponden. Esto resulta muy til para revisiones de cdigo, o para visualizar rpidamente lo
que ha pasado en las confirmaciones enviadas por un colaborador.
A veces es ms fcil revisar cambios a nivel de palabra que a nivel de lnea. Git dispone de la
opcin --word-diff, que se puede aadir al comando git log -p para obtener las diferencias por
palabras en lugar de las diferencias lnea por lnea. Formatear las diferencias a nivel de palabra es
bastante inusual cuando se aplica a cdigo fuente, pero resulta muy prctico cuando se aplica a
grandes archivos de texto, como libros o tu propia tesis. He aqu un ejemplo:
Como se puede ver, no aparecen lneas aadidas o eliminadas en la salida como en las diferencias
normales. Se puede ver la palabra aadida encerrada en {+ +} y la eliminada en [- -]. Puede
que se quiera reducir las usuales tres lneas de contexto en las diferencias a slo una lnea puesto
que el contexto es ahora de palabras, no de lneas. Se puede hacer esto con -U1, como hicimos en
el ejemplo de arriba.
Tambin puedes usar con git log una serie de opciones de resumen. Por ejemplo, si quieres ver
algunas estadsticas de cada confirmacin, puedes usar la opcin --stat:
Rakefile | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
commit 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sat Mar 15 16:40:33 2008 -0700
lib/simplegit.rb | 5 -----
1 files changed, 0 insertions(+), 5 deletions(-)
commit a11bef06a3f659402fe7563abf99ad00de2209e6
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sat Mar 15 10:31:28 2008 -0700
first commit
README | 6 ++++++
Rakefile | 23 +++++++++++++++++++++++
lib/simplegit.rb | 25 +++++++++++++++++++++++++
3 files changed, 54 insertions(+), 0 deletions(-)
Como puedes ver, la opcin --stat imprime tras cada confirmacin una lista de archivos
modificados, indicando cuntos han sido modificados y cuntas lneas han sido aadidas y
eliminadas para cada uno de ellos, y un resumen de toda esta informacin.
Otra opcin realmente til es --pretty, que modifica el formato de la salida. Tienes unos cuantos
estilos disponibles. La opcin oneline imprime cada confirmacin en una nica lnea, lo que
puede resultar til si ests analizando gran cantidad de confirmaciones. Otras opciones son
short, full y fuller, que muestran la salida en un formato parecido, pero aadiendo menos o ms
informacin, respectivamente:
La opcin ms interesante es format, que te permite especificar tu propio formato. Esto resulta
especialmente til si ests generando una salida para que sea analizada por otro programa
como especificas el formato explcitamente, sabes que no cambiar en futuras actualizaciones
de Git:
La Tabla 2-1 lista algunas de las opciones ms tiles aceptadas por format.
%H Hash de la confirmacin
%s Asunto
Puede que te ests preguntando la diferencia entre autor (author) y confirmador (committer). El
autor es la persona que escribi originalmente el trabajo, mientras que el confirmador es quien lo
aplic. Por tanto, si mandas un parche a un proyecto, y uno de sus miembros lo aplica, ambos
recibiris reconocimiento t como autor, y el miembro del proyecto como confirmador.
Veremos esta distincin en mayor profundidad en el Captulo 5.
Las opciones oneline y format son especialmente tiles combinadas con otra opcin llamada
--graph. sta aade un pequeo grfico ASCII mostrando tu histrico de ramificaciones y
uniones, como podemos ver en nuestra copia del repositorio del proyecto Grit:
stas son slo algunas de las opciones para formatear la salida de git log existen muchas ms.
La Tabla 2-2 lista las opciones vistas hasta ahora, y algunas otras opciones de formateo que
pueden resultarte tiles, as como su efecto sobre la salida.
Opcin Descripcin
--relative-date Muestra la fecha en formato relativo (por ejemplo, 2 weeks ago (hace
2 semanas)) en lugar del formato completo.
Adems de las opciones de formateo, git log acepta una serie de opciones para limitar su salida
es decir, opciones que te permiten mostrar nicamente parte de las confirmaciones. Ya has
visto una de ellas, la opcin -2, que muestra slo las dos ltimas confirmaciones. De hecho,
puedes hacer -<n>, siendo n cualquier entero, para mostrar las ltimas n confirmaciones. En
realidad es poco probable que uses esto con frecuencia, ya que Git por defecto pagina su salida
para que veas cada pgina del histrico por separado.
Sin embargo, las opciones temporales como --since (desde) y --until (hasta) s que resultan muy
tiles. Por ejemplo, este comando lista todas las confirmaciones hechas durante las dos ltimas
semanas:
Este comando acepta muchos formatos. Puedes indicar una fecha concreta (2008-01-15), o
relativa, como 2 years 1 day 3 minutes ago (hace 2 aos, 1 da y 3 minutos).
Tambin puedes filtrar la lista para que muestre slo aquellas confirmaciones que cumplen
ciertos criterios. La opcin --author te permite filtrar por autor, y --grep te permite buscar
palabras clave entre los mensajes de confirmacin. (Ten en cuenta que si quieres aplicar ambas
opciones simultneamente, tienes que aadir --all-match, o el comando mostrar las
confirmaciones que cumplan cualquiera de las dos, no necesariamente las dos a la vez.)
La ltima opcin verdaderamente til para filtrar la salida de git log es especificar una ruta. Si
especificas la ruta de un directorio o archivo, puedes limitar la salida a aquellas confirmaciones
que introdujeron un cambio en dichos archivos. sta debe ser siempre la ltima opcin, y suele ir
precedida de dos guiones (--) para separar la ruta del resto de opciones.
En la Tabla 2-3 se listan estas opciones, y algunas otras bastante comunes, a modo de referencia.
Opcin Descripcin
Por ejemplo, si quieres ver cules de las confirmaciones hechas sobre archivos de prueba del
cdigo fuente de Git fueron enviadas por Junio Hamano, y no fueron uniones, en el mes de
octubre de 2008, ejecutaras algo as:
De las casi 20.000 confirmaciones en la historia del cdigo fuente de Git, este comando muestra
las 6 que cumplen estas condiciones.
Puedes ver el histrico de confirmaciones en la mitad superior de la ventana, junto con un grfico
de ascendencia. El visor de diferencias de la mitad inferior muestra las modificaciones
introducidas en cada confirmacin que selecciones.
En cualquier momento puedes querer deshacer algo. En esta seccin veremos algunas
herramientas bsicas para deshacer cambios. Ten cuidado, porque no siempre puedes volver
atrs despus de algunas de estas operaciones. sta es una de las pocas reas de Git que pueden
provocar que pierdas datos si haces las cosas incorrectamente.
Este comando utiliza lo que haya en tu rea de preparacin para la confirmacin. Si no has hecho
ningn cambio desde la ltima confirmacin (por ejemplo, si ejecutas este comando justo
despus de tu confirmacin anterior), esta instantnea ser exactamente igual, y lo nico que
cambiars ser el mensaje de confirmacin.
Se lanzar el editor de texto para que introduzcas tu mensaje, pero ya contendr el mensaje de la
confirmacin anterior. Puedes editar el mensaje, igual que siempre, pero se sobreescribir tu
confirmacin anterior.
Por ejemplo, si confirmas y luego te das cuenta de que se te olvid preparar los cambios en uno
de los archivos que queras aadir, puedes hacer algo as:
Las dos secciones siguientes muestran cmo trabajar con las modificaciones del rea de
preparacin y del directorio de trabajo. Lo bueno es que el comando que usas para determinar el
estado de ambas reas te recuerda como deshacer sus modificaciones. Por ejemplo, digamos que
has modificado dos archivos, y quieres confirmarlos como cambios separados, pero tecleas
accidentalmente git add * y preparas ambos. Cmo puedes sacar uno de ellos del rea de
preparacin? El comando git status te lo recuerda:
$ git add .
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: README.txt
# modified: benchmarks.rb
#
Justo debajo de la cabecera Cambios a confirmar (Changes to be committed), dice que uses
git reset HEAD <archivo>... para sacar un archivo del rea de preparacin. Vamos a aplicar
ese consejo sobre benchmarks.rb:
El comando es un poco extrao, pero funciona. El archivo benchmarks.rb ahora est modificado,
no preparado.
Qu pasa si te das cuenta de que no quieres mantener las modificaciones que has hecho sobre el
archivo benchmarks.rb? Cmo puedes deshacerlas fcilmente revertir el archivo al mismo
estado en el que estaba cuando hiciste tu ltima confirmacin (o cuando clonaste el repositorio, o
como quiera que metieses el archivo en tu directorio de trabajo)? Afortunadamente, git status
tambin te dice como hacer esto. En la salida del ltimo ejemplo, la cosa estaba as:
Te dice de forma bastante explcita cmo descartar las modificaciones que hayas hecho (al menos
las versiones de Git a partir de la 1.6.1 lo hacen si tienes una versin ms antigua, te
recomendamos encarecidamente que la actualices para obtener algunas de estas mejoras de
usabilidad). Vamos a hacer lo que dice:
Puedes ver que se han revertido los cambios. Tambin deberas ser consciente del peligro de este
comando: cualquier modificacin hecha sobre este archivo ha desaparecido acabas de
sobreescribirlo con otro archivo. Nunca uses este comando a no ser que ests absolutamente
seguro de que no quieres el archivo. Si lo nico que necesitas es olvidarte de l
momentneamente, veremos los conceptos de apilamiento (stashing) y ramificacin (branching)
en el prximo captulo; en general son formas ms adecuadas de trabajar.
Recuerda, cualquier cosa que est confirmada en Git casi siempre puede ser recuperada. Incluso
confirmaciones sobre ramas que han sido eliminadas, o confirmaciones sobreescritas con la
opcin --amend, pueden recuperarse (vase el Captulo 9 para conocer ms sobre recuperacin
de datos). Sin embargo, cualquier cosa que pierdas y que no estuviese confirmada,
probablemente no vuelvas a verla nunca ms.
Gestionar repositorios remotos implica conocer cmo aadir repositorios nuevos, eliminar
aquellos que ya no son vlidos, gestionar ramas remotas e indicar si estn bajo seguimiento o no,
y ms cosas. En esta seccin veremos todos estos conceptos.
Para ver qu repositorios remotos tienes configurados, puedes ejecutar el comando git remote.
Mostrar una lista con los nombres de los remotos que hayas especificado. Si has clonado tu
repositorio, deberas ver por lo menos "origin" es el nombre predeterminado que le da Git al
servidor del que clonaste:
Tambin puedes aadir la opcin -v, que muestra la URL asociada a cada repositorio remoto:
$ git remote -v
origin git://github.com/schacon/ticgit.git (fetch)
origin git://github.com/schacon/ticgit.git (push)
Si tienes ms de un remoto, este comando los lista todos. Por ejemplo, mi repositorio Grit tiene
esta pinta:
$ cd grit
$ git remote -v
bakkdoor git://github.com/bakkdoor/grit.git
cho45 git://github.com/cho45/grit.git
defunkt git://github.com/defunkt/grit.git
koke git://github.com/koke/grit.git
origin git@github.com:mojombo/grit.git
Esto significa que podemos recibir contribuciones de cualquiera de estos usuarios de manera
bastante fcil. Pero fjate en que slo el remoto origen tiene una URL SSH, por lo que es el nico
al que podemos enviar (veremos el por qu en el Captulo 4).
$ git remote
origin
$ git remote add pb git://github.com/paulboone/ticgit.git
$ git remote -v
origin git://github.com/schacon/ticgit.git
pb git://github.com/paulboone/ticgit.git
Ahora puedes usar la cadena "pb" en la lnea de comandos, en lugar de toda la URL. Por ejemplo,
si quieres recuperar toda la informacin de Paul que todava no tienes en tu repositorio, puedes
ejecutar git fetch pb:
$ git fetch pb
remote: Counting objects: 58, done.
remote: Compressing objects: 100% (41/41), done.
remote: Total 44 (delta 24), reused 1 (delta 0)
Unpacking objects: 100% (44/44), done.
From git://github.com/paulboone/ticgit
* [new branch] master -> pb/master
* [new branch] ticgit -> pb/ticgit
La rama maestra de Paul es accesible localmente como pb/master puedes unirla a una de tus
ramas, o copiarla localmente para inspeccionarla.
Recibiendo de tus repositorios remotos
Como acabas de ver, para recuperar datos de tus repositorios remotos puedes ejecutar:
Este comando recupera todos los datos del proyecto remoto que no tengas todava. Despus de
hacer esto, deberas tener referencias a todas las ramas del repositorio remoto, que puedes unir o
inspeccionar en cualquier momento. (Veremos qu son las ramas y cmo utilizarlas en ms
detalle en el Captulo 3.)
Si has configurado una rama para seguir otra rama remota (vase la siguiente seccin y el
Captulo 3 para ms informacin), puedes usar el comando git pull para recuperar y unir
automticamente la rama remota con tu rama actual. ste puede resultarte un flujo de trabajo
ms sencillo y ms cmodo; y por defecto, el comando git clone automticamente configura tu
rama local maestra para que siga la rama remota maestra del servidor del cual clonaste
(asumiendo que el repositorio remoto tiene una rama maestra). Al ejecutar git pull, por lo
general se recupera la informacin del servidor del que clonaste, y automticamente se intenta
unir con el cdigo con el que ests trabajando actualmente.
Cuando tu proyecto se encuentra en un estado que quieres compartir, tienes que enviarlo a un
repositorio remoto. El comando que te permite hacer esto es sencillo: git push
[nombre-remoto][nombre-rama]. Si quieres enviar tu rama maestra (master) a tu servidor
origen (origin), ejecutaras esto para enviar tu trabajo al servidor:
Este comando funciona nicamente si has clonado de un servidor en el que tienes permiso de
escritura, y nadie ha enviado informacin mientras tanto. Si t y otra persona clonais a la vez, y l
enva su informacin y luego envas t la tuya, tu envo ser rechazado. Tendrs que bajarte
primero su trabajo e incorporarlo en el tuyo para que se te permita hacer un envo. Vase el
Captulo 3 para ver en detalle cmo enviar a servidores remotos.
Esto lista la URL del repositorio remoto, as como informacin sobre las ramas bajo seguimiento.
Este comando te recuerda que si ests en la rama maestra y ejecutas git pull, automticamente
unir los cambios a la rama maestra del remoto despus de haber recuperado todas las
referencias remotas. Tambin lista todas las referencias remotas que ha recibido.
El anterior es un sencillo ejemplo que te encontrars con frecuencia. Sin embargo, cuando uses
Git de forma ms avanzada, puede que git remote show muestre mucha ms informacin:
Este comando muestra qu rama se enva automticamente cuando ejecutas git push en
determinadas ramas. Tambin te muestra qu ramas remotas no tienes todava, qu ramas
remotas tienes y han sido eliminadas del servidor, y mltiples ramas que sern unidas
automticamente cuando ejecutes git pull.
Conviene mencionar que esto tambin cambia el nombre de tus ramas remotas. Lo que antes era
referenciado en pb/master ahora est en paul/master.
Si por algn motivo quieres eliminar una referencia has movido el servidor o ya no ests
usando un determinado mirror, o quizs un contribuidor ha dejado de contribuir puedes usar el
comando git remote rm:
Como muchos VCSs, Git tiene la habilidad de etiquetar (tag) puntos especficos en la historia
como importantes. Generalmente la gente usa esta funcionalidad para marcar puntos donde se
ha lanzado alguna versin (v1.0, y as sucesivamente). En esta seccin aprenders cmo listar las
etiquetas disponibles, crear nuevas etiquetas y qu tipos diferentes de etiquetas hay.
Listar las etiquetas disponibles en Git es sencillo, Simplemente escribe git tag:
$ git tag
v0.1
v1.3
Este comando lista las etiquetas en orden alfabtico; el orden en el que aparecen no es realmente
importante.
Creando etiquetas
Git usa dos tipos principales de etiquetas: ligeras y anotadas. Una etiqueta ligera es muy parecida
a una rama que no cambia un puntero a una confirmacin especfica. Sin embargo, las
etiquetas anotadas son almacenadas como objetos completos en la base de datos de Git. Tienen
suma de comprobacin; contienen el nombre del etiquetador, correo electrnico y fecha; tienen
mensaje de etiquetado; y pueden estar firmadas y verificadas con GNU Privacy Guard (GPG).
Generalmente se recomienda crear etiquetas anotadas para disponer de toda esta informacin;
pero si por alguna razn quieres una etiqueta temporal y no quieres almacenar el resto de
informacin, tambin tiene disponibles las etiquetas ligeras.
Etiquetas anotadas
Crear una etiqueta anotada en Git es simple. La forma ms fcil es especificar -a al ejecutar el
comando tag:
Puedes ver los datos de la etiqueta junto con la confirmacin que fue etiquetada usando el
comando git show:
my version 1.4
commit 15027957951b64cf874c3557a0f3547bd83b3ff6
Merge: 4a447f7... a6b4c97...
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sun Feb 8 19:02:46 2009 -0800
Esto muestra la informacin del autor de la etiqueta, la fecha en la que la confirmacin fue
etiquetada, y el mensaje de anotacin antes de mostrar la informacin de la confirmacin.
Etiquetas firmadas
Tambin puedes firmar tus etiquetas con GPG, siempre que tengas una clave privada. Lo nico
que debes hacer es usar -s en vez de -a:
Si ejecutas git show en esa etiqueta, puedes ver la firma GPG adjunta a ella:
iEYEABECAAYFAkmQurIACgkQON3DxfchxFr5cACeIMN+ZxLKggJQf0QYiQBwgySN
Ki0An2JeAVUCAiJ7Ox6ZEtK+NvZAj82/
=WryJ
-----END PGP SIGNATURE-----
commit 15027957951b64cf874c3557a0f3547bd83b3ff6
Merge: 4a447f7... a6b4c97...
Author: Scott Chacon <schacon@gee-mail.com>
Date: Sun Feb 8 19:02:46 2009 -0800
Etiquetas ligeras
Otra forma de etiquetar confirmaciones es con una etiqueta ligera. Esto es bsicamente la suma
de comprobacin de la confirmacin almacenada en un archivo ninguna otra informacin es
guardada. Para crear una etiqueta ligera no aadas las opciones -a, -s o -m:
Verificando etiquetas
Para verificar una etiqueta firmada, debes usar git tag -v [tag-name]. Este comando utiliza
GPG para verificar la firma. Necesitas la clave pblica del autor de la firma en tu llavero para que
funcione correctamente.
GIT 1.4.2.1
Minor fixes since 1.4.2, including git-mv and git-http with alternates.
gpg: Signature made Wed Sep 13 02:08:25 2006 PDT using DSA key ID F3119B9A
gpg: Good signature from "Junio C Hamano <junkio@cox.net>"
gpg: aka "[jpeg image of size 1513]"
Primary key fingerprint: 3565 2A26 2040 E066 C9A7 4A7D C0C6 D9A4 F311 9B9A
gpg: Signature made Wed Sep 13 02:08:25 2006 PDT using DSA key ID F3119B9A
gpg: Can't check signature: public key not found
error: could not verify the tag 'v1.4.2.1'
Etiquetando ms tarde
Puedes incluso etiquetar confirmaciones despus de avanzar sobre ellas. Supn que tu historico
de confirmaciones se parece a esto:
Ahora, supn que olvidaste etiquetar el proyecto en v1.2, que estaba en la confirmacin "updated
rakefile". Puedes hacerlo ahora. Para etiquetar esa confirmacin especifica la suma de
comprobacin de la confirmacin (o una parte de la misma) al final del comando:
$ git tag
v0.1
v1.2
v1.3
v1.4
v1.4-lw
v1.5
version 1.2
commit 9fceb02d0ae598e95dc970b74767f19372d61af8
Author: Magnus Chacon <mchacon@gee-mail.com>
Date: Sun Apr 27 20:43:35 2008 -0700
updated rakefile
...
Compartiendo etiquetas
Por defecto, el comando git push no transfiere etiquetas a servidores remotos. Tienes que
enviarlas explicitamente a un servidor compartido despus de haberlas creado. Este proceso es
igual a compartir ramas remotas puedes ejecutar git push origin [tagname].
Si tienes un montn de etiquetas que quieres enviar a la vez, tambin puedes usar la opcin
--tags en el comando git push. Esto transfiere todas tus etiquetas que no estn ya en el servidor
remoto.
Ahora, cuando alguien clone o reciba de tu repositorio, obtendr tambin todas tus etiquetas.
2.7 Fundamentos de Git - Consejos y
trucos
Consejos y trucos
Antes de que terminemos este capitulo de Git bsico, unos pocos trucos y consejos que harn de
tu experiencia con Git ms sencilla, fcil, o ms familiar. Mucha gente usa Git sin usar ninguno
de estos consejos, y no nos referiremos a ellos o asumiremos que los has usado ms tarde en el
libro, pero probablemente debas saber cmo hacerlos.
Autocompletado
Si usas el shell Bash, Git viene con un buen script de autocompletado que puedes activar.
Descrgalo directamente desde el cdigo fuente de Git en
https://github.com/git/git/blob/master/contrib/completion/git-completion.bash, copia
este fichero en tu directorio home y aade esto a tu archivo .bashrc:
source ~/git-completion.bash
Si quieres que Git tenga automticamente autocompletado para todos los usuarios, copia este
script en el directorio /opt/local/etc/bash_completion.d en sistemas Mac, o en el directorio
/etc/bash_completion.d/ en sistemas Linux. Este es un directorio de scripts que Bash cargar
automticamente para proveer de autocompletado.
Si ests usando Windows con el Bash de Git, el cual es el predeterminado cuando instalas Git en
Windows con msysGit, el autocompletado debera estar preconfigurado.
$ git co<tab><tab>
commit config
En este caso, escribiendo git co y presionando el tabulador dos veces sugiere commit y config.
Aadiendo m y pulsando el tabulador completa git commit automticamente.
Esto tambin funciona con opciones, que probablemente es ms til. Por ejemplo, si quieres
ejecutar git log y no recuerdas una de las opciones, puedes empezar a escribirla y presionar el
tabulador para ver qu coincide:
Alias de Git
Esto significa que, por ejemplo, en vez de escribir git commit, simplemente necesitas escribir git
ci. A medida que uses Git, probablemente uses otros comandos de forma frecuente. En este caso
no dudes en crear nuevos alias.
Esta tcnica tambin puede ser muy til para crear comandos que creas que deben existir. Por
ejemplo, para corregir el problema de usabilidad que encontramos al quitar del rea de
preparacin un archivo, puedes aadir tu propio alias:
Esto parece un poco mas claro. Tambin es comn aadir un comando last, tal que as:
$ git last
commit 66938dae3329c7aebe598c2246a8e6af90d04646
Author: Josh Goebel <dreamer3@example.com>
Date: Tue Aug 26 19:48:51 2008 +0800
Como puedes ver, Git simplemente reemplaza el nuevo comando con lo que le pongas como alias.
Sin embargo, tal vez quieres ejecutar un comando externo en lugar de un subcomando de Git. En
este caso, empieza el comando con el caracter !. Esto es til si escribes tus propias herramientas
que trabajan con un repositorio de Git. Podemos demostrarlo creando el alias git visual para
ejecutar gitk:
Algunas personas resaltan que uno de los puntos mas fuertes de Git es su sistema de
ramificaciones y lo cierto es que esto le hace resaltar sobre los otros sistemas de control de
versiones. Porqu esto es tan importante? La forma en la que Git maneja las ramificaciones es
increblemente rpida, haciendo as de las operaciones de ramificacin algo casi instantneo, al
igual que el avance o el retroceso entre distintas ramas, lo cual tambin es tremendamente
rpido. A diferencia de otros sistemas de control de versiones, Git promueve un ciclo de
desarrollo donde las ramas se crean y se unen ramas entre s, incluso varias veces en el mismo
da. Entender y manejar esta opcin te proporciona una poderosa y exclusiva herramienta que
puede, literalmente, cambiar la forma en la que desarrollas.
Para entender realmente cmo ramifica Git, previamente hemos de examinar la forma en que
almacena sus datos. Recordando lo citado en el captulo 1, Git no los almacena de forma
incremental (guardando solo diferencias), sino que los almacena como una serie de instantneas
(copias puntuales de los archivos completos, tal y como se encuentran en ese momento).
En cada confirmacin de cambios (commit), Git almacena un punto de control que conserva: un
apuntador a la copia puntual de los contenidos preparados (staged), unos metadatos con el autor
y el mensaje explicativo, y uno o varios apuntadores a las confirmaciones (commit) que sean
padres directos de esta (un padre en los casos de confirmacin normal, y mltiples padres en los
casos de estar confirmando una fusin (merge) de dos o mas ramas).
Para ilustrar esto, vamos a suponer, por ejemplo, que tienes una carpeta con tres archivos, que
preparas (stage) todos ellos y los confirmas (commit). Al preparar los archivos, Git realiza una
suma de control de cada uno de ellos (un resumen SHA-1, tal y como se mencionaba en el
captulo 1), almacena una copia de cada uno en el repositorio (estas copias se denominan
"blobs"), y guarda cada suma de control en el rea de preparacin (staging area):
Cuando creas una confirmacin con el comando git commit, Git realiza sumas de control de
cada subcarpeta (en el ejemplo, solamente tenemos la carpeta principal del proyecto), y guarda
en el repositorio Git una copia de cada uno de los archivos contenidos en ella/s. Despus, Git crea
un objeto de confirmacin con los metadatos pertinentes y un apuntador al nodo correspondiente
del rbol de proyecto. Esto permitir poder regenerar posteriormente dicha instantnea cuando
sea necesario.
En este momento, el repositorio de Git contendr cinco objetos: un "blob" para cada uno de los
tres archivos, un rbol con la lista de contenidos de la carpeta (ms sus respectivas relaciones con
los "blobs"), y una confirmacin de cambios (commit) apuntando a la raiz de ese rbol y
conteniendo el resto de metadatos pertinentes. Conceptualmente, el contenido del repositorio Git
ser algo parecido a la Figura 3-1
Figura 3-1. Datos en el repositorio tras una confirmacin sencilla.
Una rama Git es simplemente un apuntador mvil apuntando a una de esas confirmaciones. La
rama por defecto de Git es la rama master. Con la primera confirmacin de cambios que
realicemos, se crear esta rama principal master apuntando a dicha confirmacin. En cada
confirmacin de cambios que realicemos, la rama ir avanzando automticamente. Y la rama
master apuntar siempre a la ltima confirmacin realizada.
Qu sucede cuando creas una nueva rama? Bueno..., simplemente se crea un nuevo apuntador
para que lo puedas mover libremente. Por ejemplo, si quieres crear una nueva rama denominada
"testing". Usars el comando git branch:
Esto crear un nuevo apuntador apuntando a la misma confirmacin donde ests actualmente
(ver Figura 3-4).
Figura 3-4. Apuntadores de varias ramas en el registro de confirmaciones de cambio.
Y, cmo sabe Git en qu rama ests en este momento? Pues..., mediante un apuntador especial
denominado HEAD. Aunque es preciso comentar que este HEAD es totalmente distinto al
concepto de HEAD en otros sistemas de control de cambios como Subversion o CVS. En Git, es
simplemente el apuntador a la rama local en la que t ests en ese momento. En este caso, en la
rama master. Puesto que el comando git branch solamente crea una nueva rama, y no salta a
dicha rama.
Para saltar de una rama a otra, tienes que utilizar el comando git checkout. Hagamos una
prueba, saltando a la rama testing recin creada:
Cul es el significado de todo esto?. Bueno... lo veremos tras realizar otra confirmacin de
cambios:
$ vim test.rb
$ git commit -a -m 'made a change'
Observamos algo interesante: la rama testing avanza, mientras que la rama master permanece
en la confirmacin donde estaba cuando lanzaste el comando git checkout para saltar.
Volvamos ahora a la rama master:
Este comando realiza dos acciones: Mueve el apuntador HEAD de nuevo a la rama master, y
revierte los archivos de tu directorio de trabajo; dejndolos tal y como estaban en la ltima
instantnea confirmada en dicha rama master. Esto supone que los cambios que hagas desde
este momento en adelante divergirn de la antigua versin del proyecto. Bsicamente, lo que se
est haciendo es rebobinar el trabajo que habas hecho temporalmente en la rama testing; de tal
forma que puedas avanzar en otra direccin diferente.
$ vim test.rb
$ git commit -a -m 'made other changes'
Ahora el registro de tu proyecto diverge (ver Figura 3-9). Has creado una rama y saltado a ella,
has trabajado sobre ella; has vuelto a la rama original, y has trabajado tambin sobre ella. Los
cambios realizados en ambas sesiones de trabajo estn aislados en ramas independientes: puedes
saltar libremente de una a otra segn estimes oportuno. Y todo ello simplemente con dos
comandos: git branch y git checkout.
Debido a que una rama Git es realmente un simple archivo que contiene los 40 caracteres de una
suma de control SHA-1, (representando la confirmacin de cambios a la que apunta), no cuesta
nada el crear y destruir ramas en Git. Crear una nueva rama es tan rpido y simple como escribir
41 bytes en un archivo, (40 caracteres y un retorno de carro).
Esto contrasta fuertemente con los mtodos de ramificacin usados por otros sistemas de control
de versiones. En los que crear una nueva rama supone el copiar todos los archivos del proyecto a
una nueva carpeta adicional. Lo que puede llevar segundos o incluso minutos, dependiendo del
tamao del proyecto. Mientras que en Git el proceso es siempre instantneo. Y, adems, debido a
que se almacenan tambin los nodos padre para cada confirmacin, el encontrar las bases
adecuadas para realizar una fusin entre ramas es un proceso automtico y generalmente sencillo
de realizar. Animando as a los desarrolladores a utilizar ramificaciones frecuentemente.
Vamos a presentar un ejemplo simple de ramificar y de fusionar, con un flujo de trabajo que se
podra presentar en la realidad. Imagina que sigues los siquientes pasos:
En este momento, recibes una llamada avisndote de un problema crtico que has de resolver. Y
sigues los siguientes pasos:
Decides trabajar el problema #53, del sistema que tu compaa utiliza para llevar seguimiento de
los problemas. Aunque, por supuesto, Git no est ligado a ningn sistema de seguimiento de
problemas concreto. Como el problema #53 es un tema concreto y puntual en el que vas a
trabajar, creas una nueva rama para l. Para crear una nueva rama y saltar a ella, en un solo paso,
puedes utilizar el comando git checkout con la opcin -b:
Esto es un atajo a:
$ vim index.html
$ git commit -a -m 'added a new footer [issue 53]'
Entonces, recibes una llamada avisndote de otro problema urgente en el sitio web. Problema
que has de resolver inmediatamente. Usando Git, no necesitas mezclar el nuevo problema con los
cambios que ya habas realizado sobre el problema #53; ni tampoco perder tiempo revirtiendo
esos cambios para poder trabajar sobre el contenido que est en produccin. Basta con saltar de
nuevo a la rama master y continuar trabajando a partir de ella.
Pero, antes de poder hacer eso, hemos de tener en cuenta que teniendo cambios an no
confirmados en la carpeta de trabajo o en el rea de preparacin, Git no nos permitir saltar a
otra rama con la que podramos tener conflictos. Lo mejor es tener siempre un estado de trabajo
limpio y despejado antes de saltar entre ramas. Y, para ello, tenemos algunos procedimientos
(stash y commit ammend), que vamos a ver ms adelante. Por ahora, como tenemos confirmados
todos los cambios, podemos saltar a la rama master sin problemas:
Tras esto, tendrs la carpeta de trabajo exactamente igual a como estaba antes de comenzar a
trabajar sobre el problema #53. Y podrs concentrarte en el nuevo problema urgente. Es
importante recordar que Git revierte la carpeta de trabajo exactamente al estado en que estaba en
la confirmacin (commit) apuntada por la rama que activamos (checkout) en cada momento. Git
aade, quita y modifica archivos automticamente. Para asegurarte que tu copia de trabajo es
exactamente tal y como era la rama en la ltima confirmacin de cambios realizada sobre ella.
Volviendo al problema urgente. Vamos a crear una nueva rama hotfix, sobre la que trabajar
hasta resolverlo (ver Figura 3-13):
Puedes realizar las pruebas oportunas, asegurarte que la solucin es correcta, e incorporar los
cambios a la rama master para ponerlos en produccin. Esto se hace con el comando git
merge:
Figura 3-14. Tras la fusin (merge), la rama master apunta al mismo sitio que la rama hotfix.
Tras haber resuelto el problema urgente que te habi interrumpido tu trabajo, puedes volver a
donde estabas. Pero antes, es interesante borrar la rama hotfix. Ya que no la vamos a necesitar
ms, puesto que apunta exactamente al mismo sitio que la rama master. Esto lo puedes hacer
con la opcin -d del comando git branch:
Y, con esto, ya ests dispuesto para regresar al trabajo sobre el problema #53 (ver Figura 3-15):
$ git checkout iss53
Switched to branch "iss53"
$ vim index.html
$ git commit -a -m 'finished the new footer [issue 53]'
[iss53]: created ad82d7a: "finished the new footer [issue 53]"
1 files changed, 1 insertions(+), 0 deletions(-)
Cabe indicar que todo el trabajo realizado en la rama hotfix no est en los archivos de la rama
iss53. Si fuera necesario agregarlos, puedes fusionar (merge) la rama master sobre la rama
iss53 utilizando el comando git merge master. O puedes esperar hasta que decidas llevar
(pull) la rama iss53 a la rama master.
Supongamos que tu trabajo con el problema #53 est ya completo y listo para fusionarlo (merge)
con la rama master. Para ello, de forma similar a como antes has hecho con la rama hotfix, vas a
fusionar la rama iss53. Simplemente, activando (checkout) la rama donde deseas fusionar y
lanzando el comando git merge:
Figura 3-16. Git identifica automticamente el mejor ancestro comn para realizar la fusin de
las ramas.
En lugar de simplemente avanzar el apuntador de la rama, Git crea una nueva instantnea
(snapshot) resultante de la fusin a tres bandas; y crea automticamente una nueva confirmacin
de cambios (commit) que apunta a ella. Nos referimos a este proceso como "fusin confirmada".
Y se diferencia en que tiene ms de un padre.
Merece la pena destacar el hecho de que es el propio Git quien determina automticamente el
mejor ancestro comn para realizar la fusin. Diferenciandose de otros sistemas tales como CVS
o Subversion, donde es el desarrollador quien ha de imaginarse cul puede ser dicho mejor
ancestro comn. Esto hace que en Git sea mucho ms fcil el realizar fusiones.
Figura 3-17. Git crea automticamente una nueva confirmacin para la fusin.
Ahora que todo tu trabajo est ya fusionado con la rama principal, ya no tienes necesidad de la
rama iss53. Por lo que puedes borrarla. Y cerrar manualmente el problema en el sistema de
seguimiento de problemas de tu empresa.
En algunas ocasiones, los procesos de fusin no suelen ser fluidos. Si hay modificaciones dispares
en una misma porcin de un mismo archivo en las dos ramas distintas que pretendes fusionar,
Git no ser capaz de fusionarlas directamente. Por ejemplo, si en tu trabajo del problema #53 has
modificado una misma porcin que tambin ha sido modificada en el problema hotfix. Puedes
obtener un conflicto de fusin tal que:
Git no crea automticamente una nueva fusin confirmada (merge commit). Sino que hace una
pausa en el proceso, esperando a que tu resuelvas el conflicto. Para ver qu archivos permanecen
sin fusionar en un determinado momento conflictivo de una fusin, puedes usar el comando git
status:
Todo aquello que sea conflictivo y no se haya podido resolver, se marca como "sin fusionar"
(unmerged). Git aade a los archivos conflictivos unos marcadores especiales de resolucin de
conflictos. Marcadores que te guiarn cuando abras manualmente los archivos implicados y los
edites para corregirlos. El archivo conflictivo contendr algo como:
<<<<<<< HEAD:index.html
<div id="footer">contact : email.support@github.com</div>
=======
<div id="footer">
please contact us at support@github.com
</div>
>>>>>>> iss53:index.html
Donde nos dice que la versin en HEAD (la rama master, la que habias activado antes de lanzar
el comando de fusin), contiene lo indicado en la parte superior del bloque (todo lo que est
encima de =======). Y que la versin en iss53 contiene el resto, lo indicado en la parte
inferior del bloque. Para resolver el conflicto, has de elegir manualmente contenido de uno o de
otro lado. Por ejemplo, puedes optar por cambiar el bloque, dejndolo tal que:
<div id="footer">
please contact us at email.support@github.com
</div>
Esta correccin contiene un poco de ambas partes. Y se han eliminado completamente las lneas
<<<<<<< , ======= y >>>>>>> Tras resolver todos los bloques conflictivos, has de
lanzar comandos git add para marcar cada archivo modificado. Marcar archivos como
preparados (staging), indica a Git que sus conflictos han sido resueltos. Si en lugar de resolver
directamente, prefieres utilizar una herramienta grfica, puedes usar el comando git mergetool.
Esto arrancar la correspondiente herramienta de visualizacin y te permitir ir resolviendo
conflictos con ella.
$ git mergetool
merge tool candidates: kdiff3 tkdiff xxdiff meld gvimdiff opendiff emerge vimdiff
Merging the files: index.html
Si deseas usar una herramienta distinta de la escogida por defecto (en mi caso opendiff, porque
estoy lanzando el comando en un Mac), puedes escogerla entre la lista de herramientas
soportadas mostradas al principio ("merge tool candidates"). Tecleando el nombre de dicha
herramienta. En el captulo 7 se ver cmo cambiar este valor por defecto de tu entorno de
trabajo.
Tras salir de la herramienta de fusionado, Git preguntar a ver si hemos resuelto todos los
conflictos y la fusin ha sido satisfactoria. Si le indicas que as ha sido, Git marca como preparado
(staged) el archivo que acabamos de modificar.
En cualquier momento, puedes lanzar el comando git status para ver si ya has resuelto todos los
conflictos:
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: index.html
#
Si todo ha ido correctamente, y ves que todos los archivos conflictivos estn marcados como
preparados, puedes lanzar el comando git commit para terminar de confirmar la fusin. El
mensaje de confirmacin por defecto ser algo parecido a:
Merge branch 'iss53'
Conflicts:
index.html
#
# It looks like you may be committing a MERGE.
# If this is not correct, please remove the file
# .git/MERGE_HEAD
# and try again.
#
Puedes modificar este mensaje aadiendo detalles sobre cmo has resuelto la fusin, si lo
consideras til para que otros entiendan esta fusin en un futuro. Se trata de indicar porqu has
hecho lo que has hecho; a no ser que resulte obvio, claro est.
Ahora que ya has creado, fusionado y borrado algunas ramas, vamos a dar un vistazo a algunas
herramientas de gestin muy tiles cuando comienzas a utilizar ramas profusamente.
El comando git branch tiene ms funciones que las de crear y borrar ramas. Si lo lanzas sin
argumentos, obtienes una lista de las ramas presentes en tu proyecto:
$ git branch
iss53
* master
testing
Fijate en el carcter * delante de la rama master: nos indica la rama activa en este momento. Si
hacemos una confirmacin de cambios (commit), esa ser la rama que avance. Para ver la ltima
confirmacin de cambios en cada rama, puedes usar el comando git branch -v:
$ git branch -v
iss53 93b412c fix javascript issue
* master 7a98805 Merge branch 'iss53'
testing 782fd34 add scott to the author list in the readmes
Otra opcin til para averiguar el estado de las ramas, es filtrarlas y mostrar solo aquellas que
han sido fusionadas (o que no lo han sido) con la rama actualmente activa. Para ello, Git dispone,
desde la versin 1.5.6, las opciones --merged y --no-merged. Si deseas ver las ramas que han
sido fusionadas en la rama activa, puedes lanzar el comando git branch --merged:
Aparece la rama iss53 porque ya ha sido fusionada. Y no lleva por delante el caracter * porque
todo su contenido ya ha sido incorporado a otras ramas. Podemos borrarla tranquilamente con
git branch -d, sin miedo a perder nada.
Para mostrar todas las ramas que contienen trabajos sin fusionar an, puedes utilizar el comando
git branch --no-merged:
Esto nos muestra la otra rama en el proyecto. Debido a que contiene trabajos sin fusionar an, al
intentarla borrar con git branch -d, el comando nos dar un error:
Si realmente deseas borrar la rama, y perder el trabajo contenido en ella, puedes forzar el borrado
con la opcin -D; tal y como lo indica el mensaje de ayuda.
Ahora que ya has visto los procedimientos bsicos de ramificacin y fusin, qu puedes o qu
debes hacer con ellos? En este apartado vamos a ver algunos de los flujos de trabajo ms
comunes, de tal forma que puedas decidir si te gustara incorporar alguno de ellos a tu ciclo de
desarrollo.
Por la sencillez de la fusin a tres bandas de Git, el fusionar de una rama a otra multitud de veces
a lo largo del tiempo es fcil de hacer. Esto te posibilita tener varias ramas siempre abiertas, e
irlas usando en diferentes etapas del ciclo de desarrollo; realizando frecuentes fusiones entre
ellas.
Muchos desarrolladores que usan Git llevan un flujo de trabajo de esta naturaleza, manteniendo
en la rama master nicamente el cdigo totalmente estable (el cdigo que ha sido o que va a ser
liberado). Teniendo otras ramas paralelas denominadas desarrollo o siguiente, en las que
trabajan y realizan pruebas. Estas ramas paralelas no suele estar siempre en un estado estable;
pero cada vez que s lo estn, pueden ser fusionadas con la rama master. Tambin es habitual el
incorporarle (pull) ramas puntuales (ramas temporales, como la rama iss53 del anterior
ejemplo) cuando las completamos y estamos seguros de que no van a introducir errores.
Figura 3-18. Las ramas ms estables apuntan hacia posiciones ms antiguas en el registro de
cambios.
Podra ser ms sencillo pensar en las ramas como si fueran silos de almacenamiento. Donde
grupos de confirmaciones de cambio (commits) van promocionando hacia silos ms estables a
medida que son probados y depurados (ver Figura 3-19)
Figura 3-19. Puede ayudar pensar en las ramas como silos de almacenamiento.
Este sistema de trabajo se puede ampliar para diversos grados de estabilidad. Algunos proyectos
muy grandes suelen tener una rama denominada propuestas o pu (proposed updates). Donde
suele estar todo aquello integrado desde otras ramas, pero que an no est listo para ser
incorporado a las ramas siguiente o master. La idea es mantener siempre diversas ramas en
diversos grados de estabilidad; pero cuando alguna alcanza un estado ms estable, la fusionamos
con la rama inmediatamente superior a ella. Aunque no es obligatorio el trabajar con ramas de
larga duracin, realmente es prctico y til. Sobre todo en proyectos largos o complejos.
Ramas puntuales
Las ramas puntuales, en cambio, son tiles en proyectos de cualquier tamao. Una rama puntual
es aquella de corta duracin que abres para un tema o para una funcionalidad muy concretos. Es
algo que nunca habras hecho en otro sistema VCS, debido a los altos costos de crear y fusionar
ramas que se suelen dar en esos sistemas. Pero en Git, por el contrario, es muy habitual el crear,
trabajar con, fusionar y borrar ramas varias veces al da.
Tal y como has visto con las ramas iss53 y hotfix que has creado en la seccin anterior. Has
hecho unas pocas confirmaciones de cambio en ellas, y luego las has borrado tras fusionarlas con
la rama principal. Esta tcnica te posibilita realizar rpidos y completos saltos de contexto. Y,
debido a que el trabajo est claramente separado en silos, con todos los cambios de cada tema en
su propia rama, te ser mucho ms sencillo revisar el cdigo y seguir su evolucin. Puedes
mantener los cambios ah durante minutos, dias o meses; y fusionarlos cuando realmente estn
listos. En lugar de verte obligado a fusionarlos en el orden en que fueron creados y comenzaste a
trabajar en ellos.
Por ejemplo, puedes realizar cierto trabajo en la rama master, ramificar para un problema
concreto (rama iss91), trabajar en l un rato, ramificar a una segunda rama para probar otra
manera de resolverlo (rama iss92v2), volver a la rama master y trabajar un poco ms, y, por
ltimo, ramificar temporalmente para probar algo de lo que no ests seguro (rama dumbidea).
El registro de confirmaciones (commit history) ser algo parecido a la Figura 3-20.
En este momento, supongamos que te decides por la segunda solucin al problema (rama
iss92v2); y que, tras mostrar la rama dumbidea a tus compaeros, resulta que les parece una
idea genial. Puedes descartar la rama iss91 (perdiendo las confirmaciones C5 y C6), y fusionar
las otras dos. El registro ser algo parecido a la Figura 3-21.
Es importante recordar que, mientras ests haciendo todo esto, todas las ramas son
completamente locales. Cuando ramificas y fusionas, todo se realiza en tu propio repositorio Git.
No hay nign tipo de trfico con ningn servidor.
Las ramas remotas son referencias al estado de ramas en tus repositorios remotos. Son ramas
locales que no puedes mover; se mueven automticamente cuando estableces comunicaciones en
la red. Las ramas remotas funcionan como marcadores, para recordarte en qu estado se
encontraban tus repositorios remotos la ltima vez que conectaste con ellos.
Suelen referenciarse como (remoto)/(rama). Por ejemplo, si quieres saber cmo estaba la rama
master en el remoto origin. Puedes revisar la rama origin/master. O si ests trabajando en un
problema con un compaero y este envia (push) una rama iss53, tu tendrs tu propia rama de
trabajo local iss53; pero la rama en el servidor apuntar a la ltima confirmacin (commit) en la
rama origin/iss53.
Esto puede ser un tanto confuso, pero intentemos aclararlo con un ejemplo. Supongamos que
tienes un sevidor Git en tu red, en git.ourcompany.com. Si haces un cln desde ah, Git
automticamente lo denominar origin, traer (pull) sus datos, crear un apuntador hacia donde
est en ese momento su rama master, denominar la copia local origin/master; y ser
inamovible para ti. Git te proporcionar tambin tu propia rama master, apuntando al mismo
lugar que la rama master de origin; siendo en esta ltima donde podrs trabajar.
Figura 3-22. Un cln Git te proporciona tu propia rama master y otra rama
origin/masterapuntando a la rama master original.
Si haces algn trabajo en tu rama master local, y al mismo tiempo, alguna otra persona lleva
(push) su trabajo al servidor git.ourcompany.com, actualizando la rama master de all, te
encontrars con que ambos registros avanzan de forma diferente. Adems, mientras no tengas
contacto con el servidor, tu apuntador a tu rama origin/master no se mover (ver Figura 3/23).
Figura 3-23. Trabajando localmente y que otra persona est llevando (push) algo al servidor
remoto, hace que cada registro avance de forma distinta.
Para sincronizarte, puedes utilizar el comando git fetch origin. Este comando localiza en qu
servidor est el origen (en este caso git.ourcompany.com), recupera cualquier dato presente
all que tu no tengas, y actualiza tu base de datos local, moviendo tu rama origin/master para
que apunte a esta nueva y ms reciente posicin (ver Figura 3-24).
Figura 3-24. El comando git fetch actualiza tus referencias remotas.
Para ilustrar mejor el caso de tener mltiples servidores y cmo van las ramas remotas para esos
proyectos remotos. Supongamos que tienes otro servidor Git; utilizado solamente para
desarrollo, por uno de tus equipos sprint. Un servidor en git.team1.ourcompany.com. Puedes
incluirlo como una nueva referencia remota a tu proyecto actual, mediante el comando git
remote add, tal y como vimos en el captulo 2. Puedes denominar teamone a este remoto,
poniendo este nombre abreviado para la URL (ver Figura 3-25)
Figura 3-25. Aadiendo otro servidor como remoto.
Ahora, puedes usar el comando git fetch teamone para recuperar todo el contenido del
servidor que tu no tenias. Debido a que dicho servidor es un subconjunto de de los datos del
servidor origin que tienes actualmente, Git no recupera (fetch) ningn dato; simplemente
prepara una rama remota llamada teamone/master para apuntar a la confirmacin (commit)
que teamone tiene en su rama master.
Figura 3-26. Obtienes una referencia local a la posicin en la rama master de teamone.
Publicando
Cuando quieres compartir una rama con el resto del mundo, has de llevarla (push) a un remoto
donde tengas permisos de escritura. Tus ramas locales no se sincronizan automticamente con
los remotos en los que escribes. Sino que tienes que llevar (push) expresamente, cada vez, al
remoto las ramas que desees compartir. De esta forma, puedes usar ramas privadas para el
trabajo que no deseas compartir. Llevando a un remoto tan solo aquellas partes que deseas
aportar a los dems.
Si tienes una rama llamada serverfix, con la que vas a trabajar en colaboracin; puedes llevarla
al remoto de la misma forma que llevaste tu primera rama. Con el comando git push (remoto)
(rama):
Esto es un poco como un atajo. Git expande automticamente el nombre de rama serverfix a
refs/heads/serverfix:refs/heads/serverfix, que significa: "coge mi rama local serverfix y
actualiza con ella la rama serverfix del remoto". Volveremos ms tarde sobre el tema de
refs/heads/, vindolo en detalle en el captulo 9; aunque puedes ignorarlo por ahora. Tambin
puedes hacer git push origin serverfix:serverfix, que hace lo mismo; es decir: "coge mi
serverfix y hazlo el serverfix remoto". Puedes utilizar este ltimo formato para llevar una rama
local a una rama remota con otro nombre distinto. Si no quieres que se llame serverfix en el
remoto, puedes lanzar, por ejemplo, git push origin serverfix:awesomebranch; para llevar
tu rama serverfix local a la rama awesomebranch en el proyecto remoto.
La prxima vez que tus colaboradores recuperen desde el servidor, obtendrn una referencia a
donde la versin de serverfix en el servidor est bajo la rama remota origin/serverfix:
Es importante destacar que cuando recuperas (fetch) nuevas ramas remotas, no obtienes
automticamente una copia editable local de las mismas. En otras palabras, en este caso, no
tienes una nueva rama serverfix. Sino que nicamente tienes un puntero no editable a
origin/serverfix.
Para integrar (merge) esto en tu actual rama de trabajo, puedes usar el comando git merge
origin/serverfix. Y si quieres tener tu propia rama serverfix, donde puedas trabajar, puedes
crearla directamente basadote en rama remota:
Activando (checkout) una rama local a partir de una rama remota, se crea automticamente lo
que podramos denominar "una rama de seguimiento" (tracking branch). Las ramas de
seguimiento son ramas locales que tienen una relacin directa con alguna rama remota. Si ests
en una rama de seguimiento y tecleas el comando git push, Git sabe automticamente a qu
servidor y a qu rama ha de llevar los contenidos. Igualmente, tecleando git pull mientras
estamos en una de esas ramas, recupera (fetch) todas las referencias remotas y las consolida
(merge) automticamente en la correspondiente rama remota.
Cuando clonas un repositorio, este suele crear automticamente una rama master que hace
seguimiento de origin/master. Y es por eso que git push y git pull trabajan directamente, sin
necesidad de ms argumentos. Sin embargo, puedes preparar otras ramas de seguimiento si
deseas tener unas que no hagan seguimiento de ramas en origin y que no sigan a la rama
master. El ejemplo ms simple, es el que acabas de ver al lanzar el comando git checkout -b
[rama] [nombreremoto]/[rama]. Si tienes la versin 1.6.2 de Git, o superior, puedes utilizar
tambin el parmetro --track:
Para preparar una rama local con un nombre distinto a la del remoto, puedes utilizar:
As, tu rama local sf va a llevar (push) y traer (pull) hacia o desde origin/serverfix.
Imagina que ya has terminado con una rama remota. Es decir, tanto tu como tus colaboradores
habeis completado una determinada funcionalidad y la habeis incorporado (merge) a la rama
master en el remoto (o donde quiera que tengais la rama de cdigo estable). Puedes borrar la
rama remota utilizando la un tanto confusa sintaxis: git push [nombreremoto] :[rama]. Por
ejemplo, si quieres borrar la rama serverfix del servidor, puedes utilizar:
Y... Boom!. La rama en el servidor ha desaparecido. Puedes grabarte a fuego esta pgina, porque
necesitars ese comando y, lo ms probable es que hayas olvidado su sintaxis. Una manera de
recordar este comando es dndonos cuenta de que proviene de la sintaxis git push
[nombreremoto] [ramalocal]:[ramaremota]. Si omites la parte [ramalocal], lo que ests
diciendo es: "no cojas nada de mi lado y haz con ello [ramaremota]".
En Git tenemos dos formas de integrar cambios de una rama en otra: la fusin (merge) y la
reorganizacin (rebase). En esta seccin vas a aprender en qu consiste la reorganizacin, como
utilizarla, por qu es una herramienta sorprendente y en qu casos no es conveniente utilizarla.
Reorganizacin bsica
Volviendo al ejemplo anterior, en la seccin sobre fusiones (ver Figura 3-27), puedes ver que has
separado tu trabajo y realizado confirmaciones (commit) en dos ramas diferentes.
Figura 3-27. El registro de confirmaciones inicial.
La manera ms sencilla de integrar ramas, tal y como hemos visto, es el comando git merge.
Realiza una fusin a tres bandas entre las dos ltimas instantneas de cada rama (C3 y C4) y el
ancestro comn a ambas (C2); creando una nueva instantnea (snapshot) y la correspondiente
confirmacin (commit), segn se muestra en la Figura 3-28.
Figura 3-28. Fusionando una rama para integrar el registro de trabajos divergentes.
Aunque tambin hay otra forma de hacerlo: puedes coger los cambios introducidos en C3 y
reaplicarlos encima de C4. Esto es lo que en Git llamamos reorganizar. Con el comando git
rebase, puedes coger todos los cambios confirmados en una rama, y reaplicarlos sobre otra.
Haciendo que Git: vaya al ancestro comn de ambas ramas (donde ests actualmente y de donde
quieres reorganizar), saque las diferencias introducidas por cada confirmacin en la rama donde
ests, guarde esas diferencias en archivos temporales, reinicie (reset) la rama actual hasta llevarla
a la misma confirmacin en la rama de donde quieres reorganizar, y, finalmente, vuelva a aplicar
ordenadamente los cambios. El proceso se muestra en la Figura 3-29.
En este momento, puedes volver a la rama master y hacer una fusin con avance rpido
(fast-forward merge). (ver Figura 3-30)
Habitualmente, optars por esta va cuando quieras estar seguro de que tus confirmaciones de
cambio (commits) se pueden aplicar limpiamente sobre una rama remota; posiblemente, en un
proyecto donde ests intentando colaborar, pero lleves tu el mantenimiento. En casos como esos,
puedes trabajar sobre una rama y luego reorganizar lo realizado en la rama origin/master
cuando lo tengas todo listo para enviarlo al proyecto principal. De esta forma, la persona que
mantiene el proyecto no necesitar hacer ninguna integracin con tu trabajo; le bastar con un
avance rpido o una incorporacin limpia.
Cabe destacar que la instantnea (snapshot) apuntada por la confirmacin (commit) final, tanto
si es producto de una reorganizacin (rebase) como si lo es de una fusin (merge), es
exactamente la misma instantnea. Lo nico diferente es el registro. La reorganizacin vuelve a
aplicar cambios de una rama de trabajo sobre otra rama, en el mismo orden en que fueron
introducidos en la primera. Mientras que la fusin combina entre s los dos puntos finales de
ambas ramas.
Tambin puedes aplicar una reorganizacin (rebase) sobre otra cosa adems de sobre la rama de
reorganizacin. Por ejemplo, sea un registro como el de la Figura 3-31. Has ramificado a una
rama puntual (server) para aadir algunas funcionalidades al proyecto, y luego has confirmado
los cambios. Despus, vuelves a la rama original para hacer algunos cambios en la parte cliente
(rama client), y confirmas tambin esos cambios. Por ltimo, vuelves sobre la rama server y
haces algunos cambios ms.
Figura 3-31. Un registro con una rama puntual sobre otra rama puntual.
Imagina que decides incorporar tus cambios de la parte cliente sobre el proyecto principal, para
hacer un lanzamiento de versin; pero no quieres lanzar an los cambios de la parte server
porque no estn an suficientemente probados. Puedes coger los cambios del cliente que no estn
en server (C8 y C9), y reaplicarlos sobre tu rama principal usando la opcin --onto del comando
git rebase:
Esto viene a decir: "Activa la rama client, averigua los cambios desde el ancestro comn entre las
ramas client y server, y aplicarlos en la rama master. Puede parecer un poco complicado, pero
los resultados, mostrados en la Figura 3-32, son realmente interesantes.
Figura 3-32. Reorganizando una rama puntual fuera de otra rama puntual.
Figura 3-33. Avance rpido de tu rama master, para incluir los cambios de la rama client.
Ahora supongamos que decides traerlos (pull) tambin sobre tu rama server. Puedes reorganizar
(rebase) la rama server sobre la rama master sin necesidad siquiera de comprobarlo
previamente, usando el comando git rebase [ramabase] [ramapuntual]. El cual activa la
rama puntual (server en este caso) y la aplica sobre la rama base (master en este caso):
Esto vuelca el trabajo de server sobre el de master, tal y como se muestra en la Figura 3-34.
Y por ltimo puedes eliminar las ramas client y server porque ya todo su contenido ha sido
integrado y no las vas a necesitar ms. Dejando tu registro tras todo este proceso tal y como se
muestra en la Figura 3-35:
Cuando reorganizas algo, ests abandonando las confirmaciones de cambio ya creadas y ests
creando unas nuevas; que son similares, pero diferentes. Si envias (push) confirmaciones
(commits) a alguna parte, y otros las recogen (pull) de all. Y despus vas tu y las reescribes con
git rebase y las vuelves a enviar (push) de nuevo. Tus colaboradores tendrn que refusionar
(re-merge) su trabajo y todo se volver tremendamente complicado cuando intentes recoger
(pull) su trabajo de vuelta sobre el tuyo.
Vamos a verlo con un ejemplo. Imagnate que haces un clon desde un servidor central, y luego
trabajas sobre l. Tu registro de cambios puede ser algo como lo de la Figura 3-36.
Figura 3-37. Traer (fetch) algunas confirmaciones de cambio (commits) y fusionarlas (merge)
sobre tu trabajo.
A continuacin, la persona que haba llevado cambios al servidor central decide retroceder y
reorganizar su trabajo; haciendo un git push --force para sobrescribir el registro en el servidor.
Tu te traes (fetch) esos nuevos cambios desde el servidor.
Figura 3-38. Alguien envi (push) confirmaciones (commits) reorganizadas, abandonando las
confirmaciones en las que tu habas basado tu trabajo.
En ese momento, tu te ves obligado a fusionar (merge) tu trabajo de nuevo, aunque creias que ya
lo habias hecho antes. La reorganizacin cambia los resumenes (hash) SHA-1 de esas
confirmaciones (commits), haciendo que Git se crea que son nuevas confirmaciones. Cuando
realmente tu ya tenias el trabajo de C4 en tu registro.
Figura 3-39. Vuelves a fusionar el mismo trabajo en una nueva fusin confirmada.
Te ves obligado a fusionar (merge) ese trabajo en algn punto, para poder seguir adelante con
otros desarrollos en el futuro. Tras todo esto, tu registro de confirmaciones de cambio (commit
history) contendr tanto la confirmacin C4 como la C4'; teniendo ambas el mismo contenido y el
mismo mensaje de confirmacin. Si lanzas un git log en un registro como este, vers dos
confirmaciones con el mismo autor, misma fecha y mismo mensaje. Lo que puede llevar a
confusiones. Es ms, si luego tu envas (push) ese registro de vuelta al servidor, vas a introducir
todas esas confirmaciones reorganizadas en el servidor central. Lo que puede confundir an ms
a la gente.
Si solo usas la reorganizacin como una va para hacer limpieza y organizar confirmaciones de
cambio antes de enviarlas, y si nicamente reorganizas confirmaciones que nunca han sido
pblicas. Entonces no tendrs problemas. Si, por el contrario, reorganizas confirmaciones que
alguna vez han sido pblicas y otra gente ha basado su trabajo en ellas. Entonces estars en un
aprieto.
Chapter 4
Git en un servidor
A estas alturas, ya podrs realizar la mayor parte de las tareas habituales trabajando con Git.
Pero, para poder colaborar, necesitars tener un repositorio remoto de Git. Aunque tcnicamente
es posible enviar (push) y recibir (pull) cambios directamente a o desde repositorios individuales,
no es muy recomendable trabajar as por la gran facilidad de confundirte si no andas con sumo
cuidado. Es ms, si deseas que tus colaboradores puedan acceder a tu repositorio, incluso cuando
tu ordenador este apagado, puede ser de gran utilidad disponer de un repositorio comn fiable.
En este sentido, el mtodo ms recomendable para colaborar con otra persona es preparar un
repositorio intermedio donde ambos tengais acceso, enviando (push) y recibiendo (pull) a o
desde all. Nos referiremos a este repositorio como "servidor Git"; pero en seguida te dars cuenta
de que solo se necesitan unos pocos recursos para albergar un repositorio Git, y, por tanto, no
ser necesario utilizar todo un servidor entero para l.
Disponer un servidor Git es simple. Lo primero, has de elegir el/los protocolo/s que deseas para
comunicarte con el servidor. La primera parte de este captulo cubrir la gama de protocolos
disponibles, detallando los pros y contras de cada uno de ellos. Las siguientes secciones
explicarn algunas de las tpicas configuraciones utilizando esos protocolos, y cmo podemos
poner en marcha nuestro servidor con ellos. Por ltimo, repasaremos algunas opciones
albergadas on-line; por si no te preocupa guardar tu cdigo en servidores de terceros y no deseas
enredarte preparando y manteniendo tu propio servidor.
Si este es el caso, si no tienes inters de tener tu propio servidor, puedes saltar directamente a la
ltima seccin del captulo; donde vers algunas opciones para dar de alta una cuenta albergada.
Y despus puedes moverte al captulo siguiente, donde vamos a discutir algunos de los
mecanismos para trabajar en un entorno distribuido.
Git puede usar cuatro protocolos principales para transferir datos: Local, Secure Shell (SSH), Git
y HTTP. Vamos a ver en qu consisten y las circunstancias en que querrs (o no) utilizar cada uno
de ellos.
Merece destacar que, con la excepcin del protocolo HTTP, todos los dems protocolos requieren
que Git est instalado y operativo en el servidor.
Protocolo Local
Si dispones de un sistema de archivos compartido, podrs clonar (clone), enviar (push) y recibir
(pull) a/desde repositorios locales basado en archivos. Para clonar un repositorio como estos, o
para aadirlo como remoto a un proyecto ya existente, usa el camino (path) del repositorio como
su URL. Por ejemplo, para clonar un repositorio local, puedes usar algo como:
O como:
Git trabaja ligeramente distinto si indicas 'file://' de forma explcita al comienzo de la URL. Si
escribes simplemente el camino, Git intentar usar enlaces rgidos (hardlinks) o copiar
directamente los archivos que necesita. Si escribes con el prefijo 'file://', Git lanza el proceso que
usa habitualmente para transferir datos sobre una red; proceso que suele ser mucho menos
eficiente. La nica razn que puedes tener para indicar expresamente el prefijo 'file://' puede ser
el querer una copia limpia del repositorio, descartando referencias u objetos superfluos.
Normalmente, tras haberlo importado desde otro sistema de control de versiones o algo similar
(ver el Captulo 9 sobre tareas de mantenimiento). Habitualmente, usaremos el camino (path)
normal por ser casi siempre ms rpido.
Para aadir un repositorio local a un proyecto Git existente, puedes usar algo como:
Con lo que podrs enviar (push) y recibir (pull) desde dicho remoto exactamente de la misma
forma a como lo haras a travs de una red.
Ventajas
Este camino es tambin util para recuperar rpidamente el contenido del repositorio de trabajo
de alguna otra persona. Si tu y otra persona estais trabajando en el mismo proyecto y ella quiere
mostrarte algo, el usar un comando tal como 'git pull /home/john/project' suele ser ms sencillo
que el que esa persona te lo envie (push) a un servidor remoto y luego t lo recojas (pull) desde
all.
Desventajas
Cabe destacar tambin que una carpeta compartida no es precisamente la opcin ms rpida. Un
repositorio local es rpido solamente en aquellas ocasiones en que tienes un acceso rpido a l.
Normalmente un repositorio sobre NFS es ms lento que un repositorio SSH en el mismo
servidor, asumiendo que las pruebas se hacen con Git sobre discos locales en ambos casos.
El Procotolo SSH
Probablemente, SSH sea el protocolo ms habitual para Git. Debido a disponibilidad en la mayor
parte de los servidores; (pero, si no lo estuviera disponible, adems es sencillo habilitarlo). Por
otro lado, SSH es el nico protocolo de red con el que puedes facilmente tanto leer como escribir.
Los otros dos protocolos de red (HTTP y Git) suelen ser normalmente protocolos de solo-lectura;
de tal forma que, aunque los tengas disponibles para el pblico en general, sigues necesitando
SSH para tu propio uso en escritura. Otra ventaja de SSH es el su mecanismo de autentificacin,
sencillo de habilitar y de usar.
Para clonar un repositorio a travs de SSH, puedes indicar una URL ssh:// tal como:
O puedes prescindir del protocolo; Git asume SSH si no indicas nada expresamente: $ git clone
user@server:project.git
Pudiendo asimismo prescindir del usuario; en cuyo caso Git asume el usuario con el que ests
conectado en ese momento.
Ventajas
El uso de SSH tiene mltiples ventajas. En primer lugar, necesitas usarlo si quieres un acceso de
escritura autentificado a tu repositorio. En segundo lugar, SSH es sencillo de habilitar. Los
demonios (daemons) SSH son de uso comn, muchos administradores de red tienen experiencia
con ellos y muchas distribuciones del SO los traen predefinidos o tienen herramientas para
gestionarlos. Adems, el acceso a travs de SSH es seguro, estando todas las transferencias
encriptadas y autentificadas. Y, por ltimo, al igual que los procolos Git y Local, SSH es eficiente,
comprimiendo los datos lo ms posible antes de transferirlos.
Desventajas
El aspecto negativo de SSH es su imposibilidad para dar acceso annimo al repositorio. Todos
han de tener configurado un acceso SSH al servidor, incluso aunque sea con permisos de solo
lectura; lo que no lo hace recomendable para soportar proyectos abiertos. Si lo usas nicamente
dentro de tu red corporativa, posiblemente sea SSH el nico procolo que tengas que emplear.
Pero si quieres tambin habilitar accesos annimos de solo lectura, tendrs que reservar SSH
para tus envios (push) y habilitar algn otro protocolo para las recuperaciones (pull) de los
dems.
El Protocolo Git
El protocolo Git es un demonio (daemon) especial, que viene incorporado con Git. Escucha por
un puerto dedicado (9418), y nos da un servicio similar al del protocolo SSH; pero sin ningn tipo
de autentificacin. Para que un repositorio pueda exponerse a travs del protocolo Git, tienes que
crear en l un archivo 'git-daemon-export-ok'; sin este archivo, el demonio no har disponible el
repositorio. Pero, aparte de esto, no hay ninguna otra medida de seguridad. O el repositorio est
disponible para que cualquiera lo pueda clonar, o no lo est. Lo cual significa que, normalmente,
no se podr enviar (push) a travs de este protocolo. Aunque realmente si que puedes habilitar el
envio, si lo haces, dada la total falta de ningn mecanismo de autentificacin, cualquiera que
encuentre la URL a tu proyecto en Internet, podr enviar (push) contenidos a l. Ni que decir
tiene que esto solo lo necesitars en contadas ocasiones.
Ventajas
El protocolo Git es el ms rpido de todos los disponibles. Si has de servir mucho trfico de un
proyecto pblico o servir un proyecto muy grande, que no requiera autentificacin para leer de l,
un demonio Git es la respuesta. Utiliza los mismos mecanismos de transmisin de datos que el
protocolo SSH, pero sin la sobrecarga de la encriptacin ni de la autentificacin.
Desventajas
La pega del protocolo Git, es su falta de autentificacin. No es recomendable tenerlo como nico
protocolo de acceso a tus proyectos. Habitualmente, lo combinars con un acceso SSH para los
pocos desarrolladores con acceso de escritura que envien (push) material. Usando 'git://' para los
accesos solo-lectura del resto de personas. Por otro lado, es tambin el protocolo ms complicado
de implementar. Necesita activar su propio demonio, (tal y como se explica en la seccin
"Gitosis", ms adelante, en este captulo); y necesita configurar 'xinetd' o similar, lo cual no suele
estar siempre disponible en el sistema donde ests trabajando. Requiere adems abrir
expresamente acceso al puerto 9418 en el cortafuegos, ya que este no es uno de los puertos
estandares que suelen estar habitualmente permitidos en los cortafuegos corporativos.
Normalmente, este oscuro puerto suele estar bloqueado detrs de los cortafuegos corporativos.
El protocolo HTTP/S
Por ltimo, tenemos el protocolo HTTP. Cuya belleza radica en la simplicidad para habilitarlo.
Basta con situar el repositorio Git bajo la raiz de los documentos HTTP y preparar el enganche
(hook) 'post-update' adecuado. (Ver el Captulo 7 para detalles sobre los enganches Git.) A partir
de ese momento, cualquiera con acceso al servidor web podr clonar tu repositorio. Para permitir
acceso a tu repositorio a travs de HTTP, puedes hacer algo como esto:
$ cd /var/www/htdocs/
$ git clone --bare /path/to/git_project gitproject.git
$ cd gitproject.git
$ mv hooks/post-update.sample hooks/post-update
$ chmod a+x hooks/post-update
Y eso es todo. El enganche 'post-update' que viene de serie con Git se encarga de lanzar el
comando adecuado ('git update-server-info') para hacer funcionar la recuperacin (fetching) y el
clonado (cloning) va HTTP. Este comando se lanza automticamente cuando envias (push) a
este repositorio va SSh; de tal forma que otras personas puedan clonarlo usando un comando tal
que:
Es posible hacer que Git envie (push) a travs de HTTP. Pero no se suele usar demasiado, ya que
requiere lidiar con los complejos requerimientos de WebDAV. Y precisamente porque se usa
raramente, no lo vamos a cubrir en este libro. Si ests interesado en utilizar los protocolos
HTTP-push, puedes encotrar ms informacin en
http://www.kernel.org/pub/software/scm/git/docs/howto/setup-git-server-over-http.
txt. La utilidad de habilitar Git para enviar (push) a travs de HTTP es la posibilidad de utilizar
cualquier servidor WebDAV para ello, sin necesidad de requerimientos especficos para Git. De
tal forma que puedes hacerlo incluso a travs de tu proveedor de albergue web, si este soporta
WebDAV para escribir actualizaciones en tu sitio web.
Ventajas
La mejor parte del protocolo HTTP es su sencillez de preparacin. Simplemente lanzando unos
cuantos comandos, dispones de un mtodo sencillo de dar al mundo entero acceso a tu
repositorio Git. En tan solo unos minutos. Adems, el procolo HTTP no requiere de grandes
recursos en tu servidor. Por utilizar normalmente un servidor HTTP esttico, un servidor Apache
estandar puede con un trfico de miles de archivos por segundo; siendo dificil de sobrecargar
incluso con el ms pequeo de los servidores.
Puedes tambin servir tus repositorios de solo lectura a travs de HTTPS, teniendo as las
transferencias encriptadas. O puedes ir ms lejos an, requiriendo el uso de certificados SSL
especficos para cada cliente. Aunque, si pretendes ir tan lejos, es ms sencillo utilizar claves
pblicas SSH; pero ah est la posibilidad, por si en algn caso concreto sea mejor solucin el uso
de certificados SSL u otros medios de autentificacin HTTP para el acceso de solo-lectura a travs
de HTTPS.
Otro detalle muy util de emplear HTTP, es que, al ser un protocolo de uso comn, la mayora de
los cortafuegos corporativos suelen tener habilitado el trfico a traves de este puerto.
Desventajas
La pega de servir un repositorio a travs de HTTP es su relativa ineficiencia para el cliente. Suele
requerir mucho ms tiempo el clonar o el recuperar (fetch), debido a la mayor carga de
procesamiento y al mayor volumen de transferencia que se da sobre HTTP respecto de otros
protocolos de red. Y precisamente por esto, porque no es tan inteligente y no transfiere solamente
los datos imprescindibles, (no hay un trabajo dinmico por parte del servidor), el protocolo HTTP
suele ser conocido como el protocolo estpido. Para ms informacin sobre diferencias de
eficiencia entre el protocolo HTTP y los otros protocolos, ver el Captulo 9.
El primer paso para preparar un servidor Git, es exportar un repositorio existente a un nuevo
repositorio bsico, a un repositorio sin carpeta de trabajo. Normalmente suele ser sencillo. Tan
solo has de utilizar el comando 'clone' con la opcin '--bare'. Por convenio, los nombres de los
repositorios bsicos suelen terminar en '.git', por lo que lanzaremos:
$ git clone --bare my_project my_project.git
Initialized empty Git repository in /opt/projects/my_project.git/
El resultado de este comando es un poco confuso. Como 'clone' es fundamentalmente un 'git init'
seguido de un 'git fetch', veremos algunos de los mensajes de la parte 'init', concretamente de la
parte en que se crea una carpeta vacia. La copia de objetos no da ningn mensaje, pero tambin
se realiza. Tras esto, tendrs una copia de los datos en tu carpeta 'my_project.git'.
Ahora que ya tienes una copia bsica de tu repositorio, todo lo que te resta por hacer es colocarlo
en un servidor y ajustar los protocolos. Supongamos que has preparado un servidor denominado
'git.example.com', con acceso SSH. Y que quieres guardar todos los repositorios Git bajo la
carpeta '/opt/git'. Puedes colocar tu nuevo repositorio simplemente copiandolo:
A partir de entonces, cualquier otro usuario con acceso de lectura SSH a la carpeta '/opt/git' del
servidor, podr clonar el repositorio con la orden:
$ ssh user@git.example.com
$ cd /opt/git/my_project.git
$ git init --bare --shared
Como se v, es sencillo crear un repositorio bsico a partir de un repositorio Git, y ponerlo en un
servidor donde tanto t como tus colaboradores tengais acceso SSH. Ahora ya ests preparado
para trabajar con ellos en el proyecto comn.
Es importante destacar que esto es, literalmente, todo lo necesario para preparar un servidor Git
compartido. Habilitar unas cuantas cuentas SSH en un servidor; colocar un repositorio bsico en
algn lugar donde esos usuarios tengan acceso de lectura/escritura; y.... listo!, eso es todo lo que
necesitas.
Pequeos despliegues
Si tienes un proyecto reducido o ests simplemente probando Git en tu empresa y sois unos pocos
desarrolladores, el despliegue ser sencillo. Porque la gestin de usuarios es precisamente uno de
los aspectos ms complicados de preparar un servidor Git. En caso de requerir varios repositorios
de solo lectura para ciertos usuarios y de lectura/escritura para otros, preparar el acceso y los
permisos puede dar bastante trabajo.
Acceso SSH
Si ya dispones de un servidor donde todos los desarrolladores tengan acceso SSH, te ser facil
colocar los repositorios en l (tal y como se ver en el prximo apartado). En caso de que
necesites un control ms complejo y fino sobre cada repositorio, puedes manejarlos a travs de
los permisos estandar del sistema de archivos.
Si deseas colocar los repositorios en un servidor donde no todas las personas de tu equipo tengan
cuentas de acceso, tendrs que dar acceso SSH a aquellas que no lo tengan. Suponiendo que ya
tengas el servidor, que el servicio SSH est instalado y que sea esa la va de acceso que t ests
utilizando para acceder a l.
Tienes varias maneras para dar acceso a todos los miembros de tu equipo. La primera forma es el
habilitar cuentas para todos; es la manera ms directa, pero tambin la ms laboriosa. Ya que
tendrias que lanzar el comando 'adduser' e inventarte contraseas temporales para cada uno.
La segunda forma es el crear un solo usuario 'git' en la mquina y solicitar a cada persona que te
envie una clave pblica SSH, para que puedas aadirlas al archivo ~/.ssh/authorized_keys de
dicho usuario 'git'. De esta forma, todos pueden acceder a la mquina a travs del usuario 'git'.
Esto no afecta a los datos de las confirmaciones (commit), ya que el usuario SSH con el que te
conectes no es relevante para las confirmaciones de cambios que registres.
Y una tercera forma es el preparar un servidor SSH autenficado desde un servidor LDAP o desde
alguna otra fuente de autenficacin externa ya disponible. Tan solo con que cada usuario pueda
tener acceso al shell de la mquina, es vlido cualquier mecanismo de autentificacin SSH que se
emplee para ello.
Tal y como se ha comentado, muchos servidores Git utilizan la autentificacin a travs de claves
pblicas SSH. Y, para ello, cada usuario del sistema ha de generarse una, si es que no la tiene ya.
El proceso para hacerlo es similar en casi cualquier sistema operativo. Ante todo, asegurarte que
no tengas ya una clave. Por defecto, las claves de cualquier usuario SSH se guardan en la carpeta
~/.ssh de dicho usuario. Puedes verificar si tienes ya unas claves, simplemente situandote sobre
dicha carpeta y viendo su contenido:
$ cd ~/.ssh
$ ls
authorized_keys2 id_dsa known_hosts
config id_dsa.pub
Has de buscar un par de archivos con nombres tales como 'algo' y 'algo.pub'; siendo ese "algo"
normalmente 'iddsa' o 'idrsa'. El archivo terminado en '.pub' es tu clave pblica, y el otro archivo
es tu clave privada. Si no tienes esos archivos (o no tienes ni siquiera la carpeta '.ssh'), has de
crearlos; utilizando un programa llamado 'ssh-keygen', que viene incluido en el paquete SSH de
los sistemas Linux/Mac o en el paquete MSysGit en los sistemas Windows:
$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/schacon/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /Users/schacon/.ssh/id_rsa.
Your public key has been saved in /Users/schacon/.ssh/id_rsa.pub.
The key fingerprint is:
43:c5:5b:5f:b1:f1:50:43:ad:20:a6:92:6a:1f:9a:3a schacon@agadorlaptop.local
Como se v, este comando primero solicita confirmacin de dnde van a a guardarse las claves
('.ssh/id_rsa'), y luego solicita, dos veces, una contrasea (passphrase), contrasea que puedes
dejar en blanco si no deseas tener que teclearla cada vez que uses la clave.
Tras generarla, cada usuario ha de encargarse de enviar su clave pblica a quienquiera que
administre el servidor Git (en el caso de que este est configurado con SSH y as lo requiera). Esto
se puede realizar simplemente copiando los contenidos del archivo terminado en '.pub' y
enviandoselos por correo electrnico. La clave pblica ser una serie de nmeros, letras y signos,
algo as como esto:
$ cat ~/.ssh/id_rsa.pub
ssh-rsa
AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSU
GPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0c
da3
Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK
/7XA
t3FaoJoAsncM1Q9x5+3V0Ww68/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw/Pb0rwert
/En
mZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMq
VSsbx
NrRFi9wrf+M7Q== schacon@agadorlaptop.local
Para ms detalles sobre cmo crear unas claves SSH en variados sistemas operativos, consultar la
correspondiente guia en GitHub: http://github.com/guides/providing-your-ssh-key.
4.4 Git en un servidor - Preparando el
servidor
Preparando el servidor
Vamos a avanzar en los ajustes de los accesos SSH en el lado del servidor. En este ejemplo, usars
el mtodo de las 'claves autorizadas' para autentificar a tus usuarios. Se asume que tienes un
servidor en marcha, con una distribucin estandar de Linux, tal como Ubuntu. Comienzas
creando un usuario 'git' y una carpeta '.ssh' para l.
Y a continuacin aades las claves pblicas de los desarrolladores al archivo 'autorized_keys' del
usuario 'git' que has creado. Suponiendo que hayas recibido las claves por correo electrnico y
que las has guardado en archivos temporales. Y recordando que las claves pblicas son algo as
como:
$ cat /tmp/id_rsa.john.pub
ssh-rsa
AAAAB3NzaC1yc2EAAAADAQABAAABAQCB007n/ww+ouN4gSLKssMxXnBOvf9LGt4L
ojG6rs6hPB09j9R/T17/x4lhJA0F3FR1rP6kYBRsWj2aThGw6HXLm9/5zytK6Ztg3RPKK+
4k
Yjh6541NYsnEAZuXz0jTTyAUfrtU3Z5E003C4oxOj6H0rfIF1kKI9MAQLMdpGW1GYEIgS
9Ez
Sdfd8AcCIicTDWbqLAcU4UpkaX8KyGlLwsNuuGztobF8m72ALC/nLF6JLtPofwFBlgc+my
iv
O7TCUSBdLQlgMVOFq1I2uPWQOkOWQAHukEOmfjy2jctxSDBQ220ymjaNsHT4kgtZg2
AYYgPq
dAv8JggJICUvax2T9va5 gsg-keypair
Tras esto, puedes preparar un repositorio bsico vacio para ellos, usando el comando 'git init' con
la opcin '--bare' para inicializar el repositorio sin carpeta de trabajo:
$ cd /opt/git
$ mkdir project.git
$ cd project.git
$ git --bare init
Y John, Josie o Jessica podrn enviar (push) la primera versin de su proyecto a dicho
repositorio, aadiendolo como remoto y enviando (push) una rama (branch). Cabe indicar que
alguien tendr que iniciar sesin en la mquina y crear un repositorio bsico, cada vez que se
desee aadir un nuevo proyecto. Suponiendo, por ejemplo, que se llame 'gitserver' el servidor
donde has puesto el usuario 'git' y los repositorios; que dicho servidor es interno a vuestra red y
que est asignado el nombre 'gitserver' en vuestro DNS. Podrs utlizar comandos tales como:
# en la mquina de John
$ cd myproject
$ git init
$ git add .
$ git commit -m 'initial commit'
$ git remote add origin git@gitserver:/opt/git/project.git
$ git push origin master
Con este mtodo, puedes preparar rpidamente un servidor Git con acceso de lectura/escritura
para un grupo de desarrolladores.
Para una mayor proteccin, puedes restringir facilmente el usuario 'git' a realizar solamente
actividades relacionadas con Git. Utilizando un shell limitado llamado 'git-shell', que viene
incluido en Git. Si lo configuras como el shell de inicio de sesin de tu usuario 'git', dicho usuario
no tendr acceso al shell normal del servidor. Para especificar el 'git-shell' en lugar de bash o de
csh como el shell de inicio de sesin de un usuario, Has de editar el archivo '/etc/passwd':
git:x:1000:1000::/home/git:/bin/shgit:x:1000:1000::/home/git:/bin/sh
Y cambiar '/bin/sh' por '/usr/bin/git-shell' (nota: puedes utilizar el comando 'which git-shell'
para ver dnde est instalado dicho shell). Quedar una linea algo as como:
git:x:1000:1000::/home/git:/usr/bin/git-shellgit:x:1000:1000::/home/git:/usr/bin/g
it-shell
De esta forma dejamos al usuario 'git' limitado a utilizar la conexin SSH solamente para enviar
(push) y recibir (pull) repositorios, sin posibilidad de iniciar una sesin normal en el servidor. Si
pruebas a hacerlo, recibiras un rechazo de inicio de sesin:
$ ssh git@gitserver
fatal: What do you think I am? A shell?
Connection to gitserver closed.
$ cd project.git
$ mv hooks/post-update.sample hooks/post-update
$ chmod a+x hooks/post-update
Si utilizas una versin de Git anterior a la 1.6, el comando 'mv' no es necesario, ya que solo
recientemente lleva Git los anclajes de ejemplo con el sufijo .sample
Que hace este anclaje 'post-update'? Pues tiene una pinta tal como:
$ cat .git/hooks/post-update
#!/bin/sh
exec git-update-server-info
Lo que significa que cada vez que envias (push) algo al servidor va SSH, Git lanzar este
comando y actualizar as los archivos necesarios para HTTP fetching. (ipendientedetraducir)
<VirtualHost *:80>
ServerName git.gitserver
DocumentRoot /opt/git
<Directory /opt/git/>
Order allow, deny
allow from all
</Directory>
</VirtualHost>
Asimismo, has de ajustar el grupo Unix de las carpetas bajo '/opt/git' a 'www-data', para que tu
servidor web tenga acceso de lectura a los repositorios contenidos en ellas; porque la instancia de
Apache que maneja los scripts CGI trabaja bajo dicho usuario:
Una vez reinicies Apache, ya deberias ser capaz de clonar tus repositorios bajo dicha carpeta,
simplemente indicando la URL de tu projecto:
De esta manera, puedes preparar en cuestin de minutos accesos de lectura basados en HTTP a
tus proyectos, para grandes cantidades de usuarios. Otra opcin simple para habilitar accesos
pblicos sin autentificar, es arrancar el demonio Git, aunque esto supone demonizar el proceso.
(Se ver esta opcin en la siguiente seccin.)
Si quieres comprobar cmo podra quedar GitWeb con tu proyecto, Git dispone de un comando
para activar una instancia temporal, si en tu sistema tienes un servidor web ligero, como por
ejemplo 'lighttup' o 'webrick'. En las mquinas Linux, 'lighttpd' suele estar habitualmente
instalado. Por lo que tan solo has de activarlo lanzando el comando 'git instaweb', estando en la
carpeta de tu proyecto. Si tienes una mquina Mac, Leopard trae preinstalado Ruby, por lo que
'webrick' puede ser tu mejor apuesta. Para instalar 'instaweb' disponiendo de un controlador
no-lighttpd, puedes lanzarlo con la opcin '--httpd'.
Esto arranca un servidor HTTPD en el puerto 1234, y luego arranca un navegador que abre esa
pgina. Es realmente sencillo. Cuando ya has terminado y quieras apagar el servidor, puedes
lanzar el mismo comando con la opcin '--stop'.
Fijate que es necesario indicar la ubicacin donde se encuentran los repositorios Git, utilizando la
variable 'GITWEB_PROJECTROOT'. A continuacin, tienes que preparar Apache para que
utilice dicho script, Para ello, puedes aadir un VirtualHost:
<VirtualHost *:80>
ServerName gitserver
DocumentRoot /var/www/gitweb
<Directory /var/www/gitweb>
Options ExecCGI +FollowSymLinks +SymLinksIfOwnerMatch
AllowOverride All
order allow,deny
Allow from all
AddHandler cgi-script cgi
DirectoryIndex gitweb.cgi
</Directory>
</VirtualHost>
Recordar una vez ms que GitWeb puede servirse desde cualquier servidor web con capacidades
CGI. Por lo que si prefieres utilizar algn otro, no debera ser dificil de configurarlo. En este
momento, deberias poder visitar 'http://gitserver/' para ver tus repositorios online. Y utilizar
'http://git.gitserver' para clonar (clone) y recuperar (fetch) tus repositorios a travs de HTTP.
4.7 Git en un servidor - Gitosis
Gitosis
Mantener claves pblicas, para todos los usuarios, en el archivo 'authorized_keys', puede ser una
buena solucin inicial. Pero, cuanto tengas cientos de usuarios, se hace bastante pesado gestionar
as ese proceso. Tienes que iniciar sesin en el servidor cada vez. Y, ademas, no tienes control de
acceso --todo el mundo presente en el archivo tiene permisos de lectura y escritura a todos y cada
uno de los proyectos--.
En este punto, es posible que desees cambiar a un popular programa llamado Gitosis. Gitosis es
bsicamente un conjunto de scripts que te ayudarn a gestionar el archivo 'authorized_keys', as
como a implementar algunos controles de acceso simples. Lo interesante de la interfaz de usuario
para esta herramienta de gestin de usuarios y de control de accesos, es que, en lugar de un
interface web, es un repositorio especial de Git. Preparas la informacin en ese proyecto especial,
y cuando la envias (push), Gitosis reconfigura el servidor en base a ella. Realmente interesante!.
Gitosis necesita de ciertas herramientas Python, por lo que la primera tarea ser instalar el
paquete de herramientas Pyton. En Ubuntu viene como el paquete python-setuptools:
Esto instala un par de ejecutables, que sern los que Gitosis utilice. Gitosis intentar instalar sus
repositorios bajo la carpeta '/home/git', lo cual est bien. Pero si, en lugar de en esa, has
instalado tus repositorios bajo la carpeta '/opt/git'. Sin necesidad de reconfigurarlo todo, tan solo
has de crear un enlace virtual:
$ ln -s /opt/git /home/git/repositories
Gitosis manejar tus claves por t, por lo que tendrs que quitar el archivo actual, aadir de nuevo
las claves ms tarde, y dejar que Gitosis tome automticamente el control del archivo
'authorizedkeys'. Para empezar, mueve el archivo 'authorizedkeys a otro lado:
$ mv /home/git/.ssh/authorized_keys /home/git/.ssh/ak.bak
A continuacin, restaura el inicio de sesin (shell) para el usuario 'git', (si es que lo habias
cambiado al comando 'git-shell'). Los usuarios no podrn todavia iniciar sesin, pero Gitosis se
encargar de ello. As pues, cambia esta lnea en tu archivo '/etc/passwd':
git:x:1000:1000::/home/git:/usr/bin/git-shellgit:x:1000:1000::/home/git:/usr/bin/g
it-shell
de vuelta a:
git:x:1000:1000::/home/git:/bin/shgit:x:1000:1000::/home/git:/bin/sh
Esto habilita al usuario con dicha clave pblica para que pueda modificar el repositorio principal
de Git, y, con ello, pueda controlar la instalacin de Gitosis. A continuancin, has de ajustar
manualmente el bit de ejecucin en el script 'post-update' de tu nuevo repositorio de contrrol:
Y ya ests preparado para trabajar. Si lo has configurado todo correctamente, puedes intentar
conectarte, va SSH, a tu servidor como el usuario con cuya clave pblica has inicializado Gitosis.
Y debers ver algo as como esto:
$ ssh git@gitserver
PTY allocation request failed on channel 0
fatal: unrecognized command 'gitosis-serve schacon@quaternion'
Connection to gitserver closed.
Indicandote que Gitosis te ha reconocido, pero te est hechando debido a que no ests intentando
lanzar ningn comando Git. Por tanto, intentalo con un comando Git real --por ejemplo, clonar el
propio repositorio de control de Gitosis a tu ordenador personal--
Con ello, tendrs una carpeta denominada 'gitosis-admin', con dos partes principales dentro de
ella:
$ cd gitosis-admin
$ find .
./gitosis.conf
./keydir
./keydir/scott.pub
El archivo 'gitosis.conf' es el archivo de control que usars para especificar usuarios, repositorios
y permisos. La carpeta 'keydir' es donde almacenars las claves pblicas para los usuarios con
acceso a tus repositorios --un archivo por usuario--. El nombre del archivo en la carpeta 'keydir'
('scott.pub' en el ejemplo), puede ser diferente en tu instalacin, (Gitosis lo obtiene a partir de la
descripcin existente al final de la clave pblica que haya sido importada con el script
'gitosis-init').
Si miras dentro del archivo 'gitosis.conf', encontrars nicamente informacin sobre el proyecto
'gitosis-admin' que acabas de clonar:
$ cat gitosis.conf
[gitosis]
[group gitosis-admin]
writable = gitosis-admin
members = scott
Indicando que el usuario 'scott' --el usuario con cuya clave pblica se ha inicializado Gitosis-- es
el nico con acceso al proyecto 'gitosis-admin'.
A partir de ahora, puedes aadir nuevos proyectos. Por ejemplo, puedes aadir una nueva
seccin denominada 'mobile', donde poner la lista de los desarrolladores en tu equipo movil y los
proyectos donde estos vayan a trabajar. Por ser 'scott' el nico usuario que tienes definido por
ahora, lo aadirs como el nico miembro. Y puedes crear adems un proyecto llamado
'iphone_project' para empezar:
[group mobile]
writable = iphone_project
members = scott
Ten en cuenta que no es necesario indicar expresamente un camino (path), --de hecho, si lo
haces, no funcionar--. Simplemente, has de poner un punto y el nombre del proyecto, --Gitosis
se encargar de encontrarlo--.
Si deseas compartir el proyecto con tus compaeros, tienes que aadir de nuevo sus claves
pblicas. Pero en lugar de hacerlo manualmente sobre el archivo ~/.ssh/authorized_keys de
tu servidor, has de hacerlo --un archivo por clave-- en la carpeta 'keydir' del proyecto de control.
Segn pongas los nombres a estos archivos, as tendrs que referirte a los usuarios en el archivo
'gitosis.conf'. Por ejemplo, para aadir las claves pblicas de John, Josie y Jessica:
$ cp /tmp/id_rsa.john.pub keydir/john.pub
$ cp /tmp/id_rsa.josie.pub keydir/josie.pub
$ cp /tmp/id_rsa.jessica.pub keydir/jessica.pub
Y para aadirlos al equipo 'mobile', dndoles permisos de lectura y escritura sobre el proyecto
'phone_project':
[group mobile]
writable = iphone_project
members = scott john josie jessica
Tras confirmar (commit) y enviar (push) estos cambios, los cuatro usuarios podrn acceder a leer
y escribir sobre el proyecto.
Gitosis permite tambin sencillos controles de acceso. Por ejemplo, si quieres que John tenga
nicamente acceso de lectura sobre el proyecto, puedes hacer:
[group mobile]
writable = iphone_project
members = scott josie jessica
[group mobile_ro]
readonly = iphone_project
members = john
Habilitandole as para clonar y recibir actualizacines desde el servidor; pero impidiendole enviar
de vuelta cambios al proyecto. Puedes crear tantos grupos como desees, para diferentes usuarios
y proyectos. Tambin puedes indicar un grupo como miembro de otro (utilizado el prefijo '@'),
para incluir todos sus miembros automticamente:
[group mobile_committers]
members = scott josie jessica
[group mobile]
writable = iphone_project
members = @mobile_committers
[group mobile_2]
writable = another_iphone_project
members = @mobile_committers john
Si tienes problemas, puede ser util aadir loglevel=DEBUG en la seccin [gitosis]. Si, por lo
que sea, pierdes acceso de envio (push) de nuevos cambios, (por ejemplo, tras haber enviado una
configuracin problemtica); siempre puedes arreglar manualmente ,en el propio servidor, el
archivo '/home/git/.gitosis.conf', (el archivo del que Gitosis lee su configuracin). Un envio
(push) de cambios al proyecto, coge el archivo 'gitosis.conf' enviado y sobreescribe con l el del
servidor. Si lo editas manualmente, permanecer como lo dejes; hasta el prximo envio (push) al
proyecto 'gitosis-admin'.
Para dar a tus proyectos un acceso pblico, sin autentificar, de solo lectura, querrs ir ms all
del protocolo HTTP y comenzar a utilizar el protocolo Git. Principalmente, por razones de
velocidad. El protocolo Git es mucho ms eficiente y, por tanto, ms rpido que el protocolo
HTTP. Utilizndolo, ahorrars mucho tiempo a tus usuarios.
Aunque, sigue siendo solo para acceso unicamente de lectura y sin autentificar. Si lo ests
utilizando en un servidor fuera del permetro de tu cortafuegos, se debe utilizar exclusivamente
para proyectos que han de ser pblicos, visibles para todo el mundo. Si lo ests utilizando en un
servidor dentro del permetro de tu cortafuegos, puedes utilizarlo para proyectos donde un gran
nmero de personas o de ordenadores (integracin contnua o servidores de desarrollo) necesiten
acceso de solo lectura. Y donde quieras evitar la gestin de claves SSH para cada una de ellas.
En cualquier caso, el protocolo Git es relativamente sencillo de configurar. Tan solo necesitas
lanzar este comando de forma demonizada:
El parmetro '--reuseaddr' permite al servidor reiniciarse sin esperar a que se liberen viejas
conexiones; el parmetro '--base-path' permite a los usuarios clonar proyectos sin necesidad de
indicar su camino completo; y el camino indicado al final del comando mostrar al demonio Git
dnde buscar los repositorios a exportar. Si tienes un cortafuegos activo, necesitars abrir el
puerto 9418 para la mquina donde ests configurando el demnio Git.
Este proceso se puede demonizar de diferentes maneras, dependiendo del sistema operativo con
el que trabajas. En una mquina Ubuntu, puedes usar un script de arranque. Poniendo en el
siguiente archivo:
/etc/event.d/local-git-daemon
start on startup
stop on shutdown
exec /usr/bin/git daemon \
--user=git --group=git \
--reuseaddr \
--base-path=/opt/git/ \
/opt/git/
respawn
Por razones de seguridad, es recomendable lanzar este demonio con un usuario que tenga
unicamente permisos de lectura en los repositorios --lo puedes hacer creando un nuevo usuario
'git-ro' y lanzando el demonio con l--. Para simplificar, en estos ejemplos vamos a lanzar el
demonio Git bajo el mismo usuario 'git' con el que hemos lanzado Gitosis.
En otros sistemas operativos, puedes utilizar 'xinetd', un script en el sistema 'sysvinit', o alguna
otra manera --siempre y cuando demonizes el comando y puedas monitorizarlo--.
[repo iphone_project]
daemon = yes
Cuando confirmes (commit) y envies (push) estos cambios, el demonio que est en marcha en el
servidor comenzar a responder a peticiones de cualquiera que solicite dicho proyecto a travs
del puerto 9418 de tu servidor.
Si decides no utilizar Gitosis, pero sigues queriendo utilizar un demonio Git, has de lanzar este
comando en cada proyecto que desees servr va el demonio Git:
$ cd /path/to/project.git
$ touch git-daemon-export-ok
La presencia de este archivo, indica a Git que est permitido el servir este proyecto sin necesidad
de autentificacin.
Tambin podemos controlar a travs de Gitosis los proyectos a ser mostrados por GitWeb.
Previamente, has de aadir algo como esto al archivo '/etc/gitweb.conf':
$projects_list = "/home/git/gitosis/projects.list";
$projectroot = "/home/git/repositories";
$export_ok = "git-daemon-export-ok";
@git_base_url_list = ('git://gitserver');
Los proyectos a ser mostrados por GitWeb se controlarn aadiendo o quitando parmetros
'gitweb' en el archivo de configuracin de Gitosis. Por ejemplo, si quieres mostrar el proyecto
iphone, has de poner algo as como:
[repo iphone_project]
daemon = yes
gitweb = yes
A partir de ese momento, cuando confirmes cambios (commit) y envies (push) el proyecto,
GitWeb comenzar a mostrar tu proyecto iphone.
Si no quieres realizar todo el trabajo de preparar tu propio servidor Git, tienes varias opciones
para alojar tus proyectos Git en una ubicacin externa dedicada. Esta forma de trabajar tiene
varias ventajas: un alberge externo suele ser rpido de configurar y sencillo de iniciar proyectos
en l; adems de no ser necesario preocuparte de su mantenimiento ni de su monitorizacin.
Incluso en el caso de que tengas tu propio servidor interno, puede resultar interesante utilizar
tambin un lugar pblico; para albergar tu cdigo abierto --normalmente, ah suele ser ms
sencillo de localizar por parte de la comunidad--
Actualmente tienes un gran nmero de opciones del alojamiento, cada una con sus ventajas y
desventajas. Para obtener una lista actualizada, puedes mirar en la pgina GitHosting del wiki
principal de Git:
https://git.wiki.kernel.org/index.php/GitHosting
Por ser imposible el cubrir todos ellos, y porque da la casualidad de que trabajo en uno de ellos,
concretamente, en esta seccin veremos cmo crear una cuenta y nuevos proyectos albergados en
'GitHub'. As podrs hacerte una idea de cmo suelen funcionar estos alberges externos.
GitHub es, de lejos, el mayor sitio de alberge pblico de proyectos Git de cdigo abierto. Y es
tambin uno de los pocos que ofrece asimismo opciones de alberge privado; de tal forma que
puedes tener tanto tus proyectos de cdigo abierto y como los de cdigo comercial cerrado en un
mismo emplazamiento. De hecho, nosotros utilizamos tambin GitHub para colaborar
privadamente en este libro.
GitHub
GitHub es ligeramente distinto a otros sitios de alberge, en tanto en cuanto que contempla
espacios de nombres para los proyectos. En lugar de estar focalizado en los proyectos, GitHub
gira en torno a los usuarios. Esto significa que, cuando alojo mi proyecto 'grit' en GitHub, no lo
encontraras bajo 'github.com/grit', sino bajo 'github.com/schacon/grit'. No existe una versin
cannica de ningn proyecto, lo que permite a cualquiera de ellos ser movido facilmente de un
usuario a otro en el caso de que el primer autor lo abandone.
GitHub es tambin una compaia comercial, que cobra por las cuentas que tienen repositorios
privados. Pero, para albergar proyectos pblicos de cdigo abierto, cualquiera puede crear una
cuenta gratuita. Vamos a ver cmo hacerlo.
El primer paso es dar de alta una cuenta gratuita. Si visitas la pgina de Precios e Inicio de
Sesin, en 'https://github.com/pricing', y clicas sobre el botn "Registro" ("Sign Up") de las
cuentas gratuitas, vers una pgina de registro:
En ella, has de elegir un nombre de usuario que est libre, indicar una cuenta de correo
electrnico y poner una contrasea.
Figura 4-3. El formulario de registro en GitHub.
Si la tuvieras, es tambin un buen momento para aadir tu clave pblica SSH. Veremos cmo
generar una de estas claves, ms adelante, en la seccin "Ajustes Simples". Pero, si ya tienes un
par de claves SSH, puedes coger el contenido correspondiente a la clave pblica y pegarlo en la
caja de texto preparada para tal fin. El enlace "explicar claves ssh" ("explain ssh keys") te llevar a
unas detalladas instrucciones de cmo generarlas en la mayor parte de los principales sistemas
operativos. Clicando sobre el botn de "Estoy de acuerdo, registram" ("I agree, sign me up"), irs
al panel de control de tu recin creado usuario.
Figura 4-4. El panel de control del usuario GitHub.
Puedes empezar clicando sobre el enlace "crear uno nuevo" ("create a new one"), en la zona 'Tus
repositorios' ('Your Repositories') del panel de control. Irs al formulario de Crear un Nuevo
Repositrio (ver Figura 4-5).
Es suficiente con dar un nombre al proyecto, pero tambin puedes aadirle una descripcin.
Cuando lo hayas escrito, clica sobre el botn "Crear Repositrio" ("Create Repository"). Y ya
tienes un nuevo repositrio en GitHub (ver Figura 4-6)
Estas instrucciones son similares a las que ya hemos visto. Para inicializar un proyecto, no siendo
an un proyecto Git, sueles utilizar:
$ git init
$ git add .
$ git commit -m 'initial commit'
Una vez tengas un repositorio local Git, aadele el sitio GitHub como un remoto y envia (push)
all tu rama principal:
Figura 4-8. Cabecera de proyecto, con una URL pblica y otra URL privada.
El enlace "Public Clone URL", es un enlace pblico, de solo lectura; a travs del cual cualquiera
puede clonar el proyecto. Puedes comunicar libremente ese URL o puedes publicarlo en tu sitio
web o en cualquier otro mdio que desees.
El enlace "Your Clone URL", es un enlace de lectura/escritura basado en SSH; a travs del cual
puedes leer y escribir, pero solo si te conectas con la clave SSH privada correspondiente a la clave
pblica que has cargado para tu usuario. Cuando otros usuarios visiten la pgina del proyecto, no
vern esta segunda URL --solo vern la URL pblica--.
Si tienes un proyecto pblico Subversion que deseas pasar a Git, GitHub suele poder realizar la
importacin. All fondo de la pgina de instrucciones, tienes un enlace "Subversion import". Si
clicas sobre dicho enlace, vers un formulario con informacin sobre el proceso de importacin y
un cuadro de texto donde puedes pegar la URL de tu proyecto Subversion (ver Figura 4-9).
Figura 4-9. El interface de importacin desde Subversion.
Si tu proyecto es muy grande, no-estandar o privado, es muy posible que no se pueda importar.
En el captulo 7, aprenders cmo realizar importaciones manuales de proyectos complejos.
Aadiendo colaboradores
Vamos a aadir al resto del equipo. Si tanto John, como Josie, como Jessica, todos ellos registran
sus respectivas cuentas en GitHub. Y deseas darles acceso de escritura a tu repositorio. Puedes
incluirlos en tu proyecto como colaboradores. De esta forma, funcionarn los envios (push) desde
sus respectivas claves pblicas.
Has de hacer clic sobre el botn "edit" en la cabecera del proyecto o en la pestaa Admin de la
parte superior del proyecto; yendo as a la pgina de administracin del proyecto GitHub.
Figura 4-10. Pgina de administracin GitHub.
Para dar acceso de escritura a otro usuario, clica sobre el enlace "Add another collaborator".
Aparecer un cuadro de texto, donde podrs teclear un nombre. Segn tecleas, aparecer un
cuadro de ayuda, mostrando posibles nombres de usuario que encajen con lo tecleado. Cuando
localices al usuario deseado, clica sobre el botn "Add" para aadirlo como colaborador en tu
proyecto (ver Figura 4-11).
Cuando termines de aadir colaboradores, podrs ver a todos ellos en la lista "Repository
Collaborators" (ver Figura 4-12).
Si deseas revocar el acceso a alguno de ellos, puedes clicar sobre el enlace "revoke", y sus
permisos de envio (push) sern revocados. En proyectos futuros, podras incluir tambin a tu
grupo de colaboradores copiando los permisos desde otro proyecto ya existente.
Tu proyecto
Una vez hayas enviado (push) tu proyecto, o lo hayas importado desde Subversion, tendrs una
pgina principal de proyecto tal como:
Figura 4-13. Una pgina principal de proyecto GitHub.
Cuando la gente visite tu proyecto, ver esta pgina. Tiene pestaas que llevan a distintos
aspectos del proyecto. La pestaa "Commits" muestra una lista de confirmaciones de cambio, en
orden cronolgico inverso, de forma similar a la salida del comando 'git log'. La pestaa
"Network" muestra una lista de toda la gente que ha bifurcado (forked) tu proyecto y ha
contribuido a l. La pestaa "Downloads" permite cargar binarios del proyecto y enlaza con
tarballs o versiones comprimidas de cualquier punto marcado (tagged) en tu proyecto. La pestaa
"Wiki" enlaza con un espacio wiki donde puedes escribir documentacin o cualquier otra
informacin relevante sobre tu proyecto. La pestaa "Graphs" muestra diversas visualizaciones
sobre contribuciones y estadsticas de tu proyecto. La pestaa principal "Source" en la que
aterrizas cuando llegas al proyecto, muestra un listado de la carpeta principal; y muestra tambin
el contenido del archivo README, si tienes uno en ella. Esta pestaa muestra tambin un cuadro
con informacin sobre la ltima confirmacin de cambio (commit) realizada en el proyecto.
Bifurcando proyectos
De esta forma, los proyectos no han de preocuparse de aadir usuarios como colaboradores para
darles acceso de envio (push). La gente puede bifurcar (fork) un proyecto y enviar (push) sobre su
propia copia. El gestor del proyecto principal, puede recuperar (pull) esos cambios aadiendo las
copias como remotos y fusionando (merge) el trabajo en ellas contenido.
Para bifurcar un proyecto, visita su pgina (en el ejemplo, mojombo/chronic) y clica sobre el
botn "fork" de su cabecera (ver Figura 4-14)
Figura 4-14. Obtener una copia sobre la que escribir, clicando sobre el botn "fork" de un
repositorio.
Tras unos segundos, sers redirigido a la pgina del nuevo proyecto; y en ella se ver que este
proyecto es una bifuracin (fork) de otro existente (ver Figura 4-15).
Resumen de GitHub
Esto es todo lo que vamos a ver aqu sobre GitHub, pero merece la pena destacar lo rpido que
puedes hacer todo esto. Puedes crear una cuenta, aadir un nuevo proyecto y contribuir a l en
cuestin de minutos. Si tu proyecto es de cdigo abierto, puedes tener tambin una amplia
comunidad de desarrolladores que podrn ver tu proyecto, bifurcarlo (fork) y ayudar
contribuyendo a l. Y, por ltimo, comentar que esta puede ser una buena manera de iniciarte y
comenzar rpidamente a trabajar con Git.
Chapter 5
Git en entornos distribuidos
Ahora que ya tienes un repositorio Git, configurado como punto de trabajo para compartir cdigo
entre desarrolladores. Y ahora que ya conoces los comandos bsicos de Git para flujos de trabajo
locales. Puedes echar un vistazo a algunos de los flujos de trabajo distribuidos que Git permite.
En este captulo, vers cmo trabajar con Git en un entorno distribuido, bien como colaborador o
bien como integrador. Es decir, aprenders cmo contribuir adecuadamente a un proyecto; de la
forma ms efectiva posible, tanto para t, como para quien gestione el proyecto. Y aprenders
tambin a gestionar proyectos en los que colaboren multiples desarrolladores.
Esto significa que, si dos desarrolladores clonan desde el punto central, y ambos hacen cambios;
tan solo el primero de ellos en enviar sus cambios de vuelta lo podr hacer limpiamente. El
segundo desarrollador deber fusionar previamente su trabajo con el del primero, antes de
enviarlo, para evitar el sobreescribir los cambios del primero. Este concepto es tambin vlido en
Git, tanto como en Subversion (o cualquier otro CVCS), y puede ser perfectamente utilizado en
Git.
Si tienes un equipo pequeo o te sientes confortable con un flujo de trabajo centralizado, puedes
continuar usando esa forma de trabajo con Git. Solo necesitas disponer un repositorio nico, y
dar acceso en envio (push) a todo tu equipo. Git se encargar de evitar el que se sobreescriban
unos a otros. Si uno de los desarrolladores clona, hace cambios y luego intenta enviarlos; y otro
desarrollador ha enviado otros cambios durante ese tiempo; el servidor rechazar los cambios del
segundo desarrollador. El sistema le avisar de que est intentando enviar (push) cambios no
directos (non-fast-forward changes), y de que no podr hacerlo hasta que recupere (fetch) y
fusione (merge) los cambios preexistentes. Esta forma de trabajar es atractiva para mucha gente,
por ser el paradigma con el que estn familiarizados y se sienten confortables.
Esta es una forma de trabajo muy comn en sitios tales como GitHub, donde es sencillo bifurcar
(fork) un proyecto y enviar tus cambios a tu copia, donde cualquiera puede verlos. La principal
ventaja de esta forma de trabajar es que puedes continuar trabajando, y la persona gestora del
repositorio principal podr recuperar (pull) tus cambios en cualquier momento. Las personas
colaboradoras no tienen por qu esperar a que sus cambios sean incorporados al proyecto, --cada
cual puede trabajar a su propio ritmo--.
Es una variante del flujo de trabajo con multiples repositorios. Se utiliza generalmente en
proyectos muy grandes, con cientos de colaboradores. Un ejemplo muy conocido es el del kernel
de Linux. Unos gestores de integracin se encargan de partes concretas del repositorio; y se
denominan tenientes. Todos los tenientes rinden cuentas a un gestor de integracin; conocido
como el dictador benevolente. El repositorio del dictador benevolente es el repositorio de
referencia, del que recuperan (pull) todos los colaboradores. El proceso funciona como sigue (ver
Figura 5-3):
Esta manera de trabajar no es muy habitual, pero es muy util en proyectos muy grandes o en
organizacines fuertemente jerarquizadas. Permite al lider o a la lider del proyecto (el/la
dictador/a) delegar gran parte del trabajo; recolectando el fruto de multiples puntos de trabajo
antes de integrarlo en el proyecto.
Hemos visto algunos de los flujos de trabajo mas comunes permitidos por un sistema distribuido
como Git. Pero seguro que habrs comenzado a vislumbrar multiples variaciones que puedan
encajar con tu particular forma de trabajar. Espero que a estas alturas ests en condiciones de
reconocer la combinacin de flujos de trabajo que puede serte util. Vamos a ver algunos ejemplos
ms especficos, ilustrativos de los roles principales que se presentan en las distintas maneras de
trabajar.
El mayor problema al intentar describir este proceso es el gran nmero de variaciones que se
pueden presentar. Por la gran flexibilidad de Git, la gente lo suele utilizar de multiples maneras;
siendo problemtico intentar describir la forma en que deberas contribuir a un proyecto --cada
proyecto tiene sus peculiaridades--. Algunas de las variables a considerar son: la cantidad de
colaboradores activos, la forma de trabajo escogida, el nivel de acceso que tengas, y,
posiblemente, el sistema de colaboracin externa implantado.
La segunda variable es la forma de trabajo que se utilice para el proyecto. Es centralizado, con
iguales derechos de acceso en escritura para cada desarrollador?. Tiene un gestor de
integraciones que comprueba todos los parches?. Se revisan y aprueban los parches entre los
propios desarrolladores?. Participas en ese proceso de aprobacin?. Existe un sistema de
tenientes, a los que has de enviar tu trabajo en primer lugar?.
Antes de comenzar a revisar casos de uso especficos, vamos a dar una pincelada sobre los
mensajes en las confirmaciones de cambios (commits). Disponer unas reglas claras para crear
confirmaciones de cambios, y seguirlas fielmente, facilta enormemente tanto el trabajo con Git y
como la colaboracin con otras personas. El propio proyecto Git suministra un documento con
un gran nmero de buenas sugerencias sobre la creacin de confirmaciones de cambio destinadas
a enviar parches --puedes leerlo en el cdigo fuente de Git, en el archivo
'Documentation/SubmittingPatches'--.
En primer lugar, no querrs enviar ningn error de espaciado. Git nos permite buscarlos
facilmente. Previamente a realizar una confirmacin de cambios, lanzar el comando 'git diff
--check' para identificar posibles errores de espaciado. Aqu van algunos ejemplos, en los que
hemos sustituido las marcas rojas por 'X's:
Lanzando este comando antes de confirmar cambios, puedes estar seguro de si vas o no a incluir
problemas de espaciado que puedan molestar a otros desarrolladores.
En segundo lugar, intentar hacer de cada confirmacin (commit) un grupo lgico e independiente
de cambios. Siempre que te sea posible, intenta hacer digeribles tus cambios --no ests
trabajando todo el fin de semana, sobre cuatro o cinco asuntos diferentes, y luego confirmes todo
junto el lunes--. Aunque no hagas ninguna confirmacin durante el fin de semana, el lunes
puedes utilizar el rea de preparacin (staging area) para ir dividiendo tu trabajo y hacer una
confirmacin de cambios (commit) separada para cada asunto; con un mensaje adecuado para
cada una de ellas. Si algunos de los cambios modifican un mismo archivo, utiliza el comando 'git
add --patch' para almacenar parcialmente los archivos (tal y como se ver detalladamente en el
Captulo 6). El estado del proyecto al final de cada rama ser idntico si haces una sola
confirmacin o si haces cinco, en tanto en cuanto todos los cambios estn confirmados en un
determinado momento. Por consiguiente, intenta facilitar las cosas a tus compaeros y
compaeras desarroladores cuando vayan a revisar tus cambios. Adems, esta manera de
trabajar facilitar la integracin o el descarte individual de alguno de los cambios, en caso de ser
necesario. El Captulo 6 contiene un gran nmero de trucos para reescribir el historial e ir
preparando archivos interactivamente --utilizalos para ayudarte a crear un claro y comprensible
historial--.
Si todas tus confirmaciones de cambio (commit) llevan mensajes de este estilo, facilitars las
cosas tanto para t como para las personas que trabajen contigo. El proyecto Git tiene los
mensajes adecuadamente formateados. Te animo a lanzar el comando 'git log --no-merges' en
dicho proyecto, para que veas la pinta que tiene un historial de proyecto limpio y claro.
En los ejemplos siguientes, y a lo largo de todo este libro, por razones de brevedad no formatear
correctamente los mensajes; sino que emplear la opcin '-m' en los comandos 'git commit'.
Observa mis palabras, no mis hechos.
Lo ms simple que te puedes encontrar es un proyecto privado con uno o dos desarrolladores.
Por privado, me refiero a cdigo propietario --no disponible para ser leido por el mundo
exterior--. Tanto tu como el resto del equipo teneis acceso de envio (push) al repositorio.
En un entorno como este, puedes seguir un flujo de trabajo similar al que adoptaras usando
Subversion o algn otro sistema centralizado. Sigues disfrutando de ventajas tales como las
confirmaciones offline y la mayor simplicidad en las ramificaciones/fusiones. Pero, en el fondo, la
forma de trabajar ser bastante similar; la mayor diferencia radica en que las fusiones (merge) se
hacen en el lado cliente en lugar de en el servidor. Vamos a ver como actuarian dos
desarrolladores trabajando conjuntamente en un repositorio compartido. El primero de ellos,
John, clona el repositorio, hace algunos cambios y los confirma localmente: (en estos ejemplos
estoy sustituyendo los mensajes del protocolo con '...' , para acortarlos)
# John's Machine
$ git clone john@githost:simplegit.git
Initialized empty Git repository in /home/john/simplegit/.git/
...
$ cd simplegit/
$ vim lib/simplegit.rb
$ git commit -am 'removed invalid default value'
[master 738ee87] removed invalid default value
1 files changed, 1 insertions(+), 1 deletions(-)
# Jessica's Machine
$ git clone jessica@githost:simplegit.git
Initialized empty Git repository in /home/jessica/simplegit/.git/
...
$ cd simplegit/
$ vim TODO
$ git commit -am 'add reset task'
[master fbff5bc] add reset task
1 files changed, 1 insertions(+), 0 deletions(-)
# Jessica's Machine
$ git push origin master
...
To jessica@githost:simplegit.git
1edee6b..fbff5bc master -> master
John no puede enviar porque Jessica ha enviado previamente. Entender bien esto es
especialmente importante, sobre todo si ests acostumbrado a utilizar Subversion; porque habrs
notado que ambos desarrolladores han editado archivos distintos. Mientras que Subversion
fusiona automticamente en el servidor cuando los cambios han sido aplicados sobre archivos
distintos, en Git has de fusionar (merge) los cambios localmente. John se v obligado a recuperar
(fetch) los cambios de jessica y a fusionarlos (merge) con los suyos, antes de que se le permita
enviar (push):
En este punto, el repositorio local de John ser algo parecido a la Figura 5-4.
Figura 5-4. El repositorio inicial de John.
John tiene una referencia a los cambios enviados por Jessica, pero ha de fusionarlos en su propio
trabajo antes de que se le permita enviar:
Si la fusin se realiza sin problemas, el historial de John ser algo parecido a la Figura 5-5.
En este momento, John puede comprobar su cdigo para verificar que sigue funcionando
correctamente, y luego puede enviar su trabajo al servidor:
Mientras tanto, jessica ha seguido trabajando en una rama puntual (topic branch). Ha creado una
rama puntual denominada 'issue54' y ha realizado tres confirmaciones de cambios (commit) en
dicha rama. Como todavia no ha recuperado los cambios de John, su historial es como se muestra
en la Figura 5-7.
# Jessica's Machine
$ git fetch origin
...
From jessica@githost:simplegit
fbff5bc..72bbc59 master -> origin/master
Esto recupera el trabajo enviado por John durante el tiempo en que Jessica estaba trabajando. El
historial de Jessica es en estos momentos como se muestra en la figura 5-8.
Jessica considera su rama puntual terminada, pero quiere saber lo que debe integrar con su
trabajo antes de poder enviarla. Lo comprueba con el comando 'git log':
Ahora, jessica puede fusionar (merge) su trabajo de la rama puntual 'issue54' en su rama
'master'l, fusionar (merge) el trabajo de John ('origin/master') en su rama 'master', y enviarla de
vuelta al servidor. Primero, se posiciona de nuevo en su rama principal para integrar todo su
trabajo:
Puede fusionar primero tanto 'origin/master' o como 'issue54', ya que ambos estn aguas arriba.
La situacin final (snapshot) ser la misma, indistintamente del orden elegido; tan solo el
historial variar ligeramente. Elige fusionar primero 'issue54':
$ git merge issue54
Updating fbff5bc..4af4298
Fast forward
README | 1+
lib/simplegit.rb | 6 +++++-
2 files changed, 6 insertions(+), 1 deletions(-)
No hay ningn problema; como puedes observar, es un simple avance rpido (fast-forward). Tras
esto, jessica fusiona el trabajo de John ('origin/master'):
Figura 5-10. El historial de Jessica tras enviar de vuelta todos los cambios al servidor.
Este es uno de los flujos de trabajo ms simples. Trabajas un rato, normalmente en una rama
puntual de un asunto concreto, y la fusionas con tu rama principal cuando la tienes lista para
integrar. Cuando deseas compartir ese trabajo, lo fusionas (merge) en tu propia rama 'master';
luego recuperas (fetch) y fusionas (merge) la rama 'origin/master', por si hubiera cambiado; y
finalmente envias (push) la rama 'master' de vuelta al servidor. La secuencia general es algo as
como la mostrada en la Figura 5-11.
Figura 5-11. Secuencia general de eventos en un flujo de trabajo multidesarrollador simple.
En este prximo escenario, vamos a hechar un vistazo al rol de colaborador en un gran grupo
privado. Aprenders cmo trabajar en un entorno donde pequeos grupos colaboran en algunas
funcionalidades, y luego todas las aportaciones de esos equipos son integradas por otro grupo.
Supongamos que John y Jessica trabajan conjuntamente en una funcionalidad, mientras que
jessica y Josie trabajan en una segunda funcionalidad. En este caso, la compaia est utilizando
un flujo de trabajo del tipo gestor-de-integracin, donde el trabajo de algunos grupos
individuales es integrado por unos ingenieros concretos; siendo solamente estos ltimos quienes
pueden actualizar la rama 'master' del repositorio principal. En este escenario, todo el trabajo se
realiza en ramas propias de cada grupo y es consolidado por los integradores ms tarde.
Vamos a seguir el trabajo de Jessica, a medida que trabaja en sus dos funcionalidade;
colaborando en paralelo con dos desarrolladores distintos. Suponiendo que tiene su repositorio
ya clonado, ella decide trabajar primero en la funcionalidad A (featureA). Crea una nueva rama
para dicha funcionalidad y trabaja en ella:
# Jessica's Machine
$ git checkout -b featureA
Switched to a new branch "featureA"
$ vim lib/simplegit.rb
$ git commit -am 'add limit to log function'
[featureA 3300904] add limit to log function
1 files changed, 1 insertions(+), 1 deletions(-)
En ese punto, necesita compartir su trabajo con John, por lo que envia (push) al servidor las
confirmaciones (commits) en su rama 'featureA'. Como Jessica no tiene acceso de envio a la rama
'master' --solo los integradores lo tienen--, ha de enviar a alguna otra rama para poder colaborar
con John:
Jessica notifica a John por correo electrnico que ha enviado trabajo a una rama denominada
'featureA' y que puede hecharle un vistazo all. Mientras espera noticias de John, Jessica decide
comenzar a trabajar en la funcionalidad B (featureB) con Josie. Para empezar, arranca una nueva
rama puntual, basada en la rama 'master' del servidor:
# Jessica's Machine
$ git fetch origin
$ git checkout -b featureB origin/master
Switched to a new branch "featureB"
$ vim lib/simplegit.rb
$ git commit -am 'made the ls-tree function recursive'
[featureB e5b0fdc] made the ls-tree function recursive
1 files changed, 1 insertions(+), 1 deletions(-)
$ vim lib/simplegit.rb
$ git commit -am 'add ls-files'
[featureB 8512791] add ls-files
1 files changed, 5 insertions(+), 0 deletions(-)
Cuando est preparada para enviar (push) su trabajo, recibe un correo electronico de Josie de que
ha puesto en el servidor una rama denominada 'featureBee', con algo de trabajo. Jessica necesita
fusionar (merge) dichos cambios con los suyos antes de poder enviarlos al servidor. Por tanto,
recupera (fetch) los cambios de Josie:
Pero hay un pequeo problema, necesita enviar el trabajo fusionado en su rama 'featureB' a la
rama 'featureBee' del servidor. Puede hacerlo usando el comando 'git push' e indicando
especficamente el nombre de la rama local seguida de dos puntos (:) y seguida del nombre de la
rama remota:
Esto es lo que se denomina un refspec. Puedes ver el Captulo 9 para una discusin ms detallada
acerca de los refspecs de Git y los distintos usos que puedes darles.
A continuacin, John envia un correo electronico a jessica comentandole que ha enviado algunos
cambios a la rama 'featureA' y pidiendole que los verifique. Ella lanza un 'git fetch' para recuperar
dichos cambios:
A continuacin, puede ver las modificaciones realizadas, lanzando el comando 'git log':
Jessica realiza algunos ajustes, los confirma (commit) y los envia (push) de vuelta al servidor:
Figura 5-13. El historial de Jessica despus de confirmar cambios en una rama puntual.
Jessica, Josie y John informan a los integradores de que las ramas 'featureA' y 'featureBee' del
servidor estn preparadas para su integracin con la lnea principal del programa. Despues de
que dichas ramas sean integradas en la lnea principal, una recuperacin (fetch) traer de vuelta
las confirmaciones de cambios de las integraciones (merge commits), dejando un historial como
el mostrado en la Figura 5-14.
Figura 5-14. El historial de Jessica tras fusionar sus dos ramas puntuales.
Muchos grupos se estn pasando a trabajar con Git, debido a su habilidad para mantener
multiples equipos trabajando en paralelo, fusionando posteriormente las diferentes lneas de
trabajo. La habilidad para que pequeos subgrupos de un equipo colaboren a travs de ramas
remotas, sin necesidad de tener en cuenta o de perturbar el equipo completo, es un gran beneficio
de trabajar con Git. La secuencia del flujo de trabajo que hemos visto es algo as como lo
mostrado en la Figura 5-15.
Figura 5-15. Secuencia bsica de este flujo de trabajo en equipo gestionado.
Puedes querer utilizar 'rebase -i' para reducir tu trabajo a una sola confirmacin de cambios
(commit), o reorganizar el trabajo en las diversas confirmaciones para facilitar la revisin de
parche por parte del gestor del proyecto --ver el Captulo 6 para ms detalles sobre
reorganizaciones interactivas--.
Cuando el trabajo en tu rama puntual est terminado y ests listo para enviarlo al gestor del
proyecto, vete a la pgina del proyecto original y clica sobre el botn "Fork" (bifurcar), para crear
as tu propia copia editable del proyecto. Tendrs que aadir la URL a este nuevo repositorio
como un segundo remoto, y en este caso lo denominaremos 'myfork':
Tu trabajo lo enviars (push) a este segundo remoto. Es ms sencillo enviar (push) directamente
la rama puntual sobre la que ests trabajando, en lugar de fusionarla (merge) previamente con tu
rama principal y enviar esta ltima. Y la razn para ello es que, si el trabajo no es aceptado o se
integra solo parcialmente, no tendrs que rebobinar tu rama principal. Si el gestor del proyecto
fusiona (merge), reorganiza (rebase) o integra solo parcialmente tu trabajo, an podrs
recuperarlo de vuelta a travs de su repositorio:
Tras enviar (push) tu trabajo a tu copia bifurcada (fork), has de notificarselo al gestor del
proyecto. Normalmente se suele hacer a travs de una solicitud de recuperacin/integracin (pull
request). La puedes generar directamente desde el sitio web, --GitHub tiene un botn "pull
request" que notifica automticamente al gestor--, o puedes lanzar el comando 'git request-pull' y
enviar manualmente su salida por correo electrnico al gestor del proyecto.
Este comando 'request-pull' compara la rama base donde deseas que se integre tu rama puntual y
el repositorio desde cuya URL deseas que se haga, para darte un resumen de todos los cambios
que deseas integrar. Por ejemplo, si Jessica quiere enviar a John una solicitud de recuperacin, y
ha realizado dos confirmaciones de cambios sobre la rama puntual que desea enviar, lanzar los
siguientes comandos:
git://githost/simplegit.git featureA
lib/simplegit.rb | 10 +++++++++-
1 files changed, 9 insertions(+), 1 deletions(-)
Esta salida puede ser enviada al gestor del proyecto, --le indica el punto donde se ramific,
resume las confirmaciones de cambio, y le dice desde dnde recuperar estos cambios--.
En un proyecto del que no seas gestor, suele ser ms sencillo tener una rama tal como 'master'
siguiendo siempre a la rama 'origin/master'; mientras realizas todo tu trabajo en otras ramas
puntuales, que podrs descartar facilmente en caso de que alguna de ellas sea rechazada.
Manteniendo el trabajo de distintos temas aislados en sus respectivas ramas puntuales, te
facilitas tambin el poder reorganizar tu trabajo si la cabeza del repositorio principal se mueve
mientras tanto y tus confirmaciones de cambio (commits) ya no se pueden integrar limpiamente.
Por ejemplo, si deseas contribuir al proyecto en un segundo tema, no continues trabajando sobre
la rama puntual que acabas de enviar; comienza una nueva rama puntual desde la rama 'master'
del repositorio principal:
$ git checkout -b featureB origin/master
$ (work)
$ git commit
$ git push myfork featureB
$ (email maintainer)
$ git fetch origin
De esta forma, cada uno de los temas est aislado dentro de un silo, --similar a una cola de
parches--; permitiendote reescribir, reorganizar y modificar cada uno de ellos sin interferir ni
crear interdependencias entre ellos.
Supongamos que el gestor del proyecto ha recuperado e integrado un grupo de otros parches y
despus lo intenta con tu primer parche, viendo que no se integra limpiamente. En este caso,
puedes intentar reorganizar (rebase) tu parche sobre 'origin/master', arreglar los conflictos y
volver a enviar tus cambios:
Debido a que has reorganizado (rebase) tu rama de trabajo, tienes que indicar la opcin '-f' en tu
comando de envio (push), para permitir que la rama 'featureA' del servidor sea reemplazada por
una confirmacin de cambios (commit) que no es hija suya. Una alternativa podra ser el enviar
(push) este nuevo trabajo a una rama diferente del servidor (por ejemplo a 'featureAv2').
Vamos a ver otro posible escenario: el gestor del proyecto ha revisado tu trabajo en la segunda
rama y le ha gustado el concepto, pero desea que cambies algunos detalles de la implementacin.
Puedes aprovechar tambin esta oportunidad para mover el trabajo y actualizarlo sobre la actual
rama 'master' del proyecto. Para ello, inicias una nueva rama basada en la actual 'origin/master',
aplicas (squash) sobre ella los cambios de 'featureB', resuelves los posibles conflictos que se
pudieran presentar, realizas los cambios en los detalles, y la envias de vuelta como una nueva
rama:
La opcin '--squash' coge todo el trabajo en la rama fusionada y lo aplica, en una sola
confirmacin de cambios sin fusin (no-merge commit), sobre la rama en la que ests situado. La
opcin '--no-commit' indica a Git que no debe registrar automticamente una confirmacin de
cambios. Esto te permitir el aplicar todos los cambios de la otra rama y despus hacer ms
cambios, antes de guardarlos todos ellos en una nueva confirmacin (commit).
En estos momentos, puedes notificar al gestor del proyecto que has realizado todos los cambios
solicitados y que los puede encontrar en tu rama 'featureBv2' (ver Figura 5-18).
Muchos grandes proyectos suelen tener establecidos los procedimientos para aceptar parches;
--es necesario que compruebes las normas especficas para cada proyecto, ya que pueden variar
de uno a otro--. De todas formas, muchos de los proyectos pblicos ms grandes aceptar parches
a travs de una lista de correo electrnico, por lo que veremos un ejemplo de dicho
procedimiento.
El flujo de trabajo es similar a los casos de uso vistos anteriormente; --creando ramas puntuales
para cada serie de parches en los que vayas a trabajar--. La diferencia est en la forma de
enviarlos al proyecto. En lugar de bifurcar (fork) el proyecto y enviar a tu propia copia editable,
generars correos electrnicos para cada serie de parches y os enviars a la lista de correo.
El comando format-patch lista los nombres de los archivos de parche que crea. La opcin -M
indica a Git que ha de mirar por si hay algo renombrado. Los archivos sern algo como:
$ cat 0001-add-limit-to-log-function.patch
From 330090432754092d704da8e76ca5c05c198e71a8 Mon Sep 17 00:00:00 2001
From: Jessica Smith <jessica@example.com>
Date: Sun, 6 Apr 2008 10:17:23 -0700
Subject: [PATCH 1/2] add limit to log function
Limit log functionality to the first 20Limit log functionality to the first 20Limit log
functionality to the first 20
---
lib/simplegit.rb | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
Puedes incluso editar esos archivos de parche, para aadirles ms informacin , especfica para la
lista de correo, y que no desees mostrar en el propio mensaje de la confirmacin de cambios. Si
aades texto entre la lnea que comienza por --- y el comienzo del parche (la lnea
lib/simplegit.rb). Los desarrolladores de la lista de correo podrn leerlo. Pero ser ignorado al
aplicar el parche al proyecto.
Para enviar estos archivos a la lista de correo,puedes tanto pegar directamente el archivo en tu
programa de correo electrnico, como enviarlo a travs de algn programa basado en lnea de
comandos. Pegar el texto directamente suele causar problemas de formato. Especialmente con
los clientes de correo ms "inteligentes", que no preservan adecuadamente los saltos de lnea y
otros espaciados. Afortunadamente, Git suministra una herramienta que nos puede ser de gran
ayuda para enviar parches correctamente formateados a travs de protocolo IMAP,
facilitandonos as las cosas. Voy a indicar cmo enviar un parche usando Gmail, que da la
casualidad de que es el agente de correo utilizado por m. En el final del anteriormente citado
documento, Documentation/SubmittingPatches, puedes leer instrucciones detalladas para
otros agentes de correo.
[imap]
folder = "[Gmail]/Drafts"
host = imaps://imap.gmail.com
user = user@gmail.com
pass = p4ssw0rd
port = 993
sslverify = false
Las dos ltimas lneas probablente no sean necesarias si tu servidor IMAP no utiliza SSL; y, en
ese caso, el valor para host deber de ser imap:// en lugar de imaps://. Cuando tengas esto
ajustado, podrs utilizar el comando git send-email para poner series de parches en la carpeta
de borradores (Drafts) de tu servidor IMAP:
Tras esto, Git escupir una serie de informacin de registro, con pinta ms o menos como esta:
Result: OK
Recapitulacin
En esta seccin hemos visto unos cuantos de los flujos de trabajo ms habituales para lidiar con
distintos tipos de proyectos Git. Y hemos introducido un par de nuevas herramientas para
ayudarte a gestionar estos procesos. A continuacin veremos cmo trabajar en el otro lado de la
moneda: manteniendo y gestionando un proyecto Git. Vas a aprender cmo ser un dictador
benevolente o un gestor de integracin.
5.3 Git en entornos distribuidos -
Gestionando un proyecto
Gestionando un proyecto
Adems de conocer cmo contribuir de forma efectiva a un proyecto, es posible que desees saber
tambien cmo mantener uno. Lo cual implicar saber aceptar y aplicar parches generados va
format-patch, enviados a t a travs de correo electronico; o saber integrar cambios realizados
en ramas de repositorios que aadirs como remotos a tu proyecto. Tanto si gestionas un
repositorio cannico, como si deseas colaborar verificando o aprobando parches, necesitas saber
cmo aceptar trabajo de otros de la forma ms clara para tus contribuyentes y ms sostenible
para t a largo plazo.
Cuando ests pensando en integrar nuevo trabajo, suele ser buena idea utilizar una rama puntual
para cada tema concreto --una rama temporal creada especficamente para trabajar dicho tema--
De esta forma, es sencillo tratar cada parche de forma individualizada y poder "aparcar" uno
concreto cuando no trabajamos en l, hasta cuando volvamos a tener tiempo para retomarlo. Si
creas los nombres de ramas basandolos en el tema sobre el que vas a trabajar, por ejemplo 'ruby
client' o algo as de descriptivo, podrs recordar de qu iba cada rama en caso de que la
abandones por un tiempo y la retomes ms tarde. La persona gestora del proyecto Git suele
tender a nombrar cada rama de foma parecida --por ejemplo 'sc/ruby client', donde sc es la
abreviatura para la persona que ha contribuido con ese trabajo--. Como recordars, la forma de
crear una rama basandola en tu rama master es:
O, si deseas crearla y saltar inmediatamente a ella, puedes tambin utilizar la opcin '-b' del
comando 'checkout':
Tras esto, estars listo para aadir tu trabajo a esa rama puntual y ver si deseas o no fusionarla
luego con alguna otra de tus ramas de ms largo recorrido.
Aplicar parches recibidos por correo electronico
Tambien puedes utilizar 'git apply' para comprobar si un parche se puede incorporar
limpiamente; antes de intentar incorporarlo. Puedes lanzar el comando 'git apply --check':
Si obtienes una salida vacia, el parche se podr incorporar limpiamente. Adems, este comando
retorna con un status no-cero en caso de fallar la comprobacin, por lo que puedes utilizarlo en
scripts si lo deseas.
Para incorporar un parche generado con 'format-patch', utilizars el comando 'git am'.
Tcnicamente, 'git am' se construy para leer un archivo de buzn de correo (mbox file), que no
es ms que un simple formato de texto plano para almacenar uno o varios mensajes de correo
electrnico en un solo archivo de texto. Es algo parecido a esto:
Limit log functionality to the first 20Limit log functionality to the first 20Limit log
functionality to the first 20
$ git am 0001-limit-log-function.patch
Applying: add limit to log function
Observars que, tras incorporarlo limpiamente, crea automticamente una nueva confirmacin
de cambios (commit). La informacin sobre el autor o autora la recoge de las cabeceras 'From'
(Remitente) y 'Date' (Fecha). Y el mensaje para la confirmacin (commit) lo recoge de 'Subject'
(Asunto) y del cuerpo del correo electrnico. Por ejemplo, si consideramos el parche incorporado
desde el mbox del ejemplo que acabamos de mostrar; la confirmacin de camios (commit)
generada ser algo como:
$ git log --pretty=fuller -1
commit 6c5e70b984a60b3cecd395edd5b48a7575bf58e0
Author: Jessica Smith <jessica@example.com>
AuthorDate: Sun Apr 6 10:17:23 2008 -0700
Commit: Scott Chacon <schacon@gmail.com>
CommitDate: Thu Apr 9 09:19:06 2009 -0700
Limit log functionality to the first 20Limit log functionality to the first 20Limit log
functionality to the first 20
Pero tambin podra suceder que el parche no se pudiera incorporar limpiamente. Es posible que
tu rama principal diverja demasiado respecto de la rama sobre la que se construy el parche; o
que el parche tenga dependencias respecto de algn otro parche anterior que an no hayas
incorporado. En ese caso, el proceso 'git am' fallar y te preguntar qu deseas hacer:
$ git am 0001-seeing-if-this-helps-the-gem.patch
Applying: seeing if this helps the gem
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
Patch failed at 0001.
When you have resolved this problem run "git am --resolved".
If you would prefer to skip this patch, instead run "git am --skip".
To restore the original branch and stop patching run "git am --abort".
Este comando pondr marcadores de conflicto en cualquier archivo con problemas, de forma
similar a como lo hara una operacin de fusin (merge) o de reorganizacin (rebase). Y
resolvers los problemas de la misma manera: editar el archivo para resolver los conflictos,
prepararlo (stage), y lanzar 'git am --resolved' para continuar con el siguiente parche:
Si deseas ms inteligencia por parte de Git al resolver conflictos, puedes pasarle la opcin '-3',
para que intente una fusin a tres bandas (three-way merge). Esta opcin no se usa por defecto,
porque no funcionar en caso de que la confirmacin de cambios en que el parche dice estar
basado no est presente en tu repositorio. Sin embargo, si tienes dicha confirmacin de cambios
(commit), --si el parche est basado en una confirmacin pblica--, entonces la opcin '-3' suele
ser mucho ms avispada cuando incorporas un parche conflictivo:
$ git am -3 0001-seeing-if-this-helps-the-gem.patch
Applying: seeing if this helps the gem
error: patch failed: ticgit.gemspec:1
error: ticgit.gemspec: patch does not apply
Using index info to reconstruct a base tree...
Falling back to patching base and 3-way merge...
No changes -- Patch already applied.
En este caso, estamos intentando incorporar un parche que ya tenemos incorporado. Sin la
opcin '-3', tendramos problemas.
Al aplicar varios parches desde un mbox, puedes lanzar el comando 'am' en modo interactivo;
haciendo que se detenga en cada parche y preguntandote si aplicarlo o no:
$ git am -3 -i mbox
Commit Body is:
--------------------------
seeing if this helps the gem
--------------------------
Apply? [y]es/[n]o/[e]dit/[v]iew patch/[a]ccept all
Es una utilidad interesante si tienes tienes almacenados unos cuantos parches, porque puedes ir
revisando previamente cada parche y aplicarlos selectivamente.
Cuando tengas integrados y confirmados todos los parches relativos al tema puntual en que estas
trabajando, puedes plantearte cmo y cundo lo vas a integar en alguna otra rama de ms largo
recorrido.
Por ejemplo, si Jessica te envia un correo electrnico comentandote que tiene una nueva e
interesante funcionalidad en la rama 'ruby-client' de su repositorio. Puedes probarla aadiendo el
remoto correspondiente y recuperando localmente dicha rama.
Si ms tarde vuelva a enviarte otro correo electronico avisandote de otra gran funcionalidad que
ha incorporado, puedes recuperarla (fetch y checkout) directamente, porque tienes el remoto ya
definido.
Es muy util cuando trabajas regularmente con una persona. En cambio, si alguien tiene un solo
parche para enviarte, una sola vez, puede ser ms efectivo aceptarlo directamente por correo
electronico; en lugar de pedir a todo el mundo que tenga cada uno su propio servidor y tener
nosotros que estar continuamente aadiendo y quitando remotos para cada parche. Tambin es
muy posible que no quieras tener cientos de remotos, cada uno contribuyendo tan solo con un
parche o dos. De todas formas, los scripts y los servicios albergados pueden hacerte la vida ms
facil en esto, --todo depende de cmo desarrolles t y de como desarrollan las personas que
colaboran contigo--.
Otra ventaja de esta forma de trabajar es que recibes tambin el histrico de confirmaciones de
cambio (commits). A pesar de poder seguir teniendo los habituales problemas con la fusin, por
lo menos conoces en qu punto de tu historial han basado su trabajo. Por defecto, se aplicar una
genuina fusin a tres bandas, en lugar de tener que poner un '-3' y esperar que el parche haya
sido generado a partir de una confirmacin de cambios (commit) pblica a la que tengas t
tambin acceso.
Si no trabajas habitualmente con una persona, pero deseas recuperar de ella por esta va, puedes
indicar directamente el URL del repositorio remoto en el comando 'git pull'. Esto efectua una
recuperacin (pull) puntual y no conserva la URL como una referencia remota:
Revisando lo introducido
Ahora que tienes una rama puntual con trabajo aportado por otras personas. Tienes que decidir
lo que deseas hacer con l. En esta seccin revisaremos un par de comandos, que te ayudarn a
ver exactamente los cambios que introducirs si fusionas dicha rama puntual con tu rama
principal.
Suele ser util revisar todas las confirmaciones de cambios (commits) que esten es esta rama, pero
no en tu rama principal. Puedes excluir de la lista las confirmaciones de tu rama principal
aadiendo la opcin '--not' delante del nombre de la rama. Por ejemplo, si la persona
colaboradora te envia dos parches y tu creas una rama 'contrib' donde aplicar dichos parches;
puedes lanzar algo como esto:
commit 7482e0d16d04bea79d0dba8988cc78df655f16a0
Author: Scott Chacon <schacon@gmail.com>
Date: Mon Oct 22 19:38:36 2008 -0700
Para ver en detalle los cambios introducidos por cada confirmacin (commit), recuerda que
pasando la opcin '-p' al comando 'git log', obtendrs un listado extendido con las diferencias
introducidas por cada confirmacin.
Para ver plenamente todas las diferencias y lo que suceder realmente si fusionas esta rama
puntual con otra rama, tendrs que utilizar un pequeo truco para obtener los resultados
correctos. Puedes pensar en lanzar esto:
Y lo que realmente queras ver eran los cambios introducidos por la rama puntual, --el trabajo
que vas a introducir si la fusionas con la rama 'master'--. Lo puedes hacer indicando a Git que
compare la ltima confirmacin de cambios en la rama puntual, con el ms reciente ancestro
comn que tenga esta respecto de la rama 'master'.
Pero esto no es lo ms conveniente. De ah que Git suministre otro atajo para hacerlo: la sintaxis
del triple-punto. Estando en el contexto del comando 'diff', puedes indicar tres puntos entre los
nombres de las dos ramas; para comparar entre la ltima confirmacin de cambios de la rama
donde ests y la respectiva confirmacin comn con la otra rama:
Este comando mostrar nicamente el trabajo en tu actual rama puntual que haya sido
introducido a partir de su ancestro comn con la rama 'master'. Es una sintaxis muy util, que
merece recordar.
Cuando todo el trabajo presente en tu rama puntual est listo para ser integrado en una rama de
mayor rango, la cuestin es cmo hacerlo. Yendo an ms lejos, ?cual es el sistema de trabajo que
deseas utilizar para el mantenimiento de tu proyecto? Tienes bastantes opciones, y vamos a ver
algunas de ellas.
Este es probablemente el flujo de trabajo ms sencillo. Pero puede dar problemas cuando ests
tratando con grandes repositorios o grandes proyectos.
Teniendo muchos desarrolladores o proyectos muy grandes, muy posiblemente desees utilizar un
ciclo con por lo menos dos fases. En este escenario, se dispone de dos ramas de largo recorrido:
'master' y 'develop'. La primera de ellas, 'master', ser actualizada nicamente por los
lanzamientos de cdigo muy estable. La segunda rama, 'develop', es donde iremos integrando
todo el cdigo nuevo. Ambas ramas se enviarn periodicamente al repositorio pblico. Cada vez
que tengas una nueva rama puntual lista para integrar (Figura 5-21), la fusionars en la rama
'develop'. Y cuando marques el lanzamiento de una versin estable, avanzars la rama 'master'
hasta el punto donde la rama 'develop' se encuentre en ese momento (Figura 5-23).
Figura 5-21. Antes de fusionar una rama puntual.
De esta forma, cuando alguien clone el repositorio de tu proyecto, podr recuperar (checkout) y
mantener actualizadas tanto la ltima version estable como la versin con el material ms
avanzado; en las ramas 'master' y 'develop', respectivamente. Puedes continuar ampliando este
concepto, disponiendo de una rama 'integrate' donde ir fusionando todo el trabajo entre s. A
continuacin, cuando el cdigo en dicha rama sea estable y pase todas las pruebas, la fusionars
con la rama 'develop'; y, cuando se demuestre que permanece estable durante un cierto tiempo,
avanzars la rama 'master' hasta ah.
Una rama puntual se borra del repositorio cuando, finalmente, es fusionada en la rama 'master'.
El proyecto Git dispone tambin de una rama 'maint' que se bifurca (fork) a partir de la ltima
versin ya lanzada; para trabajar en parches, en caso de necesitarse alguna versin intermedia de
mantenimiento. As, cuando clonas el repositorio de Git, obtienes cuatro ramas que puedes
recuperar (checkout); pudiendo evaluar el proyecto en distintos estadios de desarrollo,
dependiendo de cun avanzado desees estar o cmo desees contribuir. Y as, los gestores de
mantenimiento disponen de un flujo de trabajo estructurado, para ayudarles en el procesado e
incorporacin de nuevas contribuciones.
El otro camino para introducir trabajo de una rama en otra, es entresacarlo. Entresacar
(cherry-pick) en Git es como reorganizar (rebase) una sola confirmacin de cambios (commit). Se
trata de coger el parche introducido por una determinada confirmacin de cambios e intentar
reaplicarlo sobre la rama donde te encuentres en ese momento. Puede ser util si tienes varias
confirmaciones de cambios en una rama puntual, y tan solo deseas integar una de ellas; o si
tienes una nica confirmacin de cambios en una rama puntual, y prefieres entresacarla en lugar
de reorganizar. Por ejemplo, suponiendo que tienes un proyecto parecido al ilustrado en la Figura
5-26.
Figura 5-26. Historial de ejemplo, antes de entresacar.
Esto introduce exactamente el mismo cambio introducido por 'e43a6', pero con un nuevo valor
SHA-1 de confirmacin; ya que es diferente la fecha en que ha sido aplicado. Tu historial quedar
tal como ilustra la Figura 5-27.
Figura 5-27. Historial tras entresacar una confirmacin de cambios de una rama puntual.
Ahora, ya puedes borrar la rama puntual y descartar las confirmaciones de cambios que no
deseas integrar.
Cuando decides dar por preparada una versin, probablemente querrs etiquetar dicho punto de
algn modo; de tal forma que, ms adelante, puedas volver a generar esa versin en cualquier
momento. Puedes crear una nueva etiqueta tal y como se ha comentado en el captulo 2. Si
decides firmar la etiqueta como gestor de mantenimientos que eres, el proceso ser algo como:
Si firmas tus etiquetas, puedes tener un problema a la hora de distribuir la clave PGP pblica
utilizada en la firma. Los gestores del proyeto Git ha resuelto este problema incluyendo sus claves
pblicas como un objeto en el repositorio, aadiendo luego una etiqueta apuntando directamente
a dicho contenido. Para ello, has de seleccionar cada clave que deseas incluir, lanzando el
comando 'gpg ---list-keys':
$ gpg --list-keys
/Users/schacon/.gnupg/pubring.gpg
---------------------------------
pub 1024D/F721C45A 2009-02-09 [expires: 2010-02-09]
uid Scott Chacon <schacon@gmail.com>
sub 2048g/45D02282 2009-02-09 [expires: 2010-02-09]
Tras esto, puedes importar directamente la clave en la base de datos Git, exportandola y
redirigiendola a travs del comando 'git hash-object'. Para, de esta forma, escribir un nuevo
objeto dentro de Git y obtener de vuelta la firma SHA-1 de dicho objeto.
Una vez tengas el contenido de tu clave guardado en Git, puedes crear una etiquta que apunte
directamente al mismo; indicando para ello el nuevo valor SHA-1 que te ha devuelto el objeto
'hash-object':
Si lanzas el comando 'git push --tags', la etiqueta 'maintainer-pgp-pub' ser compartida por
todos. Cualquiera que desee verificar la autenticidad de una etiqueta, no tiene ms que importar
tu clave PGP, sacando el objecto directamente de la base de datos e importandolo en GPG:,
De esta forma, pueden utilizar esa clave para verificar todas las etiquetas que firmes. Adems, si
incluyes instrucciones en el mensaje de etiquetado, con el comando git show <tag>, los
usuarios podrn tener directrices especficas acerca de la verificacin de etiquetas.
Debido a que Git no dispone de una serie montona ascendente de nmeros para cada
confirmacin de cambios (commit), si deseas tener un nombre humanamente comprensible por
cada confirmacin, has de utilizar el comando 'git describe'. Git te dar el nombre de la etiqueta
ms cercana, mas el nmero de confirmaciones de cambios entre dicha etiqueta y la confirmacin
que estas describiendo, ms una parte de la firma SHA-1 de la confirmacin:
$ git describe master
v1.6.2-rc1-20-g8c5b85c
De esta forma, puedes exportar una instantnea u obtener un nombre comprensible por
cualquier persona. Es ms, si compilas Git desde cdigo fuente clonado desde el repositorio Git,
el comando 'git --version' te dar algo parecido. Si solicitas descripcin de una confirmacin de
cambios (commit) etiquetada directamente con su propia etiqueta particular, obtendrs dicha
etiqueta como descripcin.
El comando 'git describe' da preferencia a las etiquetas anotativas (etiquetas creadas con las
opciones '-a' o '-s'). De esta forma las etiquetas para las versiones pueden ser creadas usando 'git
describe', asegurandose el que las confirmaciones de cambios (commit) son adecuadamente
nombradas cuando se describen. Tambien puedes utilizar esta descripcin para indicar lo que
deseas activar (checkout) o mostrar (show); pero realmente estars usando solamente la parte
final de la firma SHA-1 abreviada, por lo que no siempre ser vlida. Por ejemplo, el kernel de
Linux ha saltado recientemente de 8 a 10 caracteres, para asegurar la unicidad de los objetos
SHA-1; dando como resultado que los nombres antiguos de 'git describe' han dejado de ser
vlidos.
Si quieres lanzar una nueva versin. Una de las cosas que desearas crear es un archivo con la ms
reciente imagen de tu cdigo, para aquellas pobres almas que no utilizan Git. El comando para
hacerlo es 'git archive':
Quien abra ese archivo tarball, obtendr la ms reciente imagen de tu proyecto; puesta bajo una
carpeta de proyecto. Tambin puedes crear un archivo zip de la misma manera, tan solo
indicando la opcin '--format=zip' al comando 'git archive':
As, tendrs sendos archivos tarball y zip con tu nueva versin, listos para subirlos a tu sitio web o
para ser enviados por correo electrnico a tus usuarios.
El registro rpido
Ya va siendo hora de enviar un mensaje a tu lista de correo, informando a las personas que
desean conocer la marcha de tu proyecto. Una manera elegante de generar rpidamente una lista
con los principales cambios aadidos a tu proyecto desde la anterior versin, es utilizando el
comando 'git shortlog'. Este comando resume todas las confirmaciones de cambios (commits) en
el rango que le indiques. Por ejemplo, si tu ltimo lanzamiento de versin lo fu de la v1.0.1:
Obtendrs un claro resumen de todas las confirmaciones de cambios (commit) desde la versin
v1.0.1, agrupadas por autor, y listas para ser incorporadas en un mensaje a tu lista de correo.
Chapter 6
Las herramientas de Git
A estas alturas, hemos aprendido la mayoria de los comandos y flujos de trabajo empleados
habitualmente a la hora de utilizar, gestionar y mantener un repositorio Git para el control de
versiones de cdigo fuente. Se han visto las tareas bsicas de seguimiento y confirmacin de
cambios en archivos. Aprovechando las capacidades del rea de preparacin (staging area), de las
ramas (branches) y de los mecanismos de fusin (merging).
En este captulo se van a explorar unas cuantas tareas avanzadas de Git. Tareas que, aunque no se
utilizan en el trabajo del da a da, en algn momento pueden ser necesarias.
Confirmaciones puntuales
SHA corto
Simplemente dndole los primeros caracteres del cdigo SHA-1, Git es lo suficientemente
inteligente como para figurarse cual es la confirmacin de cambios (commit) deseada. Es
necesario teclear por lo menos 4 caracteres y estos han de ser no ambiguos --es decir, debe existir
un solo objeto en el repositorio cuyo cdigo comience por dicho trozo inicial del SHA--.
Por ejemplo, a la hora de localizar una confirmacin de cambios, supongamos que se lanza el
comando 'git log' e intentamos localizar la confirmacin de cambios concreta donde se aadi
una cierta funcionalidad:
$ git log
commit 734713bc047d87bf7eac9674765ae793478c50d3
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Jan 2 18:32:33 2009 -0800
commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Merge: 1c002dd... 35cfb2b...
Author: Scott Chacon <schacon@gmail.com>
Date: Thu Dec 11 15:08:43 2008 -0800
commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <schacon@gmail.com>
Date: Thu Dec 11 14:58:32 2008 -0800
En este caso, escogiendo '1c002dd....', para lanzar el comando 'git show' sobre esa confirmacin
de cambios concreta, seran equivalentes todos estos comandos (asumiendo la no ambiguedad de
todas las versiones cortas indicadas):
En todos estos casos, Git puede deducir el resto del valor SHA-1. Con la opcin '--abbrev-commit'
del comando 'git log', en su salida se mostrarn valores acortados, pero nicos de SHA.
Habitualmente suelen resultar valores de siete caracteres, pero alguno puede ser ms largo si es
necesario para preservar la unicidad de todos los valores SHA-1 mostrados:
$ git log --abbrev-commit --pretty=oneline
ca82a6d changed the version number
085bb3b removed unnecessary test code
a11bef0 first commit
Normalmente, entre ocho y diez caracteres suelen ser ms que suficientes para garantizar la
unicidad de todos los objetos dentro de cualquier proyecto. Aunque, en uno de los ms grandes
proyectos gestionados con Git, el kernel de Linux, estn siendo necesarios unos 12 caracteres (de
los 40 posibles) para garantizar la unicidad.
Mucha gente se suele preocupar por si, por casualidad, dos objetos en su repositorio reciben el
mismo cdigo SHA-1 para identificarlos. Y qu sucederia si se diera ese caso?
No obstante, hemos de ser conscientes de lo altamente improbable de un suceso as. Los cdigos
SHA-1 son de 20 bytes, (160 bits). El nmero de objetos, codificados aleatriamente, necesarios
para asegurar un 50% de probabilidad de darse una sola colisin es cercano a 2^80 (la frmula
para determinar la probabilidad de colisin es p = (n(n-1)/2) * (1/2^160))). 2^80 es 1'2 x
10^24, o lo que es lo mismo, 1 billn de billones. Es decir, unas 1.200 veces el nmero de granos
de arena en la Tierra.
El siguiente ejemplo puede ser bastante ilustrativo, para hacernos una idea de lo que podra
tardarse en darse una colisin en el cdigo SHA-1: Si todos los 6'5 billones de humanos en el
planeta Tierra estuvieran programando y, cada segundo, cada uno de ellos escribiera cdigo
equivalente a todo el histrico del kernel de Linux (cerca de 1 milln de objetos Git), enviandolo
todo a un enorme repositorio Git. Seran necesarios unos 5 aos antes de que dicho repositorio
contuviera suficientes objetos como para tener una probabilidad del 50% de darse una sola
colisin en el cdigo SHA-1. Es mucho ms probable que todos los miembros de nuestro equipo
de programacin fuesen atacados y matados por lobos, en incidentes no relacionados entre s,
acaecidos todos ellos en una misma noche.
Referencias a ramas
La manera ms directa de referirse a una confirmacin de cambios es teniendo una rama
apuntando a ella. De esta forma, se puede emplear el nombre de la rama en cualquier comando
Git que espere un objeto de confirmacin de cambios o un cdigo SHA-1. Por ejemplo, si se desea
mostrar la ltima confirmacin de cambios en una rama, y suponiendo que la rama 'topic1'
apunta a 'ca82a6d', los tres comandos siguientes son equivalentes:
Para ver a qu cdigo SHA apunta una determinada rama, o si se desea conocer cmo se
comportarian cualquiera de los ejemplos anteriores en trminos de SHAs, se puede emplear el
comando de fontaneria 'rev-parse'. En el captulo 9 se ver ms informacin sobre las
herramientas de fontaneria. Herramientas estas que son utilizadas para operaciones a muy bajo
nivel, y que no estan pensadas para ser utilizadas en el trabajo habitual del da a da. Pero que, sin
embargo, pueden ser muy tiles cuando se desea ver lo que realmente sucede "tras las
bambalinas", en el interior de Git. Por ejemplo, lanzando el comando 'rev-parse' sobre una rama,
esta muestra el cdigo SHA-1 de la ltima confirmacin de cambios en ella:
Una de las tareas realizadas por Git continuamente en segundo plano, mientras nosotros
trabajamos, es el mantenimiento de un registro de referencia (reflog). En este registro queda
traza de dnde han estado las referencias a HEAD y a las distintas ramas durante los ltimos
meses.
$ git reflog
734713b... HEAD@{0}: commit: fixed refs handling, added gc auto, updated
d921970... HEAD@{1}: merge phedders/rdocs: Merge made by recursive.
1c002dd... HEAD@{2}: commit: added some blame and merge stuff
1c36188... HEAD@{3}: rebase -i (squash): updating HEAD
95df984... HEAD@{4}: commit: # This is a combination of two commits.
1c36188... HEAD@{5}: rebase -i (squash): updating HEAD
7e05da5... HEAD@{6}: rebase -i (pick): updating HEAD
Cada vez que se actualiza una rama por cualquier razn, Git almacena esa informacin en este
histrico temporal. Y esta informacin se puede utilizar para referirse a confirmaciones de
cambio pasadas. Por ejemplo, si se desea ver el quinto anterior valor de HEAD en el repositorio,
se puede emplear la referencia '@{n}' mostrada por la salida de reflog:
Esta misma sintaxis puede emplearse cuando se desea ver dnde estaba una rama en un
momento especfico en el tiempo. Por ejemplo, para ver dnde apuntaba la rama 'master' en el
da de ayer, se puede teclear:
Este comando mostrar a dnde apuntaba ayer la rama. Esta tcnica tan solo funciona para
informacin presente en el registro de referencia. No se puede emplear para confirmaciones de
cambio de antiguedad superior a unos pocos meses.
Si se desea ver la informacin del registro de referencia, formateada de forma similar a la salida
del comando 'git log', se puede lanzar el comando 'git log -g':
commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Reflog: master@{1} (Scott Chacon <schacon@gmail.com>)
Reflog message: merge phedders/rdocs: Merge made by recursive.
Author: Scott Chacon <schacon@gmail.com>
Date: Thu Dec 11 15:08:43 2008 -0800
Referencias a ancestros
Otra forma de especificar una confirmacin de cambios es utilizando sus ancestros. Colocando un
'^' al final de una referencia, Git interpreta que se refiere al padre de dicha referencia.
Suponiendo que sea esta la historia de un proyecto:
Se puede visualizar la anteltima confirmacin de cambios indicando 'HEAD^', que significa "el
padre de HEAD":
Tambin es posible indicar un nmero detras de '^'. Por ejemplo d921970^2, para indicar "el
segundo padre de d921970" . Aunque esta sentencia es til tan solo en confirmaciones de
fusiones (merge), los nicos tipos de confirmacin de cambios que pueden tener ms de un
padre. El primer padre es el proveniente de la rama activa al realizar la fusin, y el segundo es la
confirmacin de cambios en la rama desde la que se fusiona.
$ git show d921970^
commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <schacon@gmail.com>
Date: Thu Dec 11 14:58:32 2008 -0800
Otra forma de referirse a los ancestros es la marca ~. Utilizada tal cual, tambin se refiere al
padre. Por lo tanto, HEAD~ y HEAD^ son equivalentes. Pero la diferencia comienza al indicar
un nmero tras ella. HEAD~2 significa "el primer padre del primer padre", es decir, "el abuelo".
Y as segn el nmero de veces que se indique. Por ejemplo, en la historia de proyecto citada
anteriormente, HEAD~3 sera:
ignore *.gem
Igualmente, se podra haber escrito HEAD^^^, que tambin se refiere al "primer padre del
primer padre del primer padre":
ignore *.gem
E incluso tambin es posible combinar las dos sintaxis. Por ejemplo, para referirse al "segundo
padre de la referencia previa" (asumiendo que es una confirmacin de cambios de fusin
-merge-), se pude escribir algo como HEAD~3^2.
Una vez vistas las formas de referirse a confirmaciones concretas de cambios. Vamos a ver cmo
referirse a un grupo de confirmaciones. Esto es especialmente til en la gestin de ramas. Si se
tienen multitud de ramas, se pueden emplear las espeficicaciones de rango para responder a
cuestiones tales como "cual es el trabajo de esta rama que an no se ha fusionado con la rama
principal?".
Doble punto
La especificacin de rango ms comn es la sintaxis doble-punto. Bsicamente, se trata de pedir a
Git que resuelva un rango de confirmaciones de cambio alcanzables desde una confirmacin
determinada, pero no desde otra. Por ejemplo, teniendo un historial de confirmaciones de
cambio tal como el de la figura 6-1.
Si se desea ver qu partes de la rama experiment estn sin fusionar an con la rama master. Se
puede pedir a Git que muestre un registro con las confirmaciones de cambio en
master..experiment. Es decir, "todas las confirmaciones de cambio alcanzables desde
experiment que no se pueden alcanzar desde master". Por razones de brevedad y claridad en los
ejemplos, para representar los objetos confirmacin de cambios (commit) se utilizarn las letras
mostradas en el diagrama en lugar de todo el registro propiamente dicho:
Esto es til si se desea mantener actualizada la rama 'experiment' y previsualizar lo que se est a
punto de fusionar en ella. Otra utilidad habitual de estas sentencias es la de ver lo que se est a
punto de enviar a un repositorio remoto:
Este comando muestra las confirmaciones de cambio de la rama activa que no estn an en la
rama 'master' del repositorio remoto 'origin'. Si se lanza el comando 'git push' (y la rama activa
actual esta relacionada con 'origin/master'), las confirmaciones de cambio mostradas por git log
origin/master..HEAD sern las que sern transferidas al servidor. Es posible tambin omitir la
parte final de la sentencia y dejar que Git asuma HEAD. Por ejemplo, se pueden obtener los
mismos resultados tecleando git log origin/master.., ya que git sustituye HEAD en la parte
faltante.
Puntos multiples
La sintaxis del doble-punto es util como atajo. Pero en algunas ocasiones interesa indicar mas de
dos ramas para precisar la revisin. Como cuando se desea ver las confirmaciones de cambio
presentes en cualquiera de varias ramas y no en la rama activa. Git permite realizar esto
utilizando o bien el caracter ^o bien la opcin --not por delante de aquellas referencias de las
que se desea no ver las confirmaciones de cambio. As, estos tres comandos son equivalentes:
Esto nos permite indicar ms de dos referencias en una misma consulta. Algo imposible con la
sintaxis dos-puntos. Por ejemplo, si se deseean ver todas las confirmaciones de cambio
alcanzables desde la 'refA' o la 'refB', pero no desde la 'refC', se puede teclear algo como esto:
$ git log refA refB ^refC
$ git log refA refB --not refC
Esto da una enorme versatilidad al sistema de consultas y permite revisar el contenido de todas
las ramas en el repositorio.
Triple-punto
La ltima de las opciones principales para seleccionar rangos es la sintaxis triple-punto. Utilizada
para especificar todas las confirmaciones de cambio alcanzables separadamente desde cualquiera
de dos referencias, pero no desde ambas a la vez. Volviendo sobre la historia de proyecto
mostrada en la figura 6-1. Si se desea ver lo que est o bien en 'master' o bien en 'experiment',
pero no en ambas simultneamente, se puede emplear el comando:
De nuevo, esto da una salida normal de 'log', pero mostrando tan solo informacin sobre las
cuatro confirmaciones de cambio, dadas en la tradicional secuencia ordenada por fechas.
Una opcin habitual a utilizar en estos casos con el comando 'log' suele ser 'left-right'. Haciendo
as que en la salida se muestre cual es el lado al que pertenece cada una de las confirmaciones de
cambio. Esto hace ms util la informacin mostrada:
Con estas herramientas, es mucho ms sencillo indicar con precisin cual o cuales son las
confirmaciones de cambios que se desean revisar.
6.2 Las herramientas de Git -
Preparacin interactiva
Preparacin interactiva
Git trae incluidos unos cuantos scripts para facilitar algunas de las tareas en la lnea de
comandos. Se van a mostrar unos pocos comandos interactivos que suelen ser de gran utilidad a
la hora de recoger en una confirmacin de cambios solo ciertas combinaciones y partes de
archivos. Estas herramientas son tiles, por ejemplo, cuando se modifican unos cuantos archivos
y luego se decide almacenar esos cambios en una serie de confirmaciones de cambio focalizadas
en lugar de en una sola confirmacin de cambio entremezclada. As, se consiguen unas
confirmaciones de cambio con agrupaciones lgicas de modificaciones, facilitando su revisin por
parte otros desarrolladores que trabajen con nosotros. Al lanzar el comando 'git add' con las
opciones '-i' o '--interactive', Git entra en un modo interactivo y muestra algo as como:
$ git add -i
staged unstaged path
1: unchanged +0/-1 TODO
2: unchanged +1/-1 index.html
3: unchanged +5/-1 lib/simplegit.rb
Segn se ve, este comando muestra una vista bastante diferente del rea de preparacin (staging
area). Bsicamente se trata de la misma informacin dada por el comando 'git status', pero mas
sucinta e informativa. Se ve una lista de cambios ya preparados, en la izquierda; y de los que
estn an sin preparar, en la derecha.
Tras esa lista, viene la seccin de comandos. Aqu se pueden lanzar acciones tales como: aadir
archivos en el area de preparacin (staging), sacar archivos de ella (unstaging), poner solo parte
de algn archivo, aadir archivos nuevos que estaban fuera del sistema de control o mostrar
diferencias en aquello que se ha aadido.
Introduciendo archivos en el area de preparacin y sacandolos de ella
Tecleando '2' o 'u' (update) tras el indicador 'What now>', el script interactivo preguntar cuales
son los archivos que se quieren aadir al rea de preparacin:
What now> 2
staged unstaged path
1: unchanged +0/-1 TODO
2: unchanged +1/-1 index.html
3: unchanged +5/-1 lib/simplegit.rb
Update>>
Update>> 1,2
staged unstaged path
* 1: unchanged +0/-1 TODO
* 2: unchanged +1/-1 index.html
3: unchanged +5/-1 lib/simplegit.rb
Update>>
El asterisco * al lado de cada archivo indica que dicho archivo ha sido seleccionado para ser
preparado. Pulsando la tecla [Enter] tras el indicador 'Update>>', Git toma lo seleccionado y lo
aade al rea de preparacin:
Update>>
updated 2 paths
En estos momentos se ve que los archivos TODO e index.html estn en el rea de preparacin y
que el archivo simplegit.rb no est an. Si se desea sacar el archivo TODO del rea, se puede
utilizar la opcin '3' o 'r' (revert):
*** Commands ***
1: status 2: update 3: revert 4: add untracked
5: patch 6: diff 7: quit 8: help
What now> 3
staged unstaged path
1: +0/-1 nothing TODO
2: +1/-1 nothing index.html
3: unchanged +5/-1 lib/simplegit.rb
Revert>> 1
staged unstaged path
* 1: +0/-1 nothing TODO
2: +1/-1 nothing index.html
3: unchanged +5/-1 lib/simplegit.rb
Revert>> [enter]
reverted one path
Volviendo a mirar el estado de Git, se comprueba que se ha sacado el archivo TODO del rea de
preparacin:
Para ver las diferencis entre lo que est preparado, se puede utilizar la opcin '6' o 'd' (diff). Esta
muestra una lista de los archivos preparados en el rea de preparacin, permitiendo la seleccion
de aquellos sobre los que se desean ver diferencias. Es muy parecido a lanzar el comando 'git diff
--cached' directamente en la lnea de comandos:
<p id="out">...</p>
<script type="text/javascript">
Con estos comandos bsicos, se ha visto cmo se puede emplear el modo interactivo para
interactuar de forma ms sencilla con el rea de preparacin.
Parches en la preparacin
Tambin es posible aadir solo ciertas partes de algunos archivos y no otras. Por ejemplo, si se
han realizado dos cambios en el archivo simplegit.rb y se desea pasar solo uno de ellos al rea de
preparacin, pero no el otro. En el indicador interactivo se ha de teclear '5' o 'p' (patch). Git
preguntar cual es el archivo a pasar parcialmente al rea de preparacin. Y despus ir
mostrando trozos de las distintas secciones modificadas en el archivo, preguntando por cada una
si se desea pasar o no al rea de preparacin:
def blame(path)
Stage this hunk [y,n,a,d,/,j,J,g,e,?]?
En estas preguntas, hay varias opciones de respuesta. Tecleando '?' se muestra una lista de las
mismas:
Habitualmente se teclear 'y' o 'n' segn se desee pasar o no cada trozo. Pero habr ocasiones
donde pueda ser til pasar todos ellos conjuntamente, o el dejar para ms tarde la decisin sobre
un trozo concreto. Si se decide pasar solo una parte de un archivo y dejar sin pasar otra parte, la
salida de estado mostrar algo as como:
What now> 1
staged unstaged path
1: unchanged +0/-1 TODO
2: +1/-1 nothing index.html
3: +1/-1 +4/-0 lib/simplegit.rb
La lnea correspondiente al estado del archivo simplegit.rb es bastante interesante. Muestra que
un par de lneas han sido preparadas (staged) en el rea de preparacin y otro par han sido
dejadas fuera de dicho rea (unstaged). Es decir, se ha pasado parcialmente ese archivo al rea de
preparacin. En este punto, es posible salir del script interactivo y lanzar el comando 'git commit'
para almacenar esa confirmacin de cambios parciales en los archivos.
Por ltimo, cabe comentar que no es necesario entrar expresamente en el modo interactivo para
preparar archivos parcialmente. Tambin se puede acceder a ese script con los comandos 'git add
-p' o con 'git add --patch', directamente desde la lnea de comandos.
6.3 Las herramientas de Git -
Guardado rpido provisional
Guardado rpido provisional
Este comando de guardado rpido (stashing) toma el estado del espacio de trabajo, con todas las
modificaciones en los archivos bajo control de cambios, y lo guarda en una pila provisional.
Desde all, se podrn recuperar posteriormente y volverlas a aplicar de nuevo sobre el espacio de
trabajo.
Por ejemplo, si se est trabajando sobre un par de archivos e incluso uno de ellos est ya aadido
al rea de preparacin para un futuro almacenamiento de sus cambios en el repositorio. Al lanzar
el comando 'git status', se podra observar un estado inconsistente tal como:
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: index.html
#
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
#
# modified: lib/simplegit.rb
#
Si justo en este momento se desea cambiar de rama, pero sin confirmar los cambios realizados
hasta entonces; la solucin es un guardado rpido provisional de los cambios. Utilizando el
comando 'git stash' y enviando un nuevo grupo de cambios a la pila de guardado rpido:
$ git stash
Saved working directory and index state \
"WIP on master: 049d078 added the index file"
HEAD is now at 049d078 added the index file
(To restore them type "git stash apply")
$ git status
# On branch master
nothing to commit, working directory clean
Y se permite cambiar de rama para ponerse a trabajar en cualquier otra parte. Con la
tranquilidad de que los cambios a medio completar estn guardados a buen recaudo en la pila de
guardado rpido. Para ver el contenido de dicha pila, se emplea el comando 'git stash list':
En este ejemplo, se habian realizado dos guardados rpidos anteriores, por lo que se ven tres
grupos de cambios guardados en la pila. Con el comando 'git stash apply', tal y como se indica en
la salida del comando stash original, se pueden volver a aplicar los ltimos cambios recien
guardados. Si lo que se desea es reaplicar alguno de los grupos ms antiguos de cambios, se ha de
indicar expresamente: git stash apply stash@{2} Si no se indica ningn grupo concreto, Git
asume que se desea reaplicar el grupo de cambios ms reciente de entre los guardados en la pila.
Como se ve en la salida del comando, Git vueve a aplicar los correspondientes cambios en los
archivos que estaban modificados. Pero no conserva la informacin de lo que estaba o no estaba
aadido al rea de preparacin. En este ejemplo se han aplicado los cambios de vuelta sobre un
espacio de trabajo limpio, en la misma rama. Pero no es esta la nica situacin en la que se
pueden reaplicar cambios. Es perfectamente posible guardar rpidamente (stash) el estado de
una rama. Cambiar posteriormente a otra rama. Y proceder a aplicar sobre esta otra rama los
cambios guardados, en lugar de sobre la rama original. Es posible incluso aplicar de vuelta
cambios sobre un espacio de trabajo inconsistente, donde haya otros cambios o algunos archivos
aadidos al rea de preparacin. (Git notificar de los correspondientes conflictos de fusin si
todo ello no se puede aplicar limpiamente.)
Las modificaciones sobre los archivos sern aplicadas; pero no as el estado de preparacin. Para
conseguir esto ltimo, es necesario emplear la opcin --index del comando git stash apply.
Con ella se le indica que debe intentar reaplicar tambin el estado de preparacin de los archivos.
Y asi se puede conseguir volver exactamente al punto original:
Los comandos git stash apply tan solo recuperan cambios almacenados en la pila de guardado
rpido, sin afectar al estado de la pila. Es decir, los cambios siguen estando guardados en la pila.
Para quitarlos de ah, es necesario lanzar expresamente el comando git stash drop e indicar el
nmero de guardado a borrar de la pila:
Tambin es posible utilizar el comando git stash pop, que aplica cambios de un guardado y lo
retira inmediatamente de la pila.
Si se almacena rpidamente (stash) un cierto trabajo, se deja en la pila durante bastante tiempo,
y se continua mientras tanto con otros trabajos sobre la misma rama. Es muy posible que se
presenten problemas al tratar de reaplicar los cambios guardados tiempo atrs. Si para recuperar
esos cambios se ha de modificar un archivo que tambin haya sido modificado en los trabajos
posteriores, se dar un conflicto de fusin (merge conflict) y ser preciso resolverlo
manualmente. Una forma ms sencilla de reaplicar cambios es utilizando el comando git stash
branch. Este comando crea una nueva rama, extrayendo (checkout) la confirmacin de cambios
original en la que se estaba cuando los cambios fueron guardados en la pila, reaplica estos sobre
dicha rama y los borra de la pila si se consigue completar el proceso con xito.
Este es un buen atajo para recuperar con facilidad un cierto trabajo desde la pila y continuar con
l en una nueva rama.
6.4 Las herramientas de Git -
Reescribiendo la historia
Reescribiendo la historia
Por razones varias, hay ocasiones en que se desea revisar el historial de confirmaciones de
cambio. Una de las grandes caracteristicas de Git es su capacidad de postponer las decisiones
hasta el ltimo momento. Las decisiones sobre qu archivos van en qu confirmaciones de
cambio se toman justo inmediatamente antes de confirmar, utilizando para ello el rea de
preparacin (staging area). En cualquier momento se puede decidir dejar de trabajar en una
cierta va y arrancar en otra, utilizando el comando de guardado rpido (stash). Y tambin es
posible reescribir confirmaciones de cambio ya realizadas, para que se muestren como si
hubieran sido realizadas de otra forma. As, es posible cambiar el orden de las confirmaciones,
cambiar sus mensajes, modificar los archivos comprendidos en ellas, juntar varias
confirmaciones en una sola, partir una en varias,o incluso borrar alguna completamente.
--Aunque todo ello es siempre recomendable hacerlo solo antes de compartir nuestro trabajo con
otros.--
En esta seccin, se ver cmo realizar todas esas tiles tareas. De tal forma que se pueda dejar el
historial de cambios exactamente tal y como se desee. Eso s, siempre antes de compartirlo con
otros desarrolladores.
Mediante este comando, el editor de textos arranca con el mensaje escrito en la ltima
confirmacin de cambios; listo para ser modificado. Al guardar y cerrar en el editor, este escribe
una nueva confirmacin de cambios y reemplaza con ella la ltima confirmacin existente.
Si se desea cambiar la instantnea (snapshot) de archivos en la ltima confirmacin de cambios,
habitualmente por haber tenido algn descuido al aadir algn archivo de reciente creacin. El
proceso a seguir es bsicamente el mismo. Se preparan en el rea de preparacin los archivos
deseados; con los comandos git add o git rm, segn corresponda. Y, a continuacin, se lanza el
comando git commit --amend. Este tendr en cuenta dicha preparacin para rehacer la
instantnea de archivos en la nueva confirmacin de cambios.
Es importante ser cuidadoso con esta tcnica. Porque al modifcar cualquier confirmacin de
cambios, cambia tambin su cdigo SHA-1. Es como si se realizara una pequea reorganizacin
(rebase). Y, por tanto, aqu tambin se aplica la regla de no modificar nunca una confirmacin de
cambios que ya haya sido enviada (push) a otros.
Para modificar una confirmacin de cambios situada bastante atrs en el historial, es necesario
emplear herramientas ms complejas. Git no dispone de herramientas directas para modifica el
historial de confirmaciones de cambio. Pero es posible emplear la herramienta de reorganizacin
(rebase) para modificar series de confirmaciones; en la propia cabeza (HEAD) donde estaban
basadas originalmente, en lugar de moverlas a otra distinta. Dentro de la herramienta de
reorganizacin interactiva, es posible detenerse justo tras cada confirmacin de cambios a
modificar. Para cambiar su mensaje, aadir archivos, o cualquier otra modificacin. Este modo
interactivo se activa utilizando la opcin -i en el comando git rebase. La profundidad en la
historia a modificar vendr dada por la confirmacin de cambios (commit) que se indique al
comando.
Por ejemplo, para modificar las tres ltimas confirmaciones de cambios, se indicara el padre de la
ltima conformacin a modificar, es decir habr que escribir HEAD~2^ or HEAD~3 tras el
comando git rebase -i. La nomenclatura ~3 es la mas sencilla de recordar, porque lo que se
desea es modificar las tres ltimas confirmaciones. Pero sin perder de vista que realmente se est
sealando a cuatro confirmaciones de cambio ms atras, al padre de la ltima de las
confirmaciones de cambio a modificar.
Es importante avisar de nuevo que se trata de un comando de reorganizacin: todas y cada una
de las confirmaciones de cambios en el rango HEAD~3..HEAD van a ser reescritas, (cambia su
cdigo SHA-1), tanto si se modifica algo en ellas como si no. Por tanto, es importante no afectar a
ninguna confirmacin de cambios que haya sido ya enviada (push) a un servidor central. So pena
de confundir a otros desarrolladores, a los cuales se estaria dando una versin alternativa de un
mismo cambio.
Al lanzar este comando, se vern una lista de confirmaciones de cambio en la pantalla del editor
de textos:
Es importante destacar que esas confirmaciones de cambios se han listado en el orden opuesto al
que normalmente son mostradas en el comando log. En este ltimo, se suele ver algo as como:
Prestar atencin al orden inverso. La reorganizacin interactiva lanza un script. Un script que,
comenzando por la confirmacin de cambios indicada en la lnea del comando (HEAD~3), va a
reaplicar los cambios introducidos en cada una de las confirmaciones, desde arriba hasta abajo.
En la lista se ven las mas antiguas encima, en lugar de las ms recientes, precisamente porque
esas van a ser las primeras en reaplicarse.
Para que el script se detenga en cada confirmacin de cambios a modificar, hay que editarlo. Y se
ha de cambiar la palabra 'pick' por la palabra 'edit' en cada una de las confirmaciones de cambio
donde se desee detener el script. Por ejemplo, para modificar solo el mensaje de la tercera
confirmacin de cambios, el script quedaria:
Cuando se guarde y cierre en el editor, Git har un rebobinado hacia atras hasta la ltima de las
confirmaciones de cambios en la lista, y mostrar algo as como:
Cambiar el mensaje de la confirmacin de cambios y salir del editor. Para luego lanzar
Las otras dos confirmaciones de cambio sern reaplicadas automticamene. Y ya estar completa
la reorganizacin. Si se ha cambiado 'pick' por 'edit' en ms de una lnea, estos pasos se habrn de
repetir por cada una de las confirmaciones de cambios a modificar. En cada una de ellas, Git se
detendr, permitiendo enmendar la confirmacin de cambios y continuar tras la modificacin.
Las reorganizaciones interactivas tambin se pueden emplear para reordenar o para eliminar
completamente ciertas confirmaciones de cambios (commits). Por ejemplo, si se desea eliminar
la confirmacin de "added cat-file" y cambiar el orden en que se han introducido las otras dos
confirmaciones de cambios, el script de reorganizacin pasara de ser:
pick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-filepick f7f3f6d changed my name a bit
pick 310154e updated README formatting and added blame
pick a5f4a0d added cat-file
Cuando se guarde y salga en el editor, Git rebobinar la rama hasta el padre de las
confirmaciones de cambio indicadas, reaplicar 310154e y luego f7f3f6d, para finalmente
detenerse. De esta forma se habr cambiado el orden de las dos confirmaciones de cambio, y se
habr eliminado completamente la de "added cat-file".
#
# Commands:
# p, pick = use commit
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
#
# If you remove a line here THAT COMMIT WILL BE LOST.
# However, if you remove everything, the rebase will be aborted.
###
Si, en lugar de 'pick' o de 'edit', se indica 'squash' delante de alguna de las confirmaciones de
cambio, Git aplicar simultneamente dicha confirmacin y la que est inmediatamente delante
de ella. Permitiendo tambin combinar los mensajes de ambas. Por ejemplo, si se desea hacer
una nica confirmacin de cambios fusionando las tres, el script quedara en algo como:
Cuando se guarde y salga en el editor, Git rebobinar la historia, reaplicar las tres
confirmaciones de cambio, y volver al editor para fusionar tambin los mensajes de esas tres
confirmaciones.
added cat-file
Al guardar esto, se tendr una sola confirmacin de cambios que introducir todos los cambios
que estaban en las tres confirmaciones de cambios previamente existentes.
Dividir una confirmacin de cambios (commit), implica deshacerla y luego volver a preparar y
confirmar trozos de la misma tantas veces como nuevas confirmaciones se desean tener al final.
Por ejemplo, si se desea dividir la confirmacin de cambios de enmedio de entre las tres citadas
en ejemplos anteriores. Es decir, si en lugar de "updated README formatting and added blame",
se desea separar esa confirmacin en dos: "updated README formatting" y "added blame". Se
puede realizar cambiando la instruccin en el script de rebase -i, desde 'split' a 'edit':
Tras esto, Git reaplicar la ltima de las confirmaciones de cambios (a5f4a0d) en el script.
Quedando la historia:
De nuevo, merece recalcar el hecho de que estas operaciones cambian los cdigos SHA-1 de todas
las confirmaciones de cambio afectadas. Y que, por tanto, no se deben hacer sobre
confirmaciones de cambio enviadas(push) a algn repositorio compartido.
Existe una opcin de reescritura del historial que se puede utilizar si se necesita reescribir un
gran nmero de confirmaciones de cambio de forma mas o menos automatizada. Por ejemplo,
para cambiar una direccin de correo electrnico globalmente, o para quitar un archivo de todas
y cada una de las confirmaciones de cambios en una determinada rama. El comando en cuestin
es filter-branch, y permite reescribir automticamente grandes porciones del historial.
Precisamente por ello, no debera utilizarse a no ser que el proyecto an no se haya hecho pblico
(es decir, otras personas no han basado su trabajo en alguna de las confirmaciones de cambio que
se van a modificar). De todas formas, all donde sea aplicable, puede ser de gran utilidad. Se van
a ilustrar unas cuantas de las ocasiones donde se podra utilizar, para dar as una idea de sus
capacidades.
Quitar un archivo de cada confirmacin de cambios
Es algo que frecuentemente suele ser necesario. Alguien confirma cambios y almacena
accidentalmente un enorme archivo binario cuando lanza un git add . sin pensarlo demasiado. Y
es necesario quitarlo del repositorio. O podria suceder que se haya confirmado y almacenado
accidentalmente un archivo que contiene una contrasea importante, Y el proyecto se va a hacer
de cdigo abierto. En estos casos, la mejor opcin es utilizar la herramienta filter-branch para
limpiar todo el historial. Por ejemplo, para quitar un archivo llamado passwords.txt del
repositorio, se puede emplear la opcin --tree-filter del comando filter-branch:
Esta opcin --tree-filter, tras cada extraccin (checkout) del proyecto, lanzar el comando
especificado y reconfirmar los cambios resultantes(recommit). En esta ocasin, se eliminar un
archivo llamado passwords.txt de todas y cada una de las instantneas (snapshot) almacenadas,
tanto si este existe como si no. Otro ejemplo: si se desean eliminar todos los archivos de respaldo
del editor que han sido almacenados por error, se podra lanzar algo as como git filter-branch
--tree-filter "find * -type f -name '*~' -delete" HEAD.
Y se iria viendo como Git reescribe rboles y confirmaciones de cambio, hasta que el apuntador
de la rama llegue al final. Una recomendacin: en general, suele ser buena idea lanzar cualquiera
de estas operaciones primero sobre una rama de pruebas y luego reinicializar (hard-reset) la
rama maestra (master), una vez se haya comprobado que el resultado de las operaciones es el
esperado. Si se desea lanzar filter-branch sobre todas las ramas del repositorio, se ha de pasar la
opcin --all al comando.
Este comando pasa por todo el repositorio y reescribe cada confirmacin de cambios donde
detecte la direccin de correo indicada, para reemplazarla por la nueva. Y, debido a que cada
confirmacin de cambios contiene el cdigo SHA-1 de sus ancestros, este comando cambia
tambin todos los cdigos SHA del historial; no solamente los de las confirmaciones de cambio
que contenian la direccin indicada.
Cuando se est rastreando un error dentro del cdigo buscando localizar cundo se introdujo y
por qu, el mejor auxiliar para hacerlo es la anotacin de archivos. Esta suele mostrar la
confirmacin de cambios (commit) que modific por ltima vez cada una de las lneas en
cualquiera de los archivos. As, cuando se est frente a una porcin de cdigo con problemas, se
puede emplear el comando git blame para anotar ese archivo y ver as cundo y por quin fue
editada por ltima vez cada una de sus lneas. En este ejemplo, se ha utilizado la opcin -L para
limitar la salida a las lneas desde la 12 hasta la 22:
Merece destacar que el primer campo mostrado en cada lnea es el cdigo SHA-1 parcial de la
confirmacin de cambios en que se modific dicha lnea por ltima vez. Los dos siguientes
campos son sendos valores extraidos de dicha confirmacin de cambios --el nombre del autor y la
fecha--, mostrando quien y cundo modifico esa lnea. Detras, vienen el nmero de lnea y el
contendido de la lnea propiamente dicha. En el caso de las lneas con la confirmacin de cambios
^4832fe2, merece comentar que son aquellas presentes en el archivo cuando se hizo la
confirmacin de cambios original; (la confirmacin en la que este archivo se incluy en el
proyecto por primera vez). No habiendo sufrido esas lneas ninguna modificacin desde
entonces. Puede ser un poco confuso, debido a que la marca ^ se utiliza tambin con otros
significados diferentes dentro de Git. Pero este es el sentido en que se utiliza aqu: para sealar la
confirmacin de cambios original.
Lo cual es realmente til. Habitualmente suele mostrarse como confirmacin de cambios original
aquella confirmacin de cambios desde la que se copi el cdigo. Por ser esa la primera ocasin
en que se han modificado las lneas en ese archivo. Git suele indicar la confirmacin de cambios
original donde se escribieron las lneas, incluso si estas fueron escritas originalmente en otro
archivo.
Bsqueda binaria
Por ejemplo, en caso de aparecer problemas justo tras enviar a produccin un cierto cdigo que
parecia funcionar bien en el entorno de desarrollo. Si, volviendo atras, resulta que se consigue
reproducir el problema, pero cuesta identificar su causa. Se puede ir biseccionando el cdigo para
intentar localizar el punto del historial desde donde se presenta el problema. Primero se lanza el
comando git bisect start para iniciar el proceso de bsqueda. Luego, con el comando git bisect
bad, se le indica al sistema cual es la confirmacin de cambios a partir de donde se han detectado
los problemas. Y despus, con el comando git bisect good [good_commit], se le indica cual es
la ltima confirmacin de cambios conocida donde el cdigo funcionaba bien:
Git averigua que se han dado 12 confirmaciones de cambio entre la confirmacin marcada como
buena y la marcada como mala. Y extrae la confirmacin central de la serie, para comenzar las
comprobaciones a partir de ah. En este punto, se pueden lanzar las pruebas pertinentes para ver
si el problema existe en esa confirmacin de cambios extraida. Si este es el caso, el problema se
introdujo en algn punto anterior a esta confirmacin de cambios intermedia. Si no, el problema
se introdujo en un punto posterior. Por ejemplo, si resultara que no se detecta el problema aqu,
se indicaria esta circunstancia a Git tecleando git bisect good; para continuar la bsqueda:
Con esto el proceso de bsqueda se completa y Git tiene la informacin necesaria para
determinar dnde comenzaron los problemas. Git reporta el cdigo SHA-1 de la primera
confirmacin de cambios problemtica y muestra una parte de la informacin relativa a esta y a
los archivos modificados en ella. As podemos irnos haciendo una idea de lo que ha podido
suceder para que se haya introducido un error en el cdigo:
Al terminar la revisin, es obligatorio teclear el comando git bisect reset para devolver HEAD
al punto donde estaba antes de comenzar todo el proceso de bsqueda. So pena de dejar el
sistema en un estado inconsistente.
Esta es una poderosa herramienta que permite chequear en minutos cientos de confirmaciones
de cambio, para determinar rpidamente en que punto se pudo introducir el error. De hecho, si
se dispone de un script que d una salida 0 si el proyecto funciona correctamente y distinto de 0
si el proyecto tiene errores, todo este proceso de bsqueda con git bisect se puede automatizar
completamente. Primero, como siempre, se indica el alcance de la bsqueda indicando las
aquellas confirmaciones de cambio conocidas donde el proyecto estaba mal y donde estaba bien.
Se puede hacer en un solo paso. Indicando ambas confirmaciones de cambios al comando bisect
start, primero la mala y luego la buena:
Suele ser frecuente encontrarse con la necesidad de utilizar otro proyecto desde dentro del que se
est trabajando. En ocasiones como, por ejemplo, cuando se utiliza una biblioteca de terceros, o
cuando se est desarrollando una biblioteca independiente para ser utilizada en mltiples
proyectos. La preocupacin tpica en estos escenarios suele ser la de cmo conseguir tratar ambos
proyectos separadamente. Pero conservando la habilidad de utilizar uno dentro del otro.
Un ejemplo concreto. Supongamos que se est desarrollando un site web y creando feeds Atom.
En lugar de escribir cdigo propio para generar los feeds Atom, se decide emplear una biblioteca
ya existente. Y dicha biblioteca se incluye desde una biblioteca compartida tal como CPAN install
o Ruby gem; o copiando directamente su cdigo fuente en el rbol del propio proyecto. La
problemtica en el primer caso radica en la dificultad de personalizar la biblioteca compartida. Y
en la dificultal para su despliegue; ya que es necesario que todos y cada uno de los clientes
dispongan de ella. La problemtica en el segundo caso radica en las complicaciones para fusionar
las personalizaciones realizadas por nosotros con futuras copias de la biblioteca original.
Git resuelve estas problemticas utilizando submdulos. Los submdulos permiten mantener un
repositorio Git como una subcarpeta de otro repositorio Git. Esto permite clonar un segundo
repositorio dentro del repositorio del proyecto en que se est trabajando, manteniendo
separadamente las confirmaciones de cambios en ambos repositorios.
Trabajando con submdulos
Suponiendo, por ejemplo, que se desea aadir la biblioteca Rack (un interface Ruby de pasarela
de servidor web) al proyecto en que se est trabajando. Posiblemente con algunas
personalizaciones, pero sin perder la capacidad de fusionar nuestros cambios con la evolucin de
la biblioteca original. La primera tarea a realizar es clonar el repositorio externo dento de una
subcarpeta dentro del proyecto. Los proyectos externos se pueden incluir como submdulos
mediante el comando git submodule add:
A partir de este momento, el proyecto Rack est dentro de nuestro proyecto; bajo una subcarpeta
denominada rack. En dicha subcarpeta es posible realizar cambios, aadir un repositorio propio
a donde enviar (push) los cambios, recuperar (fetch) y fusionar (merge) desde el repositorio
original, y mucho mas... Si se lanza git status nada mas aadir el submdulo, se aprecian dos
cosas:
$ git status
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# new file: .gitmodules
# new file: rack
#
Una: el archivo .gitmodules. un archivo de configuracin para almacenar las relaciones entre la
URL del proyecto y la subcarpeta local donde se ha colocado este.
$ cat .gitmodules
[submodule "rack"]
path = rack
url = git://github.com/chneukirchen/rack.git
En caso de haber mltipes submdulos, habr multiples entradas en este archivo. Merece
destacar que este archivo est tambin bajo el control de versiones, como lo estn otros archivos
tal como .gitignore, por ejemplo. Y ser enviado (push) y recibido (pull) junto con el resto del
proyecto. As es como otras personas que clonen el proyecto pueden saber dnde encontrar los
submdulos del mismo.
Dos: la entrada rack. Si se lanza un git diff sobre ella, se puede apreciar algo muy interesante:
Aunque rack es una subcarpeta de la carpeta de trabajo, git la contempla como un submdulo y
no realiza seguimiento de sus contenidos si no se est situado directamente sobre ella. En su
lugar, Git realiza confirmaciones de cambio particulares en ese repositorio. Cuando se realizan y
confirman cambios en esa subcarpeta, el proyecto padre detecta el cambio en HEAD y almacena
la confirmacin de cambios concreta en la que se est trabajando en ese momento. De esta forma,
cuando otras personas clonen este proyecto, sabrn cmo recrear exactamente el entorno.
Esto es importante al trabajar con submdulos: siempre son almacenados como la confirmacin
de cambios concreta en la que estn. No es posible almacenar un submdulo en master o en
cualquier otra referencia simblica.
Se puede considerar la carpeta rack como si fuera un proyecto separado. Y, como tal, de vez en
cuando se puede actualizar el proyecto padre con un puntero a la ltima confirmacin de cambios
en dicho subproyecto. Todos los comandos Git actuan independientemente en ambas carpetas:
$ git log -1
commit 0550271328a0038865aad6331e620cd7238601bb
Author: Scott Chacon <schacon@gmail.com>
Date: Thu Apr 9 09:03:56 2009 -0700
Si se tiene un proyecto con submdulos dentro de l. Cuando se recibe, se reciben tambin las
carpetas que contienen los submdulos; pero no se reciben ninguno de los archivos de dichos
submdulos:
La carpeta rack est presente, pero vacia. Son necesarios otros dos comandos: git submodule
initpara inicializar el archivo de configuracin local, y git submodule update para recuperar
(fetch) todos los datos del proyecto y extraer (checkout) la confirmacin de cambios adecuada
desde el proyecto padre:
Tras esto, la carpeta rack s que est exactamente en el estado que le corresponde estar tras la
ltima confirmacin de cambios que se realiz sobre ella. Si otra persona realiza cambios en el
cdigo de rack, los confirma y nosotros recuperamos (pull) dicha referencia y la fusionamos
(merge), se obtendr un resultado un tanto extrao:
$ git diff
diff --git a/rack b/rack
index 6c5e70b..08d709f 160000
--- a/rack
+++ b/rack
@@ -1 +1 @@
-Subproject commit 6c5e70b984a60b3cecd395edd5b48a7575bf58e0
+Subproject commit 08d709f78b8c5b0fbeb7821e37fa53e69afcf433
Siendo esto debido a que el puntero al submdulo que se tiene en este momento no corresponde
a lo que realmente hay en carpeta del submdulo. Para arreglarlo, es necesario lanzar de nuevo el
comando git submodule update:
Se necesita realizar este paso cada vez que se recupere (pull) un cambio del submdulo en el
proyecto padre. Es algo extrao, pero funciona!.
Un problema tpico se suele dar cuando un desarrollador realiza y confirma (commit) un cambio
local en el submdulo, pero no lo envia (push) a un servidor pblico. Pero, sin embargo, s que
confirma (commit) y envia (push) un puntero a dicho estado dentro del proyecto padre. Cuando
otros desarrolladores intenten lanzar un git submodule update, ser imposible encontrar la
confirmacin de cambios a la que se refiere el submdulo, ya que esta tan solo existe en el
sistema del desarrollador original. En estos casos, se suele ver un error tal como:
Forzandonos a mirar quin ha sido la persona que ha realizado los ltimos cambios en el
submdulo:
Proyectos padre
Algunas veces, dependiendo del equipo de trabajo en que se encuentren, los desarrolladores
suelen necesitar mantener una combinacin de grandes carpetas de proyecto. Se da
frecuentemente en equipos procedentes de CVS o de Subversion (donde se define una coleccin
de mdulos o carpetas), cuando desean mantener ese mismo tipo de flujo de trabajo.
La manera ms apropiada de hacer esto en Git, es la de crear diferentes repositorios, cada uno en
su carpeta; para luego crear un repositorio padre que englobe mltiples submdulos, uno por
cada carpeta. Un beneficio que se obtiene de esta manera de trabajar es la mayor especificidad en
las relaciones entre proyectos, definidas mediante etiquetas (tag) y ramas (branch) en el proyecto
padre.
El uso de submdulos tiene tambin sus contratiempos. El primero de los cuales es la necesidad
de ser bastante cuidadoso cuando se trabaja en la carpeta de un submdulo. Al lanzar git
submodule update, este comando comprueba la versin especfica del proyecto, pero sin tener
en cuenta la rama. Es lo que se conoce como "trabajar con cabecera desconectada" --es decir, el
archivo HEAD apunta directamente a una confirmacin de cambios (commit), y no a una
referencia simblica--. Este mtodo de trabajo suele tenderse a evitar, ya que trabajando en un
entorno de cabecera desconectada es bastante facil despistarse y perder cambios ya realizados. Si
se realiza un submodule update inicial, se hacen cambios y se confirman en esa carpeta de
submdulo sin haber creado antes una rama en la que trabajar. Y si, tras esto, se realiza de nuevo
un git submodule update desde el proyecto padre, sin haber confirmado cambios en este, Git
sobreescribir cambios sin aviso previo. Tcnicamente, no se pierde nada del trabajo.
Simplemente, nos quedamos sin ninguna rama apuntando a l. Con lo que resulta problemtico
recuperar el acceso a los cambios.
Para evitarlo, siempre se ha de crear una rama cuando se trabaje en la carpeta de un submdulo;
usandogit checkout -b trabajo o algo similar. Cuando se realice una actualizacin (update) del
submdulo por segunda vez, se seguir sobreescribiendo el trabajo; pero al menos se tendr un
apuntador para volver hasta los cambios realizados.
Intercambiar ramas con submdulos tiene tambin sus peculiaridades. Si se crea una rama, se
aade un submdulo en ella y luego se retorna a una rama donde dicho submdulo no exista. La
carpeta del submdulo sigue existiendo, solo que ahora queda como una carpeta sin seguimiento.
Forzandonos a removerla del camino. Lo cual obliga a volver a clonarla cuando se retome la rama
inicial --con la consiguiente prdida de los cambios locales si estos no habian sido enviados
previamente al servidor--.
Y una ltima problemtica en que se suelen encontrar quienes intercambian de carpetas a
submdulos. Si se ha estado trabajando en archivos de un proyecto al que luego se desea
convertir en un submdulo, hay que ser muy cuidadoso o Git se resentir. Asumiendo que se
tenian archivos en una carpeta 'rack' del proyecto, y que se desea intercambiarla por un
submdulo. Si se borra la carpeta y luego se lanza un comando submodule add, Git avisar de
"carpeta ya existente en el ndice":
$ rm -Rf rack/
$ git submodule add git@github.com:schacon/rack.git rack
'rack' already exists in the index
Para evitarlo, se debe sacar la carpeta 'rack' del rea de preparacin. Despus, Git permitir la
adiccin del submdulo sin problemas:
$ git rm -r rack
$ git submodule add git@github.com:schacon/rack.git rack
Initialized empty Git repository in /opt/testsub/rack/.git/
remote: Counting objects: 3184, done.
remote: Compressing objects: 100% (1465/1465), done.remote: Compressing
objects: 100% (1465/1465), done.
remote: Total 3184 (delta 1952), reused 2770 (delta 1675)
Receiving objects: 100% (3184/3184), 677.42 KiB | 88 KiB/s, done.
Resolving deltas: 100% (1952/1952), done.Resolving deltas: 100% (1952/1952),
done.Resolving deltas: 100% (1952/1952), done.
Tras esto, y suponiendo que ese paso ha sido realizado en una rama. Si se intenta retornar a dicha
rama, cuyos archivos estn an en el rbol actual en lugar de en el submdulo, se obtendr el
siguiente error:
Antes de cambiar a cualquier rama que no lo contenga, es necesario quitar de enmedio la carpeta
del submdulo 'rack'.
$ mv rack /tmp/
$ git checkout master
Switched to branch "master"
$ ls
README rack
Y, cuando se retorne a la rama anterior, se tendr una carpeta 'rack' vacia. Ante lo cual, ser
necesario lanzargit submodule update para volver a clonarla; o, si no, volver a restaurar la
carpeta/tmp/rack de vuelta sobre la carpeta vacia.
Ahora que se han visto las dificultades que se pueden presentar utilizando el sistema de
submdulos, es momento de hechar un vistazo a una va alternativa de atacar esa misma
problemtica. Cuando Git realiza una fusin, suele revisar lo que ha de fusinar entre s y, tras
ese anlisis, elige la estratgia mas adecuada para hacerlo. Si se estn fusionando dos ramas, Git
suele utilizar la estategiarecursiva_ (recursive strategy). Si se estn fusionando ms de dos
ramas, Git suele escoger la estrategiadelpulpo (octopus strategy). Estas son las estrategias
escogidas por defecto, ya que la estrategia recursiva puede manejar complejas
fusiones-de-tres-vias --por ejemplo, con ms de un antecesor comn-- pero tan solo puede
fusionar dos ramas. La fusin-tipo-pulpo puede manejar multiples ramas, pero es mucho mas
cuidadosa para evitar incurrir en complejos conflictos; y es por eso que se utiliza en los intentos
de fusionar ms de dos ramas.
Pero existen tambin otras estratgias que se pueden escoger segn se necesiten. Una de ellas, la
fusinsubrbol_ (subtree merge), es precisamente la ms adecuada para tratar con subproyectos.
En este caso se va a mostrar cmo se haria el mismo empotramiento del mdulo rack tomado
como ejemplo anteriormente, pero utilizando fusiones de subarbol en lugar de submdulos.
La idea subyacente tras toda fusin subarborea es la de que se tienen dos proyectos; y uno de
ellos est relacionado con una subcarpeta en el otro, y viceversa. Cuando se solicita una fusin
subarborea, Git es lo suficientemente inteligente como para imaginarse por si solo que uno de los
proyectos es un subrbol del otro y obrar en consecuencia. Es realmente sorprendente.
Se comienza aadiendo la aplicacin Rack al proyecto. Se aade como una referencia remota en
el propio proyecto, y luego se extrae (checkout) en su propia rama:
En este punto, se tiene la raiz del proyecto Rack en la rama rack_branch y la del propio proyecto
padre en la rama master. Si se comprueban una o la otra, se puede observar que ambos
proyectos tienen distintas raices:
$ ls
AUTHORS KNOWN-ISSUES Rakefile contrib lib
COPYING README bin example test
$ git checkout master
Switched to branch "master"
$ ls
README
Si se desea situar el proyecto Rack como una subcarpeta del proyecto master. Se ha de lanzar el
comando git read-tree. Se ver ms en detalle el comando read-tree y sus acompaantes en el
captulo 9. Pero por ahora, basta con saber que este comando se encarga de leer el rbol raiz de
una rama en el rea de preparacin (staging area) y carpeta de trabajo (working directory)
actuales. Con ello, se retorna sobre la rama master y se recupera (pull) la rama rack_branch en
la subcarpeta rack de la rama master del proyecto principal:
Cuando se confirman estos cambios, es como si se tuvieran todos los archivos Rack bajo esa
carpeta --como si se hubieran copiado desde un archivo comprimido tarball-- Lo que hace
interesante este mtodo es la posibilidad que brinda de fusionar cambios de una rama sobre la
otra de forma sencilla. De tal forma que, si se actualiza el proyecto Rack, se pueden integrar los
cambios aguas arriba simplemente cambiando a esa rama y recuperando:
Tras lo cual, es posible fusionar esos cambios de vuelta a la rama 'master'. Utilizando el comando
git merge -s subtree, que funciona correctamente; pero fusionando tambin los historiales
entre s. Un efecto secundario que posiblemente no interese. Para recuperar los cambios y
rellenar el mensaje de la confirmacin, se pueden emplear las opciones --squash y
--no-commit, junto con la opcin de estrategia -s subtree:
Con esto, todos los cambios en el proyecto Rack se encontrarn fusionados y listos para ser
confirmados localmente. Tambin es posible hacer el camino contrario: realizar los cambios en la
subcarpeta rack de la rama 'master', para posteriormente fusionarlos en la rama rack_branch y
remitirlos a los encargados del mantenimiento o enviarlos aguas arriba.
Para ver las diferencias entre el contenido de la subcarpeta rack y el cdigo en la rama
rack_branch --para comprobar si es necesario fusionarlas--, no se puede emplear el comando
diff habitual. En su lugar, se ha de emplear el comando git diff-tree con la rama que se desea
comparar:
Ahora vas a aprender un puado de nuevas e interesantes opciones que puedes utilizar para
personalizar el uso de Git.
Primeramente, vamos a repasar brevemente los detalles de configuracin de Git que ya has visto
en el primer captulo. Para determinar su comportamiento no estandar, Git emplea una serie de
archivos de configuracin. El primero de ellos es el archivo '/etc/gitconfig', que contiene valores
para todos y cada uno de los usuarios en el sistema y para todos sus repositorios. Con la opcin
'--system' del comando 'git config', puedes leer y escribir de/a este archivo.
El segundo es el archivo '~/.gitconfig', especfico para cada usuario. Con la opcin '--global', 'git
config' lee y escribe en este archivo.
Y por ltimo, Git tambin puede considerar valores de configuracin presentes en el archivo
'.git/config' de cada repositorio que ests utilizando. Estos valores se aplicarn nicamente a
dicho repositorio. Cada nivel sobreescribe los valores del nivel anterior; es decir lo configurado en
'.git/config' tiene primacia con respecto a lo configurado en '/etc/gitconfig', por ejemplo.
Tambin puedes ajustar estas configuraciones manualmente, editando directamente los archivos
correspondientes y escribiendo en ellos con la sintaxis correspondiente; pero suele ser ms
sencillo hacerlo siempre a travs del comando 'git config'.
Las opciones de configuracin reconocidas por Git pueden distribuirse en dos grandes categorias:
las del lado cliente y las del lado servidor. La mayoria de las opciones estn en el lado cliente,
--configurando tus preferencias personales de trabajo--. Aunque hay multitud de ellas, aqu
vamos a ver solamente unas pocas. Las mas comunmente utilizadas o las que afectan
significativamente a tu forma de trabajar. No vamos a revisar aquellas opciones utilizadas solo en
casos muy especiales. Si quieres consultar una lista completa, con todas las opciones
contempladas en tu versin de Git, puedes lanzar el comando:
La pgina de manual sobre 'git config' contiene una lista bastante detallada de todas las opciones
disponibles.
core.editor
Por defecto, Git utiliza cualquier editor que hayas configurado como editor de texto por defecto
de tu sistema. O, si no lo has configurado, utilizar Vi como editor para crear y editar las
etiquetas y mensajes de tus confirmaciones de cambio (commit). Para cambiar ese
comportamiento, puedes utilizar el ajuste 'core.editor':
A partir de ese comando, por ejemplo, git lanzar Emacs cada vez que vaya a editar mensajes;
indistintamente del editor configurado en la lnea de comandos (shell) del sistema.
commit.template
Si preparas este ajuste para apuntar a un archivo concreto de tu sistema, Git lo utilizar como
mensaje por defecto cuando hagas confirmaciones de cambio. Por ejemplo, imagina que creas
una plantilla en '$HOME/.gitmessage.txt'; con un contenido tal como:
subject line
what happened
[ticket: X]
Para indicar a Git que lo utilice como mensaje por defecto y que aparezca en tu editor cuando
lances el comando 'git commit', tan solo has de ajustar 'commit.template':
A partir de entonces, cada vez que confirmes cambios (commit), tu editor se abrir con algo como
esto:
subject line
what happened
[ticket: X]
# 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:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: lib/test.rb
#
~
~
".git/COMMIT_EDITMSG" 14L, 297C
Si tienes una poltica concreta con respecto a los mensajes de confirmacin de cambios, puedes
aumentar las posibilidades de que sea respetada si creas una plantilla acorde a dicha poltica y la
pones como plantilla por defecto de Git.
core.pager
El parmetro core.pager selecciona el paginador utilizado por Git cuando muestra resultados de
comandos tales como 'log' o 'diff'. Puedes ajustarlo para que utilice 'more' o tu paginador favorito,
(por defecto, se utiliza 'less'); o puedes anular la paginacin si le asignas una cadena vacia.
Si lanzas esto, Git mostrar siempre el resultado completo de todos los comandos,
independientemente de lo largo que sea este.
user.signingkey
Si tienes costumbre de firmar tus etiquetas (tal y como se ha visto en el captulo 2), configurar tu
clave de firma GPG puede facilitarte la labor. Configurando tu clave ID de esta forma:
Puedes firmar etiquetas sin necesidad de indicar tu clave cada vez en el comando 'git tag'.
core.excludesfile
Se pueden indicar expresiones en el archivo '.gitignore' de tu proyecto para indicar a Git lo que
debe considerar o no como archivos sin seguimiento, o lo que interar o no seleccionar cuando
lances el comando 'git add', tal y como se indic en el captulo 2. Sin embargo, si quieres disponer
de otro archivo fuera de tus proyectos o tener expresiones extra, puedes indicarselo a Git con el
parmetro 'core.excludesfile'. Simplemente, configuralo para que apunte a un archivo con
contenido similar al que tendra cualquier archivo '.gitignore'.
help.autocorrect
Este parmetro solo est disponible a partir de la versin 1.6.1 de Git. Cada vez que tienes un
error de tecleo en un comando, Git 1.6 te muestra algo como:
$ git com
git: 'com' is not a git-command. See 'git --help'.
Colores en Git
Git puede marcar con colores los resultados que muestra en tu terminal, ayudandote as a leerlos
ms facilmente. Hay unos cuantos parmetros que te pueden ayudar a configurar tus colores
favoritos.
color.ui
Si se lo pides, Git colorear automticamente la mayor parte de los resultados que muestre.
Puedes ajustar con precisin cada una de las partes a colorear; pero si deseas activar de un golpe
todos los colores por defecto, no tienes ms que poner a "true" el parmetro 'color.ui'.
Ajustando as este parmetro, Git colorea sus resultados cuando estos se muestran en un
terminal. Otros ajustes posibles son "false", para indicar a Git no colorear nunca ninguno de sus
resultados; y "always", para indicar colorear siempre, incluso cuando se redirija la salida a un
archivo o a otro comando. Este parmetro se aadi en la versin 1.5.5 de Git. Si tienes una
versin ms antigua, tendrs que indicar especificamente todos y cada uno de los colores
individualmente.
Ser muy raro ajustar 'color.ui = always'. En la mayor parte de las ocasiones, cuando necesites
cdigos de color en los resultados, es mejor indicar puntualmente la opcin '--color' en el
comando concreto, para obligarle a utilizar cdigos de color. Habitualmente, se trabajar con el
ajuste 'color.ui = true'.
color.*
Cuando quieras ajustar especficamente, comando a comando, donde colorear y cmo colorear,
(o cuando tengas una versin antigua de Git), puedes emplear los ajustes particulares de color.
Cada uno de ellos puede fijarse a 'true' (verdadero), 'false' (falso) o 'always' (siempre):
color.branch
color.diff
color.interactive
color.status
Adems, cada uno de ellos tiene parmetros adiccionales para asignar colores a partes
especficas, por si quieres precisar an ms. Por ejemplo, para mostrar la meta-informacin del
comando 'diff' con letra azul sobre fondo negro y con caracteres en negrita, puedes indicar:
Puedes ajustar un color a cualquiera de los siguientes valores: 'normal' (normal), 'black' (negro),
'green' (verde), 'yellow' (amarillo), 'blue' (azul oscuro), 'magenta' (rojo oscuro), 'cyan' (azul claro)
o 'white' (blanco). Tambin puedes aplicar atributos tales como 'bold' (negrita), 'dim' (tenue), 'ul'
( ), 'blink' (parpadeante) y 'reverse (video inverso).
Aunque Git lleva una implementacin interna de diff, la que se utiliza habitualmente, se puede
sustituir por una herramienta externa. Puedes incluso configurar una herramienta grfica para la
resolucin de conflictos, en lugar de resolverlos manualmente. Lo voy a demostrar configurando
Perforce Visual Merge como herramienta para realizar las comparaciones y resolver conflictos; ya
que es una buena herramienta grfica y es libre.
Si lo quieres probar, P4Merge funciona en todas las principales plataformas. Los nombres de
carpetas que utilizar en los ejemplos funcionan en sistemas Mac y Linux; para Windows, tendrs
que sustituir '/usr/local/bin' por el correspondiente camino al ejecutable en tu sistema.
http://www.perforce.com/product/components/perforce-visual-merge-and-diff-tools
Para empezar, tienes que preparar los correspondientes scripts para lanzar tus comandos. En
estos ejemplos, voy a utilizar caminos y nombres Mac para los ejecutables; en otros sistemas,
tendrs que sustituirlos por los correspondientes donde tengas instalado 'p4merge'. El primer
script a preparar es uno al que denominaremos 'extMerge', para llamar al ejecutable con los
correspodientes argumentos:
$ cat /usr/local/bin/extMerge
#!/bin/sh
/Applications/p4merge.app/Contents/MacOS/p4merge $*
El script para el comparador, ha de asegurarse de recibir siete argumentos y de pasar dos de ellos
al script de fusion (merge). Por defecto, Git pasa los siguientes argumentos al programa diff
(comparador):
Ya que solo necesitars 'old-file' y 'new-file', puedes utilizar el siguiente script para extraerlos:
$ cat /usr/local/bin/extDiff
#!/bin/sh
[ $# -eq 7 ] && /usr/local/bin/extMerge "$2" "$5"
Una vez preparado todo esto, puedes ajustar el archivo de configuracin para utilizar tus
herramientas personalizadas de comparacin y resolucin de conflictos. Tenemos varios
parmetros a ajustar: 'merge.tool' para indicar a Git la estrategia que ha de usar,
mergetool.*.cmd para especificar como lanzar el comando, 'mergetool.trustExitCode' para
decir a Git si el cdigo de salida del programa indica una fusin con xito o no, y 'diff.external'
para decir a Git qu comando lanzar para realizar comparaciones. Es decir, has de ejecutar cuatro
comandos de configuracin:
[merge]
tool = extMerge
[mergetool "extMerge"]
cmd = extMerge \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"
trustExitCode = false
[diff]
external = extDiff
Tras ajustar todo esto, si lanzas comandos tales como: $ git diff 32d1776b1 ^ 32d1776b1
En lugar de mostrar las diferencias por lnea de comandos, Git lanzar P4Merge, que tiene una
pinta como la de la Figura 7-1.
Si intentas fusionar (merge) dos ramas y tienes los consabidos conflictos de integracin, puedes
lanzar el comando 'git mergetool'; lanzar P4Merge para ayudarte a resolver los conflictos por
medio de su interfaz grfica.
Lo bonito de estos ajustes con scripts, es que puedes cambiar facilmente tus herramientas de
comparacin (diff) y de fusin (merge). Por ejemplo, para cambiar tus scripts 'extDiff' y
'extMerge' para utilizar KDiff3, tan solo has de editar el archivo 'extMerge:
$ cat /usr/local/bin/extMerge
#!/bin/sh
/Applications/kdiff3.app/Contents/MacOS/kdiff3 $*
A partir de ahora, Git utilizar la herramienta KDiff3 para mostrar y resolver conflictos de
integracin.
Git viene preparado para utilizar bastantes otras herramientas de resolucin de conflictos, sin
necesidad de andar ajustando la configuracin de cdm. Puedes utilizar kdiff3, opendiff, tkdiff,
meld, xxdiff, emerge, vimdiff, o gvimdiff como herramientas de fusionado. Por ejemplo, si no te
interesa utilizar KDiff3 para comparaciones, sino que tan solo te interesa utilizarlo para resolver
conflictos de integracin; teniendo kdiff3 en el path de ejecucin, solo has de lanzar el comando:
Si utilizas este comando en lugar de preparar los archivos 'extMerge' y 'extDiff' antes comentados,
Git utilizar KDiff3 para resolucin de conflictos de integracin y la herramienta estandar diff
para las comparaciones.
El formato y los espacios en blanco son la fuente de los problemas ms sutiles y frustrantes que
muchos desarrolladores se pueden encontrar en entornos colaborativos, especialmente si son
multi-plataforma. Es muy facil que algunos parches u otro trabajo recibido introduzcan sutiles
cambios de espaciado, porque los editores suelen hacerlo inadvertidamente o, trabajando en
entornos multi-plataforma, porque programadores Windows suelen aadir retornos de carro al
final de las lineas que tocan. Git dispone de algunas opciones de configuracin para ayudarnos
con estos problemas.
core.autocrlf
Si ests programando en Windows o utilizando algn otro sistema, pero colaborando con gente
que programa en Windows. Es muy posible que alguna vez te topes con problemas de finales de
lnea. Esto se debe a que Windows utiliza retorno-de-carro y salto-de-linea para marcar los
finales de lnea de sus archivos. Mientras que Mac y Linux utilizan solamente el caracter de
salto-de-linea. Esta es una sutil, pero molesta, diferencia cuando se trabaja en entornos
multi-plataforma.
Este ajuste dejar los finales de lnea CRLF en las extraciones de cdigo (checkout), pero los
finales LF en sistemas Mac o Linux y en el repositorio.
core.whitespace
Git viene preajustado para detectar y resolver algunos de los problemas ms tipicos relacionados
con los espacios en blanco. Puede vigilar acerca de cuatro tipos de problemas de espaciado --dos
los tiene activados por defecto, pero se pueden desactivar; y dos vienen desactivados por defecto,
pero se pueden activar--.
Los dos activos por defecto son 'trailing-space' (espaciado de relleno), que vigila por si hay
espacios al final de las lneas, y 'space-before-tab' (espaciado delante de un tabulador), que mira
por si hay espacios al principio de las lineas o por delante de los tabuladores.
Los dos inactivos por defecto son 'indent-with-non-tab' (indentado sin tabuladores), que vigila
por si alguna lnea empieza con ocho o mas espacios en lugar de con tabuladores; y 'cr-at-eol'
(retorno de carro al final de lnea), que vigila para que haya retornos de carro en todas las lneas.
Puedes decir a Git cuales de ellos deseas activar o desactivar, ajustando el parmetro
'core.whitespace' con los valores on/off separados por comas. Puedes desactivarlos tanto
dejandolos fuera de la cadena de ajustes, como aadiendo el prefijo '-' delante del valor. Por
ejemplo, si deseas activar todos menos 'cr-at-eol' puedes lanzar:
$ git config --global core.whitespace \
trailing-space,space-before-tab,indent-with-non-tab
Git detectar posibles problemas cuando lance un comando 'git diff', e intentar destacarlos en
otro color para que puedas corregirlos antes de confirmar cambios (commit). Tambin pueden
ser tiles estos ajustes cuando ests incorporando parches con 'git apply'. Al incorporar parches,
puedes pedirle a Git que te avise especficamente sobre determinados problemas de espaciado:
O puedes pedirle que intente corregir automticamente los problemas antes de aplicar el parche:
Estas opciones se pueden aplicar tambin al comando 'git rebase'. Si has confirmado cambios con
problemas de espaciado, pero no los has enviado (push) an "aguas arriba". Puedes realizar una
reorganizacin (rebase) con la opcin '--whitespace=fix' para que Git corrija automticamente los
problemas segn va reescribiendo los parches.
Configuracin de Servidor
No hay tantas opciones de configuracin en el lado servidor de Git. Pero hay unas pocas
interesantes que merecen ser tenidas en cuenta.
receive.fsckObjects
Por defecto, Git no suele comprobar la consistencia de todos los objetos que recibe durante un
envio (push). Aunque Git tiene la capacidad para asegurarse de que cada objeto sigue casando
con su suma de control SHA-1 y sigue apuntando a objetos vlidos. No lo suele hacer en todos y
cada uno de los envios (push). Es una operacin costosa, que, dependiendo del tamao del
repositorio, puede llegar a aadir mucho tiempo a cada operacin de envio (push). De todas
formas, si deseas que Git compruebe la consistencia de todos los objetos en todos los envios,
puedes forzarle a hacerlo ajustando a 'true' el parmetro 'receive.fsckObjects':
A partir de ese momento, Git comprobar la integridad del repositorio antes de aceptar ningn
envio (push), para asegurarse de que no est introduciendo datos corruptos.
receive.denyNonFastForwards
Si reorganizas (rebase) confirmaciones de cambio (commit) que ya habias enviado y tratas de
enviarlas (push) de nuevo. O si intentas enviar una confirmacin a una rama remota que no
contiene la confirmacin actualmente apuntada por la rama. Normalmente, la operacin te ser
denegada por la rama remota sobre la que pretendias realizarla. Habitualmente, este es el
comportamiento ms adecuado. Pero, en el caso de las reorganizaciones, cuando ests totalmente
seguro de lo que haces, puedes forzar el envio, utilizando la opcin '-f' en el comando 'git push' a
la rama remota.
Para impedir estos envios forzados de referencias de avance no directo (no fast-forward) a ramas
remotas, es para lo que se emplea el parmetro 'receive.denyNonFastForwards':
Otra manera de obtener el mismo resultado, es a travs de los enganches (hooks) en el lado
servidor. Enganches de los que hablaremos en breve. Esta otra va te permite realizar ajustes ms
finos, tales como denegar refencias de avance no directo, (non-fast-forwards), unicamente a un
grupo de usuarios.
receive.denyDeletes
Uno de los cortocircuitos que suelen utilizar los usuarios para saltarse la politica de
'denyNonFastForwards', suele ser el borrar la rama y luego volver a enviarla de vuelta con la
nueva referencia. En las ltimas versiones de Git (a partir de la 1.6.1), se puede evitar poniendo a
'true' el parmetro 'receive.denyDeletes':
Esto impide el borrado de ramas o de etiquetas por medio de un envio a travs de la mesa (push
across the board), --ningn usuario lo podr hacer--. Para borrar ramas remotas, tendrs que
borrar los archivos de referencia manualmente sobre el propio servidor. Existen tambin algunas
otras maneras ms interesantes de hacer esto mismo, pero para usuarios concretos, a travs de
permisos (ACLs); tal y como veremos al final de este captulo.
Algunos de los ajustes que hemos vistos, pueden ser especificados para un camino (path)
concreto, de tal forma que Git los aplicar unicamente para una carpeta o para un grupo de
archivos determinado. Estos ajustes especficos relacionados con un camino, se denominan
atributos en Git. Y se pueden fijar, bien mediante un archivo '.gitattribute' en uno de los
directorios de tu proyecto (normalmente en la raiz del proyecto), o bien mediante el archivo
'git/info/attributes en el caso de no querer guardar el archivo de atributos dentro de tu proyecto.
Por medio de los atributos, puedes hacer cosas tales como indicar diferentes estrategias de fusin
para archivos o carpetas concretas de tu proyecto, decirle a Git cmo comparar archivos no
textuales, o indicar a Git que filtre ciertos contenidos antes de guardarlos o de extraerlos del
repositorio Git. En esta seccin, aprenderas acerca de algunos atributos que puedes asignar a
ciertos caminos (paths) dentro de tu proyecto Git, viendo algunos ejemplos de cmo utilizar sus
funcionalidades de manera prctica.
Archivos binarios
Un buen truco donde utilizar los atributos Git es para indicarle cuales de los archivos son
binarios, (en los casos en que Git no podra llegar a determinarlo por s mismo), dandole a Git
instruciones especiales sobre cmo tratar estos archivos. Por ejemplo, algunos archivos de texto
se generan automticamente y no tiene sentido compararlos; mientras que algunos archivos
binarios s que pueden ser comparados --vamos a ver cmo indicar a Git cual es cual--.
Para indicar a Git que trate todos los archivos 'pbxproj' como binarios, puedes aadir esta lnea a
tu archivo '.gitattriutes':
*.pbxproj -crlf -diff
A partir de ahora, Git no intentar convertir ni corregir problemas CRLF en los finales de lnea;
ni intentar hacer comparaciones ni mostar diferencias de este archivo cuando lances comandos
'git show' o 'git diff' en tu proyecto. A partir de la versin 1.6 de Git, puedes utilizar una macro en
lugar de las dos opciones '-crlf -diff':
*.pbxproj binary
Esta es una funcionalidad muy util, pero bastante desconocida. Por lo que la ilustrar con unos
ejemplos. En el primero de ellos, utilizars esta tcnica para resolver uno de los problemas ms
engorrosos conocidos por la humanidad: el control de versiones en documentos Word. Todo el
mundo conoce el hecho de que Word es el editor ms horroroso de cuantos hay; pero,
desgraciadamente, todo el mundo lo usa. Si deseas controlar versiones en documentos Word,
puedes aadirlos a un repositorio Git e ir realizando confirmaciones de cambio (commit) cada
vez. Pero, qu ganas con ello?. Si lanzas un comando 'git diff', lo nico que vers ser algo tal
como:
$ git diff
diff --git a/chapter1.doc b/chapter1.doc
index 88839c4..4afcb7c 100644
Binary files a/chapter1.doc and b/chapter1.doc differ
No puedes comparar directamente dos versiones, a no ser que extraigas ambas y las compares
manualmente, no?. Pero resulta que puedes hacerlo bastante mejor utilizando los atributos Git.
Poniendo lo siguiente en tu archivo '.gitattributes':
*.doc diff=word
As decimos a Git que sobre cualquier archivo coincidente con el patrn indicado, (.doc), ha de
utilizar el filtro "word" cuando intentente hacer una comparacin con l. Qu es el filtro "word"?
Tienes que configurarlo t mismo. Por ejemplo, puedes configurar Git para que utilice el
programa 'strings' para convertir los documentos Word en archivos de texto planos, archivos
sobre los que poder realizar comparaciones sin problemas:
A partir de ahora, Git sabe que si intenta realizar una comparacin entre dos momentos
determinados (snapshots), y si cualquiera de los archivos a comparar termina en '.doc', tiene que
pasar antes esos archivos por el filtro "word", es decir, por el programa 'strings'. Esto prepara
versiones texto de los archivos Word, antes de intentar compararlos.
Un ejemplo. He puesto el captulo 1 de este libro en Git, le he aadido algo de texto a un prrafo y
he guardado el documento. Tras lo cual he lanzando el comando 'git diff' para ver lo que ha
cambiado:
$ git diff
diff --git a/chapter1.doc b/chapter1.doc
index c1c8a0a..b93c9e4 100644
--- a/chapter1.doc
+++ b/chapter1.doc
@@ -8,7 +8,8 @@ re going to cover Version Control Systems (VCS) and Git basics
re going to cover how to get it and set it up for the first time if you don
t already have it on your system.
In Chapter Two we will go over basic Git usage - how to use Git for the 80%
-s going on, modify stuff and contribute changes. If the book spontaneously
+s going on, modify stuff and contribute changes. If the book spontaneously
+Let's see if this works.
Git me indica correctamente que he aadido la frase "Let's see if this works". No es perfecto,
--aade bastante basura aleatoria al final--, pero realmente funciona. Si pudieras encontrar o
escribir un conversor suficientemente bueno de-Word-a-texto-plano, esta solucin sera
terriblemente efectiva. Sin embargo, ya que 'strings' est disponible para la mayor parte de los
sistemas Mac y Linux, es buena idea probar primero con l para trabajar con formatos binarios.
Otro problema donde puede ser util esta tcnica, es en la comparacin de imgenes. Un camino
puede ser pasar los archivos JPEG a travs de un filtro para extraer su informacin EXIF --los
metadatos que se graban dentro de la mayoria de formatos grficos--. Si te descargas e instalas el
programa 'exiftool', puedes utilizarlo para convertir tus imagenes a textos (metadatos), de tal
forma que diff podr al menos mostrarte algo til de cualquier cambio que se produzca:
$ echo '*.png diff=exif' >> .gitattributes
$ git config diff.exif.textconv exiftool
Si sustituyes alguna de las imagenes en tu proyecto, y lanzas el comando 'git diff' obtendrs algo
como:
Aqu se v claramente que ha cambiado el tamao del archivo y las dimensiones de la imagen.
La prxima vez que extraigas el archivo, Git le habr inyectado el SHA del objeto binario (blob):
$ rm text.txt
$ git checkout -- text.txt
$ cat test.txt
$Id: 42812b7653c7b88933f8a9d6cad0ca16714b9bb3 $
Pero esto tiene un uso bastante limitado. Si has utilizado alguna vez las sustituciones de CVS o de
Subversion, sabrs que pueden incluir una marca de fecha, --la suma de comprobacin SHA no es
igual de util, ya que, por ser bastante aleatoria, es imposible deducir si una suma SHA es anterior
o posterior a otra--.
Auque resulta que tambin puedes escribir tus propios filtros para realizar sustituciones en los
archivos al guardar o recuperar (commit/checkout). Esos son los filtros "clean" y "smudge". En el
archivo '.gitattibutes', puedes indicar filtros para carpetas o archivos determinados y luego
preparar tus propios scripts para procesarlos justo antes de confirmar cambios en ellos ("clean",
ver Figura 7-2), o justo antes de recuperarlos ("smudge", ver Figura 7-3). Estos filtros pueden
utilizarse para realizar todo tipo de acciones tiles.
Figura 7-2. El filtro "smudge" se usa al extraer (checkout).
El mensaje de confirmacin para esta funcionalidad nos da un ejemplo simple: el de pasar todo
tu cdigo fuente C por el programa'indent' antes de almacenarlo. Puedes hacerlo poniendo los
atributos adecuados en tu archivo '.gitattributes', para filtrar los archivos *.c a travs de "indent":
*.c filter=indent
E indicando despus que el filtro "indent" actuar al manchar (smudge) y al limpiar (clean):
En este ejemplo, cuando confirmes cambios (commit) en archivos con extensin *.c, Git los
pasar previamente a travs del programa 'indent' antes de confirmarlos, y los pasar a travs del
programa 'cat' antes de extraerlos de vuelta al disco. El programa 'cat' es bsicamente
transparente: de l salen los mismos datos que entran. El efecto final de esta combinacin es el de
filtrar todo el cdigo fuente C a travs de 'indent' antes de confirmar cambios en l.
Otro ejemplo interesante es el de poder conseguir una expansin de la clave '$Date$' del estilo de
RCS. Para hacerlo, necesitas un pequeo script que coja el nombre de un archivo, localice la fecha
de la ltima confirmacin de cambios en el proyecto, e inserte dicha informacin en el archivo.
Este podria ser un pequeo script Ruby para hacerlo:
#! /usr/bin/env ruby
data = STDIN.read
last_date = `git log --pretty=format:"%ad" -1`
puts data.gsub('$Date$', '$Date: ' + last_date.to_s + '$')
Simplemente, utiliza el comando 'git log' para obtener la fecha de la ltima confirmacin de
cambios, y sustituye con ella todas las cadenas '$Date$' que encuentre en el flujo de entrada
stdin; imprimiendo luego los resultados. --Debera de ser sencillo de implementarlo en cualquier
otro lenguaje que domines.-- Puedes llamar 'expanddate' a este archivo y ponerlo en el path de
ejecucin. Tras ello, has de poner un filtro en Git (podemos llamarle 'dater'), e indicarle que use
el filtro 'expanddate' para manchar (smudge) los archivos al extraerlos (checkout). Puedes
utilizar una expresin Perl para limpiarlos (clean) al almacenarlos (commit):
Esta expresin Perl extrae cualquier cosa que vea dentro de una cadena '$Date$', para devolverla
a como era en un principio. Una vez preparado el filtro, puedes comprobar su funcionamiento
preparando un archivo que contenga la clave '$Date$' e indicando a Git cual es el atributo para
reconocer ese tipo de archivo:
Al confirmar cambios (commit) y luego extraer (checkout) el archivo de vuelta, vers la clave
sutituida:
Esta es una muestra de lo poderosa que puede resultar esta tcnica para aplicaciones
personalizadas. No obstante, debes de ser cuidadoso, ya que el archivo '.gitattibutes' se almacena
y se transmite junto con el proyecto; pero no as el propio filtro, (en este caso, 'dater'), sin el cual
no puede funcionar. Cuando disees este tipo de filtros, han de estar pensados para que el
proyecto continue funcionando correctamente incluso cuando fallen.
Los atributos de Git permiten realizar algunas cosas interesantes cuando exportas un archivo de
tu proyecto.
export-ignore
Puedes indicar a Git que ignore y no exporte ciertos archivos o carpetas cuando genera un archivo
de almacenamiento. Cuando tienes alguna carpeta o archivo que no deseas incluir en tus
registros, pero quieras tener controlado en tu proyecto, puedes marcarlos a travs del atributo
'export-ignore'.
Por ejemplo, digamos que tienes algunos archivos de pruebas en la carpeta 'test/', y que no tiene
sentido incluirlos en los archivos comprimidos (tarball) al exportar tu proyecto. Puedes aadir la
siguiente lnea al archivo de atributos de Git:
test/ export-ignore
A partir de ese momento, cada vez que lances el comando 'git archive' para crear un archivo
comprimido de tu proyecto, esa carpeta no se incluir en l.
export-subst
Otra cosa que puedes realizar sobre tus archivos es algn tipo de sustitucin simple de claves. Git
te permite poner la cadena '$Format:$' en cualquier archivo, con cualquiera de las claves de
formateo de '--pretty=format' que vimos en el captulo 2. Por ejemplo, si deseas incluir un
archivo llamado 'LAST COMMIT' en tu proyecto, y poner en l automticamente la fecha de la
ltima confirmacin de cambios cada vez que lances el comando 'git archive':
Cuando lances la orden 'git archive', lo que la gente ver en ese archivo cuando lo abra ser:
$ cat LAST_COMMIT
Last commit date: $Format:Tue Apr 21 08:38:48 2009 -0700$
Estrategias de fusin
Tambin puedes utilizar los atributos Git para indicar distintas estrategias de fusin para
archivos especficos de tu proyecto. Una opcin muy util es la que nos permite indicar a Git que
no intente fusionar ciertos archivos concretos cuando tengan conflictos, manteniendo en su lugar
tus archivos sobre los de cualquier otro.
Puede ser interesante si una rama de tu proyecto es divergente o esta especializada, pero deseas
seguir siendo capaz de fusionar cambios de vuelta desde ella, e ignorar ciertos archivos. Digamos
que tienes un archivo de datos denominado database.xml, distinto en las dos ramas, y que deseas
fusionar en la otra rama sin perturbarlo. Puedes ajustar un atributo tal como:
database.xml merge=ours
Al fusionar con otra rama, en lugar de tener conflictos de fusin con el archivo database.xml,
obtendrs algo como:
Y el archivo database.xml permanecer inalterado en cualquier que fuera la versin que t tenias
originalmente.
Al igual que en otros sistemas de control de versiones, Git tambin cuenta con mecanismos para
lanzar scrips de usuario cuando suceden ciertas acciones importantes. Hay dos grupos de esos
puntos de lanzamiento: los del lado cliente y los del lado servidor. Los puntos del lado cliente
estn relacionados con operaciones tales como la confirmacin de cambios (commit) o la fusin
(merge). Los del lado servidor estn relacionados con operaciones tales como la recepcin de
contenidos enviados (push) a un servidor. Estos puntos de enganche pueden utilizarse para
multitud de aplicaciones. Vamos a ver unas pocas de ellas.
Para activar un punto de enganche para un script, pon el archivo correspondiente en la carpeta
'hooks'; con el nombre adecuado y con la marca de ejecutable. A partir de ese momento, ser
automticamente lanzado cuando se d la accin correspondiente. Vamos a ver la mayora de
nombres de puntos de enganche disponibles.
Hay muchos de ellos. En esta seccin los dividiremos en puntos de enganche en el flujo de trabajo
de confirmacin de cambios, puntos en el flujo de trabajo de correo electrnico y resto de puntos
de enganche del lado servidor.
Los scripts del lado cliente relacionados con la confirmacin de cambios pueden ser utilizados en
prcticamente cualquier flujo de trabajo. A menudo, se suelen utilizar para obligar a seguir
ciertas reglas; aunque es importante indicar que estos script no se transfieren durante el clonado.
Puedes implantar reglas en el lado servidor para rechazar envios (push) que no cumplan ciertos
estandares, pero es completamente voluntario para los desarroladores el utilizar scripts en el lado
cliente. Por tanto, estos scripts son para ayudar a los desarrolladores, y, como tales, han de ser
configurados y mantenidos por ellos, pudiendo ser sobreescritos o modificados por ellos en
cualquier momento.
El siguiente punto de enganche que se activa al aplicar parches con 'git am' es el punto
'pre-applypatch'. No recibe ningn argumento de entrada y se lanza despus de que el parche
haya sido aplicado, por lo que puedes utilizarlo para revisar la situacin (snapshot) antes de
confirmarla. Con este script, puedes lanzar pruebas o similares para chequear el arbol de trabajo.
Si falta algo o si alguna de las pruebas falla, saliendo con un cdigo de salida distinto de cero
abortar el comando 'git am' sin confirmar el parche.
El ltimo punto de enganche que se activa durante una operacin 'git am' es el punto
'post-applypatch'. Puedes utilizarlo para notificar de su aplicacin al grupo o al autor del parche.
No puedes detener el proceso de parcheo con este script.
Aparte de los puntos del lado cliente, como administrador de sistemas, puedes utilizar un par de
puntos de enganche importantes en el lado servidor; para implementar prcticamente cualquier
tipo de poltica que quieras mantener en tu proyecto. Estos scripts se lanzan antes y despus de
cada envio (push) al servidor. El script previo, puede terminar con un cdigo de salida distinto de
cero y abortar el envio, devolviendo el correspondiente mensaje de error al cliente. Este script
puede implementar polticas de recepcin tan complejas como desees.
'pre-receive' y 'post-receive'
El primer script que se activa al manejar un envio de un cliente es el correspondiente al punto de
enganche 'pre-receive'. Recibe una lista de referencias que se estn enviando (push) desde la
entrada estandar (stdin); y, si termina con un codigo de salida distinto de cero, ninguna de ellas
ser aceptada. Puedes utilizar este punto de enganche para realizar tareas tales como la de
comprobar que ninguna de las referencias actualizadas no son de avance directo
(non-fast-forward); o para comprobar que el usuario que realiza el envio tiene realmente
permisos para para crear, borrar o modificar cualquiera de los archivos que est tratando de
cambiar.
El punto de enganche 'post-receive' se activa cuando termina todo el proceso, y se puede utilizar
para actualizar otros servicios o para enviar notificaciones a otros usuarios. Recibe los mismos
datos que 'pre-receive' desde la entrada estandar. Algunos ejemplos de posibles aplicaciones
pueden ser la de alimentar una lista de correo-e, avisar a un servidor de integracin continua, o
actualizar un sistema de seguimiento de tickets de servicio --pudiendo incluso procesar el
mensaje de confirmacin para ver si hemos de abrir, modificar o dar por cerrado algn ticket--.
Este script no puede detener el proceso de envio, pero el cliente no se desconecta hasta que no se
completa su ejecucin; por tanto, has de ser cuidadoso cuando intentes realizar con l tareas que
puedan requerir mucho tiempo.
update
El punto de enganche 'update' es muy similar a 'pre-receive', pero con la diferencia de que se
activa una vez por cada rama que se est intentando actualizar con el envio. Si la persona que
realiza el envio intenta actualizar varias ramas, 'pre-receive' se ejecuta una sola vez, mientras que
'update' se ejecuta tantas veces como ramas se estn actualizando. El lugar de recibir datos desde
la entrada estandar (stdin), este script recibe tres argumentos: el nombre de la rama, la clave
SHA-1 a la que esta apuntada antes del envio, y la clave SHA-1 que el usuario est intentando
enviar. Si el script 'update' termina con un cdigo de salida distinto de cero, nicamente los
cambios de esa rama son rechazados; el resto de ramas continuarn con sus actualizaciones.
En esta seccin, utilizars lo aprendido para establecer un flujo de trabajo en Git que: compruebe
si los mensajes de confirmacin de cambios encajan en un determinado formato, obligue a
realizar solo envios de avance directo, y permita solo a ciertos usuarios modificar ciertas carpetas
del proyecto. Para ello, has de preparar los correspondientes scripts de cliente (para ayudar a los
desarrolladores a saber de antemano si sus envios van a ser rechazados o no), y los
correspondientes scripts de servidor (para obligar a cumplir esas polticas).
He usado Ruby para escribir los ejemplos, tanto porque es mi lenguaje preferido de scripting y
porque creo que es el ms parecido a pseudocdigo; de tal forma que puedas ser capaz de seguir
el cdigo, incluso si no conoces Ruby. Pero, puede ser igualmente vlido cualquier otro lenguaje.
Todos los script de ejemplo que vienen de serie con Git estn escritos en Perl o en Bash shell, por
lo que tienes bastantes ejemplos en esos lenguajes de scripting.
Todo el trabajo del lado servidor va en el script 'update' de la carpeta 'hooks'. El script 'update' se
lanza una vez por cada rama que se envia (push) al servidor; y recibe la referencia de la rama a la
que se envia, la antigua revisin en que estaba la rama y la nueva revisin que se est enviando.
Tambin puedes tener acceso al usuario que est enviando, si este los envia a travs de SSH. Si
has permitido a cualquiera conectarse con un mismo usuario (como "git", por ejemplo), has
tenido que dar a dicho usuario una envoltura (shell wraper) que te permite determinar cual es el
usuario que se conecta segn sea su clave pblica, permitiendote fijar una variable de entorno
especificando dicho usuario. Aqui, asumiremos que el usuario conectado queda reflejado en la
variable de entorno '$USER', de tal forma que el script 'update' comienza recogiendo toda la
informacin que necesitas:
$refname = ARGV[0]
$oldrev = ARGV[1]
$newrev = ARGV[2]
$user = ENV['USER']
S, estoy usando variables globales. No me juzgues por ello, --es ms sencillo mostrarlo de esta
manera--.
Puedes obtener la lista de las claves SHA-1 de todos las confirmaciones de cambios enviadas
cogiendo los valores de '$newrev' y de '$oldrev', y pasandolos a comando de mantenimiento de
Git llamado 'git rev-list'. Este comando es bsicamente el mismo que 'git log', pero por defecto,
imprime solo los valores SHA-1 y nada ms. Con l, puedes obtener la lista de todas las claves
SHA que se han introducido entre una clave SHA y otra clave SHA dadas; obtendrs algo as
como esto:
Puedes coger esta salida, establecer un bucle para recorrer cada una de esas confirmaciones de
cambios, coger el mensaje de cada una y comprobarlo contra una expresin regular de bsqueda
del patrn deseado.
Tienes que imaginarte cmo puedes obtener el mensaj ede cada una de esas confirmaciones de
cambios a comprobar. Para obtener los datos "en crudo" de una confirmacin de cambios, puedes
utilizar otro comando de mantenimiento de Git denominado 'git cat-file'. En el captulo 9
volveremos en detalle sobre estos comandos de mantenimiento; pero, por ahora, esto es lo que
obtienes con dicho comando:
Una va sencilla para obtener el mensaje, es la de ir hasta la primera lnea en blanco y luego coger
todo lo que siga a esta. En los sistemas Unix, lo puedes realizar con el comando 'sed':
Puedes usar este "hechizo mgico" para coger el mensaje de cada confirmacin de cambios que se
est enviando y salir si localizas algo que no cuadra en alguno de ellos. Para salir del script y
rechazar el envio, recuerda que debes salir con un cdigo distinto de cero. El mtodo completo
ser algo as como:
Poniendo esto en tu script 'update', sern rechazadas todas las actualizaciones que contengan
cambios con mensajes que no se ajusten a tus reglas.
Como hemos dicho, el primer paso es escribir tu lista de control de accesos (ACL). Su formato es
muy parecido al del mecanismo CVS ACL: utiliza una serie de lneas donde el primer campo es
'avail' o 'unavail' (permitido o no permitido), el segundo campo es una lista de usuarios separados
por comas, y el ltimo campo es la ubicacin (path) sobre el que aplicar la regla (dejarlo en
blanco equivale a un acceso abierto). Cada uno de esos campos se separan entre s con el caracter
barra vertical ('|').
Por ejemplo, si tienes un par de administradores, algunos redactores tcnicos con acceso a la
carpeta 'doc', y un desarrollador que nicamente accede a las carpetas 'lib' y 'test', el archivo ACL
resultante seria:
avail|nickh,pjhyett,defunkt,tpw
avail|usinclair,cdickens,ebronte|doc
avail|schacon|lib
avail|schacon|tests
Para implementarlo, hemos de leer previamente estos datos en una estructura que podamos
emplear. En este caso, por razones de simplicidad, vamos a mostrar nicamente la forma de
implementar las directivas 'avail' (permitir). Este es un mtodo que te devuelve un array
asociativo cuya clave es el nombre del usuario y su valor es un array de ubicaciones (paths) donde
ese usuario tiene acceso de escritura:
def get_acl_access_data(acl_file)
# read in ACL data
acl_file = File.read(acl_file).split("\n").reject { |line| line == '' }
access = {}
acl_file.each do |line|
avail, users, path = line.split('|')
next unless avail == 'avail'
users.split(',').each do |user|
access[user] ||= []
access[user] << path
end
end
access
end
Si lo aplicamos sobre la lista ACL descrita anteriormente, este mtodo 'get acl access_data'
devolver una estructura de datos similar a esta:
{"defunkt"=>[nil],
"tpw"=>[nil],
"nickh"=>[nil],
"pjhyett"=>[nil],
"schacon"=>["lib", "tests"],
"cdickens"=>["doc"],
"usinclair"=>["doc"],
"ebronte"=>["doc"]}
Una vez tienes los permisos en orden, necesitas averiguar las ubicaciones modificadas por las
confirmaciones de cambios enviadas; de tal forma que puedas asegurarte de que el usuario que
las est enviando tiene realmente permiso para modificarlas.
check_directory_permscheck_directory_perms
La mayor parte de este cdigo debera de ser sencillo de leer. Con 'git rev-list', obtienes una lista
de las nuevas confirmaciones de cambio enviadas a tu servidor. Luego, para cada una de ellas,
localizas los archivos modificados y te aseguras de que el usuario que las envia tiene realmente
acceso a todas las ubicaciones que pretende modificar. Un "rubysmo" que posiblemente sea un
tanto oscuro puede ser 'path.index(accesspath) == 0' . Simplemente devuelve verdadero en el
caso de que la ubicacion comience por 'accesspath' ; de esta forma, nos aseguramos de que
'access_path' no est solo contenido en una de las ubicaciones permitidas, sino sea una ubicacin
permitida la que comience con la ubicacin accedida.
Una vez implementado todo esto, tus usuarios no podrn enviar confirmaciones de cambios con
mensajes mal formados o con modificaciones sobre archivos fuera de las ubicaciones que les
hayas designado.
check_fast_forward
Una vez est todo listo. Si lanzas el comando 'chmod u+x .git/hooks/update', siendo este el
archivo donde has puesto todo este cdigo; y luego intentas enviar una referencia que no sea de
avance-rpido, obtendrs algo como esto:
Enforcing Policies...
(refs/heads/master) (fb8c72) (c56860)
Precisamente, lo enviado a la salida estandar stdout justo al principio del script de actualizacin.
Cabe destacar que todo lo que se envie a la salida estandar stdout, ser transferido al cliente.
La primera lnea la has enviado t, pero las otras dos son de Git. Indicando que el script de
actualizacin ha terminado con cdigo no-cero y, por tanto, ha rechazado la modificacin. Y, por
ltimo, se ve:
To git@gitserver:project.git
! [remote rejected] master -> master (hook declined)
error: failed to push some refs to 'git@gitserver:project.git'[remote rejected] master
-> master (hook declined)
error: failed to push some refs to 'git@gitserver:project.git'
Un mensaje por cada referencia rechazada por el enganche (hook) de actualizacin, especificando
que ha sido rechazada precisamente por un fallo en el enganche.
Es ms, si la referencia (ref marker) no se encuentra presente para alguna de las confirmaciones
de cambio, vers el mensaje de error previsto para ello:
O si alguien intenta editar un archivo sobre el que no tiene acceso y luego envia una confirmacin
de cambios con ello, ver tambin algo similar. Por ejemplo, si un editor tcnico intenta enviar
una confirmacin de cambios donde se haya modificado algo de la carpeta 'lib', ver:
Y eso es todo. De ahora en adelante, en tanto en cuando el script 'update' este presente y sea
ejecutable, tu repositorio nunca se ver perjudicado, nunca tendr un mensaje de confirmacin
de cambios sin tu plantilla y tus usuarios estarn controlados.
Lo malo del sistema descrito en la seccin anterior pueden ser los lamentos que inevitablemente
se van a producir cuando los envios de tus usuarios sean rechazados. Ver rechazado en el ltimo
minuto su tan cuidadosamente preparado trabajo, puede ser realmente frustrante. Y, an peor,
tener que reescribir su histrico para corregirlo puede ser un autntico calvario.
La solucin a este dilema es el proporcionarles algunos enganches (hook) del lado cliente, para
que les avisen cuando estn trabajando en algo que el servidor va a rechazarles. De esta forma,
pueden corregir los problemas antes de confirmar cambios y antes de que se conviertan en algo
realmente complicado de arreglar. Debido a que estos enganches no se transfieren junto con el
clonado de un proyecto, tendrs que distribuirlos de alguna otra manera. Y luego pedir a tus
usuarios que se los copien a sus carpetas '.git/hooks' y los hagan ejecutables. Puedes distribuir
esos enganches dentro del mismo proyecto o en un proyecto separado. Pero no hay modo de
implementarlos automticamente.
#!/usr/bin/env ruby
message_file = ARGV[0]
message = File.read(message_file)
if !$regex.match(message)
puts "[POLICY] Your message is not formatted correctly"
exit 1
end
$user = ENV['USER']
check_directory_permscheck_directory_perms
Este es un script prcticamente igual al del lado servidor. Pero con dos importantes diferencias.
La primera es que el archivo ACL est en otra ubicacin, debido a que el script corre desde tu
carpeta de trabajo y no desde la carpeta de Git. Esto obliga a cambiar la ubicacin del archivo
ACL de
access = get_acl_access_data('acl')
access = get_acl_access_data('.git/acl')
La segunda diferencia es la forma de listar los archivos modificados. Debido a que el metodo del
lado servidor utiliza el registro de confirmaciones de cambio, pero, sin embargo, aqu la
confirmacin no se ha registrado an, la lista de archivos se ha de obtener desde el rea de
preparacin (staging area). En lugar de
Estas dos son las nicas diferencias; en todo lo dems, el script funciona de la misma manera. Es
necesario advertir de que se espera que trabajes localmente con el mismo usuario con el que
enviars (push) a la mquina remota. Si no fuera as, tendrs que ajustar manualmente la
variable '$user'.
De todas formas, el nico aspecto accidental que puede interesante capturar son los intentos de
reorganizar confirmaciones de cambios ya enviadas. El servidor te avisar de que no puedes
enviar ningn no-avance-rapido, y el enganche te impedir cualquier envio forzado
Este es un ejemplo de script previo a reorganizacin que lo puede comprobar. Con la lista de
confirmaciones de cambio que ests a punto de reescribir, las comprueba por si alguna de ellas
existe en alguna de tus referencias remotas. Si encuentra alguna, aborta la reorganizacin:
base_branch = ARGV[0]
if ARGV[1]
topic_branch = ARGV[1]
else
topic_branch = "HEAD"
end
target_shas.each do |sha|
remote_refs.each do |remote_ref|
shas_pushed = `git rev-list ^#{sha}^@ refs/remotes/#{remote_ref}`
if shas_pushed.split(\n).include?(sha)
puts "[POLICY] Commit #{sha} has already been pushed to #{remote_ref}"
exit 1
end
end
end
Este script utiliza una sintaxis no contemplada en la seccin de Seleccin de Revisiones del
captulo 6. La lista de confirmaciones de cambio previamente enviadas, se comprueba con:
La sintaxis 'SHA^@' recupera todos los padres de esa confirmacin de cambios (commit). Estas
mirando por cualquier confirmacin que se pueda alcanzar desde la ltima en la parte remota,
pero que no se pueda alcanzar desde ninguno de los padres de cualquiera de las claves SHA que
ests intentando enviar. Es decir, confirmaciones de avance-rpido.
La mayor pega de este sistema es el que puede llegar a ser muy lento; y muchas veces es
innecesario, ya que el propio servidor te va a avisar y te impedir el envio, siempre y cuando no
intentes forzar dicho envio con la opcin '-f'. De todas formas, es un ejercicio interesante. Y, en
teoria al menos, pude ayudarte a evitar reorganizaciones que luego tengas de hechar para atras y
arreglarlas.
Chapter 8
Git y Otros Sistemas
El mundo no es perfecto. Lo ms normal es que no puedas cambiar inmediatamente a Git cada
proyecto que te encuentras. Algunas veces ests atascado en un proyecto utilizando otro VCS, y
muchas veces ese sistema es Subversion. Pasaremos la primera parte de este captulo
aprendiendo sobre git svn, la puerta de enlace de Subversion bidireccional en Git.
En un momento dado, quiz quieras convertir tu proyecto existente a Git. La segunda parte de
este captulo cubre cmo migrar tu proyecto a Git: primero desde Subversion, luego desde
Perforce, y finalmente por medio de un script de importacin a medida para casos no estndar.
Hoy por hoy, la mayora de los proyectos de cdigo abierto y un gran nmero de proyectos
corporativos usan Subversion para manejar su cdigo fuente. Es el VCS ms popular y lleva ah
casi una dcada. Tambin es muy similar en muchas cosas a CVS, que fue el rey del control de
cdigo fuente anteriormente.
Una de las grandes caractersticas de Git es el puente bidireccional llamado git svn. Esta
herramienta permite el uso de Git como un cliente vlido para un servidor Subversion, as que
puedes utilizar todas las caractersticas locales de Git y luego hacer publicaciones al servidor de
Subversion como si estuvieras usando Subversion localmente. Esto significa que puedes ramificar
y fusionar localmente, usar el rea de preparacin, reconstruir, entresacar, etc., mientras tus
colaboradores continan usando sus antiguos y oscuros mtodos. Es una buena forma de colar a
Git dentro de un ambiente corporativo y ayudar a tus colegas desarrolladores a hacerse ms
eficientes mientras tu haces presin para que se cambie la infraestructura y el soporte de Git sea
completo. El puente de Subversion es la "droga de entrada" al mundo de DVCS.
git svn
El comando bsico de Git para todos los comandos de enlace con Subversion es git svn. Siempre
debes empezar con eso. Hay unos cuantos, por lo que vamos a aprender los bsicos recorriendo
unos pocos flujos de trabajo pequeos.
Es importante fijarse en que cuando usas git svn ests interactuando con Subversion, que es un
sistema mucho menos sofisticado que Git. Aunque puedes hacer ramas y fusiones localmente, lo
mejor es mantener tu historia lo ms lineal posible mediante reorganizaciones, y evitar hacer
cosas como interactuar a la vez con un repositorio remoto de Git.
Setting Up
To demonstrate this functionality, you need a typical SVN repository that you have write access
to. If you want to copy these examples, youll have to make a writeable copy of my test repository.
In order to do that easily, you can use a tool called svnsync that comes with more recent versions
of Subversion it should be distributed with at least 1.4. For these tests, I created a new
Subversion repository on Google code that was a partial copy of the protobuf project, which is a
tool that encodes structured data for network transmission.
To follow along, you first need to create a new local Subversion repository:
$ mkdir /tmp/test-svn
$ svnadmin create /tmp/test-svn
Then, enable all users to change revprops the easy way is to add a pre-revprop-change script
that always exits 0:
$ cat /tmp/test-svn/hooks/pre-revprop-change
#!/bin/sh
exit 0;
$ chmod +x /tmp/test-svn/hooks/pre-revprop-change
You can now sync this project to your local machine by calling svnsync init with the to and from
repositories.
$ svnsync init file:///tmp/test-svn http://progit-example.googlecode.com/svn/
This sets up the properties to run the sync. You can then clone the code by running
Although this operation may take only a few minutes, if you try to copy the original repository to
another remote repository instead of a local one, the process will take nearly an hour, even
though there are fewer than 100 commits. Subversion has to clone one revision at a time and
then push it back into another repository its ridiculously inefficient, but its the only easy way
to do this.
Getting Started
Now that you have a Subversion repository to which you have write access, you can go through a
typical workflow. Youll start with the git svn clone command, which imports an entire
Subversion repository into a local Git repository. Remember that if youre importing from a real
hosted Subversion repository, you should replace the file:///tmp/test-svn here with the URL of
your Subversion repository:
This runs the equivalent of two commands git svn init followed by git svn fetch on the
URL you provide. This can take a while. The test project has only about 75 commits and the
codebase isnt that big, so it takes just a few minutes. However, Git has to check out each version,
one at a time, and commit it individually. For a project with hundreds or thousands of commits,
this can literally take hours or even days to finish.
The -T trunk -b branches -t tags part tells Git that this Subversion repository follows the
basic branching and tagging conventions. If you name your trunk, branches, or tags differently,
you can change these options. Because this is so common, you can replace this entire part with -s,
which means standard layout and implies all those options. The following command is
equivalent:
At this point, you should have a valid Git repository that has imported your branches and tags:
$ git branch -a
* master
my-calc-branch
tags/2.0.2
tags/release-2.0.1
tags/release-2.0.2
tags/release-2.0.2rc1
trunk
Its important to note how this tool namespaces your remote references differently. When youre
cloning a normal Git repository, you get all the branches on that remote server available locally as
something like origin/[branch] - namespaced by the name of the remote. However, git svn
assumes that you wont have multiple remotes and saves all its references to points on the remote
server with no namespacing. You can use the Git plumbing command show-ref to look at all
your full reference names:
$ git show-ref
1cbd4904d9982f386d87f88fce1c24ad7c0f0471 refs/heads/master
aee1ecc26318164f355a883f5d99cff0c852d3c4 refs/remotes/my-calc-branch
03d09b0e2aad427e34a6d50ff147128e76c0e0f5 refs/remotes/tags/2.0.2
50d02cc0adc9da4319eeba0900430ba219b9c376 refs/remotes/tags/release-2.0.1
4caaa711a50c77879a91b8b90380060f672745cb refs/remotes/tags/release-2.0.2
1c4cb508144c513ff1214c3488abe66dcb92916f refs/remotes/tags/release-2.0.2rc1
1cbd4904d9982f386d87f88fce1c24ad7c0f0471 refs/remotes/trunk
$ git show-ref
83e38c7a0af325a9722f2fdc56b10188806d83a1 refs/heads/master
3e15e38c198baac84223acfc6224bb8b99ff2281 refs/remotes/gitserver/master
0a30dd3b0c795b80212ae723640d4e5d48cabdff refs/remotes/origin/master
25812380387fdd55f916652be4881c6f11600d6f refs/remotes/origin/testing
You have two remote servers: one named gitserver with a master branch; and another named
origin with two branches, master and testing.
Notice how in the example of remote references imported from git svn, tags are added as remote
branches, not as real Git tags. Your Subversion import looks like it has a remote named tags with
branches under it.
Now that you have a working repository, you can do some work on the project and push your
commits back upstream, using Git effectively as a SVN client. If you edit one of the files and
commit it, you have a commit that exists in Git locally that doesnt exist on the Subversion server:
Next, you need to push your change upstream. Notice how this changes the way you work with
Subversion you can do several commits offline and then push them all at once to the
Subversion server. To push to a Subversion server, you run the git svn dcommit command:
This takes all the commits youve made on top of the Subversion server code, does a Subversion
commit for each, and then rewrites your local Git commit to include a unique identifier. This is
important because it means that all the SHA-1 checksums for your commits change. Partly for
this reason, working with Git-based remote versions of your projects concurrently with a
Subversion server isnt a good idea. If you look at the last commit, you can see the new git-svn-id
that was added:
$ git log -1
commit 938b1a547c2cc92033b74d32030e86468294a5c8
Author: schacon <schacon@4c93b258-373f-11de-be05-5f7a86268029>
Date: Sat May 2 22:06:44 2009 +0000
git-svn-id: file:///tmp/test-svn/trunk@79
4c93b258-373f-11de-be05-5f7a86268029
Notice that the SHA checksum that originally started with 97031e5 when you committed now
begins with 938b1a5. If you want to push to both a Git server and a Subversion server, you have
to push (dcommit) to the Subversion server first, because that action changes your commit data.
If youre working with other developers, then at some point one of you will push, and then the
other one will try to push a change that conflicts. That change will be rejected until you merge in
their work. In git svn, it looks like this:
To resolve this situation, you can run git svn rebase, which pulls down any changes on the
server that you dont have yet and rebases any work you have on top of what is on the server:
$ git svn rebase
M README.txt
r80 = ff829ab914e8775c7c025d741beb3d523ee30bc4 (trunk)
First, rewinding head to replay your work on top of it...
Applying: first user change
Now, all your work is on top of what is on the Subversion server, so you can successfully
dcommit:
Its important to remember that unlike Git, which requires you to merge upstream work you dont
yet have locally before you can push, git svn makes you do that only if the changes conflict. If
someone else pushes a change to one file and then you push a change to another file, your
dcommit will work fine:
You should also run this command to pull in changes from the Subversion server, even if youre
not ready to commit yourself. You can run git svn fetch to grab the new data, but git svn
rebase does the fetch and then updates your local commits.
Running git svn rebase every once in a while makes sure your code is always up to date. You
need to be sure your working directory is clean when you run this, though. If you have local
changes, you must either stash your work or temporarily commit it before running git svn
rebase otherwise, the command will stop if it sees that the rebase will result in a merge
conflict.
When youve become comfortable with a Git workflow, youll likely create topic branches, do
work on them, and then merge them in. If youre pushing to a Subversion server via git svn, you
may want to rebase your work onto a single branch each time instead of merging branches
together. The reason to prefer rebasing is that Subversion has a linear history and doesnt deal
with merges like Git does, so git svn follows only the first parent when converting the snapshots
into Subversion commits.
Suppose your history looks like the following: you created an experiment branch, did two
commits, and then merged them back into master. When you dcommit, you see output like this:
Running dcommit on a branch with merged history works fine, except that when you look at
your Git project history, it hasnt rewritten either of the commits you made on the experiment
branch instead, all those changes appear in the SVN version of the single merge commit.
When someone else clones that work, all they see is the merge commit with all the work squashed
into it; they dont see the commit data about where it came from or when it was committed.
Subversion Branching
Branching in Subversion isnt the same as branching in Git; if you can avoid using it much, thats
probably best. However, you can create and commit to branches in Subversion using git svn.
This does the equivalent of the svn copy trunk branches/opera command in Subversion and
operates on the Subversion server. Its important to note that it doesnt check you out into that
branch; if you commit at this point, that commit will go to trunk on the server, not opera.
Switching Active Branches
Git figures out what branch your dcommits go to by looking for the tip of any of your Subversion
branches in your history you should have only one, and it should be the last one with a
git-svn-id in your current branch history.
If you want to work on more than one branch simultaneously, you can set up local branches to
dcommitto specific Subversion branches by starting them at the imported Subversion commit
for that branch. If you want an opera branch that you can work on separately, you can run
Now, if you want to merge your opera branch into trunk (your master branch), you can do so
with a normal git merge. But you need to provide a descriptive commit message (via -m), or the
merge will say "Merge branch opera" instead of something useful.
Remember that although youre using git merge to do this operation, and the merge likely will
be much easier than it would be in Subversion (because Git will automatically detect the
appropriate merge base for you), this isnt a normal Git merge commit. You have to push this
data back to a Subversion server that cant handle a commit that tracks more than one parent; so,
after you push it up, it will look like a single commit that squashed in all the work of another
branch under a single commit. After you merge one branch into another, you cant easily go back
and continue working on that branch, as you normally can in Git. The dcommit command that
you run erases any information that says what branch was merged in, so subsequent merge-base
calculations will be wrong the dcommit makes your git merge result look like you ran git
merge --squash. Unfortunately, theres no good way to avoid this situation Subversion cant
store this information, so youll always be crippled by its limitations while youre using it as your
server. To avoid issues, you should delete the local branch (in this case, opera) after you merge it
into trunk.
Subversion Commands
The git svn toolset provides a number of commands to help ease the transition to Git by
providing some functionality thats similar to what you had in Subversion. Here are a few
commands that give you what Subversion used to.
autogen change
------------------------------------------------------------------------
r86 | schacon | 2009-05-02 16:00:21 -0700 (Sat, 02 May 2009) | 2 lines
------------------------------------------------------------------------
r85 | schacon | 2009-05-02 16:00:09 -0700 (Sat, 02 May 2009) | 2 lines
You should know two important things about git svn log. First, it works offline, unlike the real
svn log command, which asks the Subversion server for the data. Second, it only shows you
commits that have been committed up to the Subversion server. Local Git commits that you
havent dcommited dont show up; neither do commits that people have made to the Subversion
server in the meantime. Its more like the last known state of the commits on the Subversion
server.
SVN Annotation
Much as the git svn log command simulates the svn log command offline, you can get the
equivalent of svn annotate by running git svn blame [FILE]. The output looks like this:
Again, it doesnt show commits that you did locally in Git or that have been pushed to Subversion
in the meantime.
This is like blame and log in that it runs offline and is up to date only as of the last time you
communicated with the Subversion server.
The second command is git svn show-ignore, which prints to stdout the lines you need to put
in a .gitignore file so you can redirect the output into your project exclude file:
That way, you dont litter the project with .gitignore files. This is a good option if youre the only
Git user on a Subversion team, and your teammates dont want .gitignore files in the project.
Git-Svn Summary
The git svn tools are useful if youre stuck with a Subversion server for now or are otherwise in a
development environment that necessitates running a Subversion server. You should consider it
crippled Git, however, or youll hit issues in translation that may confuse you and your
collaborators. To stay out of trouble, try to follow these guidelines:
Keep a linear Git history that doesnt contain merge commits made by git merge.
Rebase any work you do outside of your mainline branch back onto it; dont merge it
in.
Dont set up and collaborate on a separate Git server. Possibly have one to speed up
clones for new developers, but dont push anything to it that doesnt have a
git-svn-id entry. You may even want to add a pre-receive hook that checks each
commit message for a git-svn-id and rejects pushes that contain commits without it.
If you follow those guidelines, working with a Subversion server can be more bearable. However,
if its possible to move to a real Git server, doing so can gain your team a lot more.
If you have an existing codebase in another VCS but youve decided to start using Git, you must
migrate your project one way or another. This section goes over some importers that are included
with Git for common systems and then demonstrates how to develop your own custom importer.
Importing
Youll learn how to import data from two of the bigger professionally used SCM systems
Subversion and Perforce both because they make up the majority of users I hear of who are
currently switching, and because high-quality tools for both systems are distributed with Git.
Subversion
If you read the previous section about using git svn, you can easily use those instructions to git
svn clone a repository; then, stop using the Subversion server, push to a new Git server, and
start using that. If you want the history, you can accomplish that as quickly as you can pull the
data out of the Subversion server (which may take a while).
However, the import isnt perfect; and because it will take so long, you may as well do it right.
The first problem is the author information. In Subversion, each person committing has a user on
the system who is recorded in the commit information. The examples in the previous section
show schacon in some places, such as the blame output and the git svn log. If you want to map
this to better Git author data, you need a mapping from the Subversion users to the Git authors.
Create a file called users.txtthat has this mapping in a format like this:
To get a list of the author names that SVN uses, you can run this:
That gives you the log output in XML format you can look for the authors, create a unique list,
and then strip out the XML. (Obviously this only works on a machine with grep, sort, and perl
installed.) Then, redirect that output into your users.txt file so you can add the equivalent Git
user data next to each entry.
You can provide this file to git svn to help it map the author data more accurately. You can also
tell git svn not to include the metadata that Subversion normally imports, by passing
--no-metadata to the clone or init command. This makes your import command look like this:
Now you should have a nicer Subversion import in your my_project directory. Instead of
commits that look like this
commit 37efa680e8473b615de980fa935944215428a35a
Author: schacon <schacon@4c93b258-373f-11de-be05-5f7a86268029>
Date: Sun May 3 00:12:22 2009 +0000
git-svn-id: https://my-project.googlecode.com/svn/trunk@94
4c93b258-373f-11de-
be05-5f7a86268029
commit 03a8785f44c8ea5cdb0e8834b7c8e6c469be2ff2
Author: Scott Chacon <schacon@geemail.com>
Date: Sun May 3 00:12:22 2009 +0000
Not only does the Author field look a lot better, but the git-svn-id is no longer there, either.
You need to do a bit of post-import cleanup. For one thing, you should clean up the weird
references that git svn set up. First youll move the tags so theyre actual tags rather than strange
remote branches, and then youll move the rest of the branches so theyre local.
This takes the references that were remote branches that started with tag/ and makes them real
(lightweight) tags.
Next, move the rest of the references under refs/remotes to be local branches:
Now all the old branches are real Git branches and all the old tags are real Git tags. The last thing
to do is add your new Git server as a remote and push to it. Because you want all your branches
and tags to go up, you can run this:
All your branches and tags should be on your new Git server in a nice, clean import.
Perforce
The next system youll look at importing from is Perforce. A Perforce importer is also distributed
with Git, but only in the contrib section of the source code it isnt available by default like git
svn. To run it, you must get the Git source code, which you can download from git.kernel.org:
In this fast-import directory, you should find an executable Python script named git-p4. You
must have Python and the p4 tool installed on your machine for this import to work. For
example, youll import the Jam project from the Perforce Public Depot. To set up your client, you
must export the P4PORT environment variable to point to the Perforce depot:
$ export P4PORT=public.perforce.com:1666
Run the git-p4 clone command to import the Jam project from the Perforce server, supplying
the depot and project path and the path into which you want to import the project:
If you go to the /opt/p4import directory and run git log, you can see your imported work:
$ git log -2
commit 1fd4ec126171790efd2db83548b85b1bbbc07dc2
Author: Perforce staff <support@perforce.com>
Date: Thu Aug 19 10:18:45 2004 -0800
Drop 'rc3' moniker of jam-2.5. Folded rc2 and rc3 RELNOTES into
the main part of the document. Built new tar/zip balls.
commit ca8870db541a23ed867f38847eda65bf4363371d
Author: Richard Geiger <rmg@perforce.com>
Date: Tue Apr 22 20:51:34 2003 -0800
Update derived jamgram.c
You can see the git-p4 identifier in each commit. Its fine to keep that identifier there, in case you
need to reference the Perforce change number later. However, if youd like to remove the
identifier, now is the time to do so before you start doing work on the new repository. You can
use git filter-branch to remove the identifier strings en masse:
If you run git log, you can see that all the SHA-1 checksums for the commits have changed, but
the git-p4 strings are no longer in the commit messages:
$ git log -2
commit 10a16d60cffca14d454a15c6164378f4082bc5b0
Author: Perforce staff <support@perforce.com>
Date: Thu Aug 19 10:18:45 2004 -0800
Drop 'rc3' moniker of jam-2.5. Folded rc2 and rc3 RELNOTES into
the main part of the document. Built new tar/zip balls.
commit 2b6c6db311dd76c34c66ec1c40a49405e6b527b2
Author: Richard Geiger <rmg@perforce.com>
Date: Tue Apr 22 20:51:34 2003 -0800
A Custom Importer
If your system isnt Subversion or Perforce, you should look for an importer online quality
importers are available for CVS, Clear Case, Visual Source Safe, even a directory of archives. If
none of these tools works for you, you have a rarer tool, or you otherwise need a more custom
importing process, you should use git fast-import. This command reads simple instructions
from stdin to write specific Git data. Its much easier to create Git objects this way than to run the
raw Git commands or try to write the raw objects (see Chapter 9 for more information). This way,
you can write an import script that reads the necessary information out of the system youre
importing from and prints straightforward instructions to stdout. You can then run this program
and pipe its output through git fast-import.
To quickly demonstrate, youll write a simple importer. Suppose you work in current, you back up
your project by occasionally copying the directory into a time-stamped back_YYYY_MM_DD
backup directory, and you want to import this into Git. Your directory structure looks like this:
$ ls /opt/import_from
back_2009_01_02
back_2009_01_04
back_2009_01_14
back_2009_02_03
current
In order to import a Git directory, you need to review how Git stores its data. As you may
remember, Git is fundamentally a linked list of commit objects that point to a snapshot of
content. All you have to do is tell fast-import what the content snapshots are, what commit data
points to them, and the order they go in. Your strategy will be to go through the snapshots one at
a time and create commits with the contents of each directory, linking each commit back to the
previous one.
As you did in the "An Example Git Enforced Policy" section of Chapter 7, well write this in Ruby,
because its what I generally work with and it tends to be easy to read. You can write this example
pretty easily in anything youre familiar with it just needs to print the appropriate information
to stdout. And, if you are running on Windows, this means you'll need to take special care to not
introduce carriage returns at the end your lines git fast-import is very particular about just
wanting line feeds (LF) not the carriage return line feeds (CRLF) that Windows uses.
To begin, youll change into the target directory and identify every subdirectory, each of which is
a snapshot that you want to import as a commit. Youll change into each subdirectory and print
the commands necessary to export it. Your basic main loop looks like this:
last_mark = nil
You run print_export inside each directory, which takes the manifest and mark of the previous
snapshot and returns the manifest and mark of this one; that way, you can link them properly.
"Mark" is the fast-import term for an identifier you give to a commit; as you create commits,
you give each one a mark that you can use to link to it from other commits. So, the first thing to
do in your print_exportmethod is generate a mark from the directory name:
mark = convert_dir_to_mark(dir)
Youll do this by creating an array of directories and using the index value as the mark, because a
mark must be an integer. Your method looks like this:
$marks = []
def convert_dir_to_mark(dir)
if !$marks.include?(dir)
$marks << dir
end
($marks.index(dir) + 1).to_s
end
Now that you have an integer representation of your commit, you need a date for the commit
metadata. Because the date is expressed in the name of the directory, youll parse it out. The next
line in your print_export file is
date = convert_dir_to_date(dir)
where convert_dir_to_date is defined as
def convert_dir_to_date(dir)
if dir == 'current'
return Time.now().to_i
else
dir = dir.gsub('back_', '')
(year, month, day) = dir.split('_')
return Time.local(year, month, day).to_i
end
end
That returns an integer value for the date of each directory. The last piece of meta-information
you need for each commit is the committer data, which you hardcode in a global variable:
Now youre ready to begin printing out the commit data for your importer. The initial
information states that youre defining a commit object and what branch its on, followed by the
mark youve generated, the committer information and commit message, and then the previous
commit, if any. The code looks like this:
You hardcode the time zone (-0700) because doing so is easy. If youre importing from another
system, you must specify the time zone as an offset. The commit message must be expressed in a
special format:
data (size)\n(contents)
The format consists of the word data, the size of the data to be read, a newline, and finally the
data. Because you need to use the same format to specify the file contents later, you create a
helper method, export_data:
def export_data(string)
print "data #{string.size}\n#{string}"
end
All thats left is to specify the file contents for each snapshot. This is easy, because you have each
one in a directory you can print out the deleteall command followed by the contents of each
file in the directory. Git will then record each snapshot appropriately:
puts 'deleteall'
Dir.glob("**/*").each do |file|
next if !File.file?(file)
inline_data(file)
end
Note: Because many systems think of their revisions as changes from one commit to another,
fast-import can also take commands with each commit to specify which files have been added,
removed, or modified and what the new contents are. You could calculate the differences between
snapshots and provide only this data, but doing so is more complex you may as well give Git all
the data and let it figure it out. If this is better suited to your data, check the fast-import man
page for details about how to provide your data in this manner.
The format for listing the new file contents or specifying a modified file with the new contents is
as follows:
Here, 644 is the mode (if you have executable files, you need to detect and specify 755 instead),
and inline says youll list the contents immediately after this line. Your inline_data method looks
like this:
The last thing you need to do is to return the current mark so it can be passed to the next
iteration:
return mark
NOTE: If you are running on Windows you'll need to make sure that you add one extra step. As
mentioned before, Windows uses CRLF for new line characters while git fast-import expects only
LF. To get around this problem and make git fast-import happy, you need to tell ruby to use LF
instead of CRLF:
$stdout.binmode
Thats it. If you run this script, youll get content that looks something like this:
$ git init
Initialized empty Git repository in /opt/import_to/.git/
$ ruby import.rb /opt/import_from | git fast-import
git-fast-import statistics:
---------------------------------------------------------------------
Alloc'd objects: 5000
Total objects: 18 ( 1 duplicates )
blobs : 7( 1 duplicates 0 deltas)
trees : 6( 0 duplicates 1 deltas)
commits: 5( 0 duplicates 0 deltas)
tags : 0( 0 duplicates 0 deltas)
Total branches: 1( 1 loads )
marks: 1024 ( 5 unique )
atoms: 3
Memory total: 2255 KiB
pools: 2098 KiB
objects: 156 KiB
---------------------------------------------------------------------
pack_report: getpagesize() = 4096
pack_report: core.packedGitWindowSize = 33554432
pack_report: core.packedGitLimit = 268435456
pack_report: pack_used_ctr = 9
pack_report: pack_mmap_calls = 5
pack_report: pack_open_windows = 1/ 1
pack_report: pack_mapped = 1356 / 1356
---------------------------------------------------------------------
As you can see, when it completes successfully, it gives you a bunch of statistics about what it
accomplished. In this case, you imported 18 objects total for 5 commits into 1 branch. Now, you
can run git log to see your new history:
$ git log -2
commit 10bfe7d22ce15ee25b60a824c8982157ca593d41
Author: Scott Chacon <schacon@example.com>
Date: Sun May 3 12:57:39 2009 -0700
There you go a nice, clean Git repository. Its important to note that nothing is checked out
you dont have any files in your working directory at first. To get them, you must reset your
branch to where master is now:
$ ls
$ git reset --hard master
HEAD is now at 10bfe7d imported from current
$ ls
file.rb lib
You can do a lot more with the fast-import tool handle different modes, binary data, multiple
branches and merging, tags, progress indicators, and more. A number of examples of more
complex scenarios are available in the contrib/fast-import directory of the Git source code; one
of the better ones is the git-p4 script I just covered.
Chapter 9
Los entresijos internos de Git
Puedes que hayas llegado a este captulo saltando desde alguno previo o puede que hayas llegado
tras leer todo el resto del libro. --En uno u otro caso, aqu es donde aprenders acerca del
funcionamiento interno y la implementacin de Git--. Me parece que esta informacin es
realmente importante para entender can util y potente es Git. Pero algunas personas opinan que
puede ser confuso e innecesariamente complejo para novatos. Por ello, lo he puesto al final del
libro; de tal forma que puedas leerlo antes o despus, en cualquier momento, a lo largo de tu
proceso de aprendizaje. Lo dejo en tus manos.
Y, ahora que estamos aqu, comencemos con el tema. Ante todo, por si no estuviera
suficientemente claro ya, Git es fundamentalmente un sistema de archivo (filesystem) con un
interface de usuario (VCS) escrito sobre l. En breve lo veremos con ms detalle.
En los primeros tiempos de Git (principalmente antes de la versin 1.5), el interface de usuario
era mucho ms complejo, ya que se centraba en el sistema de archivos en lugar de en el VCS. En
los ltimos aos, el IU se ha refinado hasta llegar a ser tan limpio y sencillo de usar como el de
cualquier otro sistema; pero frecuentemente, el estereotipo sigue mostrando a Git como complejo
y dificil de aprender.
La capa del sistema de archivos que almacena el contenido es increiblemente interesante; por
ello, es lo primero que voy a desarrollar en este captulo. A continuacin mostrar los
mecanismos de transporte y las tareas de mantenimiento del repositorio que posiblemente
necesites usar alguna vez.
Este libro habla acerca de como utilizar Git con ms o menos 30 verbos, tales como 'checkout',
'branch', 'remote', etc. Pero, debido al origen de Git como una caja de herramientas para un VCS
en lugar de como un completo y amigable sistema VCS, existen unos cuantos verbos para realizar
tareas de bajo nivel y que se disearon para poder ser utilizados de forma encadenada al estilo
UNIX o para ser utilizados en scripts. Estos comandos son conocidos como los "comandos de
fontanera", mientras que los comandos ms amigables son conocidos como los "comandos de
porcelana".
Los primeros ocho captulos de este libro se encargan casi exclusivamente de los comandos de
porcelana. Pero en este captulo trataremos sobre todo con los comandos de fontaneria;
comandos que te darn acceso a los entresijos internos de Git y que te ayudarn a comprender
cmo y por qu hace Git lo que hace como lo hace. Estos comando no estn pensados para ser
utilizados manualmente desde la lnea de comandos; sino ms bien para ser utilizados como
bloques de construccin para nuevas herramientas y scripts de usuario personalizados.
Cuando lanzas 'git init' sobre una carpeta nueva o sobre una ya existente, Git crea la carpeta
auxiliar '.git'; la carpeta donde se ubica prcticamente todo lo almacenado y manipulado por Git.
Si deseas hacer una copia de seguridad de tu repositorio, con tan solo copiar esta carpeta a
cualquier otro lugar ya tienes tu copia completa. Todo este captulo se encarga de repasar el
contenido en dicha carpeta. Tiene un aspecto como este:
$ ls
HEAD
branches/
config
description
hooks/
index
info/
objects/
refs/
Puede que veas algunos otros archivos en tu carpeta '.git', pero este es el contenido de un
repositorio recin creado tras ejecutar 'git init', --es la estructura por defecto--. La carpeta
'branches' no se utiliza en las ltimas versiones de Git, y el archivo 'description' se utiliza solo en
el programa GitWeb; por lo que no necesitas preocuparte por ellos. El archivo 'config' contiene
las opciones de configuracin especficas de este proyecto concreto, y la carpeta 'info' guarda un
archivo global de exclusin con los patrones a ignorar ademas de los presentes en el archivo
.gitignore. La carpeta 'hooks' contiene tus scripts, tanto de la parte cliente como de la parte
servidor, tal y como se ha visto a detalle en el captulo 6.
Esto nos deja con cuatro entradas importantes: los archivos 'HEAD' e 'index' y las carpetas
'objects' y 'refs'. Estos elementos forman el ncleo de Git. La carpeta 'objects' guarda el contenido
de tu base de datos, la carpeta 'refs' guarda los apuntadores a las confirmaciones de cambios
(commits), el archivo 'HEAD' apunta a la rama que tengas activa (checked out) en este momento,
y el archivo 'index' es donde Git almacena la informacin sobre tu area de preparacin (staging
area). Vamos a mirar en detalle cada una de esas secciones, para ver cmo trabaja Git.
9.2 Los entresijos internos de Git -
Los objetos Git
Los objetos Git
$ mkdir test
$ cd test
$ git init
Initialized empty Git repository in /tmp/test/.git/
$ find .git/objects
.git/objects
.git/objects/info
.git/objects/pack
$ find .git/objects -type f
$
Git ha inicializado la carpeta 'objects', creando en ella las subcarpetas 'pack' e 'info'; pero an no
hay archivos en ellas. Luego, guarda algo de texto en la base de datos de Git:
La opcin '-w' indica a 'hash-object' que ha de guardar el objeto; de otro modo, el comando solo
te respondera cual sera su clave. La opcin '--stdin' indica al comando de leer desde la entrada
estandar stdin; si no lo indicas, 'hash-object' espera encontrar la ubicacin de un archivo. La
salida del comando es una suma de comprobacin (checksum hash) de 40 caracteres. Este
checksum hash SHA-1 es una suma de comprobacin del contenido que ests guardando ms una
cabecera; cabecera sobre la que trataremos en breve. En estos momentos, ya puedes comprobar
la forma en que Git ha guardado tus datos:
Como puedes ver, hay un archivo en la carpeta 'objects'. En principio, esta es la forma en que
guarda Git los contenidos; como un archivo por cada pieza de contenido, nombrado con la suma
de comprobacin SHA-1 del contenido y su cabecera. La subcarpeta se nombra con los primeros 2
caracteres del SHA, y el archivo con los restantes 38 caracteres.
Puedes volver a recuperar los contenidos usando el comando 'cat-file'. Este comando es algo as
como una "navaja suiza" para inspeccionar objetos Git. Pasandole la opcin '-p', puedes indicar al
comando 'cat-file' que deduzca el tipo de contenido y te lo muestre adecuadamente:
Ahora que sabes cmo aadir contenido a Git y cmo recuperarlo de vuelta. Lo puedes hacer
tambin con el propio contenido de los archivos. Por ejemplo, puedes realizar un control simple
de versiones sobre un archivo. Para ello, crea un archivo nuevo y guarda su contenido en tu base
de datos:
Tu base de datos contendr las dos nuevas versiones del archivo, as como el primer contenido
que habias guardado en ella antes:
o a su segunda versin
Pero no es prctico esto de andar recordando la clave SHA-1 para cada versin de tu archivo; es
ms, realmente no ests guardando el nombre de tu archivo en el sistema, --solo su contenido--.
Este tipo de objeto se denomina un blob (binary large object). Con la orden 'cat-file -t' puedes
comprobar el tipo de cualquier objeto almacenado en Git, sin mas que indicar su clave SHA-1':
El siguiente tipo de objeto a revisar sern los objetos tipo arbol. Estos se encargan de resolver el
problema de guardar un nombre de archivo, a la par que guardamos conjuntamente un grupo de
archivos. Git guarda contenido de manera similar a un sistema de archivos UNIX, pero de forma
algo ms simple. Todo el contenido se guarda como objetos arbol (tree) u objetos binarios (blob).
Correspondiendo los rboles a las entradas de carpetas; y correspondiendo los binarios, mas o
menos, a los contenidos de los archivos (inodes). Un objeto tipo rbol tiene una o ms entradas
de tipo arbol. Y cada una de ellas consta de un puntero SHA-1 a un objeto binario (blob) o a un
subrbol, ms sus correspondientes datos de modo, tipo y nombre de archivo. Por ejemplo, el
rbol que hemos utilizado recientemente en el proyecto simplegit, puede resultar algo as como:
Puedes crear tu propio rbol. Habitualmente Git suele crear un rbol a partir del estado de tu
rea de preparacin (staging area) o ndice, escribiendo un objeto rbol con l. Por tanto, para
crear un objeto rbol, previamente has de crear un ndice preparando algunos archivos para ser
almacenados. Puedes utilizar el comando de "fontaneria" update-index para crear un ndice con
una sola entrada, --la primera version de tu archivo text.txt--. Este comando se utiliza para aadir
artificialmente la versin anterior del archivo test.txt. a una nueva rea de preparacin Has de
utilizar la opcin --add, porque el archivo no existe an en tu rea de preparacin (es ms, ni
siquiera tienes un rea de preparacin). Y has de utilizar tambin la opcin --cacheinfo, porque
el archivo que estas aadiendo no est en tu carpeta, sino en tu base de datos. Para terminar, has
de indicar el modo, la clave SHA-1 y el nombre de archivo:
En este caso, indicas un modo 100644, el modo que denota un archivo normal. Otras opciones
son 100755, para un achivo ejecutable; o 120000, para un enlace simblico. Estos modos son
como los modos de UNIX, pero mucho menos flexibles. Solo estos tres modos son vlidos para
archivos (blobs) en Git; (aunque tambin se permiten otros modos para carpetas y submdulos).
Tras esto, puedes usar el comando write-tree para escribir el rea de preparacn a un objeto
tipo rbol. Sin necesidad de la opcin -w, solo llamando al comando write-tree, y si dicho rbol
no existiera ya, se crea automticamente un objeto tipo rbol a partir del estado del ndice.
$ git write-tree
d8329fc1cc938780ffdd9f94e0d364e0ea74f579
$ git cat-file -p d8329fc1cc938780ffdd9f94e0d364e0ea74f579
100644 blob 83baae61804e65cc73a7201a7252750c76066a30 test.txt
Vamos a crear un nuevo rbol con la segunda versin del archivo test.txt y con un nuevo archivo.
El rea de preparacin contendr ahora la nueva versin de test.txt, as como el nuevo archivo
new.txt. Escribiendo este rbol, (guardando el estado del rea de preparacin o ndice), podrs
ver que aparece algo as como:
$ git write-tree
0155eb4229851634a0f03eb265b69f5a2d56f341
$ git cat-file -p 0155eb4229851634a0f03eb265b69f5a2d56f341
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a test.txt
Aqu se vn las entradas para los dos archivos y tambin el que la suma de comprobacin SHA-1
de test.txt es la "segunda versin" de la anterior (1f7a7a). Simplemente por diversin, puedes
aadir el primer rbol como una subcarpeta de este otro. Para leer rboles al rea de preparacin
puedes utilizar el comando read-tree. Y, en este caso, puedes hacerlo como si fuera una
subcarpeta utilizando la opcin --prefix:
Si crearas una carpeta de trabajo a partir de este nuevo rbol que acabas de escribir, obtendras
los dos archivos en el nivel principal de la carpeta de trabajo y una subcarpeta llamada bak
conteniendo la primera versin del archivo test.txt. Puedes pensar en algo parecido a la Figura
9-2 para representar los datos guardados por Git para estas estructuras.
Figura 9-2. La estructura del contenido Git para tus datos actuales.
Tienes tres rboldes que representan diferentes momentos interesantes de tu proyecto, pero el
problema principal sigue siendo el estar obligado a recordar los tres valores SHA-1 para poder
recuperar cualquiera de esos momentos. Asimismo, careces de informacin alguna sobre quin
guard las instantneas de esos momentos, cundo fueron guardados o por qu se guardaron.
Este es el tipo de informacin que almacenan para t los objetos de confirmacin de cambios.
Para crearlos, tan solo has de llamar al comando commit-tree, indicando uno de los rboles
SHA-1 y los objetos de confirmacin de cambios que lo preceden (si es que lo precede alguno).
Empezando por el primer rbol que has escrito:
Con el comando cat-file puedes revisar el nuevo objeto de confirmacin de cambios recin
creado:
first commit
Puedes seguir adelante, escribiendo los otros dos objetos de confirmacin de cambios. Y
relacionando cada uno de ellos con su inmediato anterior:
third commit
bak/test.txt | 1+
1 files changed, 1 insertions(+), 0 deletions(-)
commit cac0cab538b970a37ea1e769cbbde608743bc96d
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:14:29 2009 -0700
second commit
new.txt | 1+
test.txt | 2 +-
2 files changed, 2 insertions(+), 1 deletions(-)
commit fdf4fc3344e67ab068f836878b6c4951e3b15f3d
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:09:34 2009 -0700
first commit
test.txt | 1+
1 files changed, 1 insertions(+), 0 deletions(-)
Siguiendo todos los enlaces internos, puedes llegar a un grfico similar al de la figura 9-3.
He citado anteriormente que siempre se almacena una cabecera junto al contenido. Vamos a
hechar un vistazo a cmo Git almacena sus objetos. Te mostrar el proceso de guardar un objeto
binario grande (blob), --en este caso la cadena de texto "what is up, doc?" (qu hay de nuevo,
viejo?)--, interactivamente, en el lenguaje de script Ruby. Puedes arrancar el modo interactivo de
Ruby con el comando irb:
$ irb
>> content = "what is up, doc?"
=> "what is up, doc?"
Git construye la cabecera comenzando por el tipo de objeto, en este caso un objeto binario grande
(blob). Despus aade un espacio, seguido del tamao del contenido y termina con un byte nulo:
Git concatena la cabecera y el contenido original, para calcular la suma de control SHA-1
conjunta. En Ruby, para calcular el valor SHA-1 de una cadena de texto: has de incluir la libreria
de generacin SHA1 con el comando require y llamar luego a la orden
Digest::SHA1.hexdigest():
Git comprime todo el contenido con zlib. Y tu puedes hacer lo mismo en Ruby con la libreria zlib.
Primero has de incluir la libreria y luego lanzar la orden Zlib::Deflate.deflate() sobre el
contenido:
Para terminar, has de escribir el contenido comprimido en un objeto en disco. Para fijar el lugar
donde almacenarlo, utilizaremos como nombre de carpeta los dos primeros caracteres del valor
SHA-1 y como nombre de archivo los restantes 38 caracteres de dicho valor SHA-1. En Ruby,
puedes utilizar la funcin FileUtils.mkdir_p() para crear una carpeta. Despus, puedes abrir un
archivo con la orden File.open() y escribir contenido en l con la orden write():
Y esto es todo!. --acabas de crear un autntico objeto Git binario grande (blob)--. Todos los
demas objetos Git se almacenan de forma similar, pero con la diferencia de que sus cabeceras
comienzan con un tipo diferente. En lugar de 'blob' (objeto binario grande), comenzarn por
'commit' (confirmacin de cambios), o por 'tree' (rbol). Adems, el contenido de un binario
(blob) puede ser prcticamente cualquier cosa. Mientras que el contenido de una confirmacin de
cambios (commit) o de un rbol (tree) han de seguir unos formatos internos muy concretos.
Puedes utilizar algo as como git log 1a410e para hechar un vistazo a lo largo de toda tu
historia, recorriendola y encontrando todos tus objetos. Pero para ello has necesitado recordar
que la ltima confirmacin de cambios es 1a410e. Necesitarias un archivo donde almacenar los
valores de las sumas de comprobacin SHA-1, junto con sus respectivos nombres simples que
puedas utilizar como enlaces en lugar de la propia suma de comprobacin.
En Git, estp es lo que se conoce como "referencias" o "refs". En la carpeta .git/refs puedes
encontrar esos archivos con valores SHA-1 y nombres . En el proyecto actual, la carpeta an no
contiene archivos, pero s contiene una estructura simple:
$ find .git/refs
.git/refs
.git/refs/heads
.git/refs/tags
$ find .git/refs -type f
$
Para crear una nueva referencia que te sirva de ayuda para recordar cual es tu ltima
confirmacin de cambios, puedes realizar tcnicamente algo tan simple como:
A partir de ese momento, puedes utilizar esa referencia principal que acabas de crear, en lugar
del valor SHA-1, en todos tus comandos:
Esto es lo que es bsicamente una rama en Git: un simple apuntador o referencia a la cabeza de
una lnea de trabajo. Para crear una rama hacia la segunda confirmacin de cambios, puedes
hacer:
Y la rama contendr nicamente trabajo desde esa confirmacin de cambios hacia atrs.
Figura 9-4. Objetos en la carpeta Git, con referencias a las cabeceras de las ramas.
Cuando lanzas comandos como git branch (nombrederama). Lo que hace Git es aadir, a
cualquier nueva referencia que vayas a crear, el valor SHA-1 de la ltima confirmacin de
cambios en esa rama.
La CABEZA (HEAD)
Y ahora nos preguntamos, al lanzar el comando git branch (nombrederama), cmo sabe Git
cul es el valor SHA-1 de la ltima confirmacin de cambios?. La respuesta a esta pregunta es el
archivo HEAD (CABEZA). El archivo HEAD es una referencia simblica a la rama donde te
encuentras en cada momento. Por referencia simblica me refiero a que, a diferencia de una
referencia normal, esta contiene un enlace a otra referencia en lugar de un valor SHA-1. Si miras
dentro del archivo, podrs observar algo como:
$ cat .git/HEAD
ref: refs/heads/master
Si lanzas el comando git checkout test, Git actualiza el contenido del archivo:
$ cat .git/HEAD
ref: refs/heads/test
Cuando lanzas una orden git commit, se crea un nuevo objeto de confirmacin de cambios
teniendo como padre la confirmacin con valor SHA-1 a la que en ese momento est apuntando
la referencia en HEAD.
Puedes editar manualmente este archivo. Pero, tambin para esta tarea existe un comando ms
seguro: symbolic-ref. Puedes leer el valor de HEAD a travs de l:
Etiquetas
Acabas de conocer los tres principales tipos de objetos Git, pero hay un cuarto. El objeto tipo
etiqueta es muy parecido al tipo confirmacin de cambios, --contiene un marcador, una fecha, un
mensaje y un enlace--. Su principal diferencia reside en que apunta a una confirmacin de
cambios (commit) en lugar de a un rbol (tree). Es como una referencia a una rama, pero
permaneciendo siempre inmovil, --apuntando siempre a la misma confirmacin de cambios--,
dndo un nombre mas amigable a esta.
Tal y como se ha comentado en el captulo 2, hay dos tipos de etiquetas: las anotativas y las
ligeras. Puedes crear una etiqueta ligera lanzando un comando tal como:
Una etiqueta ligera es simplemente eso: una rama que nunca se mueve. Sin embargo, una
etiqueta anotativa es ms compleja. Al crear una etiqueta anotativa, Git crea un objeto tipo
etiqueta y luego escribe una referencia apuntando a l en lugar de apuntar directamente a una
confirmacin de cambios. Puedes comprobarlo creando una: (la opcin -a indica que la etiqueta
es anotativa)
$ cat .git/refs/tags/v1.1
9585191f37f7b0fb9444f35a9bf50de191beadc2
test tag
Merece destacar que el inicio del objeto apunta al SHA-1 de la confirmacin de cambios recin
etiquetada. Y tambin el que no ha sido necesario apuntar directamente a una confirmacin de
cambios. Realmente puedes etiquetar cualquier tipo de objeto Git. Por ejemplo, en el cdigo
fuente de Git los gestores han aadido su clave GPG pblica como un objeto binario (blob) y lo
han etiquetado. Puedes ver esta clave pblica con el comando
lanzado sobre el cdigo fuente de Git. El kernel de Linux tiene tambin un objeto tipo etiqueta
apuntando a un objeto que no es una confirmacin de cambios (commit). La primera etiqueta
que se cre es la que apunta al rbol (tree) inicial donde se import el cdigo fuente.
Remotos
El tercer tipo de referencia que puedes observar es la referencia remota. Si aades un remoto y
envias algo a l, Git almacenar en dicho remoto el ltimo valor para cada rama presente en la
carpeta refs/remotes. Por ejemplo, puedes aadir un remoto denominado origin y enviar a l tu
ramamaster:
$ git remote add origin git@github.com:schacon/simplegit-progit.git
$ git push origin master
Counting objects: 11, done.
Compressing objects: 100% (5/5), done.
Writing objects: 100% (7/7), 716 bytes, done.
Total 7 (delta 2), reused 4 (delta 1)
To git@github.com:schacon/simplegit-progit.git
a11bef0..ca82a6d master -> master
Tras lo cual puedes confirmar cual era la rama master en el remoto origin la ltima vez que
comunicase con el servidor. Comprobando el archivo refs/remotes/origin/master:
$ cat .git/refs/remotes/origin/master
ca82a6dff817ec66f44342007202690a93763949
Las referencias remotas son distintas de las ramas normales, (referencias en refs/heads); y no
se pueden recuperar (checkout) al espacio de trabajo. Git las utiliza solamente como marcadores
al ltimo estado conocido de cada rama en cada servidor remoto declarado.
Volviendo a los objetos en la base de datos de tu repositorio Git de pruebas. En este momento,
tienes 11 objetos --4 binarios, 3 rboles, 3 confirmaciones de cambios y 1 etiqueta--.
Git comprime todos esos archivos con zlib, por lo que ocupan ms bien poco. Entre todos
suponen solamente 925 bytes. Puedes aadir algn otro archivo de gran contenido al repositorio.
Y vers una interesante funcionalidad de Git. Aadiendo el archivo repo.rb de la libreria Grit con
la que has estado trabajando anteriormente, supondr aadir un achivo con unos 12 Kbytes de
cdigo fuente.
Si hechas un vistazo al rbol resultante, podrs observar el valor SHA-1 del objeto binario
correspondiente a dicho archivo repo.rb:
Revisando el rbol creado por esta ltima confirmacin de cambios, vers algo interesante:
$ git cat-file -p master^{tree}
100644 blob fa49b077972391ad58037050f2a75f74e3671e92 new.txt
100644 blob 05408d195263d853f09dca71d55116663690c27c repo.rb
100644 blob e3f094f522629ae358806b17daf78246c27c007b test.txt
El objeto binario es ahora un binario completamente diferente. Aunque solo has aadido una
nica lnea al final de un archivo que ya contenia 400 lneas, Git ha almacenado el resultado
como un objeto completamente nuevo.
Y, as, tienes en tu disco dos objetos de 12 Kbytes prcticamente idnticos. No seria prctico si
Git pudiera almacenar uno de ellos completo y luego solo las diferencias del segundo con respecto
al primero?
Pues bien, Git lo puede hacer. El formato inicial como Git guarda sus objetos en disco es el
formato conocido como "relajado" (loose). Pero, sin embargo, de vez en cuando, Git suele agrupar
varios de esos objetos en un nico binario denominado archivo "empaquetador". Para ahorrar
espacio y hacer as ms eficiente su almacenamiento. Esto sucede cada vez que tiene demasiados
objetos en formato "relajado"; o cuando tu invocas manualmente al comando git gc; o justo
antes de enviar cualquier cosa a un servidor remoto. Puedes comprobar el proceso pidiendole
expresamente a Git que empaquete objetos, utilizando el comando git gc:
$ git gc
Counting objects: 17, done.
Delta compression using 2 threads.
Compressing objects: 100% (13/13), done.
Writing objects: 100% (17/17), done.
Total 17 (delta 1), reused 10 (delta 0)
Tras esto, si miras los objetos presentes en la carpeta, veras que han desaparecido la mayoria de
los que habia anteriormente. Apareciendo un par de objetos nuevos:
Solo han quedado aquellos objetos binarios no referenciados por ninguna confirmacin de
cambios --en este caso, el ejemplo de "que hay de nuevo, viejo?" y el ejemplo de "contenido de
pruebas"-- Porque nunca los has llegado a incluir en ninguna confirmacin de cambios, no se han
considerado como objetos definitivos y, por tanto, no han sido empaquetados.
Los otros archivos presentes son el nuevo archivo empaquetador y un ndice. El archivo
empaquetador es un nico archivo conteniendo dentro de l todos los objetos sueltos eliminados
del sistema de archivo. El ndice es un archivo que contiene las posiciones de cada uno de esos
objetos dentro del archivo empaquetador. Permitiendonos as buscarlos y extraer rpidamente
cualquiera de ellos. Lo que es interesante es el hecho de que, aunque los objetos originales
presentes en el disco antes del gc ocupaban unos 12 Kbytes, el nuevo archivo empaquetador
apenas ocupa 6 Kbytes. Empaquetando los objetos, has conseguido reducir a la mitad el uso de
disco.
Cmo consigue Git esto? Cuando Git empaqueta objetos, va buscando archivos de igual nombre
y tamao similar. Almacenando nicamente las diferencias entre una versin de cada archivo y la
siguiente. Puedes comprobarlo mirando en el interior del archivo empaquetador. Y, para eso, has
de utilizar el comando "de fontaneria" git verify-pack:
$ git verify-pack -v \
.git/objects/pack/pack-7a16e4488ae40c7d2bc56ea2bd43e25212a66c45.idx
0155eb4229851634a0f03eb265b69f5a2d56f341 tree 71 76 5400
05408d195263d853f09dca71d55116663690c27c blob 12908 3478 874
09f01cea547666f58d6a8d809583841a7c6f0130 tree 106 107 5086
1a410efbd13591db07496601ebc7a059dd55cfe9 commit 225 151 322
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a blob 10 19 5381
3c4e9cd789d88d8d89c1073707c3585e41b0e614 tree 101 105 5211
484a59275031909e19aadb7c92262719cfcdf19a commit 226 153 169
83baae61804e65cc73a7201a7252750c76066a30 blob 10 19 5362
9585191f37f7b0fb9444f35a9bf50de191beadc2 tag 136 127 5476
9bc1dc421dcd51b4ac296e3e5b6e2a99cf44391e blob 7 18 5193 1 \
05408d195263d853f09dca71d55116663690c27c
ab1afef80fac8e34258ff41fc1b867c702daa24b commit 232 157 12
cac0cab538b970a37ea1e769cbbde608743bc96d commit 226 154 473
d8329fc1cc938780ffdd9f94e0d364e0ea74f579 tree 36 46 5316
e3f094f522629ae358806b17daf78246c27c007b blob 1486 734 4352
f8f51d7d8a1760462eca26eebafde32087499533 tree 106 107 749
fa49b077972391ad58037050f2a75f74e3671e92 blob 9 18 856
fdf4fc3344e67ab068f836878b6c4951e3b15f3d commit 177 122 627
chain length = 1: 1 object
pack-7a16e4488ae40c7d2bc56ea2bd43e25212a66c45.pack: ok
Puedes observar que el objeto binario 9bc1d, (correspondiente a la primera versin de tu archivo
repo.rb), tiene una referencia al binario 05408 (la segunda versin de ese archivo). La tercera
columna refleja el tamao de cada objeto dentro del paquete. Observandose que 05408 ocupa
unos 12 Kbytes; pero 9bc1d solo ocupa 7 bytes. Resulta curioso que se almacene completa la
segunda versin del archivo, mientras que la versin original es donde se almacena solo la
diferencia. Esto se debe a la mayor probabilidad de que vayamos a recuperar rpidamente la
versin mas reciente del archivo.
A lo largo del libro has utilizado sencillos mapeados entre ramas remotas y referencias locales;
pero las cosas pueden ser bastante ms complejas. Supn que aades un remoto tal que:
Esto aade una nueva seccin a tu archivo .git/config, indicando el nombre del remoto (origin),
la ubicacin (URL) del repositorio remoto y la referencia para recuperar (fench) desde l:
[remote "origin"]
url = git@github.com:schacon/simplegit-progit.git
fetch = +refs/heads/*:refs/remotes/origin/*
El formato para esta referencia es un signo + opcional, seguido de una sentencia
<orig>:<dest>; donde <orig> es la plantilla para referencias en el lado remoto y <dest> el
lugar donde esas referencias se escribirn en el lado local. El +, si est presente, indica a Git que
debe actualizar la referencia incluso en los casos en que no se d un avance rpido (fast-forward).
En el caso por defecto en que es escrito por un comando git remote add, Git recupera del
servidor todas las referencias bajo refs/heads/, y las escribe localmente en
refs/remotes/origin/. De tal forma que, si existe una rama master en el servidor, puedes
acceder a ella localmente a travs de
Todas estas sentencias son equivalentes, ya que Git expande cada una de ellas a
refs/remotes/origin/master.
Si, en cambio, quisieras hacer que Git recupere nicamente la rama master y no cualquier otra
rama en el servidor remoto. Puedes cambiar la linea de recuperacin a
fetch = +refs/heads/master:refs/remotes/origin/master
Quedando as esta referencia como la referencia por defecto para el comando git fetch para ese
remoto. Para hacerlo puntualmente en un momento concreto, puedes especificar la referencia
directamente en la linea de comando. Para recuperar la rama master del servidor remoto a tu
rama origin/mymaster local, puedes lanzar el comando
Puedes incluso indicar multiples referencias en un solo comando. Escribiendo algo asi como:
Es posible asimismo indicar referencias multiples en el archivo de configuracin. Si, por ejemplo,
siempre recuperas las ramas 'master' y 'experiment', puedes poner dos lineas:
[remote "origin"]
url = git@github.com:schacon/simplegit-progit.git
fetch = +refs/heads/master:refs/remotes/origin/master
fetch = +refs/heads/experiment:refs/remotes/origin/experiment
Pero, en ningn caso puedes poner referencias genricas parciales; por ejemplo, algo como esto
sera erroneo:
fetch = +refs/heads/qa*:refs/remotes/origin/qa*
Aunque, para conseguir algo similar, puedes utilizar los espacios de nombres . Si tienes un equipo
QA que envia al servidor una serie de ramas. Y deseas recuperar la rama master y cualquiera otra
de las ramas del equipo; pero no recuperar ninguna rama de otro equipo. Puedes utilizar una
seccin de configuracin como esta:
[remote "origin"]
url = git@github.com:schacon/simplegit-progit.git
fetch = +refs/heads/master:refs/remotes/origin/master
fetch = +refs/heads/qa/*:refs/remotes/origin/qa/*
De esta forma, puedes asignar facilmente espacios de nombres. Y resolver as complejos flujos de
trabajo donde tengas simultneamente , por ejemplo, un equipo QA enviando ramas, varios
desarrolladores enviando ramas tambin y equipos integradores enviando y colaborando en
ramas remotas.
Es util poder recuperar (fetch) referencias relativas en espacios de nombres, tal y como hemos
visto. Pero, cmo pueden enviar (push) sus ramas al espacio de nombres qa/ los miembros de
equipo QA ?. Pues utilizando las referencias (refspecs) para enviar.
Si alguien del equipo QA quiere enviar su rama master a la ubicacin qa/master en el servidor
remoto, puede lanzar algo asi como:
$ git push origin master:refs/heads/qa/master
Y, para que se haga de forma automtica cada vez que ejecute git push origin, puede aadir una
entrada push a su archivo de configuracin:
[remote "origin"]
url = git@github.com:schacon/simplegit-progit.git
fetch = +refs/heads/*:refs/remotes/origin/*
push = refs/heads/master:refs/heads/qa/master
Esto har que un simple comando git push origin envie por defecto la rama local master a la
rama remota qa/master,
Borrando referencias
Se pueden utilizar las referencias (refspec) para borrar en el servidor remoto. Por ejemplo,
lanzando algo como:
Se elimina la rama 'topic' del servidor remoto, ya que la sustituimos or nada. (Al ser la referencia
<origen>:<destino>, si no indicamos la parte <origen>, realmente estamos diciendo que
enviamos 'nada' a <destino>.)
Git puede transferir datos entre dos repositorios utilizando uno de sus dos principales
mecanismos de transporte: sobre HTTP (protocolo tonto), o sobre los denominados protocolos
inteligentes (utilizados enfile://, ssh:// o git://). En esta parte, se vern sucintamente cmo
trabajan esos dos tipos de protocolo.
Lo primero que hace este comando es recuperar el archivo info/refs. Este es un archivo escrito
por el comando update-server-info, el que has de habilitar como enganche (hook)
post-receive para permitir funcionar correctamente al transporte HTTP:
A partir de ahi, ya tienes una lista de las referencias remotas y sus SHAs. Lo siguiente es mirar
cual es la referencia a HEAD, de tal forma que puedas saber el punto a activar (checkout) cuando
termines:
Ves que es la ramamaster la que has de activar cuando el proceso est completado.
sus ramas al espacio de nombres qa/En este punto, ya ests preparado para seguir procesando el
resto de los objetos. En el archivo info/refs se ve que el punto de partida es la confirmacin de
cambios (commit) ca82a6, y, por tanto, comenzaremos recuperandola:
Cuando recuperas un objeto, dicho objeto se encuentra suelto (loose) en el servidor y lo traes
mediante una peticin esttica HTTP GET. Puedes descomprimirlo, quitarle la cabecera y mirar
el contenido:
Tras esto, ya tienes ms objetos a recuperar --el rbol de contenido cfda3b al que apunta la
confirmacin de cambios; y la confirmacin de cambios padre 085bb3--.
Pero... Ay!... parece que el objeto rbol no est suelto en el servidor. Por lo que obtienes una
respuesta 404 (objeto no encontrado). Puede haber un par de razones para que suceda esto: el
objeto est en otro repositorio alternativo; o el objeto est en este repositorio, pero dentro de un
objeto empaquetador (packfile). Git comprueba primero a ver si en el listado hay alguna
alternativa:
En el caso de que esto devolviera una lista de ubicaciones (URL) alternativas, Git busca en ellas.
(Es un mecanismo muy adecuado en aquellos proyectos donde hay segmentos derivados uno de
otro compartiendo objetos en disco.) Pero, en este caso, no hay altenativas. Por lo que el objeto
debe encontrarse dentro de un empaquetado. Para ver que empaquetados hay disponibles en el
servidor, has de recuperar el archivo objects/info/packs. Este contiene una lista de todos ellos:
(que ha sido generada por update-server-info)
Vemos que hay un archivo empaquetado, y el objeto buscado ha de encontrarse dentro de l; pero
merece comprobarlo revisando el archivo de ndice, para asegurarse. Hacer la comprobacion es
sobre todo util en aquellos casos donde existan multiples archivos empaquetados en el servidor,
para determinar as en cual de ellos se encuentra el objeto que necesitas:
Una vez tengas el ndice del empaquetado, puedes mirar si el objeto buscado est en l, (Dicho
ndice contiene la lista de SHAs de los objetos dentro del empaquetado y las ubicaciones -offsets-
de cada uno de llos dentro de l.) Una vez comprobada la presencia del objeto, adelante con la
recuperacin de todo el archivo empaquetado:
Cuando tengas el objeto rbol, puedes continuar avanzando por las confirmaciones de cambio. Y,
como ests tambin estn dentro del archivo empaquetado que acabas de descargar, ya no
necesitas hacer mas peticiones al servidor. Git activa una copia de trabajo de la rama master
sealada por la referencia HEAD que has descargado al principio.
Por ejemplo, si lanzas el comando git push origin master en tu proyecto y origin est definida
como una ubicacin que utiliza el protocolo SSH. Git lanzar el proceso send-pack , con el que
establece conexin SSH con tu servidor. En el servidor remoto, a travs de una llamada SSH,
intentar lanzar un comando tal como:
El comando git-receive-pack responde con una linea por cada una de las referencias que tenga,
--en este caso, la rama master y su SHA--. La primera linea suele indicar tambin una lista con
las capacidades del servidor, (en este caso report-status --dar situacin-- y delete-refs --borrar
referencias--).
Cada linea comienza con un valor de 4 bytes, en hexadecimal, indicando la longitud del resto de
la linea. La primera de las lineas comienza con 005b, 91 en decimal, indicandonos que hay 91
bytes ms en esa lnea. La siguiente lnea comienza con 003e, 62 en decimal, por lo que has de
leer otros 62 bytes hasta el final de la linea. Y la ltima linea comienza con 0000, indicando as
que la lista de referencias ha terminado.
Con esta informacin, el proceso send-pack ya puede determnar las confirmaciones de cambios
(commits) presentes en el servidor. Para cada una de las referencias que se van a actualizar, el
proceso send-pack llama al proceso receive-pack con la informacin pertinente. Por ejemplo,
si ests actualizando la rama master y aadiendo otra rama experiment, la respuesta del
proceso send-pack ser algo as como:
0085ca82a6dff817ec66f44342007202690a93763949
15027957951b64cf874c3557a0f3547bd83b3ff6 refs/heads/master report-status
00670000000000000000000000000000000000000000
cdfdb42577e2506715f8cfeacdbabc092bf63e8d refs/heads/experiment
0000
Una clave SHA-1 con todo '0's, nos indica que no habia nada anteriormente, y que, por tanto,
estamos aadiendo una nueva referencia. Si estuvieras borrando una referencia existente, verias
lo contrario: una clave todo '0's en el lado derecho.
Git envia una linea por cada referencia a actualizar, indicando el viejo SHA, el nuevo SHA y la
referencia a actualizar. La primera linea indica tambin las capacidades disponibles en el cliente.
A continuacin, el cliente envia un archivo empaquetado con todos los objetos que faltan en el
servidor. Y, por ultimo, el servidor responde con un indicador de xito (o fracaso) de la
operacin:
000Aunpack ok
Hay varias maneras de iniciar un proceso upload-pack en el repositorio remoto. Se puede lanzar
a travs de SSH, de la misma forma que se arrancaba el proceso receive-pack. O se puede
arrancar a traves del demonio Git, que suele estar escuchando por el puerto 9418. Tras la
conexin, el proceso fetch-pack envia datos de una forma parecida a esta:
003fgit-upload-pack schacon/simplegit-progit.git\0host=myserver.com\0
Como siempre, comienza con 4 bytes indicadores de cuantos datos siguen a continuacin,
siguiendo con el comando a lanzar, y terminando con un byte nulo, el nombre del servidor y otro
byte nulo ms. El demonio Git realizar las comprobaciones de si el comando se puede lanzar, si
el repositorio existe y si tenemos permisos. Siendo todo correcto, el demonio lanzar el proceso
upload-pack y procesara nuestra peticin.
Si en lugar de utilizar el demonio Git, ests utilizando el protocolo SSH. fetch-pack lanzar algo
como esto:
La respuesta es muy similar a la dada por receive-pack, pero las capacidades que se indican son
diferentes. Adems, nos indica la referencia HEAD, para que el cliente pueda saber qu ha de
activar (check out) en el caso de estar requiriendo un clon.
En este punto, el proceso fetch-pack revisa los objetos que tiene y responde indicando los
objetos que necesita. Enviando "want" (quiero) y la clave SHA que necesita. Los objetos que ya
tiene, los envia con "have" (tengo) y la correspondiente clave SHA. Llegando al final de la lista,
escribe "done" (hecho). Para indicar al proceso upload-pack que ya puede comenzar a enviar el
archivo empaquetado con los datos requeridos:
Este es un caso muy sencillo para ilustrar los protocolos de trasferencia. En casos ms complejos,
el cliente explota las capacidades de multi_ack (multiples confirmaciones) o side-band (banda
lateral). Pero este ejemplo muestra los intercambios bsicos empleados en los protocolos
inteligentes.
Mantenimiento
De cuando en cuando, Git lanza automticamente un comando llamado "auto gc". La mayor parte
de las veces, este comando no hace nada. Pero, cuando hay demasiados objetos sueltos, (objetos
fuera de un archivo empaquetador), o demasiados archivos empaquetadores, Git lanza un
comando git gccompleto. gc corresponde a "recogida de basura" (garbage collect), y este
comando realiza toda una serie de acciones: recoge los objetos sueltos y los agrupa en archivos
empaquetadores; consolida los archivos empaquetadores pequeos en un solo gran archivo
empaquetador; retira los objetos antiguos no incorporados a ninguna confirmacin de cambios.
$ git gc --auto
Y, habitualmente, no har nada. Ya que es necesaria la presencia de unos 7.000 objetos sueltos o
ms de 50 archivos empaquetadores para que Git termine lanzando realmente un comando "gc".
Estos lmites pueden configurarse con las opciones de configuracin gc.auto y
gc.autopacklimit, respectivamente.
Otra tarea realizada por gc es el empaquetar referencias en un solo archivo. Por ejemplo,
suponiendo que tienes las siguientes ramas y etiquetas en tu repositorio:
Lanzando el comando git gc, dejars de tener esos archivos en la carpeta refs. En aras de la
eficiencia, Git los mover a un archivo denominado .git/packed-refs:
$ cat .git/packed-refs
# pack-refs with: peeled
cac0cab538b970a37ea1e769cbbde608743bc96d refs/heads/experiment
ab1afef80fac8e34258ff41fc1b867c702daa24b refs/heads/master
cac0cab538b970a37ea1e769cbbde608743bc96d refs/tags/v1.0
9585191f37f7b0fb9444f35a9bf50de191beadc2 refs/tags/v1.1
^1a410efbd13591db07496601ebc7a059dd55cfe9
Si actualizas alguna de las referencias, Git no modificar este archivo. Sino que, en cambio,
escribir uno nuevo en refs/heads. Para obtener la clave SHA correspondiente a una
determinada referencia, Git comprobar primero en la carpeta refs y luego en el archivo
packed-refs. Cualquier referencia que no puedas encontrar en la carpeta refs, es muy posible
que la encuentres en el archivo packed-refs.
Merece destacar que la ltima lnea de este archivo comenzaba con ^- Esto nos indica que la
etiqueta inmediatamente anterior es una etiqueta anotada y que esa lnea es la confirmacin de
cambios a la que apunta dicha etiqueta anotada.
Recuperacin de datos
En algn momento de tu trabajo con Git, perders por error una confirmacin de cambios.
Normalmente, esto suele suceder porque has forzado el borrado de una rama con trabajos no
confirmados en ella, y luego te has dado cuenta de que realmente necesitabas dicha rama; o
porque has reculado (hard-reset) una rama, abandonando todas sus confirmaciones de cambio, y
luego te has dado cuenta que necesitabas alguna de ellas. Asumiendo que estas cosas pasan,
cmo podras recuperar tus confirmaciones de cambio perdidas?
Vemos que se han perdido las dos ltimas confirmaciones de cambios, --no tienes ninguna rama
que te permita acceder a ellas--. Necesitas localizar el SHA de la ltima confirmacin de cambios
y luego aadir una rama que apunte hacia ella. El problema es cmo localizarla, --porque, no te
la sabrs de memoria, no?--.
El mtodo ms rpido para conseguirlo suele ser utilizar una herramienta denominada git
reflog. Segn trabajas, Git suele guardar un silencioso registro de donde est HEAD en cada
momento. Cada vez que confirmas cambios o cambias de rama, el registro (reflog) es actualizado.
El registro reflog se actualiza incluso cuando utilizas el comando git update-ref. Siendo esta
otra de las razones por las que es recomendable utilizar ese comando en lugar de escribir
manualmente los valores SHA en los archivos de referencia, tal y como hemos visto
anteriormente en la seccin "Referencias Git". Con el comando git reflog puedes revisar donde
has estado en cualquier momento pasado:
$ git reflog
1a410ef HEAD@{0}: 1a410efbd13591db07496601ebc7a059dd55cfe9: updating
HEAD
ab1afef HEAD@{1}: ab1afef80fac8e34258ff41fc1b867c702daa24b: updating HEAD
Se pueden ver las dos confirmaciones de cambios que hemos activado, pero no hay mucha ms
informacin al respecto. Para ver la misma informacin de manera mucho ms amigable,
podemos utilizar el comando git log -g. Este nos muestra una salida normal de registro:
$ git log -g
commit 1a410efbd13591db07496601ebc7a059dd55cfe9
Reflog: HEAD@{0} (Scott Chacon <schacon@gmail.com>)
Reflog message: updating HEAD
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:22:37 2009 -0700
third commit
commit ab1afef80fac8e34258ff41fc1b867c702daa24b
Reflog: HEAD@{1} (Scott Chacon <schacon@gmail.com>)
Reflog message: updating HEAD
Author: Scott Chacon <schacon@gmail.com>
Date: Fri May 22 18:15:24 2009 -0700
modified repo a bit
Parece que la confirmacin de cambios perdida es esta ltima. Puedes recuperarla creando una
nueva rama apuntando a ella. Por ejemplo, puedes iniciar una rama llamada recover-branch
con dicha confirmacin de cambios (ab1afef):
Bravo!, acabas de aadir una rama denominada recover-branch al punto donde estaba
originalmente tu rama master; permitiendo as recuperar el acceso a las dos primeras
confirmaciones de cambios.
A continuacin, supongamos que la prdida se ha producido por alguna razn que no se refleja en
el registro (reflog). Puedes simularlo borrando la rama recover-branch y borrando asimismo el
registro. Con eso, perdemos completamente el acceso a las dos primeras confirmaciones de
cambio:
La informacin de registro (reflog) se guarda en la carpeta .git/logs/; por lo que, borrandola, nos
quedamos efectivamente sin registro. Cmo podriamos ahora recuperar esas confirmaciones de
cambio? Un camino es utilizando el comando de chequeo de integridad de la base de datos: git
fsck. Si lo lanzas con la opcin --full, te mostrar todos los objetos sin referencias a ningn otro
objeto:
Borrando objetos
Git tiene grandes cosas. Pero el hecho de que un git clone siempre descarge la historia completa
del proyecto (incluyendo todas y cada una de las versiones de todos y cada uno de los archivos).
Puede casusar problemas. Todo suele ir bien si el contenido es nicamente cdigo fuente. Ya que
Git est tremendamente optimizado para comprimir eficientemente ese tipo de datos. Pero, si
alguien, en cualquier momento de tu proyecto, ha aadido un solo archivo enorme. A partir de
ese momento, todos los clones, siempre, se vern obligados a copiar ese enorme archivo. Incluso
si ya ha sido borrado del proyecto en la siguiente confirmacin de cambios realizada
inmediatamente tras la que lo aadi. Porque en algn momento form parte del proyecto,
siempre permanecer ah.
Esto suele dar bastantes problemas cuando ests convirtiendo repositorios de Subversion o de
Perforce a Git. En esos sistemas, uno no se suele descargar la historia completa. Y, por tanto, los
archivos enormes no tienen mayores consecuencias. Si, tras una importacin de otro sistema, o
por otras razones, descubres que tu repositorio es mucho mayor de lo que deberia ser. Es
momento de buscar y borrar objetos enormes en l.
Para probarlo por t mismo, puedes aadir un archivo enorme a tu repositorio de pruebas y
retirarlo en la siguiente confirmacin de cambios. As podrs practicar la busqueda y borrado
permanente del repositorio. Para emprezar, aade un objeto enorme a tu historial:
!Ouch!, --no querias aadir un archivo tan grande a tu proyecto--. Mejor si lo quitas:
$ git rm git.tbz2
rm 'git.tbz2'
$ git commit -m 'oops - removed large tarball'
[master da3f30d] oops - removed large tarball
1 files changed, 0 insertions(+), 0 deletions(-)
delete mode 100644 git.tbz2
Ahora, puedes limpiar gc tu base de datos y comprobar cunto espacio ests ocupando:
$ git gc
Counting objects: 21, done.
Delta compression using 2 threads.
Compressing objects: 100% (16/16), done.
Writing objects: 100% (21/21), done.
Total 21 (delta 3), reused 15 (delta 1)
$ git count-objects -v
count: 4
size: 16
in-pack: 21
packs: 1
size-pack: 2016
prune-packable: 0
garbage: 0
El archivo enorme es el ltimo: 2 MB (2056716 Bytes para ser exactos). Para concretar cual es el
archivo, puedes utilizar el comando rev-list que ya vimos brevemente en el captulo 7. Con la
opcin --objects, obtendrs la lista de todas las SHA de todas las confirmaciones de cambio,
junto a las SHA de los objetos binarios y las ubicaciones (paths) de cada uno de ellos. Puedes usar
esta informacin para localizar el nombre del objeto binario:
Una vez tengas ese dato, lo puedes utilizar para borrar ese archivo en todos los rboles pasados.
Es sencillo revisar cuales son las confirmaciones de cambios donde interviene ese archivo:
Para borrar realmente ese archivo de tu historial Git, has de reescribir todas las confirmaciones
de cambio aguas abajo de 6df76. Y, para ello, puedes emplear el comando filter-branch que se
vi en el captulo 6.
Tras esto, el historial ya no contiene ninguna referencia a ese archivo. Pero, sin embargo, quedan
referencias en el registro (reflog) y en el nuevo conjunto de referencias en .git/refs/original que
Git ha aadido al procesar filter-branch. Por lo que has de borrar tambin ests y reempaquetar
la base de datos. Antes de reempaquetar, asegurate de acabar completamente con cualquier
elemento que apunte a las viejas confirmaciones de cambios:
$ rm -Rf .git/refs/original
$ rm -Rf .git/logs/
$ git gc
Counting objects: 19, done.
Delta compression using 2 threads.
Compressing objects: 100% (14/14), done.
Writing objects: 100% (19/19), done.
Total 19 (delta 3), reused 16 (delta 1)
$ git count-objects -v
count: 8
size: 2040
in-pack: 19
packs: 1
size-pack: 7
prune-packable: 0
garbage: 0
El tamao del repositorio ahora es de 7 KB, mucho mejor que los 2 MB anteriores. Por el valor de
"size", puedes ver que el objeto enorme sigue estando entre tus objetos sueltos; es decir, no
hemos acabado completamente con l. Pero lo importante es que ya no ser considerado al
transferir o clonar el proyecto. Si realmente quieres acabar del todo, puedes lanzar la orden git
prune --expire para retirar incluso ese archivo suelto.