Git, sistema de control de versiones distribuido

Hace ya algún tiempo escribí un post sobre Bazaar NG (bzr), el cual es un sistema de control de versiones distribuido que tiene el soporte de Canonical (empresa que también da soporte a Ubuntu). No obstante, .

Git es un sistema de control de versiones distribuido creado por Linus Torvalds para mantener el desarrollo del kernel Linux. Últimamente parece que Git esta ganando más y más adeptos. Por ejemplo, (p.ej. Ruby on Rails) ha migrado su sistema de control de versiones de Subversion a Git.

Git tiene bastantes similitudes con Bazaar NG o bzr (del cual ya hablé en un post anterior) dado que ambos son sistemas distribuidos (a diferencia de Subversion o CVS). Bzr fue diseñado e implementando pensando en la facilidad de uso, mientras que Git esta más orientado a proporcionar la mayor cantidad posible de funcionalidades. Por otra parte, Bzr esta desarrollado en Python cosa que lo hace muy portable pero menos eficiente, mientras que Git ha sido implementado en C ofreciendo un mejor rendimiento (en términos de espacio y CPU).

Personalmente, para el tipo de uso que doy a estas herramientas, no he apreciado grandes diferencias en cuanto a rendimiento (si bien nunca he mantenido proyectos con tantas LOC como puede tener el Kernel de Linux) y tampoco me ha parecido mucho más complicado Git. En cualquier caso, debo decir que la sensación general es que Git es el más atractivo de los dos. Y por supuesto, ambos son mejores opciones que el uso del obsoleto CVS o el popular Subversion… una vez pruebas los distribuidos, ya no quieres otra cosa 😉

Veamos como podemos utilizar Git…

Para instalar Git en Windows actualmente la mejor opción es utilizar cygwin, mientras que para instalarlo en Ubuntu es tan sencillo como:

aptitude install git-core gitk git-gui

Gitk y git-gui son completamente opcionales, simplemente aportan algunas herramientas gráficas para visualizar históricos, etc.

* Todos los comandos que utilizaremos en este tutorial tienen su correspondiente ayuda, por ejemplo la ayuda de ‘git commit’ o ‘git diff’ es visualizable mediante ‘man git-commit’ o ‘man git-diff’ respectivamente.

Configuración básica

En primer lugar vamos a configurar el nombre de usuario que aparecerá en cada commit que realicemos a los repositorios, para ello crearemos el fichero ‘~/.gitconfig’:

[user]
        name = Sergi Blanco Cuaresma
        email = marble@marble-laptop.(none)

Uso básico

En segundo lugar, vamos a crear nuestro propio repositorio en un directorio local:

$ mkdir original
$ cd original
$ git init
Initialized empty Git repository in .git/

El repositorio que hemos creado contiene tanto la información de versionado (en el directorio ‘.git’) como el directorio de trabajo. Por tanto, ya podemos empezar a programar creando un sencillo “Hello World” en Ruby. Fichero “hello.rb”:

#!/usr/bin/ruby
puts "Hello world!"

Únicamente creando un fichero dentro del repositorio, Git no lo tiene en consideración automáticamente:

$ git status
# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add file>..." to include in what will be committed)
#
#	hello.rb
nothing added to commit but untracked files present (use "git add" to track)

Por tanto, podemos añadir el fichero al índice de ficheros a versionar mediante:

$ git add hello.rb

En este sentido Git también nos permite:

  • Borrar un fichero (del indice y del directorio de trabajo) que esté siendo versionado: ‘git rm fichero’.
  • Renombrar o mover un fichero que esté siendo versionado (así no perdemos su historial): ‘git mv fichero1 fichero2’.
  • Ignorar ficheros que se encuentren en el directorio de trabajo y que no queremos que sean versionado: creamos el fichero ‘.gitignore’ por ejemplo con el siguiente contenido…
    # Lines starting with '#' are considered comments.
    # Ignore any file named foo.txt.
    foo.txt
    # Ignore (generated) html files,
    *.html
    # except foo.html which is maintained by hand.
    !foo.html
    # Ignore objects and archives.
    *.[oa]
    

