{"id":654,"date":"2008-05-06T22:36:31","date_gmt":"2008-05-06T21:36:31","guid":{"rendered":"http:\/\/www.marblestation.com\/blog\/?p=654"},"modified":"2012-04-26T16:27:24","modified_gmt":"2012-04-26T14:27:24","slug":"git-sistema-de-control-de-versiones-distribuido","status":"publish","type":"post","link":"https:\/\/www.marblestation.com\/?p=654","title":{"rendered":"Git, sistema de control de versiones distribuido"},"content":{"rendered":"<p>Hace ya alg\u00fan tiempo escrib\u00ed un post sobre <a href=\"http:\/\/www.marblestation.com\/blog\/?p=605\">Bazaar NG (bzr)<\/a>, el cual es un sistema de control de versiones distribuido que tiene el soporte de <a href=\"http:\/\/www.canonical.com\/\">Canonical<\/a> (empresa que tambi\u00e9n da soporte a <a href=\"http:\/\/www.ubuntu.com\/\">Ubuntu<\/a>). No obstante, .<\/p>\n<p><a href=\"http:\/\/git.or.cz\/\">Git<\/a> es un sistema de control de versiones distribuido creado por <a href=\"http:\/\/es.wikipedia.org\/wiki\/Linus_Torvalds\">Linus Torvalds<\/a> para mantener el desarrollo del kernel Linux. \u00daltimamente parece que <a href=\"http:\/\/git.or.cz\/\">Git<\/a> esta ganando m\u00e1s y m\u00e1s adeptos. Por ejemplo, (p.ej. <a href=\"http:\/\/weblog.rubyonrails.org\/2008\/4\/2\/rails-is-moving-from-svn-to-git\">Ruby on Rails<\/a>) ha migrado su sistema de control de versiones de <a href=\"http:\/\/es.wikipedia.org\/wiki\/Subversion\">Subversion<\/a> a Git.<\/p>\n<p>Git tiene bastantes similitudes con <a href=\"http:\/\/www.marblestation.com\/blog\/?p=605\">Bazaar NG o bzr<\/a> (del cual ya habl\u00e9 en un <a href=\"http:\/\/www.marblestation.com\/blog\/?p=605\">post<\/a> anterior) dado que ambos son sistemas distribuidos (a diferencia de Subversion o CVS). Bzr fue dise\u00f1ado e implementando pensando en la facilidad de uso, mientras que Git esta m\u00e1s 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\u00e9rminos de espacio y CPU). <\/p>\n<p>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\u00e1s complicado Git. En cualquier caso, debo decir que la sensaci\u00f3n general es que Git es el m\u00e1s atractivo de los dos. Y por supuesto, ambos son mejores opciones que el uso del obsoleto CVS o el popular Subversion&#8230; una vez pruebas los distribuidos, ya no quieres otra cosa \ud83d\ude09<\/p>\n<p>Veamos como podemos utilizar Git&#8230;<br \/>\n<!--more--><\/p>\n<p>Para instalar Git en Windows actualmente la mejor opci\u00f3n es utilizar <a href=\"http:\/\/www.marblestation.com\/blog\/?p=649\">cygwin<\/a>, mientras que para instalarlo en Ubuntu es tan sencillo como:<\/p>\n<pre>\r\naptitude install git-core gitk git-gui\r\n<\/pre>\n<p>Gitk y git-gui son completamente opcionales, simplemente aportan algunas herramientas gr\u00e1ficas para visualizar hist\u00f3ricos, etc.<\/p>\n<p><i>* Todos los comandos que utilizaremos en este tutorial tienen su correspondiente ayuda, por ejemplo la ayuda de &#8216;git commit&#8217; o &#8216;git diff&#8217; es visualizable mediante &#8216;man git-commit&#8217; o &#8216;man git-diff&#8217; respectivamente.<\/i><\/p>\n<h3>Configuraci\u00f3n b\u00e1sica<\/h3>\n<p>En primer lugar vamos a configurar el nombre de usuario que aparecer\u00e1 en cada commit que realicemos a los repositorios, para ello crearemos el fichero &#8216;~\/.gitconfig&#8217;:<\/p>\n<pre>\r\n[user]\r\n        name = Sergi Blanco Cuaresma\r\n        email = marble@marble-laptop.(none)\r\n<\/pre>\n<h3>Uso b\u00e1sico<\/h3>\n<p>En segundo lugar, vamos a crear nuestro propio repositorio en un directorio local:<\/p>\n<pre>\r\n$ mkdir original\r\n$ cd original\r\n$ git init\r\nInitialized empty Git repository in .git\/\r\n<\/pre>\n<p>El repositorio que hemos creado contiene tanto la informaci\u00f3n de versionado (en el directorio &#8216;.git&#8217;) como el directorio de trabajo. Por tanto, ya podemos empezar a programar creando un sencillo &#8220;Hello World&#8221; en Ruby. Fichero &#8220;hello.rb&#8221;:<\/p>\n<pre>\r\n#!\/usr\/bin\/ruby\r\nputs \"Hello world!\"\r\n<\/pre>\n<p>\u00danicamente creando un fichero dentro del repositorio, Git no lo tiene en consideraci\u00f3n autom\u00e1ticamente:<\/p>\n<pre>\r\n$ git status\r\n# On branch master\r\n#\r\n# Initial commit\r\n#\r\n# Untracked files:\r\n#   (use \"git add file>...\" to include in what will be committed)\r\n#\r\n#\thello.rb\r\nnothing added to commit but untracked files present (use \"git add\" to track)\r\n<\/pre>\n<p>Por tanto, podemos a\u00f1adir el fichero al \u00edndice de ficheros a versionar mediante:<\/p>\n<pre>\r\n$ git add hello.rb\r\n<\/pre>\n<p>En este sentido Git tambi\u00e9n nos permite:<\/p>\n<ul>\n<li>Borrar un fichero (del indice y del directorio de trabajo) que est\u00e9 siendo versionado: &#8216;git rm fichero&#8217;.<\/li>\n<li>Renombrar o mover un fichero que est\u00e9 siendo versionado (as\u00ed no perdemos su historial): &#8216;git mv fichero1 fichero2&#8217;.<\/li>\n<li>Ignorar ficheros que se encuentren en el directorio de trabajo y que no queremos que sean versionado: creamos el fichero &#8216;.gitignore&#8217; por ejemplo con el siguiente contenido&#8230;\n<pre>\r\n# Lines starting with '#' are considered comments.\r\n# Ignore any file named foo.txt.\r\nfoo.txt\r\n# Ignore (generated) html files,\r\n*.html\r\n# except foo.html which is maintained by hand.\r\n!foo.html\r\n# Ignore objects and archives.\r\n*.[oa]\r\n<\/pre>\n<\/li>\n<\/ul>\n<p>Sigamos&#8230; acab\u00e1bamos de a\u00f1adir el fichero nuevo al \u00edndice mediante &#8216;git add&#8217; y ahora seria un buen momento para realizar el commit al repositorio, de esta forma se guardara la primera version del directorio de trabajo:<\/p>\n<pre>\r\n$ git commit -m \"My first Hello World\"\r\nCreated initial commit 5c4c271: My first Hello World\r\n 1 files changed, 2 insertions(+), 0 deletions(-)\r\n create mode 100755 hello.rb\r\n<\/pre>\n<p>Como se puede observar, hemos asociado el mensaje &#8220;My first Hello World&#8221; como descripci\u00f3n de las modificaciones que hemos realizado. En caso que volvamos a ejecutar &#8216;git status&#8217;, veremos que no hay nada nuevo en la versi\u00f3n actual del directorio que difiera de la \u00faltima versi\u00f3n registrada en el repositorio.<\/p>\n<p>Por otra parte, disponemos del comando &#8216;git show&#8217; que nos muestra informaci\u00f3n sobre el commit m\u00e1s reciente:<\/p>\n<pre>\r\n$ git show\r\ncommit 5c4c2717a427bf3e87b8ab4ad3ed6853c194f6a0\r\nAuthor: Sergi Blanco Cuaresma marble @marble-laptop.(none)\r\nDate:   Mon May 5 20:56:37 2008 +0200\r\n\r\n    My first Hello World\r\n\r\ndiff --git a\/hello.rb b\/hello.rb\r\nnew file mode 100755\r\nindex 0000000..6a2ce6e\r\n--- \/dev\/null\r\n+++ b\/hello.rb\r\n@@ -0,0 +1,2 @@\r\n+#!\/usr\/bin\/ruby\r\n+puts \"Hello world!\"\r\n<\/pre>\n<p>Este comando nos informa del parche que ha sido aplicado para pasar de la versi\u00f3n anterior (repositorio vacio) a la actual (repositorio con fichero hello.rb), tambi\u00e9n podriamos utilizar &#8216;git diff&#8217; para visualizar diferencias entre diversos commits o un commit y nuestro directorio de trabajo.<\/p>\n<p>Otro aspecto a destacar es que &#8216;git show&#8217; nos muestra el identificador un\u00edvoco del commit (en nuestro caso: 5c4c2717a427bf3e87b8ab4ad3ed6853c194f6a0) el cual se genera a partir un SHA1 de las modificaciones implementadas.<\/p>\n<p>Sigamos modificando nuestro proyecto&#8230; editamos el fichero &#8220;hello.rb&#8221; para a\u00f1adir una nueva l\u00ednea:<\/p>\n<pre>\r\n#!\/usr\/bin\/ruby\r\nputs \"Hello world!\"\r\nputs \"Comentario 1\"\r\n<\/pre>\n<p>Y realizamos el commit de la nueva versi\u00f3n &#8216;git commit -m &#8220;My second commit&#8221; -a&#8217;. Con el par\u00e1metro &#8216;-a&#8217; nos ahorramos tener que volver a indicarle a Git que queremos que tenga en consideraci\u00f3n el fichero hello.rb (&#8216;git add hello.rb&#8217;).<\/p>\n<pre>\r\n$ git commit -m \"My second commit\" -a\r\nCreated commit 0c0b74c: My second commit\r\n 1 files changed, 1 insertions(+), 0 deletions(-)\r\n<\/pre>\n<p>Una de las cosas que m\u00e1s 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\u00eda interesar realizar 3 tipos de acciones:<\/p>\n<ul>\n<li>Eliminar por completo un commit mediante &#8216;git reset &#8211;hard identificador&#8217;. Podemos a\u00f1adir el modificador &#8216;&#8211;hard&#8217; para que adem\u00e1s 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\u00ed me parece realmente \u00fatil.<\/li>\n<li>Revertir un commit mediante &#8216;git revert identificador&#8217;. 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\u00fan problema en caso de existir ramas derivadas de la actual.<\/li>\n<li>Modificar el \u00faltimo commit realizado mediante &#8216;git commit &#8211;amend&#8217;. Imaginar que nos hemos olvidado de realizar un \u00faltimo retoque al c\u00f3digo y no queremos generar un nuevo commit, podemos modificar el \u00faltimo para que incorpore esos cambios. Obviamente esto solo tiene sentido si sabemos que nadie m\u00e1s ha utilizado la revisi\u00f3n del commit que estamos modificando.<\/li>\n<\/ul>\n<p>Y hasta aqu\u00ed hemos visto lo b\u00e1sico para crear un repositorio local e ir almacenando las diferentes versiones de nuestro c\u00f3digo mediante commits.<\/p>\n<h3>Etiquetar las revisiones clave<\/h3>\n<p>Como hemos comentado, cada revisi\u00f3n tiene asociado un identificador un\u00edvoco de 40 caracteres, pero es posible a\u00f1adir tags a las revisiones clave que nos interesen. Por ejemplo, en nuestro caso podr\u00edamos etiquetar como &#8216;v0.1&#8217; la revisi\u00f3n anterior y &#8216;v0.2&#8217; la actual (HEAD). El segundo caso es tan sencillo como ejecutar (si \u00fanicamente indicamos el tag sin especificar el id de commit, asigna la etiqueta a la \u00faltima revisi\u00f3n):<\/p>\n<pre>\r\ngit tag v0.2\r\n<\/pre>\n<p>En el primer caso, necesitamos encontrar el identificador de la revisi\u00f3n anterior. Podemos averiguarlo utilizando la interfaz gr\u00e1fica que nos brinda el comando &#8216;guitk&#8217; o mediante el comando:<\/p>\n<pre>\r\n$ git log --color --pretty=oneline -10\r\n0c0b74c8662b0deee98b34b1475648d15fd61f8a My second commit\r\n5c4c2717a427bf3e87b8ab4ad3ed6853c194f6a0 My first Hello World\r\n<\/pre>\n<p>Este comando nos muestra los identificadores y la descripci\u00f3n de los \u00faltimos 10 commits.<\/p>\n<p>Una vez localizado el identificador que nos interesa, podemos etiquetar dicha revisi\u00f3n mediante:<\/p>\n<pre>\r\n$ git tag v0.1 5c4c2717a427bf3e87b8ab4ad3ed6853c194f6a0\r\n<\/pre>\n<p>Adicionalmente, es posible listar los tags mediante &#8216;git tag -l&#8217; y borrar los que no nos interesen con &#8216;git tag -d nombre_del_tag&#8217;.<\/p>\n<h3>Ramas<\/h3>\n<p>En los apartados anteriores hemos trabajado exclusivamente con una \u00fanica rama del c\u00f3digo, la rama &#8216;master&#8217; (el asterisco marca la rama activa actual):<\/p>\n<pre>\r\n$ git branch\r\n* master\r\n<\/pre>\n<p>Imaginemos la hipot\u00e9tica situaci\u00f3n donde la rama actual ha llegado a ser una versi\u00f3n estable del c\u00f3digo (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\u00f1os cambios de la versi\u00f3n actual 1.0 mientras desarrollamos la nueva versi\u00f3n de nuestro software. En cualquier caso, esta puede ser una excusa tan valida como cualquier otra para generar distintas ramas \ud83d\ude09<\/p>\n<pre>\r\n         o--o--o < -- Branch A\r\n        \/\r\n o--o--o <-- master\r\n        \\\r\n         o--o--o <-- Branch B\r\n<\/pre>\n<p>Por ejemplo, creamos una nueva rama basada en la revision etiquetada v0.2 (si omiti\u00e9semos ese par\u00e1metro, derivar\u00edamos de la HEAD que en nuestro caso coincide con la v0.2):<\/p>\n<pre>\r\ngit branch devel v0.2\r\n<\/pre>\n<p>A diferencia de Bzr, Git gestionar las diferentes ramas en el mismo directorio. Es decir, para cambiar de una rama a otra debemos ejecutar &#8216;git checkout nombre_de_la_rama&#8217; y el directorio de trabajo cambiara de contenido:<\/p>\n<pre>\r\n$ git checkout devel\r\n$ git branch\r\n* devel\r\n  master\r\n<\/pre>\n<p>El comando checkout no solo lo podemos utilizar para alternar entre ramas, sino que tambi\u00e9n nos permite acceder a revisiones antiguas del c\u00f3digo indicando su identificador o tag, por ejemplo:<\/p>\n<pre>\r\ngit checkout v0.1\r\n<\/pre>\n<p>Este comando nos crear\u00e1 una rama temporal que nos muestra la revisi\u00f3n etiquetada con &#8220;v0.1&#8221;. Para volver a la rama donde est\u00e1bamos podemos ejecutar &#8216;git branch devel&#8217;.<\/p>\n<p>Modifiquemos el fichero &#8216;hello.rb&#8217; de nuestra rama devel:<\/p>\n<pre>\r\n#!\/usr\/bin\/ruby\r\nputs \"Hello world!\"\r\nputs \"Comentario 1\"\r\nputs \"Rama Devel\"\r\n<\/pre>\n<p>Y realizemos el commit de la nueva versi\u00f3n:<\/p>\n<pre>\r\n$ git commit -a -m \"Commit devel 1\"\r\nCreated commit 6484083: Commit devel 1\r\n 1 files changed, 1 insertions(+), 0 deletions(-)\r\n<\/pre>\n<p>A continuaci\u00f3n nos encontramos con una rama &#8216;master&#8217; que no ha sufrido ning\u00fan cambio y una rama &#8216;devel&#8217; que incorpora una modificaci\u00f3n. Podriamos realizar un merge de ambas e incorporar los cambios de &#8216;devel&#8217; en &#8216;master&#8217;:<\/p>\n<pre>\r\n$ git checkout master\r\nSwitched to branch \"master\"\r\n$ git merge devel\r\n<\/pre>\n<p>En caso de que exista alg\u00fan conflicto entre ambas ramas, Git nos avisar\u00e1:<\/p>\n<pre>\r\n$ git merge devel\r\nAuto-merged hello.rb\r\nCONFLICT (content): Merge conflict in hello.rb\r\nAutomatic merge failed; fix conflicts and then commit the result.\r\n<\/pre>\n<p>Entonces podemos editar los ficheros afectados, dejando la versi\u00f3n del c\u00f3digo que nos interese y una vez resulto el conflicto, ejecutamos el commit:<\/p>\n<pre>\r\ngit commit -a -m \"Devel incorporado a master\"\r\n<\/pre>\n<p>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\u00edamos:<\/p>\n<pre>\r\n$ git branch devel -D\r\n<\/pre>\n<h4>Cambio de la base de una rama<\/h4>\n<p>En esta secci\u00f3n voy a desvincularme del ejemplo que hemos ido siguiendo durante todo el art\u00edculo y voy a exponer una situaci\u00f3n hipot\u00e9tica que creo interesante. Es posible que en alg\u00fan momento de nuestros desarrollos nos encontremos frente a dos ramas que han ido evolucionando en el tiempo:<\/p>\n<pre>\r\n          A---B---C devel\r\n         \/\r\n    D---E---F---G master\r\n<\/pre>\n<p>Quiz\u00e1s nos interese incorporar a nuestra rama &#8216;devel&#8217; los cambios que han tenido lugar en &#8216;master&#8217; de forma que nuestra estructura quede as\u00ed:<\/p>\n<pre>\r\n                  A'--B'--C' devel\r\n                 \/\r\n    D---E---F---G master\r\n<\/pre>\n<p>A modo de ejemplo, esta modificaci\u00f3n nos podr\u00eda interesar porque en &#8216;master&#8217; tenemos una versi\u00f3n estable del c\u00f3digo donde hemos solucionado varios bugs de seguridad importantes y queremos incorporar los cambios a la versi\u00f3n de desarrollo (que tambi\u00e9n se ve afectada por los mismos bugs).<\/p>\n<p>Para poder efectuar este tipo de operaciones podemos utilizar:<\/p>\n<pre>\r\n$ git rebase master devel\r\nAlready on \"devel\"\r\nFirst, rewinding head to replay your work on top of it...\r\nHEAD is now at 20ad3a4 Prueba 4\r\nApplying XXXX\r\n<\/pre>\n<p>En caso de que aparezcan conflictos, podemos modificar los ficheros afectados y continuar mediante &#8216;git rebase &#8211;continue&#8217; o directamente cancelar con &#8216;git rebase &#8211;abort&#8217;.<\/p>\n<h3>Guardar\/recuperar estado actual del directorio de trabajo<\/h3>\n<p>Una funcionalidad interesante de Git es la capacidad para guardar el estado actual del directorio de trabajo (sin realizar commits), volver la revisi\u00f3n 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 &#8216;bye.rb&#8217; (aparte del &#8216;hello.rb&#8217; que ya ten\u00edamos):<\/p>\n<pre>\r\n#!\/usr\/bin\/ruby\r\nputs \"Bye bye!\"\r\n<\/pre>\n<p>Y ya hemos indicado a Git que tenga en cuenta este fichero:<\/p>\n<pre>\r\n$ git add bye.rb\r\n<\/pre>\n<p>Imaginemos ahora que antes de que podamos considerar que &#8216;bye.rb&#8217; se encuentra listo para ser incorporado en un commit, nos surge la necesidad de modificar urgentemente &#8216;hello.rb&#8217; para incorporar un requisito de nuestro cliente. Con Git podemos guardar el estado actual sin realizar ning\u00fan commit:<\/p>\n<pre>\r\n$ git stash save \"Pendiente acabar bye.rb\"\r\nSaved working directory and index state \"On master: Pendiente acabar bye.rb\"\r\nHEAD is now at 6484083... Commit devel 1\r\n<\/pre>\n<p>Efectuamos la modificaci\u00f3n urgente en &#8216;hello.rb&#8217;:<\/p>\n<pre>\r\n#!\/usr\/bin\/ruby\r\nputs \"Hello world!\"\r\nputs \"Comentario 1\"\r\nputs \"Rama Devel\"\r\nputs \"Urgent modification!!\"\r\n<\/pre>\n<p>Y realizamos un commit con el nuevo cambio:<\/p>\n<pre>\r\n$ git commit -a -m \"Requisito cliente\"\r\nCreated commit b1d6506: Requisito cliente\r\n 1 files changed, 1 insertions(+), 0 deletions(-)\r\n<\/pre>\n<p>Ahora que ya hemos &#8220;apagado el fuego&#8221;, podemos volver a nuestro trabajo y recuperamos el punto donde est\u00e1bamos:<\/p>\n<pre>\r\n$ git stash apply\r\n# On branch master\r\n# Changes to be committed:\r\n#   (use \"git reset HEAD file...\" to unstage)\r\n#\r\n#\tnew file:   bye.rb\r\n#\r\n$ git stash clear\r\n<\/pre>\n<h3>B\u00fasqueda de commits que introducen errores<\/h3>\n<p>El comando bisect nos ayuda a identificar commits que han introducido errores en la aplicaci\u00f3n. Por ejemplo, imaginemos que una caracter\u00edstica que implementamos en la reversion &#8216;v0.1&#8217; deja de funcionar en la revision &#8216;HEAD&#8217; y no sabemos en que commit ha sido introducido el error. Entonces podemos iniciar bisect e indicar que queremos partir de la version &#8216;v0.1&#8217; como la buena y &#8216;HEAD&#8217; como la mala: <\/p>\n<pre>\r\n$ git bisect start\r\n$ git bisect good v0.1\r\n$ git bisect bad HEAD\r\nBisecting: 1 revisions left to test after this\r\n[64840836bb8f4db9a21ff6c3072b3626a912a6c9] Commit devel 1\r\n<\/pre>\n<p>A partir de aqu\u00ed tendremos que ir revisando el c\u00f3digo y efectuando las pruebas que consideremos oportunas. Si la revisi\u00f3n actual es buena, ejecutaremos &#8216;git bisect good&#8217; o de lo contrario &#8216;git bisect bad&#8217;. Cada vez que indiquemos si la revisi\u00f3n es buena o mala, Git pasar\u00e1 a una siguiente para que la validemos hasta que encontremos aquella(s) que introduce(n) el error.<\/p>\n<p>En nuestro caso, solo validaremos 1 revisi\u00f3n (dado que entre v0.1 y HEAD solo hay 1 commit de diferencia) que, por ejemplo, podemos considerar buena:<\/p>\n<pre>\r\n$ git bisect good\r\nb1d65061810e6980a2b22e96d8d5d055f60d8720 is first bad commit\r\ncommit b1d65061810e6980a2b22e96d8d5d055f60d8720\r\nAuthor: Sergi Blanco Cuaresma <marble @marble-laptop.(none)>\r\nDate:   Mon May 5 22:58:33 2008 +0200\r\n\r\n    Requisito cliente\r\n\r\n:100755 100755 8c2d1346928fe7c2db481476a887498e4bb98523 b6dfb3a3274f256cb9aa3be1cdfd13521a657014 M\thello.rb\r\n<\/marble><\/pre>\n<p>Una vez identificados los commits problem\u00e1ticos, podemos proceder a corregirlos tal y como hemos visto anteriormente (eliminando el commit con &#8216;reset&#8217;, revirtiendo con &#8216;revert&#8217;, etc.).<\/p>\n<p>Cabe destacar que al usar bisect se genera una nueva rama:<\/p>\n<pre>\r\n$ git branch\r\n* bisect\r\n  devel\r\n  master\r\n<\/pre>\n<p>La cual podemos abandonar una vez hayamos acabado de trabajar con bisect:<\/p>\n<pre>\r\ngit checkout master\r\ngit branch -d bisect\r\n<\/pre>\n<h3>Creaci\u00f3n de un repositorio p\u00fablico<\/h3>\n<p>En general es posible trabajar sin tener que generar un repositorio p\u00fablico. Pero es posible que seg\u00fan como definamos el flujo de trabajo, nos pueda interesar mantener un repositorio &#8216;oficial&#8217; donde se centralicen todos los cambios consolidados.<\/p>\n<p>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\u00ed esta persona tiene la oportunidad de revisar los cambios antes de a\u00f1adirlos al repositorio principal.<\/p>\n<p>Lo ideal es generar un repositorio p\u00fablico accesible remotamente (p.ej. SSH, HTTP, etc.) el cual \u00fanicamente disponga de la informaci\u00f3n de versionado sin un directorio de trabajo asociado (dado que nadie trabajar\u00e1 directamente con el).<\/p>\n<p>Para crear un repositorio p\u00fablico, primero debemos partir de un repositorio ya existente. Como ejemplo, crearemos un repositorio inicial que contendr\u00e1 un fichero en blanco (p.ej. README):<\/p>\n<pre>\r\n$ mkdir inicial\r\n$ cd inicial\/\r\n$ git init\r\nInitialized empty Git repository in .git\/\r\n$ touch README\r\n$ git add README\r\n$ git commit -a -m \"Initial\"\r\nCreated initial commit 9dda6f6: Initial\r\n 0 files changed, 0 insertions(+), 0 deletions(-)\r\n create mode 100644 README\r\n$ cd ..\r\n<\/pre>\n<p>Acto seguido tenemos dos opciones para crear el repositorio p\u00fablico final, el cual carecer\u00e1 de directorio de trabajo (no podremos programar directamente sobre el):<\/p>\n<p><b>1) Inicializaci\u00f3n por fetch<\/b><\/p>\n<blockquote>\n<p>Creamos un nuevo repositorio:<\/p>\n<pre>\r\n$ mkdir repo-publico\r\n$ cd repo-publico\/\r\n$ git --bare init --shared\r\nInitialized empty shared Git repository in \/home\/usuario\/repo-publico\/\r\n<\/pre>\n<p>Inicializamos este con el contenido del repositorio inicial que hemos creado anteriormente:<\/p>\n<pre>\r\n$ git --bare fetch ..\/inicial\/ master:master\r\nwarning: no common commits\r\nremote: Counting objects: 3, done.\r\nremote: Total 3 (delta 0), reused 0 (delta 0)\r\nUnpacking objects: 100% (3\/3), done.\r\nFrom ..\/inicial\r\n * [new branch]      master     -> master\r\n<\/pre>\n<\/blockquote>\n<p><b>2) Creaci\u00f3n e inicializaci\u00f3n por clone<\/b><\/p>\n<blockquote><p>\nClonamos el repositorio inicial:<\/p>\n<pre>\r\ngit clone inicial repo-publico\r\n<\/pre>\n<\/blockquote>\n<p>Sea cual sea el camino seguido, en el directorio &#8220;repo-publico\/&#8221; tenemos un repositorio listo para ser compartido p\u00fablicamente via SSH o sistema de ficheros.<\/p>\n<p>En caso de que queramos compartir mediante protocolo Git (puerto 9418), necesitaremos crear el fichero &#8216;git-daemon-export-ok&#8217; y configurar el demonio git (consultar man git-daemon):<\/p>\n<pre>\r\n$ touch repo-publico\/git-daemon-export-ok\r\n<\/pre>\n<p>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):<\/p>\n<pre>\r\n$ cd repo-publico\r\n$ git --bare update-server-info\r\n$ chmod a+x hooks\/post-update\r\n<\/pre>\n<h3>Trabajo colaborativo a partir de un repositorio p\u00fablico<\/h3>\n<p>Es habitual que utilicemos Git para desarrollar un proyecto donde participan varios programadores, donde se disponga de un repositorio p\u00fablico al cual podemos acceder v\u00eda SSH, HTTP, protocolo Git o por el mismo sistema de fichero. Por ejemplo:<\/p>\n<pre>\r\ngit clone ssh:\/\/ejemplo.com\/~usuario\/repo-publico repo-clonado\r\ngit clone http:\/\/ejemplo.com\/~usuario\/repo-publico repo-clonado\r\ngit clone git:\/\/ejemplo.com\/usuario\/repo-publico repo-clonado\r\ngit clone \/home\/usuario\/repo-publico repo-clonado\r\n<\/pre>\n<p>Este comando crear\u00e1 el directorio &#8216;repo-clonado&#8217; que contendr\u00e1 referencias a las ramas remotas del repositorio original:<\/p>\n<pre>\r\n$ git branch -r\r\n  origin\/HEAD\r\n  origin\/devel\r\n  origin\/master\r\n<\/pre>\n<p>Y, adicionalmente, generar\u00e1 una rama local equivalente a la rama que estuviese activa en el repositorio original (en nuestro caso, ha sido la rama &#8216;master&#8217;):<\/p>\n<pre>\r\n$ git branch\r\n* master\r\n<\/pre>\n<p>A partir de aqu\u00ed 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.<\/p>\n<h4>Actualizaci\u00f3n del repositorio local clonado<\/h4>\n<p>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:<\/p>\n<pre>\r\n$ git fetch\r\nremote: Counting objects: 5, done.\r\nremote: Total 3 (delta 0), reused 0 (delta 0)\r\nUnpacking objects: 100% (3\/3), done.\r\nFrom \/cygdrive\/c\/Documents and Settings\/usuario\/repo-original\/\r\n   1574c72..2939243  master     -> origin\/master\r\n<\/pre>\n<p>De esta forma habremos actualizado localmente las ramas remotas que hacen referencia al repositorio original:<\/p>\n<pre>\r\n$ git branch -r\r\n  origin\/HEAD\r\n  origin\/devel\r\n  origin\/master\r\n<\/pre>\n<p>A continuaci\u00f3n nos podr\u00eda 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:<\/p>\n<pre>\r\n$ git checkout master\r\nSwitched to branch \"master\"\r\nYour branch and the tracked remote branch 'origin\/master' have diverged,\r\nand respectively have 3 and 2 different commit(s) each.\r\n\r\n$ git merge origin\/master\r\nMerge made by recursive.\r\n test.txt |    2 +-\r\n 1 files changed, 1 insertions(+), 1 deletions(-)\r\n<\/pre>\n<p>Tambi\u00e9n disponemos del comando &#8216;git pull&#8217; que realizado en 1 \u00fanica ejecuci\u00f3n el fetch y el merge que acabamos de describir:<\/p>\n<pre>\r\n$ git pull\r\nremote: Counting objects: 5, done.\r\nremote: Total 3 (delta 0), reused 0 (delta 0)\r\nUnpacking objects: 100% (3\/3), done.\r\nFrom \/cygdrive\/c\/Documents and Settings\/usuario\/borrar\/\r\n   20ad3a4..d5e4715  master     -> origin\/master\r\nMerge made by recursive.\r\n test.txt |    1 +\r\n 1 files changed, 1 insertions(+), 0 deletions(-)\r\n<\/pre>\n<h4>Actualizaci\u00f3n del repositorio p\u00fablico<\/h4>\n<p>Por otra parte, el responsable del repositorio p\u00fablico puede que tenga inter\u00e9s en incorporar los cambios que hemos realizado nosotros. En ese caso, podr\u00edamos o bien hacerle llegar las modificaciones en forma de parche por correo (&#8216;man git-format-patch&#8217;, &#8216;man git-am&#8217; y &#8216;man git-send-email&#8217;) o bien proporcionandole acceso a nuestro repositorio clonado para que realice un pull. Por ejemplo:<\/p>\n<pre>\r\n$ git pull \/home\/colaborador\/repo-clonado\/ master\r\nUpdating 2939243..6a8b950\r\nFast forward\r\n hola.txt |    1 +\r\n 1 files changed, 1 insertions(+), 0 deletions(-)\r\n create mode 100644 hola.txt\r\n<\/pre>\n<p>Si va a incorporar frecuentemente los cambios que vayamos realizando, quiz\u00e1s le interese crear un enlace remoto para poder efectuar fetch y merge:<\/p>\n<pre>\r\n$ git remote add clonado \/home\/colaborador\/repo-clonado\/\r\n\r\n$ git fetch clonado\r\nFrom ..\/repo-clonado\r\n * [new branch]      master     -> clonado\/master\r\n<\/pre>\n<p>Es posible visualizar las ramas remotas registradas mediante:<\/p>\n<pre>\r\n$ git branch -r\r\n  clonado\/master\r\n<\/pre>\n<p>Si en alg\u00fan momento se desea eliminar el enlace a la rama remota podemos ejecutar &#8216;git remote rm clonado&#8217;.<\/p>\n<h3>Repositorio p\u00fablico centralizado estilo CVS\/Subversion<\/h3>\n<p>En el flujo de trabajo que hemos dibujado hasta el momento, los desarrolladores no sub\u00edan directamente cambios al repositorio p\u00fablico, sino que era el responsable del mismo quien aceptaba los cambios y los incorporaba despu\u00e9s de revisarlos. Sin embargo, Git tambi\u00e9n soporta que los desarrolladores puedan subir sus modificaciones directamente a un repositorio centralizado al m\u00e1s puro estilo CVS o Subversion (eliminando el papel de responsable del repositorio p\u00fablico).<\/p>\n<p>Para que un repositorio p\u00fablico pueda ser utilizado de esta forma, es necesario permitir la ejecuci\u00f3n del comando &#8216;push&#8217; (que veremos m\u00e1s adelante). En primer lugar deberemos crear el repositorio p\u00fablico (tal y como hemos visto en secciones anteriores) y<br \/>\n habilitar alguno de los siguientes accesos:<\/p>\n<ul>\n<li>Acceso directo por sistema de ficheros con permisos de escritura para el usuario sobre el directorio del repositorio.<\/li>\n<li>Acceso remoto v\u00eda SSH (consultar man git-shell) y permisos de escritura para el usuario sobre el directorio del repositorio.<\/li>\n<li>Acceso HTTP con WebDav debidamente configurado (<a href=\"http:\/\/www.google.es\/search?q=git+webdav\">google<\/a>).<\/li>\n<li>Acceso mediante protocolo Git con el servicio &#8220;receive-pack&#8221; activado en el git daemon.<\/li>\n<\/ul>\n<p>Una vez tenemos el repositorio compartido preparado para ser utilizado, cada desarrollador deber\u00e1 clonarlo para poder trabajar localmente (en el ejemplo suponemos que tenemos acceso directo por sistema de ficheros local &#8216;\/home\/central\/repo-publico\/&#8217;):<\/p>\n<pre>\r\n$ git clone \/home\/central\/repo-publico\/ repo-personal\r\nInitialized empty Git repository in \/home\/usuario\/repo-personal\/.git\/\r\n0 bloques\r\n\r\n$ echo \"Hola\" > README\r\n$ git commit -a -m \"Test 1\"\r\nCreated commit 27cfec1: Test 1\r\n 1 files changed, 1 insertions(+), 0 deletions(-)\r\n<\/pre>\n<p>Cuando el desarrollador lo consideren oportuno, podr\u00e1 sincronizar su repositorio local con el central (pull) y subir los cambios que han desarrollado localmente (push): <\/p>\n<pre>\r\n$ git pull\r\n\r\n$ git push\r\nCounting objects: 5, done.\r\nUnpacking objects: 100% (3\/3), done.\r\nWriting objects: 100% (3\/3), 248 bytes, done.\r\nTotal 3 (delta 0), reused 0 (delta 0)\r\nTo \/home\/usuario\/repo-compartido\r\n   9dda6f6..27cfec1  master -> master\r\n<\/pre>\n<p>De esta forma, todos los commits realizados que han sido guardados localmente, se traspasan al repositorio central compartido de forma autom\u00e1tica.<\/p>\n<h3>Chequeo y optimizaci\u00f3n de repositorios<\/h3>\n<p>Chequeo de errores en el repositorio:<\/p>\n<pre>\r\n$ git fsck\r\ndangling commit 06578a4d9893bc60cd8d2e40a6f3322a777a8d09\r\ndangling tree d5935ebd21acf31cde0221fa87947fd050aef4a2\r\ndangling tree 3615b1a2ae5372c63a757085f41197656c5e2c11\r\n<\/pre>\n<p>Optimizaci\u00f3n del repositorio, comprimiendo y eliminando informaci\u00f3n obsoleta:<\/p>\n<pre>\r\n$ git gc\r\nCounting objects: 51, done.\r\nCompressing objects: 100% (29\/29), done.\r\nWriting objects: 100% (51\/51), done.\r\nTotal 51 (delta 4), reused 0 (delta 0)\r\n<\/pre>\n<h3>Conclusi\u00f3n<\/h3>\n<p>Git es un sistema de control de versiones extremadamente potente y flexible. Su uso es 100% recomendable en cualquier tipolog\u00eda de proyecto dado que se adapta a cualquier situaci\u00f3n o necesidad. S\u00f3lo el tiempo dir\u00e1 cual ser\u00e1 el sistema distribuido que ganar\u00e1 m\u00e1s adeptos (bzr, darcs, arch, git, etc.) y Git es un buen candidato.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hace ya alg\u00fan tiempo escrib\u00ed 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\u00e9n da soporte a Ubuntu). No obstante, . Git es un sistema de control de versiones distribuido creado por Linus Torvalds para mantener el desarrollo del &hellip; <a href=\"https:\/\/www.marblestation.com\/?p=654\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Git, sistema de control de versiones distribuido<\/span> <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1,6],"tags":[],"class_list":["post-654","post","type-post","status-publish","format-standard","hentry","category-espanyol","category-tecnologia"],"_links":{"self":[{"href":"https:\/\/www.marblestation.com\/index.php?rest_route=\/wp\/v2\/posts\/654","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.marblestation.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.marblestation.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.marblestation.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.marblestation.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=654"}],"version-history":[{"count":1,"href":"https:\/\/www.marblestation.com\/index.php?rest_route=\/wp\/v2\/posts\/654\/revisions"}],"predecessor-version":[{"id":1250,"href":"https:\/\/www.marblestation.com\/index.php?rest_route=\/wp\/v2\/posts\/654\/revisions\/1250"}],"wp:attachment":[{"href":"https:\/\/www.marblestation.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=654"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.marblestation.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=654"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.marblestation.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=654"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}