lunes, 14 de abril de 2014

El experto

Hoy voy a publicar un vídeo que he visto en Microsiervos. El vídeo se titula The Expert (el experto). Es una parodia perfecta de aquellas reuniones de trabajo en las que los jefes llaman a un empleado experto en cierto campo pero luego no tienen en cuenta sus opiniones. Como nota final decir que yo me he encontrado más de una vez en situaciones como esta, algunas igual de surrealistas y que en cierta ocasión tras una reunión así tuve yo otra reunión con mi manager donde me acusó de actitud negativa y falta de visión comercial. Mi campo es el desarrollo de aplicaciones informáticas, pero este se puede aplicar a casi cualquier trabajo de oficina. El vídeo está en inglés pero se entiende bien y además se pueden activar los subtítulos que funcionan perfectamente.

viernes, 28 de marzo de 2014

Symfony2 avanzado: Usando un objeto Query para crear un QueryBuilder

Este caso es un poco difícil de explicar. Vamos a suponer lo siguiente: Tenemos una entidad Factura y un repositorio FacturaRepository que tiene una función findFacturas(). Dicha función recibe un parámetro obligatorio $usuario de clase Usuario y otro opcional $filtros que por defecto es un array vacío. La mencionada función, que está perfectamente testeada y no desea cambiarse, prepara dinámicamente una query DQL teniendo en cuenta los permisos del usuario y un montón de filtros y devuelve un objeto de clase Query que al ejecutarla devuelve la lista de facturas del usuario.

namespace Company\ContabilidadBundle\Entity;

class Factura {
  //...
}

class FacturaRepository {
  public function findFacturas(Usuario $usuario, $filtros = array( )) {
    //...
    return $query;
  }
}

Por otro lado, en otra parte de la aplicación, tenemos un formulario en el que necesitamos poder seleccionar una de las facturas del usuario mediante un cuadro de selección (combobox). Para ello lo más lógico es añadir al formulario un campo de tipo entity. El problema es que que no podemos aprovechar la query que tenemos en nuestro repositorio porque los campos entity se cargan con un QueryBuilder.

Hoy me he encontrado con este mismo problema. Quedaba totalmente descartado Convertir en un QueryBuilder una query DQL de dos palmos y medio de largo, perfectamente testeada, por su complejidad (y porque no me daba la gana), así que me he puesto a investigar si había alguna forma de convertir o cargar un objeto Query en un objeto QueryBuilder. No ha sido fácil pero lo he conseguido...

Este es el resultado:

namespace Company\ContabilidadBundle\Form\Type;

class FormularioFormType extends AbstractType {

  //...

  public function buildForm(FormBuilderInterface $builder, array $options) {

    //...

    $builder->add(
      'factura',
      'entity',
      array(
        'class' => 'CompanyContabilidadBundle:Factura',
        'query_builder' => function(EntityRepository $repository) use($usuario) {

          $query = $repository->findFacturas($usuario);
          $qb = $repository->createQueryBuilder('f');
          $qb->where($qb->expr()->in('f', $query->getDql()));
          $qb->setParameters($query->getParameters());
          return $qb;
        }
    ));
  }
}

Veámoslo paso a paso:

  1. En primer lugar obtenemos el objeto Query del repositorio. Dicho objeto estará listo para ser ejecutado devolviendo el resultado que deseamos.
  2. En segundo lugar extraemos la sentencia DQL y los parámetros del objeto Query con las instrucciones $query->getDql() y $query->getParameters().
  3. En último lugar creamos un nuevo objeto QueryBuilder y simulamos una subquery como esta: SELECT * FROM Facturas f WHERE f IN (...), poniendo la sentencia DQL original en el lugar de los puntos suspensivos.

Es rebuscado pero funciona perfectamente.

sábado, 1 de marzo de 2014

Symfony 2: Añadiendo bundles externos

Esta es la tercera parte del mini-curso de Symfony 2 que empecé hace tiempo. En el último capítulo creamos nuestro primer proyecto Symfony 2. El proyecto recién creado traerá todas las dependencias necesarias para empezar a programar con el framework Symfony. Sin embargo una de las grandes ventajas de Symfony 2 es que es fácilmente ampliable mediante librerías externas programadas por terceros llamadas bundles.