Sigamos… acabábamos de añadir el fichero nuevo al índice mediante ‘git add’ y ahora seria un buen momento para realizar el commit al repositorio, de esta forma se guardara la primera version del directorio de trabajo:

$ git commit -m "My first Hello World"
Created initial commit 5c4c271: My first Hello World
 1 files changed, 2 insertions(+), 0 deletions(-)
 create mode 100755 hello.rb

Como se puede observar, hemos asociado el mensaje “My first Hello World” como descripción de las modificaciones que hemos realizado. En caso que volvamos a ejecutar ‘git status’, veremos que no hay nada nuevo en la versión actual del directorio que difiera de la última versión registrada en el repositorio.

Por otra parte, disponemos del comando ‘git show’ que nos muestra información sobre el commit más reciente:

$ git show
commit 5c4c2717a427bf3e87b8ab4ad3ed6853c194f6a0
Author: Sergi Blanco Cuaresma marble @marble-laptop.(none)
Date:   Mon May 5 20:56:37 2008 +0200

    My first Hello World

diff --git a/hello.rb b/hello.rb
new file mode 100755
index 0000000..6a2ce6e
--- /dev/null
+++ b/hello.rb
@@ -0,0 +1,2 @@
+#!/usr/bin/ruby
+puts "Hello world!"

Este comando nos informa del parche que ha sido aplicado para pasar de la versión anterior (repositorio vacio) a la actual (repositorio con fichero hello.rb), también podriamos utilizar ‘git diff’ para visualizar diferencias entre diversos commits o un commit y nuestro directorio de trabajo.

Otro aspecto a destacar es que ‘git show’ nos muestra el identificador unívoco del commit (en nuestro caso: 5c4c2717a427bf3e87b8ab4ad3ed6853c194f6a0) el cual se genera a partir un SHA1 de las modificaciones implementadas.

Sigamos modificando nuestro proyecto… editamos el fichero “hello.rb” para añadir una nueva línea:

#!/usr/bin/ruby
puts "Hello world!"
puts "Comentario 1"

Y realizamos el commit de la nueva versión ‘git commit -m “My second commit” -a’. Con el parámetro ‘-a’ nos ahorramos tener que volver a indicarle a Git que queremos que tenga en consideración el fichero hello.rb (‘git add hello.rb’).

$ git commit -m "My second commit" -a
Created commit 0c0b74c: My second commit
 1 files changed, 1 insertions(+), 0 deletions(-)

Una de las cosas que más me gusta de Git es las funcionalidades que proporciona a la hora de realizar commits y rectificarlos. Una vez tenemos un proyecto sobre el que hemos realizado varios commits, nos podría interesar realizar 3 tipos de acciones:

  • Eliminar por completo un commit mediante ‘git reset –hard identificador’. Podemos añadir el modificador ‘–hard’ para que además de modificar el repositorio, deshaga los cambios de los ficheros que tenemos en nuestro directorio de trabajo. Es importante saber que este comando puede traer problemas si existen ramas derivadas que incorporan el commit que estamos borrando, pero de no ser así me parece realmente útil.
  • Revertir un commit mediante ‘git revert identificador’. A diferencia del anterior, este comando no borra directamente el commit sino que genera uno nuevo que anula los cambios del commit al que hacemos referencia. Por tanto, no implica ningún problema en caso de existir ramas derivadas de la actual.
  • Modificar el último commit realizado mediante ‘git commit –amend’. Imaginar que nos hemos olvidado de realizar un último retoque al código y no queremos generar un nuevo commit, podemos modificar el último para que incorpore esos cambios. Obviamente esto solo tiene sentido si sabemos que nadie más ha utilizado la revisión del commit que estamos modificando.

Y hasta aquí hemos visto lo básico para crear un repositorio local e ir almacenando las diferentes versiones de nuestro código mediante commits.

Etiquetar las revisiones clave

Como hemos comentado, cada revisión tiene asociado un identificador unívoco de 40 caracteres, pero es posible añadir tags a las revisiones clave que nos interesen. Por ejemplo, en nuestro caso podríamos etiquetar como ‘v0.1’ la revisión anterior y ‘v0.2’ la actual (HEAD). El segundo caso es tan sencillo como ejecutar (si únicamente indicamos el tag sin especificar el id de commit, asigna la etiqueta a la última revisión):

