joe di castrohttp://joedicastro.com2012-04-26T21:15:00+02:00Sincronizar Bitbucket y GitHub2012-04-26T21:15:00+02:00joe di castrohttp://joedicastro.com/sincronizar-bitbucket-y-github.html<p>Para mis proyectos empleo generalmente <a href="http://mercurial.selenic.com/">mercurial</a> (hg) como sistema de
control de versiones, porque está hecho en Python y me parece más elegante y
agradable de usar que <a href="http://git-scm.com/">git</a>, aunque empleo git para algunas tareas, como
gestionar los plugins de <a href="http://www.vim.org">Vim</a>. Del mismo modo, el emplear hg como
<a href="http://es.wikipedia.org/wiki/Programas_para_control_de_versiones">DCVS</a> me llevaba de forma natural a emplear <a href="http://bitbucket.org">Bitbucket</a> como
alojamiento para mis repositorios públicos. </p>
<p>Siempre me ha gustado <strong>Bitbucket</strong>, su estilo sencillo, pero muy potente y creo que
tiene algunas características que son superiores a las de sus rivales (el <a href="http://blog.bitbucket.org/2011/12/08/pull-requests-with-side-by-side-diffs/">diff
side-by-side</a>, por ejemplo<sup id="fnref:gt"><a href="#fn:gt" rel="footnote">1</a></sup>). Pero también tengo claro que si hay algún
alojamiento de código en la red que destaca sobre todos los demás es <a href="http://github.com">GitHub</a>,
"todo" el mundo está allí y de algún modo, estás "obligado" a estar. <strong>GitHub</strong>
tiene algunas características muy potentes y en ciertos aspectos es muy superior
a Bitbucket, aunque me sigue gustando más el <em>feeling</em> de este último. </p>
<h2 id="hg__git">Hg != Git</h2>
<p>Me planteé entonces hace unos días que lo mejor era mantener una replica de mis
repositorios alojados en Bitbucket en GitHub, como dice el refrán, <em>Nunca tengas
todos tus huevos en una misma cesta</em>. El problema es que aunque Bitbucket soporta
repositorios tanto en mercurial como en git (para competir con GitHub), GitHub
solo soporta repositorios en Git. Y dado el éxito que tienen, dudo mucho que
tengan intención alguna de soportar otro sistema de versiones distinto a Git. </p>
<p>Técnicamente es posible mantener un repositorio con los dos dcvs a la vez, pero
maldita la gracia que me hacía, ademas de que no es nada aconsejable por el
incremento de tamaño en disco que esto supondría. Entonces, ¿como hacer para poder
alojar un repositorio mantenido con mercurial en un alojamiento que solo soporta
Git? La solución, <strong>hg-git</strong>.</p>
<h3 id="hg-git">hg-git</h3>
<p><a href="http://hg-git.github.com/">hg-git</a> es un plugin para mercurial que permite sincronizar el repositorio
local en hg con un repositorio en git, admitiendo tanto <em>push</em> como <em>pull</em> y sin
perdidas de información. Gracias a este plugin, podemos alojar el repositorio en
los dos sitios a la vez, empleando solo mercurial para gestionarlo.</p>
<p>Instalarlo es muy fácil (desde <code>easy_install</code> o <code>pip</code>) y emplearlo también.
Primero necesitas habilitarlo en el fichero <code>~/.hgrc</code>, así como la extensión
bookmarks que necesita para trabajar.</p>
<div class="codehilite"><pre><span class="k">[extensions]</span>
<span class="na">hgext.bookmarks</span> <span class="o">=</span>
<span class="na">hggit</span> <span class="o">=</span>
</pre></div>
<p>A continuación tienes que ir a tu repositorio y asignarle un <code>bookmark</code> a la
rama que tengas por defecto (suele ser <code>default</code>) o a <code>tip</code> con el nombre de
<code>master</code> (el nombre de las ramas por defecto en git), es decir:</p>
<div class="codehilite"><pre><span class="gp">$</span> hg bookmark -r default master -f
</pre></div>
<p>Y luego emplearlo es tan sencillo como si fuera un repositorio de mercurial, por
ejemplo un push:</p>
<div class="codehilite"><pre><span class="gp">$</span> hg push git+ssh://git@github.com/joedicastro/joedicastro.com.git
</pre></div>
<h2 id="sincronizar_el_repositorio_con_bitbucket_y_github">Sincronizar el repositorio con Bitbucket y GitHub</h2>
<p>Ahora, lo que tampoco me apetecía era tener que andar haciendo un push cada vez
para cada alojamiento, lo ideal es que cada vez que hiciera un push a un
sitio se sincronizara también el otro de forma automática. La solución
pasa por emplear los <em>paths</em> para definir alias para los repositorios remotos y
un <em>hook</em> para automatizar la sincronización. </p>
<p>Definir los alias con <em>paths</em> es realmente sencillo, nos vamos al fichero
<code>.hg/hgrc</code> del repositorio local y añadimos esto (e.g. el repo de este blog):</p>
<div class="codehilite"><pre><span class="k">[paths]</span>
<span class="na">github</span> <span class="o">=</span> <span class="s">git+ssh://git@github.com:joedicastro/joedicastro.com.git</span>
<span class="na">bitbucket</span> <span class="o">=</span> <span class="s">ssh://hg@bitbucket.org/joedicastro/joedicastro.com</span>
</pre></div>
<p>De este modo, realizar un <code>push</code> es tan sencillo como:</p>
<div class="codehilite"><pre><span class="gp">$</span> hg push bitbucket
</pre></div>
<p>Ahora necesitamos crear el <code>hook</code> que nos sincronice los dos alojamientos. Hay
en la red varias soluciones para esto, por ejemplo <a href="http://morgangoose.com/blog/2010/09/29/github-and-bitbucket-hooks/">esta</a> y <a href="http://wiki.ddenis.com/index.php?title=Sync_BitBucket_and_GitHub">esta</a>,
pero ninguna de las dos acababa de convencerme, la una por emplear un script
bash que entraba muy fácilmente en un bucle infinito y la otra por necesitar
otro modulo externo, que en mi caso no acababa de funcionar. Así
que basándome en la idea del script bash del primero, decidí crearme uno en
Python que funcionara correctamente y me solucionara el problema. </p>
<p>El código del <code>hook</code> es el siguiente:</p>
<div class="codehilite"><pre><span class="c">#!/usr/bin/env python</span>
<span class="c"># encoding: utf-8</span>
<span class="sd">"""</span>
<span class="sd"> bb_gh_sync.py: Mercurial hook to keep synced a repo to Bitbucket & GitHub.</span>
<span class="sd">"""</span>
<span class="c">#==============================================================================</span>
<span class="c"># This script maintain synced a repository to booth github and bitbucket sites,</span>
<span class="c"># using only a local mercurial repository. To do this, makes use of hg-git, the</span>
<span class="c"># paths defined in my local hg repo and the environment variable given by hg, to</span>
<span class="c"># push to the site non described in the command line argument. This way, it's</span>
<span class="c"># irrelevant which site I decided to push every time, booth are done by this</span>
<span class="c"># hook.</span>
<span class="c">#===============================================================================</span>
<span class="c">#==============================================================================</span>
<span class="c"># Copyright 2012 joe di castro <joe@joedicastro.com></span>
<span class="c">#</span>
<span class="c"># This program is free software: you can redistribute it and/or modify</span>
<span class="c"># it under the terms of the GNU General Public License as published by</span>
<span class="c"># the Free Software Foundation, either version 3 of the License, or</span>
<span class="c"># (at your option) any later version.</span>
<span class="c">#</span>
<span class="c"># This program is distributed in the hope that it will be useful,</span>
<span class="c"># but WITHOUT ANY WARRANTY; without even the implied warranty of</span>
<span class="c"># MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the</span>
<span class="c"># GNU General Public License for more details.</span>
<span class="c">#</span>
<span class="c"># You should have received a copy of the GNU General Public License</span>
<span class="c"># along with this program. If not, see <http://www.gnu.org/licenses/>.</span>
<span class="c">#==============================================================================</span>
<span class="n">__author__</span> <span class="o">=</span> <span class="s">"joe di castro <joe@joedicastro.com>"</span>
<span class="n">__license__</span> <span class="o">=</span> <span class="s">"GNU General Public License version 3"</span>
<span class="n">__date__</span> <span class="o">=</span> <span class="s">"23/04/2012"</span>
<span class="n">__version__</span> <span class="o">=</span> <span class="s">"0.1"</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">from</span> <span class="nn">tempfile</span> <span class="kn">import</span> <span class="n">gettempdir</span>
<span class="kn">from</span> <span class="nn">subprocess</span> <span class="kn">import</span> <span class="n">call</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="sd">"""Main section"""</span>
<span class="n">tmp_dir</span> <span class="o">=</span> <span class="n">gettempdir</span><span class="p">()</span>
<span class="n">lock_file</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">tmp_dir</span><span class="p">,</span> <span class="s">"bb_gh_sync.lock"</span><span class="p">)</span>
<span class="c"># make sure that only runs once for each repository</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">exists</span><span class="p">(</span><span class="n">lock_file</span><span class="p">):</span>
<span class="nb">open</span><span class="p">(</span><span class="n">lock_file</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
<span class="c"># if pushed to bitbucket, push to github too</span>
<span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s">'HG_ARGS'</span><span class="p">]</span> <span class="o">==</span> <span class="s">"push bitbucket"</span><span class="p">:</span>
<span class="n">call</span><span class="p">([</span><span class="s">"/usr/bin/env"</span><span class="p">,</span> <span class="s">"hg"</span><span class="p">,</span> <span class="s">"push"</span><span class="p">,</span> <span class="s">"github"</span><span class="p">])</span>
<span class="c"># et viceversa...</span>
<span class="k">if</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s">'HG_ARGS'</span><span class="p">]</span> <span class="o">==</span> <span class="s">"push github"</span><span class="p">:</span>
<span class="n">call</span><span class="p">([</span><span class="s">"/usr/bin/env"</span><span class="p">,</span> <span class="s">"hg"</span><span class="p">,</span> <span class="s">"push"</span><span class="p">,</span> <span class="s">"bitbucket"</span><span class="p">])</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">os</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">lock_file</span><span class="p">)</span>
<span class="k">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">"__main__"</span><span class="p">:</span>
<span class="n">main</span><span class="p">()</span>
</pre></div>
<p>Ahora solo tenemos que editar nuestro fichero <code>~/.hgrc</code> para habilitarlo y ya
estaría listo para funcionar. Editamos el fichero y le añadimos estas lineas:</p>
<div class="codehilite"><pre><span class="k">[hooks]</span>
<span class="na">post-push</span> <span class="o">=</span> <span class="s">$HOME/dotfiles/hg/bb_gh_sync.py</span>
</pre></div>
<p>Ahora, si hacemos un push a Bitbucket, el hace automáticamente un push también a
GitHub al acabar el primero, y viceversa. De este modo, hacer un push a ambos
alojamientos es tan sencillo como:</p>
<div class="codehilite"><pre><span class="gp">$</span> hg push bitbucket
<span class="go">pushing to ssh://hg@bitbucket.org/joedicastro/joedicastro.com</span>
<span class="go">running ssh hg@bitbucket.org 'hg -R joedicastro/joedicastro.com serve --stdio'</span>
<span class="go">searching for changes</span>
<span class="go">no changes found</span>
<span class="go">running hook post-push: $HOME/dotfiles/hg/bb_gh_sync.py</span>
<span class="go">pushing to git+ssh://git@github.com:joedicastro/joedicastro.com.git</span>
<span class="go">creating and sending data</span>
<span class="go">["git-receive-pack 'joedicastro/joedicastro.com.git'"]</span>
<span class="go"> github::refs/heads/master => GIT:198e8cc9</span>
<span class="go">running hook post-push: $HOME/dotfiles/hg/bb_gh_sync.py</span>
<span class="gp">$</span>
</pre></div>
<p>De este modo puedo mantener copias de los repositorios locales en ambos sitios
de manera automática y sincronizada, sin preocuparme, ni hacer un trabajo extra.
Eso si, conviene prescindir de los wikis y documentarlo todo a través de ficheros
<code>README.md</code> en formato Markdown para facilitar la integración de los dos sitios.
Lo que por otro lado también ayuda a mantenerlos actualizados de manera más sencilla.</p>
<div class="footnote">
<hr />
<ol>
<li id="fn:gt">
<p>Bueno, algunos rivales como <a href="http://gitorious.org/">Gitorius</a>, también soportan esta característica
 <a href="#fnref:gt" rev="footnote" title="Jump back to footnote 1 in the text">↩</a></p>
</li>
</ol>
</div>