Hay bundles para todos los gustos: unos añaden características nuevas a Symfony 2, otros mejoran su seguridad, otros amplían funcionalidades, etc. La forma de instalar un bundle externo es nuevamente mediante el uso de composer. Para ello hay que añadir las dependencias en el archivo composer.json que encontraremos en la raíz de nuestro proyecto Symfony de la siguiente forma:

{
    "require": {
        "vendor/package": "version",
    }
}

En este caso vendor y package forma el nombre del bundle mientras que version es el número de versión (por ejemplo: 1.2.3 o 1.2.* o >1.2.3 o >1.2.3,<1.3, etc. Para buscar bundles compatibles podemos ir a Packagist, que es el repositorio de paquetes instalables vía Composer.


En ejemplo: añadiendo Bootstrap

Vamos a verlo con un ejemplo. Supongamos que queremos añadir el framework CSS Bootstrap a nuestro proyecto Symfony. Podríamos descargarnos la librería de Bootstrap y añadirla manualmente a nuestro proyecto, pero en Symfony siempre que sea posible preferiremos hacerlo vía bundle externo. Buscando en Packagist encontramos que hay varios bundles que añaden Bootstrap a Symfony. Yo he elegido Mopa Bootstrap porque lo conozco de antes, pero además es uno de los que más descargas y mejor valoración tienen en Packagist. Visitamos la página web oficial del proyecto Mopa Bootstrap en Github y allí la sección de instalación nos indica como añadir el bundle a Symfony 2; hay que añadir el siguiente código en el archivo composer.json:

{
    "require": {
        "mopa/bootstrap-bundle": "v3.0.0-beta3",
        "twbs/bootstrap": "v3.0.0"
    }
}

Una vez hecho esto hay que ejecutar composer update desde el directorio principal del proyecto para que se actualicen las dependencias y se descarguen las librerías.

$ composer update
Loading composer repositories with package information
Updating dependencies (including require-dev)
  - Installing mopa/composer-bridge (v1.3.0)
    Downloading: 100%

  - Installing mopa/bootstrap-bundle (v3.0.0-beta3)
    Downloading: 100%

  - Installing twbs/bootstrap (v3.0.0)
    Downloading: 100%

Como podemos ver además de los dos bundles indicados nos ha instalado un tercero: mopa/composer-bridge (v1.3.0). Esto es porque Composer sabe que es necesario instalarlo o sino mopa/bootstrap-bundle (v3.0.0-beta3) no podrá funcionar. Ahora ya podemos empezar a usar Bootstrap en nuestro proyecto web. Pero, ¿Como se usa mopa/bootstrap en Symfony? Para saber eso hay que leerse la documentación.


Mini-curso de Symfony 2

  • Primeros pasos: Conceptos y definiciones sobre Symfony 2.
  • Primer proyecto: Como empezar un proyecto Symfony 2.
  • Añadiendo bundles externos: Agregar dependencias a composer.json.

martes, 11 de febrero de 2014

Google Adwords (i)

¿Como consigo visitantes en mi nueva tienda on-line? ¿Por qué nadie visita mi página? ¿Qué hago para ser "popular" en Internet? Estas cuestiones y otras similares las hemos escuchado decenas de veces de los clientes. Básicamente hay dos formas de conseguir visitantes para una página web y ambas se conocen por sus acrónimos: SEO y SEM. Las dos se basan en lo mismo: conseguir aparecer bien posicionado por una o varias palabras clave en los principales buscadores (Google, Yahoo, Bing, etc). La diferencia es el camino que toman para conseguir el objetivo.

SEO (Search Engine Optimization) es un conjunto de técnicas encaminadas a la optimización de una página web para que sea mejor considerada por un buscador (por ejemplo: Google, Yahoo, Bing, ...) de forma que aparezca mejor posicionada en los resultados de búsqueda para un conjunto de palabras clave.

SEM (Search Engine Marketing) es una de las formas que existen de hacer publicidad de una página web. Consiste en insertar anuncios patrocinados entre los resultados de búsqueda de un buscador (por ejemplo: Google, Yahoo, Bing, ...) para un conjunto de palabras clave a cambio de dinero. Existen muchos programas de SEM pero el más popular es Google Adwords.

Resumiendo, SEO es optimizar la página web para hacerla atractiva a los buscadores y SEM es pagar para aparecer en los resultados de búsqueda. La compra de enlaces patrocinados es una muy buena estrategia para complementar el SEO sobre todo en páginas web que empiezan.

Ya hemos comentado que Google Adwords es el programa de publicidad SEM de Google para sitios web. Adwords se utiliza para hacer publicidad patrocinada y cuenta con enormes cantidades de clientes con sitios web de todo tipo y de todas partes del mundo. Se trata de anuncios que se muestran de forma relevante en los resultados de la búsqueda del usuario. Google permite pagar para que un sitio web aparezca por determinadas palabras claves en los resultados de búsqueda.


Primeros pasos con Google Adwords

Usar el programa Adwords es muy sencillo: Sólo se necesita una cuenta Google y un navegador. Tanto si se dispone de cuenta Google como si no la primera vez hay que acceder al programa Adwords, seleccionar la zona horaria y la moneda y confirmar la creación. A partir de este momento ya se puede crear la primera campaña publicitaria, pero ésta no comenzará a publicarse hasta que se indiquen las preferencias de facturación.


Como configurar una campaña Adwords de éxito

Mucha gente que crea su primera campaña Adwords se encuentra con que si sigue los consejos que la apropia aplicación Adwords le ofrece, lo que consigue son unas pocas visitas muy caras que no siempre soy visitas útiles. Esto ocurre porque si bien Google trata siempre de que todos los anuncios de los clientes aparezcan en los resultados de las búsquedas, su objetivo principal es maximizar sus propios beneficios por lo tanto los anuncios que más clics reciban serán los que más aparezcan hasta agotar el presupuesto del cliente. Por ello diseñar una campaña Adwords de éxito no es sencillo y se requieren ajustes continuos. El objetivo de Google es que haya los máximos clics posibles mientras que el objetivo del cliente suele ser realizar las máximas ventas desde su página web. Una campaña Adwords tiene que conseguir que tanto Google como el cliente alcancen sus objetivos. Y eso no es fácil de hacer.

Así, lo primero que se debe hacer al crear una campaña de Google Adwords es preguntarse, ¿Que busco conseguir con mi campaña? ¿Ventas? ¿Registros? ¿Visitas? Dependiendo del objetivo final la campaña será totalmente diferente y el precio máximo por clic que deberá pagarse será muy distinto

Por ejemplo el objetivo de la campaña de una web de venta de productos será realizar las mayores ventas, pero no a cualquier precio. Si el precio pagado por los clics de la campaña supera el margen de beneficio de los productos el usuario estará perdiendo dinero. Según nuestra experiencia en el mejor de los casos el rendimiento de una campaña Adwords (relación entre clics y ventas) nunca superará el 10%. Pero ese es el mejor de los casos. Una buena campaña debe tener como objetivo tener un rendimiento de entre el 3% y el 5%. Así supongamos que tenemos un rendimiento esperado del 3% y que el margen de beneficio medio por producto es de 3€. En este caso si el precio pagado por clic supera los 0,09 € el usuario perderá dinero.

En cambio si el objetivo del usuario fuera el de conseguir registros el cálculo del precio máximo por clic sería muy diferente. Por ejemplo tuvimos un cliente que ofrecía a través de su web muestras gratuitas de un producto y su objetivo era el de conseguir nuevos clientes. En este caso a priori no podíamos calcular con exactitud el precio máximo por clic ya que dependía de la aceptación del producto y de otros factores más subjetivos. En ese caso la experiencia en hacer campañas Adwords es muy importante. Un porcentaje elevado de los clientes que pidieron muestras gratuitas se convirtieron en clientes en los siguientes meses y la campaña fue un éxito.

Pero no sólo es importante el precio máximo por clic. Hay muchos otros factores que influyen en el éxito de una campaña Adwords como la elección de las palabras clave y la construcción de los anuncios... pero eso lo dejaremos para otros artículos.

jueves, 5 de diciembre de 2013

Symfony 2 avanzado: Ordenando relaciones OneToMany (Doctrine)

En Doctrine, Las relaciones OneToMany son un mecanismo muy potente para acceder a una lista de objetos hijo. El ejemplo más claro de uso de las relaciones OneToMany sería una factura y las líneas de factura o un pedido y el detalle del pedido; y en general cualquier relación maestro-esclavo o padre-hijo en la que la clave foránea se encuentra en la tabla hija.

Así en los proyectos Symfony 2 que usan Doctrine obtener la lista de objetos hijo desde el padre es tan sencillo como acceder a la propiedad del objeto. Internamente Doctrine convierte eso en una query a la base de datos, pero el resultado obtenido por defecto no viene ordenado. Podemos garantizar el orden añadiendo la cláusula OrderBy a la declaración de la propiedad OneToMany, pero esa ordenación es muy limitada. Veamos un ejemplo:

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity()
 * @ORM\Table(name="facturas")
 */
class Factura
{
  /**
   * @var integer
   * 
   * @ORM\Id
   * @ORM\Column(name="id_factura", type="integer", nullable=false)
   */
  private $id;

  /**
   * @var \DateTime
   *
   * @ORM\Column(name="fac_fecha", type="text", nullable=false)
   */
  private $fecha;

  /**
   *
   * @ORM\OneToMany(targetEntity="Lineas", mappedBy="factura", cascade={"all"})
   * @ORM\OrderBy({"id" = "ASC"})
   */
  private $lineas;

  // ...
}

/**
 * @ORM\Entity()
 * @ORM\Table(name="lineas_factura")
 */
class Lineas
{
  /**
   * @var integer
   * 
   * @ORM\Id
   * @ORM\Column(name="id_linea", type="integer", nullable=false)
   */
  private $id;

  /**
   * @var \DateTime
   *
   * @ORM\Column(name="lin_fecha", type="text", nullable=false)
   */
  private $fecha;

  /**
   * @var integer
   * 
   * @ORM\ManyToOne(targetEntity="Factura", inversedBy="lineas")
   * @ORM\JoinColumn(name="id_factura", referencedColumnName="id_factura")
   */
  private $factura;

  /**
   * @var integer
   *
   * @ORM\OneToOne(targetEntity="Producto", cascade={"all"})
   * @ORM\JoinColumn(name="id_producto", referencedColumnName="id_producto")
   */
  private $producto;

  // ...
}

/**
 * @ORM\Entity()
 * @ORM\Table(name="productos")
 */
class Producto
{
  /**
   * @var integer
   * 
   * @ORM\Id
   * @ORM\Column(name="id_producto", type="integer", nullable=false)
   */
  private $id;

  /**
   * @var string
   *
   * @ORM\Column(name="prod_nombre", type="text", nullable=false)
   */
  private $nombre;

  /**
   * @var float
   *
   * @ORM\Column(name="prod_precio", type="float", nullable=false)
   */
  private $precio;

  // ...
}

En este ejemplo la propiedad lineas de la clase Factura tiene una cláusula OrderBy que hace que la lista resultado se obtenga ordenada por id. ¿Pero que ocurriría si quisiéramos que el resultado viniese ordenado por algún dato del producto asociado como el nombre o el precio? ¿No hay forma de hacerlo con Doctrine?

Pues no, no la hay. Pero algo se puede hacer: podríamos ordenar el resultado después de obtenerlo. Al fin y al cabo se trata de un simple array de objetos. ¿Pero como hacerlo para que esté disponible en toda la aplicación y para cualquier tipo de lista? Sencillo: Con un servicio y más concretamente con una extensión Twig.

class MyExtension extends \Twig_Extension
{
  public function getName()
  {
    return 'MyExtension';
  }

  public function getFilters()
  {
    return array(
      'sortCollection' => new \Twig_Filter_Method($this, 'sortCollection'),
    );
  }

  public function sortCollection($collection, $properties) {

    $objects = $collection->getValues();
    if($properties == null) {
      return $objects;
    }

    if(is_string($properties)) {
      $property = $properties;
      $properties = array( );
      $properties[] = $property;
    }

    if(is_array($properties)) {

      usort($objects, function ($a, $b) use ($properties) {

        foreach($properties as $property) {

          $objA = $a;
          $objB = $b;

          if(is_string($property)) {

            $property = explode('.', $property);
            foreach($property as $method) {

              if($objA == null || $objB == null) {
                break;
              }

              $getter = 'get' . $method;

              if(method_exists($objA, $method)) {
                $objA = $objA->$method();
              }
              elseif(method_exists($objA, $getter)) {
                $objA = $objA->$getter();
              }
              else {
                $objA = null;
              }

              if(method_exists($objB, $method)) {
                $objB = $objB->$method();
              }
              elseif(method_exists($objB, $getter)) {
                $objB = $objB->$getter();
              }
              else {
                $objB = null;
              }
            }
          }

          if($objA != null && $objB == null) {
            return -1;
          }
          elseif($objA == null && $objB != null) {
            return 1;
          }
          elseif($objA < $objB) {
            return -1;
          }
          elseif($objA > $objB) {
            return 1;
          }
        }

        return 0;
      });
    }

    return $objects;
  }
}

¿Pero como lo usamos? Pues básicamente en Twig, para obtener una lita de líneas de factura ordenadas por el nombre del producto, haríamos lo siguiente:

<ul>
{% for linea in factura.lineas | sortCollection([ 'producto.nombre' ]) %}
  <li>
    {{ linea.producto.nombre }}
  </li>
{% endfor %}
</ul>

Una extensión Twig, también puede definirse como servicio y por lo tanto, también podríamos ordenar una colección de objetos Doctrine directamente desde el controlador. Por ejemplo podríamos necesitar una lista de líneas de factura ordenadas primero por precio del producto asociado y segundo por el nombre (para productos del mismo precio). Sería como sigue:

$myExtension = $this->get('twig.extension.MyExtension');
$list = $myExtension->sortCollection($factura->getLineas(), array(
  'producto.precio',
  'producto.nombre'
));

jueves, 28 de noviembre de 2013

Crea tu propio repositorio Git usando Gitolite

Git es un software diseñado por Linus Torvalds, el creador de Linux, que sirve para crear repositorios de código fuente permitiendo realizar el control de versiones de cientos de archivos, soportando múltiples usuarios y mantenimiento distribuido. En los proyectos que usan Git cada usuario dispone de una copia local completa del código fuente (repositorio local), incluyendo el historial de cambios, pero a la hora de fusionar cambios es conveniente tener un repositorio central, accesible por todos los usuarios.

En Internet se pueden encontrar sitios como GitHub o Bitbucket que permiten alojar repositorios de código fuente Git. En concreto GitHub se usa sobre todo para proyectos de software libre, pues es gratuito, aunque también tiene planes de pago para proyectos privativos. Por su parte Bitbucket ofrece cuentas gratuitas de hasta 5 usuarios para todo tipo de proyectos y también dispone de planes de pago para proyectos más grandes.

Tanto Github como Bitbucket ofrecen máxima seguridad y privacidad para alojar allí nuestro código fuente, pero si queremos tener un mayor control sobre donde está realmente nuestro código y quien tiene acceso, podemos crear nuestro propio repositorio central de fuentes Git con Gitolite. En realidad hay otras herramientas tanto gratuitas como de pago para crear y mantener repositorios Git, pero Gitolite es bastante sencilla de usar, además de ser de código abierto y gratuita, al igual que el propio Git.


Instalando y configurando Gitolite

Podríamos definir Gitolite como capa de control de acceso para repositorios Git que permite la administración de múltiples repositorios y usuarios indicando en cada caso quién puede hacer qué y dónde. Gitolite crea un usuario llamado gitolite en el servidor que es el que realiza todas las operaciones sobre el repositorio central. La invocación de acciones (clone, push, pull, etc.) se realiza mediante el protocolo ssh, por lo que será necesario que cada usuario disponga de una clave pública. Vamos a verlo todo en detalle.

Supongamos que tenemos un servidor Linux al que llamaremos SERVIDOR y tres usuarios USUARIO1, USUARIO2 y USUARIO3. Necesitamos tener acceso con permisos de root a la consola del SERVIDOR (puede hacerse vía ssh). Lo primero que haremos será instalar y configurar Git si no está ya instalado.

-- Instalación para Debian, Ubuntu, Mint, etc.
$ sudo apt-get install git git-core

-- Instalación para RedHat, Fedora, CentoOS, etc.
$ sudo yum install git git-core

-- Configuración (todos)
$ git config --global user.name "{usuario}"
$ git config --global user.email "{email}"
$ git config --global core.editor "{editor}"

Hay que sustituir {usuario} por el nombre del usuario, {email} por un email válido y {editor} por tu editor preferido (nano, vim, emacs, etc). Una vez configurado Git procederemos a crear una clave RSA para el servidor, ya que la necesitaremos tras instalar Gitolite:

$ ssh-keygen -t rsa -C "{email}"
$ ssh-add ~/.ssh/id_rsa
$ cp ~/.ssh/id_rsa.pub /tmp/administrador.pub

Hay que sustituir {email} por un email válido (generalmente el mismo que se usó en el paso anterior). También hemos copiado la clave pública id_rsa.pub al directorio /tmp con el nombre administrador.pub. A continuación instalamos Gitolite. Hay varias formas de hacerlo.

Esta página explica todas las formas de instalar Gitolite [en inglés]. Yo he elegido la más sencilla: La instalación por paquetes:

-- Instalación para Debian, Ubuntu, Mint, etc.
$ sudo apt-get install gitolite

-- Instalación para RedHat, Fedora, CentoOS, etc.
$ sudo yum install gitolite

-- Configuración de Gitolite (para todos)
$ sudo su - gitolite
$ gl-setup /tmp/administrador.pub

Al ejecutar esta instrucción Gitolite usa el editor por defecto para mostrarnos el archivo de configuración del programa. En cualquier momento podemos volver a editar este archivo de configuración que se llama .gitolite.rc y está en /var/lib/gitolite/. Es conveniente modificar la configuración por defecto poniendo $GL_WILDREPOS = 1; y $REPO_UMASK = 0027;. Esta página explica todo sobre la configuración de .girolite.rc [en inglés]. Una vez finalizada la edición Gitolite habrá dado permisos de administración a la clave pública que creamos anteriormente. También habrá creado varios archivos y carpetas en el usuario gitolite:

  • ~/.gitolite.rc: Archivo de configuración de gitolite.
  • ~/.gitolite/conf/gitolite.conf: Configuración de repositorios.
  • ~/.gitolite/keydir/: Carpeta de claves públicas de los usuarios.
  • ~/.gitolite/keydir/administrador.pub: Clave pública administrador.
  • ~/projects.list: Lista de proyectos.
  • ~/repositories/: Carpeta de repositorios.

Una vez hecho esto podemos salir del usuario gitolite pues hemos acabado con él:

$ exit

La configuración del programa Gitolite está realizada. Ahora veremos como añadir repositorios y como dar permiso a otros usuarios. Esto se hace desde el usuario cuya clave pública hemos indicado que es el administrador. En este caso un usuario de nuestro propio servidor. Gitolite usa un repositorio Git como método de configuración, lo cual nos sirve a la vez como sistema para comprobar que la instalación de Gitolite fue correcta. Así que lo primero que hay que hacer el clonar el repositorio gitolite-admin.git.

$ git clone gitolite@localhost:gitolite-admin.git

Si todo funciona correctamente se habrá creado una carpeta llamada gitolite-admin en la carpeta home de nuestro usuario del servidor. Seguidamente editaremos el archivo ~/gitolite-admin/conf/gitolite.conf que es el que contiene la configuración de repositorios y usuarios. Esta página explica lo necesario sobre la configuración de gitolite.conf [en inglés]. Para añadir un repositorio o un usuario basta con añadir una entrada a ese archivo siguiendo la notación. Por ejemplo:

@developers = usuario1 usuario2 usuario3
repogitolite-admin
RW+=administrador
repotesting
RW+=@all
repomi-proyecto
RW+=@developers

Ya sólo nos queda crear una clave RSA para cada uno de nuestros usuarios y copiar la clave pública a la carpeta ~/gitolite-admin/keydir/ del servidor, poniendo el mismo nombre que le hemos dado a los usuarios en el archivo de configuración. En nuestro caso usuario1.pub, usuario2.pub y usuario3.pub. Por último subiremos los cambios del proyecto usando el propio Git.

$ cd ~/gitolite-admin/
$ git add .
$ git commit -m '{motivo de la modificación}'
$ git push origin master


Copiando un repositorio desde otro servidor

Puede darse el caso de que tengamos nuestro repositorio central en un servidor y queramos moverlo a otro. Este proceso es en realidad es bastante sencillo de realizar: lo primero es instalar Gitolite en el servidor nuevo, tal como se ha explicado anteriormente. Lo siguiente es entrar en el servidor antiguo y seguir los siguientes pasos:

-- Copiamos el repositorio entero del proyecto antiguo
$ cd /var/lib/gitolite/
$ scp -r repositories/mi-proyecto.git/* gitolite@{servidor}:repositories/mi-proyecto.git/

-- Copiamos .gitolite.rc como .gitolite.rc.bak y salimos
$ scp .gitolite.rc gitolite@{servidor}:.gitolite.rc.bak
$ exit

Debes cambiar {servidor} Por la IP o la URL del servidor nuevo. A continuación debemos entrar en el usuario gitolite del servidor nuevo y hacer lo siguiente:

-- Comprobamos las diferencias entre las dos versiones .gitolite.rc
-- y realizamos los cambios que sean necesarios
$ diff .gitolite.rc .gitolite.rc.bak

-- Reconfiguramos gitolite con los nuevos cambios y salimos
$ gl-setup
$ exit

Ya sólo queda abrir el proyecto gitolite-admin, asegurarnos de que mi-proyecto esté en gitolite.conf, asignar los usuarios al proyecto y subir los cambios. Aquí puedes encontrar más información sobre como mover un repositorio Gitolite a otro servidor.

Bunus track: Si queremos desactivar el servidor antiguo para que los usuarios no puedan enviar cambios basta con editar el archivo .gitolite.rc y poner en la primera línea exit 1;.


Referencias

domingo, 20 de octubre de 2013

Arrancando Ubuntu 13.10 desde Grub Rescue

Tras actualizar uno de nuestros PCs a Ubuntu 13.10 Saucy Salamander he visto que quedaba poco espacio en disco y he decidido eliminar el otro sistema operativo que había instalado (que era Windows 7, pero eso da igual). Tras borrar la partición de Windows he intentado hacer más grande la partición de Linux y ahí han empezado los problemas. Para empezar no se puede tocar la partición en uso, así que he arrancado con el Live CD de Ubuntu y lo he intentado de nuevo con GParted...

La partición sobre la que estaba instalado Ubuntu era una partición lógica dentro de una partición extendida y se encontraba al final del disco. Mal asunto. He intentado mover las particiones al principio para hacerlas más grandes pero GParted no me lo ha permitido, así que he hecho una copia de la partición entera, proceso que ha tardado más una hora. He probado de arrancar Ubuntu desde esa partición y todo ha ido bien así que he vuelto arrancar desde el Live CD, he eliminado la partición extendida y he agrandado al máximo la nueva partición.

Todo ha ido aparentemente bien pero al reiniciar el sistema ha salido un error y ha aparecido Grub Rescue que es una especie de intérprete de comandos de Grub que permite reparar una instalación. Entonces me he puesto a investigar en Internet y he encontrado la solución:

  1. Lo primero que hay que hacer es averiguar cual es el disco duro y la partición sobre la que tienes instalado Ubuntu. Eso se hace con el comando ls:
  2. Grub Rescue> ls
    (hd0) (hd0,msdos1) (hd0,msdos2) (hd1) (hd1,msdos2) (hd2,msdos1)
    Grub Rescue> ls (hd0)/
    Error: Unknown filesystem.
    Grub Rescue> ls (hd0,msdos2)/
    Error: Unknown filesystem.
    Grub Rescue> ls (hd0,msdos1)/
    bin/  boot/  cdrom/  dev/  etc/  home/  lib/  lib64/
    media/  mnt/  opc/  proc/  root/  run/  sbin/  selinux/
    srv/  sys/  tmp/  usr/  var/
    

    Se trata de averiguar que partición tiene instalado nuestro Linux. Para ello hay que mirar una a una el contenido de las particiones ls (hdx,y)/ (ojo con la barra final), hasta dar con la adecuada que en mi caso es la (hd0,msdos1)

  3. Una vez sabemos el nombre interno de la partición a iniciar hay que arrancar Ubuntu desde esa partición. Pare ello basta con ejecutar las siguientes instrucciones, cambiando (hd0,msdos1) por el nombre real de la partición:
  4. Grub Rescue> set root=(hd0,msdos1)
    Grub Rescue> set prefix=(hd0,msdos1)/boot/grub
    Grub Rescue> insmod normal
    Grub Rescue> normal
    
  5. Por último una vez arrancado Ubuntu hay que arreglar Grub para que la siguiente vez funcione correctamente. Para ello hay que abrir un terminar y ejecutar las siguientes instrucciones:
  6. $ sudo update-grub2
    $ sudo grub-install /dev/sda
    

    Hay que elegir bien en que dispositivo instalamos Grub: /dev/sda se corresponde con el primer disco duro (hd0) en Grub Rescue. /dev/sdb sería el segundo disco duro (hd1), /dev/sdc sería (hd2), etc.

Y ya está. Con esto debería funcionar. Puedes encontrar más información aquí, aquí y aquí.