git tag v0.2

En el primer caso, necesitamos encontrar el identificador de la revisión anterior. Podemos averiguarlo utilizando la interfaz gráfica que nos brinda el comando ‘guitk’ o mediante el comando:

$ git log --color --pretty=oneline -10
0c0b74c8662b0deee98b34b1475648d15fd61f8a My second commit
5c4c2717a427bf3e87b8ab4ad3ed6853c194f6a0 My first Hello World

Este comando nos muestra los identificadores y la descripción de los últimos 10 commits.

Una vez localizado el identificador que nos interesa, podemos etiquetar dicha revisión mediante:

$ git tag v0.1 5c4c2717a427bf3e87b8ab4ad3ed6853c194f6a0

Adicionalmente, es posible listar los tags mediante ‘git tag -l’ y borrar los que no nos interesen con ‘git tag -d nombre_del_tag’.

Ramas

En los apartados anteriores hemos trabajado exclusivamente con una única rama del código, la rama ‘master’ (el asterisco marca la rama activa actual):

$ git branch
* master

Imaginemos la hipotética situación donde la rama actual ha llegado a ser una versión estable del código (p.ej. 1.0), pero queremos seguir desarrollando intensivamente para llegar a una futura 2.0. Es mejor disponer de ramas separadas porque es posible que durante el desarrollo de la futura 2.0 surjan bugs de la 1.0 que necesitaremos corregir. Por tanto, si tenemos 2 ramas paralelas podremos gestionar los pequeños cambios de la versión actual 1.0 mientras desarrollamos la nueva versión de nuestro software. En cualquier caso, esta puede ser una excusa tan valida como cualquier otra para generar distintas ramas 😉

         o--o--o < -- Branch A
        /
 o--o--o <-- master
        \
         o--o--o <-- Branch B

Por ejemplo, creamos una nueva rama basada en la revision etiquetada v0.2 (si omitiésemos ese parámetro, derivaríamos de la HEAD que en nuestro caso coincide con la v0.2):

git branch devel v0.2

A diferencia de Bzr, Git gestionar las diferentes ramas en el mismo directorio. Es decir, para cambiar de una rama a otra debemos ejecutar ‘git checkout nombre_de_la_rama’ y el directorio de trabajo cambiara de contenido:

$ git checkout devel
$ git branch
* devel
  master

El comando checkout no solo lo podemos utilizar para alternar entre ramas, sino que también nos permite acceder a revisiones antiguas del código indicando su identificador o tag, por ejemplo:

git checkout v0.1

Este comando nos creará una rama temporal que nos muestra la revisión etiquetada con “v0.1”. Para volver a la rama donde estábamos podemos ejecutar ‘git branch devel’.

Modifiquemos el fichero ‘hello.rb’ de nuestra rama devel:

#!/usr/bin/ruby
puts "Hello world!"
puts "Comentario 1"
puts "Rama Devel"

Y realizemos el commit de la nueva versión:

$ git commit -a -m "Commit devel 1"
Created commit 6484083: Commit devel 1
 1 files changed, 1 insertions(+), 0 deletions(-)

A continuación nos encontramos con una rama ‘master’ que no ha sufrido ningún cambio y una rama ‘devel’ que incorpora una modificación. Podriamos realizar un merge de ambas e incorporar los cambios de ‘devel’ en ‘master’:

$ git checkout master
Switched to branch "master"
$ git merge devel

En caso de que exista algún conflicto entre ambas ramas, Git nos avisará:

$ git merge devel
Auto-merged hello.rb
CONFLICT (content): Merge conflict in hello.rb
Automatic merge failed; fix conflicts and then commit the result.

Entonces podemos editar los ficheros afectados, dejando la versión del código que nos interese y una vez resulto el conflicto, ejecutamos el commit:

git commit -a -m "Devel incorporado a master"

Hacer un merge no significa borrar la rama que ha sido fusionada, sino que simplemente incorpora los cambios y se mantienen las dos ramas. Si quisieramos borrar una, ejecutaríamos:

