Idealmente un sitio web es el resultado del trabajo un grupo interdisciplinario de profesionales: escritores, diseñadores gráficos, informáticos, músicos, etc. Al igual que cualquier otro desarrollo de software donde colaboran varios expertos, se debe trabajar bajo el auspicio de algún sistema de control de versiones (VCS, Version Control System). Incluso aunque sólo sea un profesional, hay ganancia en el uso de estos sistemas. Esta sección contiene un resumen de Git, basado en el libro Pro Git de Scott Chacón; el cual es libre y su texto completo es accesible vía web.
Control de versiones es un sistema que registra modificaciones en un conjunto de archivos a lo largo del tiempo de tal forma que puede recordar cambios específicos luego. El control de versiones se puede hacer local, pero impide que varios desarrolladores colaboren simultáneamente. Por eso nacieron los sistemas de control de versiones centralizados (CVCS, Centralized Version Control System), tales como CVS (1990), Subversion (2000) y Perforce, los cuales almacenan en un único servidor todos los archivos versionados en algo que se conoce como repositorio, y todos los desarrolladores que necesiten colaborar con el proyecto, obtienen copias de trabajo a partir de ese servidor centralizado. Este ha sido el esquema de trabajo durante años.
El más obvio problema que tiene el control de versiones centralizado es el peligro de que el servidor central falle, sea por unas horas, por corrupción del repositorio o cualquier otra razón. Durante ese tiempo los desarrolladores no podrán colaborar con el proyecto. Esto es inaceptable para proyectos de software muy grandes o complejos, por lo que nacieron los sistemas de control de versiones distribuidos (DVCS, Distributed Version Control System) como Git (2005), Mercurial (2005), Bazaar (Canonical, 2007) y Darcs (2003).
En un sistema de control de versiones distribuido, los clientes no sólo obtienen copias de las últimas versiones de los archivos, sino una copia de todo el repositorio, es decir, se convierten en un espejo (mirror) del repositorio. Si el servidor fallece, cualquiera de los repositorios cliente puede usarse para reestablecer el servidor.
La historia de Git está relacionada con el kernel de Linux. Entre 1991-2002 (11 años) el control de versiones del código fuente del kernel fue hecho manualmente. Entre 2002 y 2005 se utilizó un sistema propietario llamado BitKeeper hasta que éste no fue más gratis para proyectos de software libre. A partir de las lecciones aprendidas, Linus Torvals y el equipo del kernel de Linux decidieron crear uno propio y libre: Git.
Muchos CVS almacenan en sus repositorios diferencias: lo que ha cambiado en un archivo respecto a la última vez que se modificó. Si el archivo no ha variado, no almacena nada de él en la última revisión. Git en cambio almacena un snapshot (una copia entera) de cada archivo en el proyecto en la última revisión, si éste no ha variado respecto a la revisión anterior, se crea algo similar a un enlace simbólico (symbolic link) hacia el anterior. De esta forma, una revisión cualquiera es como un sistema de archivos, en el cual se pueden correr comandos, hacer operaciones de búsqueda y otras, de manera muy eficiente.
Ya que se tiene una copia del repositorio de Git localmente, se puede trabajar y enviar commits estando en el avión o sin conexión a Internet. Cualquier operación sobre el repositorio será casi instantánea. En cambio en los sistemas de control de versiones centralizados (CVCS), la red impone latencia y si ésta se interrumpe, el usuario no podrá hacer commits o en algunos casos no podrá trabajar del todo.
Git calcula y almacena una suma de comparación (checksum) cada vez que hay un cambio en uno o más archivos. De esta forma Git puede saber cuándo algo ha realmente cambiado o si algo llegó mal al servidor de repositorios. Git utiliza SHA-1 hash que produce un string de 40 dígitos hexadecimales. Este número es tan importante que sirve para identificar cada revisión en lugar de números.
Un proyecto de Git consta de tres áreas: el directorio de Git, el directorio de trabajo (working directory) y el staging area. El directorio de Git (.git
) contiene la base de datos y los objetos; es lo que se copia cuando se clona un repositorio de una computadora a otra. El directorio de trabajo (working directory) es un checkout de una revisión del proyecto; es decir, una carpeta con los archivos originales que componen el proyecto y con los que trabaja el usuario; son el resultado de expandir los almacenados en el directorio Git. El staging area es un archivo dentro del directorio de Git que va almacenando detalles para el próximo commit.
El ciclo de trabajo habitual con Git es el siguiente. El usuario modifica archivos en el directorio de trabajo (modified files). Los agrega al stage area (staged files). Cuando ha terminado de hacer cambios, hace un commit, el cual almacena los archivos del staged area en el Git directory (committed file). Nótese que el commit es local, y no necesita tener acceso a un servidor en Internet.
Git se puede configurar mediante archivos, en orden de prioridad: ${myproject}/.git/config
, ~/.gitconfig
y /etc/gitconfig
. El comando git config
cambia valores en esos archivos, con las opciones respectivas: --file
, --global
, y --system
. Si quiere obtener un valor o todos los valores actuales emita:
Si ve varios valores para una misma llave, la última gana. Es importante al menos configurar su identidad, ya que cada commit que haga llevará una copia de ella; el editor que Git usará para que introduzca datos; y el programa para visualizar diferencias de archivos (kdiff3, tkdiff, meld, xxdiff, emerge, vimdiff, gvimdiff, ecmerge, opendiff):
Para obtener ayuda emita alguno de los siguientes comandos, por ejemplo git help config
:
$ git--help $ man git- ]]>
Esta sección explica varios conceptos y comandos que utiliza un desarrollador en su trabajo cotidiano con el sistema de control de versiones Git.
Para trabajar un proyecto con Git hay dos formas: (1) tomar un proyecto existente o directorio e importarlo en Git, (2) clonar un repositorio existente de otro servidor. Para poner un proyecto existente bajo control de versiones con Git, vaya al directorio del proyecto y emita:
Lo cual crea un subdirectorio llamado .git
con un repositorio vacío. Ninguno de los archivos de su proyecto están aún bajo control de versiones, debe agregarlos manualmente al repositorio y cuando esté listo, crear la primera revisión con un commit:
Si en lugar de crear un repositorio vacío lo que necesita es trabajar con uno existente, debe hacer una copia (casi) exacta del repositorio en el servidor, con el comando git clone <url>
, por ejemplo:
El comando anterior creará un directorio llamado miproy
en la máquina local, dentro de él un directorio .git
en el cual copiará lo que hay en el subdirectorio homónimo del servidor; finalmente creará un working directory en miproy
haciendo un checkout de la última versión disponible en el repositorio copiado. De esta forma, el usuario verá en miproy
los archivos que conforman el proyecto y una subcarpeta .git
oculta.
Cuando se crea un nuevo archivo en un working directory, Git no lo agrega automáticamente al control de versiones, sino que el usuario debe hacerlo explícitamente con el comando git add
. Un archivo que no está en control de versiones se dice que está untracked, una vez que se ha agregado pasa al staging area y se dice que está staged. En este momento Git calcula una suma de comprobación (checksum) y la almacena en el staged area, si el archivo se vuelve a modificar, serán cambios que NO están en el staged area, como se ve en la siguiente sesión:
Cuando se hace commit, Git envía sólo los cambios en el staged area al repositorio (y no los cambios en el directorio de trabajo); se dice entonces que el archivo nuevo estará ahora en estado tracked (es decir, bajo control de versiones) y la copia en el working directory pasa al estado unmodified, si no tiene cambios respecto a la que está ahora en el repositorio. Si los tiene o el usuario le hace cambios, el archivo pasará al estado modified, como es de esperarse. Git reporta estos archivos como cambios en el working copy que no están en el staged area.
Como es evidente, el comando git status
indica el estado de cada archivo en el proyecto. Pero para ver exactamente qué ha cambiado en los archivos del working directory con respecto al stage area, utilice el comando git diff
. Para ver exactamente qué ha cambiado en el stage area respecto al repositorio, utilice el comando git diff --staged
(o también git diff --cached
). Git hará una comparación y mostrará los cambios utilizando una notación ideada para programas informáticos en lugar de para humanos. Líneas antecedidas con un símbolo de menos fueron eliminadas, y con un símbolo de suma fueron agregadas.
+ Jeisson Hidalgo-Céspedes
-Página personal
+Sitio personal
Es importante que el desarrollador siempre revise el estado de los archivos y los cambios en ellos antes de enviarlos al repositorio con un commit. Un commit es un grupo de cambios que tienen sentido en conjunto para el proyecto y es de relevancia. Idealmente un commit debe mantener el proyecto en operación, es decir, el código fuente compila, las páginas web son válidas, etc., de tal modo que otro desarrollador que actualice su proyecto pueda continuar trabajando naturalmente en lugar de enfrentar errores ajenos.
El comando para producir una nueva versión en el repositorio es git commit
, el cual recibe un mensaje con el parámetro -m
. Si se omite, Git lo preguntará utilizando el editor configurado con git config --global core.editor
. Este mensaje debe ser relativamente corto (normalmente una oración o dos) pero suficientemente descriptivo para explicar a otros desarrolladores el propósito del cambio.
El comando git commit
enviará al repositorio sólo los cambios que esté en el stage area y no los que haya hecho en el working directory. Esto es especialmente confuso y propenso al olvido por parte de desarrolladores acostumbrados a Subversion y otros sistemas de control de versiones. Por esto, si se agrega la opción -a
de la forma git commit -a
, Git agregará cada cambio del working directory al stage area y luego hará el commit automáticamente.
Puede ocurrir que inmediatamente después de haber hecho un commit descubra que un archivo hizo falta, o el mensaje que escribió no era apropiado, o cualquier otro motivo. Git le permite deshacer el último commit con el comando git commit --amend
.
Git advertirá por cualquier nuevo archivo que se cree en el working directory que no está bajo control de versiones (tracked). Esto puede resultar inconveniente para ciertos archivos que no tienen sentido estar bajo control de versiones, como archivos de código objeto generado por un compilador, bitácoras de algún proceso, etc. Para indicarle a Git que ignore estos archivos, basta listarlos en un archivo de texto con nombre .gitignore
, el cual se crea en la carpeta donde están los archivos que se quieren ignorar o una carpeta superior.
Cada línea del archivo .gitignore
contiene un nombre de archivo o carpeta (terminada en un slash (/
); o un glob pattern, que son expresiones regulares reducidas para especificar que se deben ignorar los archivos que cumplen el patrón. El git_gitignore_file muestra un archivo .gitignore
de ejemplo.
Cuando un usuario trabaja con los archivos del working directory, lo hace como cualquier otro archivo de su computadora: lo abre en un editor o programa asociado y le hace modificaciones. Pero cuando quiere realizar alguna operación del sistema de archivos, como renombrarlo, eliminarlo, copiarlo o moverlo a otra carpeta; habrá un problema si utiliza los comandos o el explorador de archivos de su sistema operativo, ya que estos no reflejarán el cambio en el repositorio. Por esto Git provee sus propios mecanismos para hacer operaciones con archivos:
Comando | Descripción |
---|---|
git rm <files> |
Elimina el archivo del repositorio, del stage area y del working copy en el próximo commit |
git rm --cached <files> |
Elimina el archivo del repositorio y del staged area, pero no del working directory. Posiblemente porque el usuario quiere ignorarlo en un archivo .gitignore . |
git mv <old_file> <new_file> |
Mueve el archivo de posición o lo renombra. El cambio tiene efecto hasta el próximo commit. |
git cp <old_file> <new_file> |
Crea una copia de un archivo existente. Ambos archivos quedarán bajo control de versiones. El cambio tiene efecto en el próximo commit. |
Git permite visualizar el historial de versiones (commit history), hacer búsquedas en él, filtrar la salida y muchas otras operaciones con el comando git log
. Es conveniente revisar su manual con git help log
.
Date: Thu Mar 29 12:38:10 2012 -0600 Fixed several errors. Now index is XHTML5 valid commit a95709c5e0aa65a7eed6f74877be749c86acd9e0 Author: Jeisson HidalgoDate: Thu Mar 29 12:29:42 2012 -0600 Simple index page with no styles $ git log --pretty=oneline 531e4d6f194b77da1b2dd096d14be7e8f25b69be Fixed several errors. Now index is XHTML5 valid a95709c5e0aa65a7eed6f74877be749c86acd9e0 Simple index page with no styles ]]>
Dado a que el working directory y el repositorio están en la misma máquina, cada commit que emite el desarrollador se almacena localmente. Si el desarrollador quiere compartir su trabajo con otros, necesitarán un repositorio compartido, el cual puede estar alojado en un servidor de Internet y es clonado por los desarrolladores en sus máquinas. El comando git clone
crea una copia local de un repositorio remoto. El comando git remote -v
permite conocer los repositorios remotos que sigue el repositorio local y si tiene permiso de lectura (fetch) o escritura (push) en ellos.
En el ejemplo del git_show_remote_repos el repositorio local sólo sigue un repositorio remoto. Sin embargo, Git es un sistema de control de versiones descentralizado y permite que un repositorio pueda seguir un número arbitrario de repositorios remotos, siempre y cuando todos compartan un repositorio ancestro común. Por ejemplo, si cinco desarrolladores están trabjando en el mismo proyecto y sus máquinas pueden acceder a las demás; se puede hacer que el repositorio local de cada desarrollador tenga como repositorios remotos los otros cuatro de sus compañeros, sin la necesidad de un servidor centralizado. El comando git remote add <remote_name> <url>
permite agregar un repositorio remoto al repositorio local, y darle un nombre corto. Por ejemplo:
laura/master ]]>
El nombre corto sirve para identificar el repositorio remoto. Cuando se clona, el comando git clone
asigna el nombre origin
por defecto. El comando git fecth <remote_name>
actualiza el repositorio local con todos los cambios nuevos que se hayan hecho en el repositorio remoto identificado con remote_name
. Estos cambios no están disponibles para trabajar de inmediato, sino que el usuario tiene que unirlos (merge) manualmente. El comando git pull <remote_name>
trata de hacer ambas cosas, pero necesitará que el repositorio indique cuales branches son los que deben unirse. Convenientemente el comando git clone
configura el branch por defecto, llamado master
en la copia local, para que pueda hacer merge con el branch master
remoto automáticamente cuando se emite git pull
. Sobre branches se hablará en la próxima sección.
Cuando un desarrollador quiere compartir sus cambios, debe enviarlos de su repositorio local a su repositorio remoto con el comando git push [remote_name] [branch_name]
. El parámetro -all
le indica a Git actualizar todos los repositorios remotos automáticamente. Para que una operación de push sea exitosa, el desarrollador debe tener la última versión del repositorio remoto en su repositorio local.