$ git branch devel -D

Cambio de la base de una rama

En esta sección voy a desvincularme del ejemplo que hemos ido siguiendo durante todo el artículo y voy a exponer una situación hipotética que creo interesante. Es posible que en algún momento de nuestros desarrollos nos encontremos frente a dos ramas que han ido evolucionando en el tiempo:

          A---B---C devel
         /
    D---E---F---G master

Quizás nos interese incorporar a nuestra rama ‘devel’ los cambios que han tenido lugar en ‘master’ de forma que nuestra estructura quede así:

                  A'--B'--C' devel
                 /
    D---E---F---G master

A modo de ejemplo, esta modificación nos podría interesar porque en ‘master’ tenemos una versión estable del código donde hemos solucionado varios bugs de seguridad importantes y queremos incorporar los cambios a la versión de desarrollo (que también se ve afectada por los mismos bugs).

Para poder efectuar este tipo de operaciones podemos utilizar:

$ git rebase master devel
Already on "devel"
First, rewinding head to replay your work on top of it...
HEAD is now at 20ad3a4 Prueba 4
Applying XXXX

En caso de que aparezcan conflictos, podemos modificar los ficheros afectados y continuar mediante ‘git rebase –continue’ o directamente cancelar con ‘git rebase –abort’.

Guardar/recuperar estado actual del directorio de trabajo

Una funcionalidad interesante de Git es la capacidad para guardar el estado actual del directorio de trabajo (sin realizar commits), volver la revisión HEAD (u otra), realizar cambios y recuperar el estado original. Por ejemplo, supongamos que hemos estado trabajando en nuestro proyecto y hemos creado un nuevo fichero denominado ‘bye.rb’ (aparte del ‘hello.rb’ que ya teníamos):

#!/usr/bin/ruby
puts "Bye bye!"

Y ya hemos indicado a Git que tenga en cuenta este fichero:

$ git add bye.rb

Imaginemos ahora que antes de que podamos considerar que ‘bye.rb’ se encuentra listo para ser incorporado en un commit, nos surge la necesidad de modificar urgentemente ‘hello.rb’ para incorporar un requisito de nuestro cliente. Con Git podemos guardar el estado actual sin realizar ningún commit:

$ git stash save "Pendiente acabar bye.rb"
Saved working directory and index state "On master: Pendiente acabar bye.rb"
HEAD is now at 6484083... Commit devel 1

Efectuamos la modificación urgente en ‘hello.rb’:

#!/usr/bin/ruby
puts "Hello world!"
puts "Comentario 1"
puts "Rama Devel"
puts "Urgent modification!!"

Y realizamos un commit con el nuevo cambio:

$ git commit -a -m "Requisito cliente"
Created commit b1d6506: Requisito cliente
 1 files changed, 1 insertions(+), 0 deletions(-)

Ahora que ya hemos “apagado el fuego”, podemos volver a nuestro trabajo y recuperamos el punto donde estábamos:

$ git stash apply
# On branch master
# Changes to be committed:
#   (use "git reset HEAD file..." to unstage)
#
#	new file:   bye.rb
#
$ git stash clear

Búsqueda de commits que introducen errores

El comando bisect nos ayuda a identificar commits que han introducido errores en la aplicación. Por ejemplo, imaginemos que una característica que implementamos en la reversion ‘v0.1’ deja de funcionar en la revision ‘HEAD’ y no sabemos en que commit ha sido introducido el error. Entonces podemos iniciar bisect e indicar que queremos partir de la version ‘v0.1’ como la buena y ‘HEAD’ como la mala:

$ git bisect start
$ git bisect good v0.1
$ git bisect bad HEAD
Bisecting: 1 revisions left to test after this
[64840836bb8f4db9a21ff6c3072b3626a912a6c9] Commit devel 1

A partir de aquí tendremos que ir revisando el código y efectuando las pruebas que consideremos oportunas. Si la revisión actual es buena, ejecutaremos ‘git bisect good’ o de lo contrario ‘git bisect bad’. Cada vez que indiquemos si la revisión es buena o mala, Git pasará a una siguiente para que la validemos hasta que encontremos aquella(s) que introduce(n) el error.

En nuestro caso, solo validaremos 1 revisión (dado que entre v0.1 y HEAD solo hay 1 commit de diferencia) que, por ejemplo, podemos considerar buena:

$ git bisect good
b1d65061810e6980a2b22e96d8d5d055f60d8720 is first bad commit
commit b1d65061810e6980a2b22e96d8d5d055f60d8720
Author: Sergi Blanco Cuaresma 
Date:   Mon May 5 22:58:33 2008 +0200

    Requisito cliente

:100755 100755 8c2d1346928fe7c2db481476a887498e4bb98523 b6dfb3a3274f256cb9aa3be1cdfd13521a657014 M	hello.rb

Una vez identificados los commits problemáticos, podemos proceder a corregirlos tal y como hemos visto anteriormente (eliminando el commit con ‘reset’, revirtiendo con ‘revert’, etc.).

Cabe destacar que al usar bisect se genera una nueva rama:

$ git branch
* bisect
  devel
  master

La cual podemos abandonar una vez hayamos acabado de trabajar con bisect:

git checkout master
git branch -d bisect

Creación de un repositorio público

En general es posible trabajar sin tener que generar un repositorio público. Pero es posible que según como definamos el flujo de trabajo, nos pueda interesar mantener un repositorio ‘oficial’ donde se centralicen todos los cambios consolidados.

A modo de ejemplo, en un equipo de trabajo se puede designar a una persona como responsable del repositorio principal y cada uno de los desarrolladores clonan ese repositorio para trabajar de forma local, independiente y distribuida. Cuando los desarrolladores han implementado sus respectivas funcionalidades, pueden solicitar al responsable del repositorio principal que incorpore los cambios (pull del repositorio clonado hacia el principal). Así esta persona tiene la oportunidad de revisar los cambios antes de añadirlos al repositorio principal.

Lo ideal es generar un repositorio público accesible remotamente (p.ej. SSH, HTTP, etc.) el cual únicamente disponga de la información de versionado sin un directorio de trabajo asociado (dado que nadie trabajará directamente con el).

Para crear un repositorio público, primero debemos partir de un repositorio ya existente. Como ejemplo, crearemos un repositorio inicial que contendrá un fichero en blanco (p.ej. README):

$ mkdir inicial
$ cd inicial/
$ git init
Initialized empty Git repository in .git/
$ touch README
$ git add README
$ git commit -a -m "Initial"
Created initial commit 9dda6f6: Initial
 0 files changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 README
$ cd ..

Acto seguido tenemos dos opciones para crear el repositorio público final, el cual carecerá de directorio de trabajo (no podremos programar directamente sobre el):

1) Inicialización por fetch

Creamos un nuevo repositorio:

$ mkdir repo-publico
$ cd repo-publico/
$ git --bare init --shared
Initialized empty shared Git repository in /home/usuario/repo-publico/

Inicializamos este con el contenido del repositorio inicial que hemos creado anteriormente:

$ git --bare fetch ../inicial/ master:master
warning: no common commits
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From ../inicial
 * [new branch]      master     -> master

2) Creación e inicialización por clone

Clonamos el repositorio inicial:

git clone inicial repo-publico

Sea cual sea el camino seguido, en el directorio “repo-publico/” tenemos un repositorio listo para ser compartido públicamente via SSH o sistema de ficheros.

En caso de que queramos compartir mediante protocolo Git (puerto 9418), necesitaremos crear el fichero ‘git-daemon-export-ok’ y configurar el demonio git (consultar man git-daemon):

$ touch repo-publico/git-daemon-export-ok

Por contra, si queremos compartir por HTTP, se requiere realizar algunos ajustes extra y ubicar el repositorio en un directorio accesible por el servidor web que hayamos configurado (p.ej. apache):

$ cd repo-publico
$ git --bare update-server-info
$ chmod a+x hooks/post-update

Trabajo colaborativo a partir de un repositorio público

Es habitual que utilicemos Git para desarrollar un proyecto donde participan varios programadores, donde se disponga de un repositorio público al cual podemos acceder vía SSH, HTTP, protocolo Git o por el mismo sistema de fichero. Por ejemplo:

git clone ssh://ejemplo.com/~usuario/repo-publico repo-clonado
git clone http://ejemplo.com/~usuario/repo-publico repo-clonado
git clone git://ejemplo.com/usuario/repo-publico repo-clonado
git clone /home/usuario/repo-publico repo-clonado

Este comando creará el directorio ‘repo-clonado’ que contendrá referencias a las ramas remotas del repositorio original:

$ git branch -r
  origin/HEAD
  origin/devel
  origin/master

Y, adicionalmente, generará una rama local equivalente a la rama que estuviese activa en el repositorio original (en nuestro caso, ha sido la rama ‘master’):

$ git branch
* master

A partir de aquí podemos ir implementando nuestros cambios y realizando commits que se almacenaran localmente en nuestro repositorio clonado. Por tanto, podremos trabajar sin necesidad de conectividad constante con el repositorio original.

Actualización del repositorio local clonado

Al cabo del tiempo, aparte de nuestros cambios locales, es posible que las diferentes ramas remotas hayan ido sufriendo cambios que deseemos incorporar a nuestro repositorio local:

$ git fetch
remote: Counting objects: 5, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /cygdrive/c/Documents and Settings/usuario/repo-original/
   1574c72..2939243  master     -> origin/master

De esta forma habremos actualizado localmente las ramas remotas que hacen referencia al repositorio original:

$ git branch -r
  origin/HEAD
  origin/devel
  origin/master

A continuación nos podría interesar realizar un merge de alguna de esas ramas (p.ej. origin/master) con alguna de nuestras propias ramas (p.ej. master). El procedimiento es el mismo que el que hemos visto en secciones anteriores de este tutorial:

$ git checkout master
Switched to branch "master"
Your branch and the tracked remote branch 'origin/master' have diverged,
and respectively have 3 and 2 different commit(s) each.

$ git merge origin/master
Merge made by recursive.
 test.txt |    2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

También disponemos del comando ‘git pull’ que realizado en 1 única ejecución el fetch y el merge que acabamos de describir:

$ git pull
remote: Counting objects: 5, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /cygdrive/c/Documents and Settings/usuario/borrar/
   20ad3a4..d5e4715  master     -> origin/master
Merge made by recursive.
 test.txt |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)

Actualización del repositorio público

Por otra parte, el responsable del repositorio público puede que tenga interés en incorporar los cambios que hemos realizado nosotros. En ese caso, podríamos o bien hacerle llegar las modificaciones en forma de parche por correo (‘man git-format-patch’, ‘man git-am’ y ‘man git-send-email’) o bien proporcionandole acceso a nuestro repositorio clonado para que realice un pull. Por ejemplo:

$ git pull /home/colaborador/repo-clonado/ master
Updating 2939243..6a8b950
Fast forward
 hola.txt |    1 +
 1 files changed, 1 insertions(+), 0 deletions(-)
 create mode 100644 hola.txt

Si va a incorporar frecuentemente los cambios que vayamos realizando, quizás le interese crear un enlace remoto para poder efectuar fetch y merge:

$ git remote add clonado /home/colaborador/repo-clonado/

$ git fetch clonado
From ../repo-clonado
 * [new branch]      master     -> clonado/master

Es posible visualizar las ramas remotas registradas mediante:

$ git branch -r
  clonado/master

Si en algún momento se desea eliminar el enlace a la rama remota podemos ejecutar ‘git remote rm clonado’.

Repositorio público centralizado estilo CVS/Subversion

En el flujo de trabajo que hemos dibujado hasta el momento, los desarrolladores no subían directamente cambios al repositorio público, sino que era el responsable del mismo quien aceptaba los cambios y los incorporaba después de revisarlos. Sin embargo, Git también soporta que los desarrolladores puedan subir sus modificaciones directamente a un repositorio centralizado al más puro estilo CVS o Subversion (eliminando el papel de responsable del repositorio público).

Para que un repositorio público pueda ser utilizado de esta forma, es necesario permitir la ejecución del comando ‘push’ (que veremos más adelante). En primer lugar deberemos crear el repositorio público (tal y como hemos visto en secciones anteriores) y
habilitar alguno de los siguientes accesos:

  • Acceso directo por sistema de ficheros con permisos de escritura para el usuario sobre el directorio del repositorio.
  • Acceso remoto vía SSH (consultar man git-shell) y permisos de escritura para el usuario sobre el directorio del repositorio.
  • Acceso HTTP con WebDav debidamente configurado (google).
  • Acceso mediante protocolo Git con el servicio “receive-pack” activado en el git daemon.

Una vez tenemos el repositorio compartido preparado para ser utilizado, cada desarrollador deberá clonarlo para poder trabajar localmente (en el ejemplo suponemos que tenemos acceso directo por sistema de ficheros local ‘/home/central/repo-publico/’):

$ git clone /home/central/repo-publico/ repo-personal
Initialized empty Git repository in /home/usuario/repo-personal/.git/
0 bloques

$ echo "Hola" > README
$ git commit -a -m "Test 1"
Created commit 27cfec1: Test 1
 1 files changed, 1 insertions(+), 0 deletions(-)

Cuando el desarrollador lo consideren oportuno, podrá sincronizar su repositorio local con el central (pull) y subir los cambios que han desarrollado localmente (push):

$ git pull

$ git push
Counting objects: 5, done.
Unpacking objects: 100% (3/3), done.
Writing objects: 100% (3/3), 248 bytes, done.
Total 3 (delta 0), reused 0 (delta 0)
To /home/usuario/repo-compartido
   9dda6f6..27cfec1  master -> master

De esta forma, todos los commits realizados que han sido guardados localmente, se traspasan al repositorio central compartido de forma automática.

Chequeo y optimización de repositorios

Chequeo de errores en el repositorio:

$ git fsck
dangling commit 06578a4d9893bc60cd8d2e40a6f3322a777a8d09
dangling tree d5935ebd21acf31cde0221fa87947fd050aef4a2
dangling tree 3615b1a2ae5372c63a757085f41197656c5e2c11

Optimización del repositorio, comprimiendo y eliminando información obsoleta:

$ git gc
Counting objects: 51, done.
Compressing objects: 100% (29/29), done.
Writing objects: 100% (51/51), done.
Total 51 (delta 4), reused 0 (delta 0)

Conclusión

Git es un sistema de control de versiones extremadamente potente y flexible. Su uso es 100% recomendable en cualquier tipología de proyecto dado que se adapta a cualquier situación o necesidad. Sólo el tiempo dirá cual será el sistema distribuido que ganará más adeptos (bzr, darcs, arch, git, etc.) y Git es un buen candidato.

5 thoughts on “Git, sistema de control de versiones distribuido”

  1. Muy buen articulo… lastima de haberlo escrito hace 2 meses cuando empece a mirarme git…:p
    Podrias ponerlo en pdf? es un muy buen resumen y hay detalles que no conocia. Me gustaria imprimirlo para usarlo como referencia.

  2. Joan, no estaria dedicar algun dia a veure com es podria versionar els .ACL. Però crec que lo interessant seria muntar algun script que descomposes l’arxiu ACL en diversos fitxers de text (1 per programa per exemple), versionar per separar y després tornar a juntar-ho tot. Si algun dia arribem a estar a PT…

    Gerard, prueba a copiar solo el texto del artículo y a pegarlo en OpenOffice, creo que sale lo suficientemente decente como para imprimirlo. Me alegro que al menos haya servido para conocer algún detalle nuevo… si conoces algo que no haya explicado y que creas que es útil, no te cortes! Que yo todavía soy un novato con Git 😉

  3. tengo una pregunta, hice unos cambios en una mi rama shop y luego hice pull para que se actualice, pero ahora quisiera ver los cambios que yo habia agregado, y no todos los que se agrego con git pull, se podria ver atraves de diff solo mis cambios??

  4. Hola euge:

    si consultas el manual de git-diff (man git-diff), te explica como utilizar git diff para ver las diferencias entre diferentes commits. Espero que localices ahí aquello que buscas 😀

Leave a Reply

Your email address will not be published. Required fields are marked *