Git
Chapters ▾ 2nd Edition

9.1 Git y Otros Sistemas - Git como Cliente

El mundo no es perfecto. Por lo general, no se puede cambiar inmediatamente cada proyecto con el que está en contacto Git. A veces estás atrapado en un proyecto usando otro VCS, y desearías poder usar Git. Pasaremos la primera parte de este capítulo aprendiendo sobre cómo utilizar Git como cliente cuando el proyecto en el que se está trabajando está alojado en un sistema diferente.

En algún momento, puede que desees convertir tu proyecto existente a Git. La segunda parte de este capítulo describe cómo migrar tu proyecto en Git desde varios sistemas específicos, así como un método que funcionará si no existe una herramienta de importación pre-construida.

Git como Cliente

Git proporciona una experiencia tan agradable para los desarrolladores que muchas personas han descubierto cómo usarlo en su estación de trabajo, incluso si el resto de su equipo está usando un VCS completamente diferente. Hay un número de estos adaptadores disponibles, llamados “bridges”. Aquí vamos a cubrir los que es más probable que se encuentren en la naturaleza.

Git y Subversion

Una gran parte de los proyectos de desarrollo de código abierto y un gran número de proyectos corporativos usan Subversion para administrar su código fuente. Ha existido por más de una década, y la gran parte de ese tiempo fue la elección de facto de VCS para proyectos de código abierto. También es similar en muchos aspectos a CVS, que fué el sistema mas popular de control de código del mundo antes de eso.

Una de las mejores características de Git es un puente birideccional para Subversion llamado git svn. Esta herramienta te permite usar Git como un cliente válido para un servidor de Subversion, por lo que puedes usar todas las características locales de Git y enviarlo a un servidor de Subversion como si estuvieras usando Subversion localmente. Esto significa que puedes realizar bifurcaciones y fusiones locales, usar el área de preparación, usar el rebasamiento, la selección selectiva y demás, mientras tus colaboradores continuan trabajando en sus formas oscuras y antiguas. Es una buena forma de introducir Git en el ambiente corporativo y ayudar a tus compañeros desarrolladores a ser más eficientes mientras presionas para que se modifique la infraestructura y que sea compatible con Git por completo. El puente de Subversion es la puerta de entrada al mundo de DVCS.

git svn

El comando base en Git para todos los comandos de puente de Subversion es git svn. Se requieren bastantes comandos, por lo que mostraremos los más comunes mientras realizamos algunos flujos de trabajo simples.

Es importante tener en cuenta que cuando usas git svn, estás interactuando con Subversion, que es un sistema que funciona de manera muy diferente a Git. Aunque * puede * realizar bifurcaciones y fusiones locales, generalmente es mejor mantener su historial lo más lineal posible al volver a basar su trabajo y evitar hacer cosas como interactuar simultáneamente con un repositorio remoto de Git.

No reescribas su historial y trates de volver a presionar, y no presiones a un repositorio paralelo de Git para colaborar con otros desarrolladores de Git al mismo tiempo. Subversion sólo puede tener un único historial lineal, y confundirlo es muy fácil. Si trabajas con un equipo, y algunos utilizan SVN y otros usan Git, asegúrate de que todos estén usando el servidor SVN para colaborar, ya que hacerlo te hará la vida más sencilla.

Configurando

Para demostrar esta funcionalidad, necesitas un repositorio SVN típico al que tengas acceso de escritura. Si deseas copiar estos ejemplos, tendrás que hacer una copia escribible del repositorio de prueba. Para hacer eso fácilmente, puedes usar una herramienta llamada svnsync que viene con Subversion.

Para seguir, primero necesitas crear un nuevo repositorio local de Subversion:

$ mkdir /tmp/test-svn
$ svnadmin create /tmp/test-svn

Luego, habilita a todos los usuarios a cambiar revprops – la manera más facil es añadir un script pre-revprop-change que siempre muestra 0:

$ cat /tmp/test-svn/hooks/pre-revprop-change
#!/bin/sh
exit 0;
$ chmod +x /tmp/test-svn/hooks/pre-revprop-change

Ahora puedes sincronizar este proyecto con tu máquina local llamando a svnsync init con los repositorios

$ svnsync init file:///tmp/test-svn \
  http://progit-example.googlecode.com/svn/

Esto configura las propiedades para ejecutar la sincronización. A continuación, puedes clonar el código ejecutando

$ svnsync sync file:///tmp/test-svn
Committed revision 1.
Copied properties for revision 1.
Transmitting file data .............................[...]
Committed revision 2.
Copied properties for revision 2.
[…]

A pesar de que esta operación puede tardar sólo unos minutos, si intentas copiar el repositorio original a otro repositorio remoto en vez de uno local, el proceso tardará cerca de una hora, aunque haya menos de 100 commits. Subversion tiene que clonar una revisión a la vez y luego ponerlas en otro repositorio – es ridículamente ineficiente, pero es la única forma fácil de hacerlo.

Empezando

Ahora que tienes un repositorio de Subversion al que tienes acceso para escribir, puedes ir a través de un flujo de trabajo típico. Comenzarás con el comando git svn clone, que importa un repositorio completo de Subversion en un repositorio local de Git. Recuerda que si estás importando desde un repositorio de Subversion alojado real, debes reemplazar el file: /// tmp / test-svn aquí con la URL de tu repositorio de Subversion:

$ git svn clone file:///tmp/test-svn -T trunk -b branches -t tags
Initialized empty Git repository in /private/tmp/progit/test-svn/.git/
r1 = dcbfb5891860124cc2e8cc616cded42624897125 (refs/remotes/origin/trunk)
    A	m4/acx_pthread.m4
    A	m4/stl_hash.m4
    A	java/src/test/java/com/google/protobuf/UnknownFieldSetTest.java
    A	java/src/test/java/com/google/protobuf/WireFormatTest.java
…
r75 = 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae (refs/remotes/origin/trunk)
Found possible branch point: file:///tmp/test-svn/trunk => file:///tmp/test-svn/branches/my-calc-branch, 75
Found branch parent: (refs/remotes/origin/my-calc-branch) 556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae
Following parent with do_switch
Successfully followed parent
r76 = 0fb585761df569eaecd8146c71e58d70147460a2 (refs/remotes/origin/my-calc-branch)
Checked out HEAD:
  file:///tmp/test-svn/trunk r75

Esto ejecuta el equivalente de dos comandos - git svn init seguido de` git svn fetch` - en la URL que proporciones. Esto puede tomar un rato. El proyecto de prueba sólo tiene alrededor de 75 confirmaciones y la base de código no es tan grande, pero Git tiene que verificar cada versión, una a la vez, y enviarla por separado. Para un proyecto con cientos o miles de confirmaciones, esto puede demorar literalmente horas o incluso días.

La parte -T trunk -b branches -t tags le dice a Git que este repositorio de Subversion sigue las convenciones básicas de bifurcación y etiquetado. Si nombras el tronco, las ramas o las etiquetas de manera diferente, puedes cambiar estas opciones. Debido a que esto es tan común, puedes reemplazar esta parte completa con -s, lo que significa diseño estándar e implica todas esas opciones. El siguiente comando es equivalente:

$ git svn clone file:///tmp/test-svn -s

En este punto, debes tener un repositorio de Git válido que haya importado sus ramas y etiquetas:

$ git branch -a
* master
  remotes/origin/my-calc-branch
  remotes/origin/tags/2.0.2
  remotes/origin/tags/release-2.0.1
  remotes/origin/tags/release-2.0.2
  remotes/origin/tags/release-2.0.2rc1
  remotes/origin/trunk

Ten en cuenta cómo esta herramienta gestiona las etiquetas de Subversion como referencias remotas. Echemos un vistazo más de cerca con el comando de plomería de Git show-ref:

$ git show-ref
556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae refs/heads/master
0fb585761df569eaecd8146c71e58d70147460a2 refs/remotes/origin/my-calc-branch
bfd2d79303166789fc73af4046651a4b35c12f0b refs/remotes/origin/tags/2.0.2
285c2b2e36e467dd4d91c8e3c0c0e1750b3fe8ca refs/remotes/origin/tags/release-2.0.1
cbda99cb45d9abcb9793db1d4f70ae562a969f1e refs/remotes/origin/tags/release-2.0.2
a9f074aa89e826d6f9d30808ce5ae3ffe711feda refs/remotes/origin/tags/release-2.0.2rc1
556a3e1e7ad1fde0a32823fc7e4d046bcfd86dae refs/remotes/origin/trunk

Git no hace esto cuando se clona desde un servidor Git; esto es lo que parece un repositorio con etiquetas después de un nuevo clon:

$ git show-ref
c3dcbe8488c6240392e8a5d7553bbffcb0f94ef0 refs/remotes/origin/master
32ef1d1c7cc8c603ab78416262cc421b80a8c2df refs/remotes/origin/branch-1
75f703a3580a9b81ead89fe1138e6da858c5ba18 refs/remotes/origin/branch-2
23f8588dde934e8f33c263c6d8359b2ae095f863 refs/tags/v0.1.0
7064938bd5e7ef47bfd79a685a62c1e2649e2ce7 refs/tags/v0.2.0
6dcb09b5b57875f334f61aebed695e2e4193db5e refs/tags/v1.0.0

Git busca las etiquetas directamente en refs / tags, en lugar de tratarlas como ramas remotas.

Comprometerse con la subversión

Ahora que tienes un repositorio en funcionamiento, puedes hacer algo de trabajo en el proyecto e impulsar tus confirmaciones en sentido ascendente, utilizando Git de manera efectiva como un cliente SVN. Si editas uno de los archivos y lo confirmas, tienes una confirmación que existe en Git localmente y que no existe en el servidor de Subversion:

$ git commit -am 'Adding git-svn instructions to the README'
[master 4af61fd] Adding git-svn instructions to the README
 1 file changed, 5 insertions(+)

A continuación, debes impulsar tu cambio en sentido ascendente. Observa cómo esto cambia la forma en que trabaja con Subversion: puedes hacer varias confirmaciones fuera de línea y luego enviarlas todas de una vez al servidor de Subversion. Para enviar a un servidor de Subversion, ejecuta el comando git svn dcommit:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	README.txt
Committed r77
    M	README.txt
r77 = 95e0222ba6399739834380eb10afcd73e0670bc5 (refs/remotes/origin/trunk)
No changes between 4af61fd05045e07598c553167e0f31c84fd6ffe1 and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

Esto toma todas las confirmaciones que hayas realizado sobre el código del servidor de Subversion, una confirmación de Subversion para cada una, y luego reescribe tu compromiso local de Git para incluir un identificador único. Esto es importante porque significa que todas las sumas de comprobación de SHA-1 cambian. En parte por esta razón, trabajar con versiones remotas basadas en Git de tus proyectos simultáneamente con un servidor de Subversion no es una buena idea. Si miras la última confirmación, puedes ver el nuevo git-svn-id que se agregó:

$ git log -1
commit 95e0222ba6399739834380eb10afcd73e0670bc5
Author: ben <ben@0b684db3-b064-4277-89d1-21af03df0a68>
Date:   Thu Jul 24 03:08:36 2014 +0000

    Adding git-svn instructions to the README

    git-svn-id: file:///tmp/test-svn/trunk@77 0b684db3-b064-4277-89d1-21af03df0a68

Observa que la suma de comprobación SHA-1 que originalmente comenzó con 4af61fd cuando acometiste, ahora comienza con` 95e0222`. Si deseas enviar tanto a un servidor de Git como a un servidor de Subversion, primero tienes que presionar (commit) al servidor de Subversion, porque esa acción cambia sus datos de confirmación.

Tirando hacia nuevos cambios

Si estás trabajando con otros desarrolladores, en algún momento uno de ustedes empujará, y luego el otro tratará de impulsar un cambio que entra en conflicto. Ese cambio será rechazado hasta que te combines en su trabajo. En git svn, se verá así:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...

ERROR from SVN:
Transaction is out of date: File '/trunk/README.txt' is out of date
W: d5837c4b461b7c0e018b49d12398769d2bfc240a and refs/remotes/origin/trunk differ, using rebase:
:100644 100644 f414c433af0fd6734428cf9d2a9fd8ba00ada145 c80b6127dd04f5fcda218730ddf3a2da4eb39138 M	README.txt
Current branch master is up to date.
ERROR: Not all changes have been committed into SVN, however the committed
ones (if any) seem to be successfully integrated into the working tree.
Please see the above messages for details.

Para resolver esta situación, puedes ejecutar git svn rebase, que elimina cualquier cambio en el servidor que aún no tengas y rebases cualquier trabajo que tengas encima de lo que hay en el servidor:

$ git svn rebase
Committing to file:///tmp/test-svn/trunk ...

ERROR from SVN:
Transaction is out of date: File '/trunk/README.txt' is out of date
W: eaa029d99f87c5c822c5c29039d19111ff32ef46 and refs/remotes/origin/trunk differ, using rebase:
:100644 100644 65536c6e30d263495c17d781962cfff12422693a b34372b25ccf4945fe5658fa381b075045e7702a M	README.txt
First, rewinding head to replay your work on top of it...
Applying: update foo
Using index info to reconstruct a base tree...
M	README.txt
Falling back to patching base and 3-way merge...
Auto-merging README.txt
ERROR: Not all changes have been committed into SVN, however the committed
ones (if any) seem to be successfully integrated into the working tree.
Please see the above messages for details.

Ahora, todo tu trabajo está encima de lo que hay en el servidor de Subversion, por lo que puedes aplicar dcommit con éxito:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	README.txt
Committed r85
    M	README.txt
r85 = 9c29704cc0bbbed7bd58160cfb66cb9191835cd8 (refs/remotes/origin/trunk)
No changes between 5762f56732a958d6cfda681b661d2a239cc53ef5 and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

Ten en cuenta que, a diferencia de Git, que requiere fusionar el trabajo original que todavía no tienes localmente antes de poder enviarlo, git svn lo hace sólo si los cambios entran en conflicto (muy parecido a cómo funciona Subversion). Si alguien más presiona un cambio en un archivo y luego presiona un cambio en otro archivo, su dcommit funcionará bien:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	configure.ac
Committed r87
    M	autogen.sh
r86 = d8450bab8a77228a644b7dc0e95977ffc61adff7 (refs/remotes/origin/trunk)
    M	configure.ac
r87 = f3653ea40cb4e26b6281cec102e35dcba1fe17c4 (refs/remotes/origin/trunk)
W: a0253d06732169107aa020390d9fefd2b1d92806 and refs/remotes/origin/trunk differ, using rebase:
:100755 100755 efa5a59965fbbb5b2b0a12890f1b351bb5493c18 e757b59a9439312d80d5d43bb65d4a7d0389ed6d M	autogen.sh
First, rewinding head to replay your work on top of it...

Es importante recordar esto, porque el resultado es un estado del proyecto que no existía en ninguna de las computadoras cuando se aplicó. Si los cambios son incompatibles pero no conflictivos, es posible que tengas problemas que son difíciles de diagnosticar. Esto es diferente a usar un servidor Git: en Git, puedes probar completamente el estado en tu sistema cliente antes de publicarlo, mientras que en SVN, nunca puedes estar seguro de que los estados inmediatamente antes de la confirmación y después de la confirmación sean idénticos.

También debes ejecutar este comando para obtener cambios del servidor de Subversion, incluso si no estás listo para confirmar. Puedes ejecutar git svn fetch para obtener los nuevos datos, pero git svn rebase hace la búsqueda y luego actualiza tus confirmaciones locales.

$ git svn rebase
    M	autogen.sh
r88 = c9c5f83c64bd755368784b444bc7a0216cc1e17b (refs/remotes/origin/trunk)
First, rewinding head to replay your work on top of it...
Fast-forwarded master to refs/remotes/origin/trunk.

Ejecutar git svn rebase de vez en cuando asegura que tu código esté siempre actualizado. Debes asegurarte de que tu directorio de trabajo esté limpio cuando lo ejecutes. Si tienes cambios locales, debes esconder tu trabajo o confirmarlo temporalmente antes de ejecutar git svn rebase; de lo contrario, el comando se detendrá si ve que la rebase dará como resultado un conflicto de fusión.

Problemas de Git Branching

Cuando te sientas cómodo con un flujo de trabajo de Git, probablemente crearás ramas temáticas, trabajarás en ellas y luego las fusionarás. Si estás presionando un servidor de Subversion a través de git svn, es posible que desees volver a establecer tu trabajo en una sola rama cada vez, en lugar de fusionar ramas. La razón para preferir el rebasamiento es que Subversion tiene un historial lineal y no trata con fusiones como Git, por lo que git svn sigue al primer padre cuando convierte las instantáneas en confirmaciones de Subversion.

Supongamos que tu historial se parece a lo siguiente: creaste una rama experiment, hiciste dos confirmaciones y luego las fusionaste de nuevo en master. Con dcommit, ve resultados como este:

$ git svn dcommit
Committing to file:///tmp/test-svn/trunk ...
    M	CHANGES.txt
Committed r89
    M	CHANGES.txt
r89 = 89d492c884ea7c834353563d5d913c6adf933981 (refs/remotes/origin/trunk)
    M	COPYING.txt
    M	INSTALL.txt
Committed r90
    M	INSTALL.txt
    M	COPYING.txt
r90 = cb522197870e61467473391799148f6721bcf9a0 (refs/remotes/origin/trunk)
No changes between 71af502c214ba13123992338569f4669877f55fd and refs/remotes/origin/trunk
Resetting to the latest refs/remotes/origin/trunk

Ejecutar dcommit en una rama con historial combinado funciona bien, excepto que cuando miras el historial de tu proyecto Git, no ha reescrito ninguno de los commits que hiciste en la rama` experiment`; sin embargo, todos esos cambios aparecen en el Versión SVN de la confirmación de fusión única.

Cuando alguien más clona ese trabajo, todo lo que ven es la combinación de fusión con todo el trabajo aplastado en ella, como si ejecutaras git merge --squash; no ven los datos de confirmación sobre su procedencia o cuándo se cometieron.

Ramificación en Subversion

La ramificación en Subversion no es lo mismo que la bifurcación en Git; si puedes evitar usarlo mucho, probablemente sea lo mejor. Sin embargo, puedes crear y comprometerte con ramas en Subversion usando git svn.

Creando una nueva rama de SVN

Para crear una nueva rama en Subversion, ejecuta git svn branch [branchname]:

$ git svn branch opera
Copying file:///tmp/test-svn/trunk at r90 to file:///tmp/test-svn/branches/opera...
Found possible branch point: file:///tmp/test-svn/trunk => file:///tmp/test-svn/branches/opera, 90
Found branch parent: (refs/remotes/origin/opera) cb522197870e61467473391799148f6721bcf9a0
Following parent with do_switch
Successfully followed parent
r91 = f1b64a3855d3c8dd84ee0ef10fa89d27f1584302 (refs/remotes/origin/opera)

Esto hace el equivalente del comando svn copy trunk branches / opera en Subversion y opera en el servidor de Subversion. Es importante tener en cuenta que NO te echa un vistazo en esa rama sin más; si confirmas en este punto, esa confirmación irá a trunk en el servidor, no en opera.

Cambio de ramas activas

Git averigua a qué rama se dirigen tus dcommits buscando la punta de cualquiera de tus ramas de Subversion en tu historial: debes tener sólo una, y debería ser la última con un git-svn-id en tu rama actual del historial.

Si deseas trabajar en más de una rama al mismo tiempo, puedes configurar las ramas locales para comprometer a las ramas de Subversion específicas, comenzándolas en la confirmación de Subversion importada para esa rama. Si deseas que una rama `opera ' pueda trabajar por separado, puedes ejecutar:

$ git branch opera remotes/origin/opera

Ahora, si deseas fusionar tu rama opera en trunk (tu rama` master`), puedes hacerlo con un git merge normal. Pero debes proporcionar un mensaje de compromiso descriptivo (mediante -m), o la combinación dirá` ‘Merge branch opera '’ en lugar de algo útil.

Recuerda que aunque estás usando git merge para hacer esta operación, la fusión probablemente sea mucho más fácil de lo que sería en Subversion (porque Git detectará automáticamente la base de confirmación apropiada para ti), esto no es una situación de confirmación normal de git merge Tienes que volver a enviar estos datos a un servidor de Subversion que no puede manejar una confirmación que rastrea más de un padre; por lo tanto, después de subirlo, se verá como una única confirmación que aplastó todo el trabajo de otra rama en una sola confirmación. Después de fusionar una rama con otra, no puedes volver atrás fácilmente y continuar trabajando en esa rama, como lo haces normalmente en Git. El comando dcommit que ejecutaste, borra cualquier información que indique en qué rama se fusionó, por lo que los cálculos subsiguientes de la base de confirmación serán incorrectos: el compromiso hace que el resultado de git merge parezca que ejecutaste git merge --squash. Desafortunadamente, no hay una buena forma de evitar esta situación: Subversion no puede almacenar esta información, por lo que siempre estará paralizado por sus limitaciones mientras lo usas como su servidor. Para evitar problemas, debes eliminar la rama local (en este caso, opera) después de fusionarla en el enlace troncal.

Comandos de Subversion

El conjunto de herramientas git svn proporciona una serie de comandos para ayudar a facilitar la transición a Git al proporcionar una funcionalidad similar a la que tenía en Subversion. Aquí hay algunos comandos que te dan lo que solías hacer en Subversion.

Historial de estilo de SVN

Si estás acostumbrado a Subversion y quieres ver tu historial en el estilo de salida SVN, puedes ejecutar git svn log para ver tu historial de commits en formato SVN:

$ git svn log
------------------------------------------------------------------------
r87 | schacon | 2014-05-02 16:07:37 -0700 (Sat, 02 May 2014) | 2 lines

autogen change

------------------------------------------------------------------------
r86 | schacon | 2014-05-02 16:00:21 -0700 (Sat, 02 May 2014) | 2 lines

Merge branch 'experiment'

------------------------------------------------------------------------
r85 | schacon | 2014-05-02 16:00:09 -0700 (Sat, 02 May 2014) | 2 lines

updated the changelog

Debes saber dos cosas importantes sobre git svn log. En primer lugar, funciona sin conexión, a diferencia del comando real svn log, que solicita al servidor de Subversion los datos. En segundo lugar, sòlo muestra los compromisos que se han comprometido hasta el servidor de Subversion. Local Git confirma que no te has confirmado al no aparecer; tampoco hay confirmaciones que la gente haya hecho al servidor de Subversion mientras tanto. Es más como el último estado conocido de las confirmaciones en el servidor de Subversion.

Anotación de SVN

Al igual que el comando git svn log simula el comando svn log fuera de línea, puedes obtener el equivalente de svn annotate ejecutando `git svn blame [FILE] `. La salida se ve así:

$ git svn blame README.txt
 2   temporal Protocol Buffers - Google's data interchange format
 2   temporal Copyright 2008 Google Inc.
 2   temporal http://code.google.com/apis/protocolbuffers/
 2   temporal
22   temporal C++ Installation - Unix
22   temporal =======================
 2   temporal
79    schacon Committing in git-svn.
78    schacon
 2   temporal To build and install the C++ Protocol Buffer runtime and the Protocol
 2   temporal Buffer compiler (protoc) execute the following:
 2   temporal

De nuevo, no muestra confirmaciones que hiciste localmente en Git o que se han enviado a Subversion mientras tanto.

Información del servidor SVN

También puedes obtener el mismo tipo de información que svn info te brinda al ejecutar git svn info:

$ git svn info
Path: .
URL: https://schacon-test.googlecode.com/svn/trunk
Repository Root: https://schacon-test.googlecode.com/svn
Repository UUID: 4c93b258-373f-11de-be05-5f7a86268029
Revision: 87
Node Kind: directory
Schedule: normal
Last Changed Author: schacon
Last Changed Rev: 87
Last Changed Date: 2009-05-02 16:07:37 -0700 (Sat, 02 May 2009)

Esto es como blame y log ya que se ejecuta fuera de línea y está actualizado sólo a partir de la última vez que se comunicó con el servidor de Subversion.

Ignorar lo que subversión ignora

Si clonas un repositorio de Subversion que tiene propiedades svn: ignore establecidas en cualquier lugar, es probable que desees establecer los archivos correspondientes '.gitignore` para que no confirmes accidentalmente los archivos que no deberías. git svn tiene dos comandos para ayudar con este problema. El primero es git svn create-ignore, que crea automáticamente los archivos correspondientes '.gitignore` para que tu próxima confirmacion pueda incluirlos.

El segundo comando es git svn show-ignore, que imprime para extender las líneas que necesitas poner en un archivo` .gitignore` para que pueda redirigir el resultado al archivo de exclusión de proyecto:

$ git svn show-ignore > .git/info/exclude

De esta forma, no descarta el proyecto con archivos .gitignore. Esta es una buena opción si eres el único usuario de Git en un equipo de Subversion, y tus compañeros de equipo no quieren archivos .gitignore en el proyecto.

Resumen de Git-Svn

Las herramientas git svn son útiles si estás atascado con un servidor de Subversion, o si estás en un entorno de desarrollo que necesita ejecutar un servidor de Subversion. Sin embargo, deberías considerar que paralizó a Git, o llegará a problemas de traducción que pueden confundirte a ti y a tus colaboradores. Para evitar problemas, intenta seguir estas pautas:

  • Mantener un historial lineal de Git que no contenga uniones de fusión hechas por git merge. Haz rebase a cualquier trabajo que realices fuera de tu rama principal; no se fusiona.

  • No configures y colabores en un servidor Git separado. Posiblemente tengas uno para acelerar clones para nuevos desarrolladores, pero no le empujes nada que no tenga una entrada git-svn-id. Incluso puedes desear agregar un gancho pre-receive que compruebe cada mensaje de confirmación para un git-svn-id y rechaza los empujes que contienen commits sin él.

Si sigues esas pautas, trabajar con un servidor de Subversion puede ser más llevadero. Sin embargo, si es posible pasar a un servidor Git real, hacerlo puede hacer que tu equipo gane mucho más.

Git y Mercurial

El universo DVCS es más grande que el de Git. De hecho, hay muchos otros sistemas en este espacio, cada uno con su propio ángulo sobre cómo hacer el control de versión distribuida correctamente. Aparte de Git, el más popular es Mercurial, y los dos son muy similares en muchos aspectos.

La buena noticia, si prefiere el comportamiento del cliente de Git pero está trabajando con un proyecto cuyo código fuente está controlado con Mercurial, es que hay una manera de usar Git como cliente para un repositorio alojado en Mercurial. Dado que la forma en que Git habla con los repositorios del servidor es a través de controles remotos, no debería sorprendernos que este puente se implemente como un ayudante remoto. El nombre del proyecto es git-remote-hg, y se puede encontrar en https://github.com/felipec/git-remote-hg.

git-remote-hg

Primero, necesita instalar git-remote-hg. Esto básicamente implica dejar caer su archivo en algún lugar de su camino, así:

$ curl -o ~/bin/git-remote-hg \
  https://raw.githubusercontent.com/felipec/git-remote-hg/master/git-remote-hg
$ chmod +x ~/bin/git-remote-hg
  1. asumiendo que ~ / bin está en su` $ PATH`. Git-remote-hg tiene otra dependencia: la biblioteca mercurial para Python. Si tiene instalado Python, es tan sencillo como:

$ pip install mercurial

(Si no tiene instalado Python, visite https://www.python.org/ y consígalo primero.)

Lo último que necesitará es el cliente Mercurial. Vaya a https://www.mercurial-scm.org/ e instálelo si aún no lo ha hecho.

Ahora está listo para el rock. Todo lo que necesita es un repositorio Mercurial al que pueda presionar. Afortunadamente, todos los repositorios de Mercurial pueden actuar de esta manera, así que sólo usaremos el repositorio de "hola mundo" que todos usan para aprender Mercurial:

$ hg clone http://selenic.com/repo/hello /tmp/hello

Empezando

Ahora que tenemos un repositorio “server-side” adecuado, podemos pasar por un flujo de trabajo típico. Como verá, estos dos sistemas son lo suficientemente similares como para que no haya mucha fricción.

Como siempre con Git, primero clonamos:

$ git clone hg::/tmp/hello /tmp/hello-git
$ cd /tmp/hello-git
$ git log --oneline --graph --decorate
* ac7955c (HEAD, origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master, master) Create a makefile
* 65bb417 Create a standard "hello, world" program

Notará que el uso de un repositorio de Mercurial utiliza el comando git clone estándar. Esto se debe a que git-remote-hg está funcionando a un nivel bastante bajo, utilizando un mecanismo similar a como se implementa el protocolo HTTP / S de Git (auxiliares remotos). Dado que Git y Mercurial están diseñados para que cada cliente tenga una copia completa del historial del repositorio, este comando hace un clon completo, incluyendo todo el historial del proyecto, y lo hace con bastante rapidez.

El comando de registro muestra dos confirmaciones, la última de las cuales es señalada por un montón de refs. Resulta que algunos de estos no están realmente allí. Echemos un vistazo a lo que realmente está en el directorio .git:

$ tree .git/refs
.git/refs
├── heads
│   └── master
├── hg
│   └── origin
│       ├── bookmarks
│       │   └── master
│       └── branches
│           └── default
├── notes
│   └── hg
├── remotes
│   └── origin
│       └── HEAD
└── tags

9 directories, 5 files

Git-remote-hg está tratando de hacer las cosas más idiomáticamente Git-esque, pero bajo el capó es la gestión de la cartografía conceptual entre dos sistemas ligeramente diferentes. El directorio refs/hg es donde se almacenan las referencias remotas reales. Por ejemplo, el refs/hg/origen/branches/default es un archivo ref de Git que contiene el SHA-1 que comienza con “ac7955c”, que es el commit que señala master. Así que el directorio refs/hg es como un`refs/remotes/origen' falso, pero tiene la distinción añadida entre marcadores y ramas.

El archivo notes/hg es el punto de partida de cómo git-remote-hg asigna los hashes de commit de Git a los identificadores de cambios de Mercurial. Vamos a explorar un poco:

$ cat notes/hg
d4c10386...

$ git cat-file -p d4c10386...
tree 1781c96...
author remote-hg <> 1408066400 -0800
committer remote-hg <> 1408066400 -0800

Notes for master

$ git ls-tree 1781c96...
100644 blob ac9117f...	65bb417...
100644 blob 485e178...	ac7955c...

$ git cat-file -p ac9117f
0a04b987be5ae354b710cefeba0e2d9de7ad41a9

Así que refs/notes/hg apunta a un árbol, que en la base de datos de objetos Git es una lista de otros objetos con nombres. Git ls-tree genera el modo, el tipo, el hash de objeto y el nombre de archivo de elementos dentro de un árbol. Una vez que excavamos hacia abajo a uno de los elementos del árbol, encontramos que en su interior hay un blob llamado “ac9117f” (el hash SHA-1 del commit apuntado por master), con contenidos “0a04b98”. Que es el identificador del conjunto de cambios Mercurial en la punta de la rama default).

La buena noticia es, que en general, no tenemos que preocuparnos por todo esto. El flujo de trabajo típico no será muy diferente de trabajar con un control remoto de Git.

Hay una cosa más a la que debemos atender antes de continuar: ignore. Mercurial y Git usan un mecanismo muy similar para esto, pero es probable que no quiera realmente comprometer un archivo .gitignore en un repositorio de Mercurial. Afortunadamente, Git tiene una forma de ignorar los archivos que son locales a un repositorio en disco, y el formato Mercurial es compatible con Git, por lo que sólo tiene que copiarlo:

$ cp .hgignore .git/info/exclude

El archivo .git / info / exclude 'actúa como un .gitignore`, pero no está incluido en commits.

Flujo de Trabajo

Supongamos que hemos hecho algunos trabajos e hicimos algunos commit en la rama master y estamos listos para enviarlo al repositorio remoto. A continuación, le mostramos nuestro repositorio:

$ git log --oneline --graph --decorate
* ba04a2a (HEAD, master) Update makefile
* d25d16f Goodbye
* ac7955c (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Create a makefile
* 65bb417 Create a standard "hello, world" program

Nuestra rama master está a dos compromisos por delante de origin/master, pero estos dos commits sólo existen en nuestra máquina local. Veamos si alguien más ha estado haciendo un trabajo importante al mismo tiempo:

$ git fetch
From hg::/tmp/hello
   ac7955c..df85e87  master     -> origin/master
   ac7955c..df85e87  branches/default -> origin/branches/default
$ git log --oneline --graph --decorate --all
* 7b07969 (refs/notes/hg) Notes for default
* d4c1038 Notes for master
* df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
| * ba04a2a (HEAD, master) Update makefile
| * d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard "hello, world" program

Puesto que utilizamos el indicador --all, vemos las “notes refs” que son utilizadas internamente por git-remote-hg, pero podemos ignorarlas. El resto es lo que esperábamos; origin / master ha avanzado por una comisión, y nuestra historia ha divergido ahora. A diferencia de los otros sistemas con los que trabajamos en este capítulo, Mercurial es capaz de manejar fusiones, por lo que no vamos a hacer nada extravagante.

$ git merge origin/master
Auto-merging hello.c
Merge made by the 'recursive' strategy.
 hello.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git log --oneline --graph --decorate
*   0c64627 (HEAD, master) Merge remote-tracking branch 'origin/master'
|\
| * df85e87 (origin/master, origin/branches/default, origin/HEAD, refs/hg/origin/branches/default, refs/hg/origin/bookmarks/master) Add some documentation
* | ba04a2a Update makefile
* | d25d16f Goodbye
|/
* ac7955c Create a makefile
* 65bb417 Create a standard "hello, world" program

Perfecto. Hacemos las pruebas y todo pasa, así que estamos listos para compartir nuestro trabajo con el resto del equipo:

$ git push
To hg::/tmp/hello
   df85e87..0c64627  master -> master

¡Eso es! Si echa un vistazo al repositorio de Mercurial, verá que esto hizo lo que esperábamos:

$ hg log -G --style compact
o    5[tip]:4,2   dc8fa4f932b8   2014-08-14 19:33 -0700   ben
|\     Merge remote-tracking branch 'origin/master'
| |
| o  4   64f27bcefc35   2014-08-14 19:27 -0700   ben
| |    Update makefile
| |
| o  3:1   4256fc29598f   2014-08-14 19:27 -0700   ben
| |    Goodbye
| |
@ |  2   7db0b4848b3c   2014-08-14 19:30 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard "hello, world" program

El conjunto de cambios numerado 2 fue hecho por Mercurial, y los conjuntos de cambios numerados 3 y 4 fueron hechos por git-remote-hg, al empujar los commit hechos con Git.

Branches(Ramas) y Bookmarks(Marcadores)

Git tiene sólo un tipo de rama: una referencia que se mueve cuando se hacen los compromisos. En Mercurial, este tipo de referencia se llama marcador, y se comporta de la misma manera que una rama de Git.

El concepto de Mercurial de una "rama" es más pesado. La rama en la que se realiza un conjunto de cambios se registra con el conjunto de cambios, lo que significa que siempre estará en el historial del repositorio. He aquí un ejemplo de un commit que se hizo en la rama develop:

$ hg log -l 1
changeset:   6:8f65e5e02793
branch:      develop
tag:         tip
user:        Ben Straub <ben@straub.cc>
date:        Thu Aug 14 20:06:38 2014 -0700
summary:     More documentation

Observe la línea que comienza con “branch”. Git no puede realmente replicar esto (y no necesita, ambos tipos de rama puede representarse como una referencia Git), pero git-remote-hg necesita entender la diferencia, porque Mercurial se preocupa.

Crear marcadores de Mercurial es tan fácil como crear ramas de Git. En el lado Git:

$ git checkout -b featureA
Switched to a new branch 'featureA'
$ git push origin featureA
To hg::/tmp/hello
 * [new branch]      featureA -> featureA

Eso es todo al respecto. En el lado mercurial, se ve así:

$ hg bookmarks
   featureA                  5:bd5ac26f11f9
$ hg log --style compact -G
@  6[tip]   8f65e5e02793   2014-08-14 20:06 -0700   ben
|    More documentation
|
o    5[featureA]:4,2   bd5ac26f11f9   2014-08-14 20:02 -0700   ben
|\     Merge remote-tracking branch 'origin/master'
| |
| o  4   0434aaa6b91f   2014-08-14 20:01 -0700   ben
| |    update makefile
| |
| o  3:1   318914536c86   2014-08-14 20:00 -0700   ben
| |    goodbye
| |
o |  2   f098c7f45c4f   2014-08-14 20:01 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard "hello, world" program

Tenga en cuenta la nueva etiqueta [featureA] en la revisión 5. Éstos actúan exactamente como las ramas de Git en el lado de Git, con una excepción: usted no puede suprimir un marcador del lado de Git (ésta es una limitación de ayudantes remotos).

Puede trabajar con una rama “heavyweight” de Mercurial si: introduce ramas en los espacios para branches así:

$ git checkout -b branches/permanent
Switched to a new branch 'branches/permanent'
$ vi Makefile
$ git commit -am 'A permanent change'
$ git push origin branches/permanent
To hg::/tmp/hello
 * [new branch]      branches/permanent -> branches/permanent

Esto es lo que aparece en el lado de Mercurial:

$ hg branches
permanent                      7:a4529d07aad4
develop                        6:8f65e5e02793
default                        5:bd5ac26f11f9 (inactive)
$ hg log -G
o  changeset:   7:a4529d07aad4
|  branch:      permanent
|  tag:         tip
|  parent:      5:bd5ac26f11f9
|  user:        Ben Straub <ben@straub.cc>
|  date:        Thu Aug 14 20:21:09 2014 -0700
|  summary:     A permanent change
|
| @  changeset:   6:8f65e5e02793
|/   branch:      develop
|    user:        Ben Straub <ben@straub.cc>
|    date:        Thu Aug 14 20:06:38 2014 -0700
|    summary:     More documentation
|
o    changeset:   5:bd5ac26f11f9
|\   bookmark:    featureA
| |  parent:      4:0434aaa6b91f
| |  parent:      2:f098c7f45c4f
| |  user:        Ben Straub <ben@straub.cc>
| |  date:        Thu Aug 14 20:02:21 2014 -0700
| |  summary:     Merge remote-tracking branch 'origin/master'
[...]

El nombre de la rama “permanent” se registró en el conjunto de cambios marcados con 7.

Desde el lado de Git, el trabajo con cualquiera de estos estilos de rama es el mismo: sólo “checkout”, “commit”, “fetch”, “merge”, “pull” y “push” como lo haría normalmente. Una cosa que usted debe saber es que Mercurial no apoya la historia de la reescritura, agregando solamente a ella. Esto es lo que nuestro repositorio de Mercurial parece después de un “rebase interactivo” y un “force-push”:

$ hg log --style compact -G
o  10[tip]   99611176cbc9   2014-08-14 20:21 -0700   ben
|    A permanent change
|
o  9   f23e12f939c3   2014-08-14 20:01 -0700   ben
|    Add some documentation
|
o  8:1   c16971d33922   2014-08-14 20:00 -0700   ben
|    goodbye
|
| o  7:5   a4529d07aad4   2014-08-14 20:21 -0700   ben
| |    A permanent change
| |
| | @  6   8f65e5e02793   2014-08-14 20:06 -0700   ben
| |/     More documentation
| |
| o    5[featureA]:4,2   bd5ac26f11f9   2014-08-14 20:02 -0700   ben
| |\     Merge remote-tracking branch 'origin/master'
| | |
| | o  4   0434aaa6b91f   2014-08-14 20:01 -0700   ben
| | |    update makefile
| | |
+---o  3:1   318914536c86   2014-08-14 20:00 -0700   ben
| |      goodbye
| |
| o  2   f098c7f45c4f   2014-08-14 20:01 -0700   ben
|/     Add some documentation
|
o  1   82e55d328c8c   2005-08-26 01:21 -0700   mpm
|    Create a makefile
|
o  0   0a04b987be5a   2005-08-26 01:20 -0700   mpm
     Create a standard "hello, world" program

CHangesets 8, 9 y 10 han sido creados y pertenecen a la rama permanent, pero los viejos “changesets” siguen ahí. Esto puede ser muy confuso para sus compañeros de equipo que están usando Mercurial, así que trate de evitarlo.

Resumen de Mercurial

Git y Mercurial son bastante similares, por lo que trabajar a través de la frontera es bastante indoloro. Si evita cambiar el historial que ha dejado su máquina (como se recomienda generalmente), puede que ni siquiera sepa que el otro extremo es Mercurial.

Git y Perforce

Perforce es un sistema de control de versiones muy popular en entornos corporativos. Ha existido desde 1995, conviertiéndolo en el sistema más antiguo cubierto en este capítulo. Como tal, está diseñado con las limitaciones de su día; supone que siempre está conectado a un solo servidor central y sólo se conserva una versión en el disco local. Para estar seguro, sus características y limitaciones son adecuadas para varios problemas específicos, pero hay muchos proyectos que usan Perforce donde Git realmente funcionaría mejor.

Hay dos opciones si desea mezclar el uso de Perforce y Git. La primera que veremos es el puente “'Git Fusion”' de los creadores de Perforce, que le permite exponer los subárboles de su depósito de Perforce como repositorios de lectura y escritura de Git. La segunda es git-p4, un puente del lado del cliente que le permite usar Git como un cliente Perforce, sin requerir ninguna reconfiguración del servidor Perforce.

Git Fusion

Perforce proporciona un producto llamado 'Git Fusion' (disponible en http://www.perforce.com/git-fusion), que sincroniza un servidor Perforce con repositorios Git en el lado del servidor.

Configurando

Para nuestros ejemplos, utilizaremos el método de instalación más fácil para Git Fusion: descargando una máquina virtual que ejecuta Perforce Daemon y Git Fusion. Puede obtener la imagen de la máquina virtual desde http://www.perforce.com/downloads/Perforce/20-User, y una vez que haya finalizado la descarga, impórtela en su software de virtualización favorito (utilizaremos VirtualBox).

Al iniciar la máquina por primera vez, le solicita que personalice la contraseña para tres usuarios de Linux (root,` perforce` y git), y proporcione un nombre de instancia, que se puede usar para distinguir esta instalación de otras en el misma red. Cuando todo haya terminado, verás esto:

The 'Git Fusion' virtual machine boot screen.
Figura 146. The Git Fusion virtual machine boot screen.

Debe tomar nota de la dirección IP que se muestra aquí, la usaremos más adelante. A continuación, crearemos un usuario de Perforce. Seleccione la opción “Iniciar sesión” en la parte inferior y presione enter (o SSH en la máquina) e inicie sesión como root. Luego use estos comandos para crear un usuario:

$ p4 -p localhost:1666 -u super user -f john
$ p4 -p localhost:1666 -u john passwd
$ exit

El primero abrirá un editor VI para personalizar al usuario, pero puede aceptar los valores predeterminados escribiendo :wq y pulsando enter. El segundo le pedirá que ingrese una contraseña dos veces. Eso es todo lo que tenemos que hacer con un intérprete de comandos de shell, así que salga de la sesión.

Lo siguiente que tendrá que hacer es decirle a Git que no verifique los certificados SSL. La imagen de Git Fusion viene con un certificado, pero es para un dominio que no coincidirá con la dirección IP de su máquina virtual, por lo que Git rechazará la conexión HTTPS. Si va a hacer una instalación permanente, consulte el manual de Perforce de Git Fusion para instalar un certificado diferente; para nuestros propósitos de ejemplo, esto será suficiente:

$ export GIT_SSL_NO_VERIFY=true

Ahora podemos probar que todo está funcionando.

$ git clone https://10.0.1.254/Talkhouse
Cloning into 'Talkhouse'...
Username for 'https://10.0.1.254': john
Password for 'https://john@10.0.1.254':
remote: Counting objects: 630, done.
remote: Compressing objects: 100% (581/581), done.
remote: Total 630 (delta 172), reused 0 (delta 0)
Receiving objects: 100% (630/630), 1.22 MiB | 0 bytes/s, done.
Resolving deltas: 100% (172/172), done.
Checking connectivity... done.

La imagen de la máquina virtual viene equipada con un proyecto de muestra que puede clonar. Aquí estamos clonando a través de HTTPS, con el usuario john que creamos anteriormente; Git solicita credenciales para esta conexión, pero la caché de credenciales nos permitirá omitir este paso para cualquier solicitud posterior.

Configuración de Fusion

Una vez que haya instalado Git Fusion, querrá modificar la configuración.Esto es bastante fácil de hacer usando su cliente Perforce favorito; simplemente asigne el directorio //.git-fusion en el servidor Perforce en su espacio de trabajo.La estructura del archivo se ve así:

$ tree
.
├── objects
│   ├── repos
│   │   └── [...]
│   └── trees
│       └── [...]
│
├── p4gf_config
├── repos
│   └── Talkhouse
│       └── p4gf_config
└── users
    └── p4gf_usermap

498 directories, 287 files

El directorio objects es usado internamente por Git Fusion para asignar objetos Perforce a Git y viceversa, no tendrá que meterse con nada allí. Hay un archivo global p4gf_config en este directorio, así como uno para cada repositorio – estos son los archivos de configuración que determinan cómo se comporta Git Fusion. Echemos un vistazo al archivo en la raíz:

[repo-creation]
charset = utf8

[git-to-perforce]
change-owner = author
enable-git-branch-creation = yes
enable-swarm-reviews = yes
enable-git-merge-commits = yes
enable-git-submodules = yes
preflight-commit = none
ignore-author-permissions = no
read-permission-check = none
git-merge-avoidance-after-change-num = 12107

[perforce-to-git]
http-url = none
ssh-url = none

[@features]
imports = False
chunked-push = False
matrix2 = False
parallel-push = False

[authentication]
email-case-sensitivity = no

No entraremos en el significado de estos indicadores aquí, pero tenga en cuenta que esto es sólo un archivo de texto con formato INI, muy parecido al que Git usa para la configuración. Este archivo especifica las opciones globales, que luego pueden ser reemplazadas por archivos de configuración específicos del repositorio, como repos/Talkhouse/p4gf_config. Si abre este archivo, verá una sección [@repo] con algunas configuraciones que son diferentes de los valores predeterminados globales. También verá secciones que se ven así:

[Talkhouse-master]
git-branch-name = master
view = //depot/Talkhouse/main-dev/... ...

Este es un mapeo entre una rama Perforce y una rama Git. La sección se puede nombrar como prefiera, siempre que el nombre sea único. git-branch-name le permite convertir una ruta de depósito que sería engorrosa bajo Git a un nombre más amigable. La configuración view controla cómo se asocian los archivos de Perforce en el repositorio de Git, usando la sintaxis de mapeo de vista estándar. Se puede especificar más de un mapeo, como en este ejemplo:

[multi-project-mapping]
git-branch-name = master
view = //depot/project1/main/... project1/...
       //depot/project2/mainline/... project2/...

De esta manera, si la asignación normal del espacio de trabajo incluye cambios en la estructura de los directorios, puede replicar eso con un repositorio Git.

El último archivo que discutiremos es users/p4gf_usermap, que mapea los usuarios de Perforce a los usuarios de Git, y que quizás ni siquiera necesite. Al convertir un conjunto de cambios de Perforce a una commit de Git, el comportamiento predeterminado de Git Fusion es buscar al usuario de Perforce y usar la dirección de correo electrónico y el nombre completo almacenados allí para el campo autor/committer en Git. Al realizar la conversión de otra manera, el valor predeterminado es buscar al usuario de Perforce con la dirección de correo electrónico almacenada en el campo de autoría del commit de Git y enviar el conjunto de cambios como ese usuario (con la aplicación de permisos). En la mayoría de los casos, este comportamiento funcionará bien, pero considere el siguiente archivo de mapeo:

john john@example.com "John Doe"
john johnny@appleseed.net "John Doe"
bob employeeX@example.com "Anon X. Mouse"
joe employeeY@example.com "Anon Y. Mouse"

Cada línea tiene el formato <usuario> <correo electrónico> "<nombre completo>" y crea una sola asignación de usuario. Las dos primeras líneas asignan dos direcciones de correo electrónico distintas a la misma cuenta de usuario de Perforce. Esto es útil si ha creado commits de Git en varias direcciones de correo electrónico diferentes (o cambia direcciones de correo electrónico), pero quiere que se mapeen al mismo usuario de Perforce. Al crear una commit de Git a partir de un conjunto de cambios de Perforce, la primera línea que coincide con el usuario de Perforce se utiliza para la información de autoría de Git.

Las últimas dos líneas ocultan los nombres reales y las direcciones de correo electrónico de Bob y Joe de las commits de Git que se crean. Esto es bueno si desea abrir un proyecto interno de fuente abierta, pero no desea publicar su directorio de empleados en todo el mundo. Tenga en cuenta que las direcciones de correo electrónico y los nombres completos deben ser únicos, a menos que desee que todos los commit de Git se atribuyan a un único autor ficticio.

Flujo de trabajo

Perforce de Git Fusion es un puente de dos vías entre Perforce y el control de versiones de Git. Echemos un vistazo a cómo se siente trabajar desde el lado de Git. Asumiremos que hemos mapeado en el proyecto “Jam” usando un archivo de configuración como se muestra arriba, el cual podemos clonar así:

$ git clone https://10.0.1.254/Jam
Cloning into 'Jam'...
Username for 'https://10.0.1.254': john
Password for 'https://ben@10.0.1.254':
remote: Counting objects: 2070, done.
remote: Compressing objects: 100% (1704/1704), done.
Receiving objects: 100% (2070/2070), 1.21 MiB | 0 bytes/s, done.
remote: Total 2070 (delta 1242), reused 0 (delta 0)
Resolving deltas: 100% (1242/1242), done.
Checking connectivity... done.
$ git branch -a
* master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/origin/rel2.1
$ git log --oneline --decorate --graph --all
* 0a38c33 (origin/rel2.1) Create Jam 2.1 release branch.
| * d254865 (HEAD, origin/master, origin/HEAD, master) Upgrade to latest metrowerks on Beos -- the Intel one.
| * bd2f54a Put in fix for jam's NT handle leak.
| * c0f29e7 Fix URL in a jam doc
| * cc644ac Radstone's lynx port.
[...]

La primera vez que hace esto, puede tomar algún tiempo. Lo que sucede es que Git Fusion está convirtiendo todos los conjuntos de cambios aplicables en el historial de Perforce en commits de Git. Esto ocurre localmente en el servidor, por lo que es relativamente rápido, pero si tiene un montón de historia, aún puede tomar algo de tiempo. Las recuperaciones posteriores realizan conversiones incrementales, por lo que se parecerá más a la velocidad nativa de Git.

Como puede ver, nuestro repositorio se ve exactamente como cualquier otro repositorio de Git con el que pueda trabajar. Hay tres ramas, y Git ha creado una rama master local que rastrea origin/master. Hagamos un poco de trabajo y creemos un par de nuevos commits:

# ...
$ git log --oneline --decorate --graph --all
* cfd46ab (HEAD, master) Add documentation for new feature
* a730d77 Whitespace
* d254865 (origin/master, origin/HEAD) Upgrade to latest metrowerks on Beos -- the Intel one.
* bd2f54a Put in fix for jam's NT handle leak.
[...]

Tenemos dos nuevos commits. Ahora revisemos si alguien más ha estado trabajando:

$ git fetch
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 2), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From https://10.0.1.254/Jam
   d254865..6afeb15  master     -> origin/master
$ git log --oneline --decorate --graph --all
* 6afeb15 (origin/master, origin/HEAD) Update copyright
| * cfd46ab (HEAD, master) Add documentation for new feature
| * a730d77 Whitespace
|/
* d254865 Upgrade to latest metrowerks on Beos -- the Intel one.
* bd2f54a Put in fix for jam's NT handle leak.
[...]

¡Parece que alguien lo está! No lo sabría desde esta vista, pero el commit 6afeb15 se creó realmente utilizando un cliente Perforce. Sòlo parece otro commit desde el punto de vista de Git, que es exactamente el punto. Veamos cómo el servidor Perforce trata con un commit de fusión:

$ git merge origin/master
Auto-merging README
Merge made by the 'recursive' strategy.
 README | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git push
Counting objects: 9, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (9/9), done.
Writing objects: 100% (9/9), 917 bytes | 0 bytes/s, done.
Total 9 (delta 6), reused 0 (delta 0)
remote: Perforce: 100% (3/3) Loading commit tree into memory...
remote: Perforce: 100% (5/5) Finding child commits...
remote: Perforce: Running git fast-export...
remote: Perforce: 100% (3/3) Checking commits...
remote: Processing will continue even if connection is closed.
remote: Perforce: 100% (3/3) Copying changelists...
remote: Perforce: Submitting new Git commit objects to Perforce: 4
To https://10.0.1.254/Jam
   6afeb15..89cba2b  master -> master

Git cree que funcionó. Echemos un vistazo al historial del archivo README desde el punto de vista de Perforce, usando la función de gráfico de revisión de p4v:

Perforce revision graph resulting from Git push.
Figura 147. Perforce revision graph resulting from Git push.

Si nunca antes has visto esta interfaz, puede parecer confusa, pero muestra los mismos conceptos que un visor gráfico para el historial de Git. Estamos viendo el historial del archivo README, por lo que el árbol de directorios en la parte superior izquierda solo muestra ese archivo a medida que aparece en varias ramas. En la parte superior derecha, tenemos un gráfico visual de cómo se relacionan las diferentes revisiones del archivo, y la vista general de este gráfico se encuentra en la parte inferior derecha. El resto da a la vista de detalles para la revisión seleccionada (2 en este caso).

Una cosa para notar es que el gráfico se ve exactamente como el del historial de Git. Perforce no tenía una rama con nombre para almacenar las commits 1 y 2, por lo que creó una rama “anónima” en el directorio .git-fusion para contenerla. Esto también ocurrirá para las ramas nombradas de Git que no se corresponden con una rama de Perforce con nombre (y luego puede asignarlas a una rama de Perforce usando el archivo de configuración).

La mayoría de esto sucede detrás de escena, pero el resultado final es que una persona en un equipo puede estar usando Git, otra puede estar usando Perforce, y ninguno de ellos conocerá la elección del otro.

Resumen de Git-Fusion

Si tiene (o puede obtener) acceso a su servidor Perforce, Git Fusion es una excelente manera de hacer que Git y Perforce hablen entre sí. Hay un poco de configuración involucrada, pero la curva de aprendizaje no es muy pronunciada. Esta es una de las pocas secciones en este capítulo donde las precauciones sobre el uso de la potencia total de Git no aparecerán. Eso no quiere decir que Perforce esté contento con todo lo que le arroja – si trata de reescribir la historia que ya ha sido empujada, Git Fusion la rechazará – pero Git Fusion trata muy fuertemente de sentirse nativo. Incluso puede usar submódulos de Git (aunque parecerán extraños a los usuarios de Perforce) y unir ramas (esto se registrará como una integración en el lado de Perforce).

Si no puede convencer al administrador de su servidor para configurar Git Fusion, todavía hay una manera de utilizar estas herramientas juntas.

Git-p4

Git-p4 es un puente de dos vías entre Git y Perforce. Funciona completamente dentro de su repositorio Git, por lo que no necesitará ningún tipo de acceso al servidor Perforce (aparte de las credenciales de usuario, por supuesto). Git-p4 no es tan flexible ni una solución completa como Git Fusion, pero le permite hacer la mayor parte de lo que le gustaría hacer sin ser invasivo en el entorno del servidor.

Nota

Necesitará la herramienta p4 en algún lugar de su` PATH` para trabajar con git-p4. Al momento de escribir esto, está disponible gratuitamente en http://www.perforce.com/downloads/Perforce/20-User.

Configurando

Por ejemplo, ejecutaremos el servidor Perforce desde Git Fusion OVA como se muestra arriba, pero omitiremos el servidor de Git Fusion y pasaremos directamente al control de versión de Perforce.

Para utilizar el cliente de línea de comandos p4 (del cual depende git-p4), deberá establecer un par de variables de entorno:

$ export P4PORT=10.0.1.254:1666
$ export P4USER=john
Empezando

Al igual que con cualquier cosa en Git, el primer comando es clonar:

$ git p4 clone //depot/www/live www-shallow
Importing from //depot/www/live into www-shallow
Initialized empty Git repository in /private/tmp/www-shallow/.git/
Doing initial import of //depot/www/live/ from revision #head into refs/remotes/p4/master

Esto crea lo que en términos de Git es un clon “superficial”; sólo la última versión de Perforce se importa a Git; recuerde, Perforce no está diseñado para dar cada revisión a cada usuario. Esto es suficiente para usar Git como cliente de Perforce, pero para otros fines no es suficiente.

Una vez que está terminado, tenemos un repositorio de Git completamente funcional:

$ cd myproject
$ git log --oneline --all --graph --decorate
* 70eaf78 (HEAD, p4/master, p4/HEAD, master) Initial import of //depot/www/live/ from the state at revision #head

Tenga en cuenta que hay un control remoto “p4” para el servidor de Perforce, pero todo lo demás parece un clon estándar. En realidad, eso es un poco engañoso; no hay realmente un control remoto allí.

$ git remote -v

No hay controles remotos en este repositorio en lo absoluto. Git-p4 ha creado algunas refs para representar el estado del servidor, y se ven como refs remotas para git log, pero no son administradas por Git, y no puede presionarlas.

Flujo de trabajo

De acuerdo, hagamos un poco de trabajo. Supongamos que ha hecho algún progreso en una característica muy importante y está listo para mostrársela al resto de su equipo.

$ git log --oneline --all --graph --decorate
* 018467c (HEAD, master) Change page title
* c0fb617 Update link
* 70eaf78 (p4/master, p4/HEAD) Initial import of //depot/www/live/ from the state at revision #head

Hemos realizado dos nuevos commits que estamos listos para enviar al servidor de Perforce. Comprobemos si alguien más estaba trabajando hoy:

$ git p4 sync
git p4 sync
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
Import destination: refs/remotes/p4/master
Importing revision 12142 (100%)
$ git log --oneline --all --graph --decorate
* 75cd059 (p4/master, p4/HEAD) Update copyright
| * 018467c (HEAD, master) Change page title
| * c0fb617 Update link
|/
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Parece que sí, ya que master y p4/master han divergido. El sistema de ramificación de Perforce es nada similar al de Git, por lo que enviar commits de fusión no tiene ningún sentido. Git-p4 recomienda que haga rebase de sus commits, e incluso viene con un atajo para hacerlo:

$ git p4 rebase
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
No changes to import!
Rebasing the current branch onto remotes/p4/master
First, rewinding head to replay your work on top of it...
Applying: Update link
Applying: Change page title
 index.html | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

Probablemente pueda parecer simple desde la salida, pero git p4 rebase es un atajo para` git p4 sync` seguido de git rebase p4/master. Es un poco más inteligente que eso, especialmente cuando se trabaja con múltiples ramas, pero esta es una buena aproximación.

Ahora nuestra historia es lineal nuevamente y estamos listos para enviar nuestros cambios de vuelta en Perforce. El comando git p4 submit intentará crear una nueva revisión de Perforce para cada commit de Git entre p4/master y master. Al ejecutarlo, nos deja en nuestro editor favorito, y los contenidos del archivo se ven algo así:

# A Perforce Change Specification.
#
#  Change:      The change number. 'new' on a new changelist.
#  Date:        The date this specification was last modified.
#  Client:      The client on which the changelist was created.  Read-only.
#  User:        The user who created the changelist.
#  Status:      Either 'pending' or 'submitted'. Read-only.
#  Type:        Either 'public' or 'restricted'. Default is 'public'.
#  Description: Comments about the changelist.  Required.
#  Jobs:        What opened jobs are to be closed by this changelist.
#               You may delete jobs from this list.  (New changelists only.)
#  Files:       What opened files from the default changelist are to be added
#               to this changelist.  You may delete files from this list.
#               (New changelists only.)

Change:  new

Client:  john_bens-mbp_8487

User: john

Status:  new

Description:
   Update link

Files:
   //depot/www/live/index.html   # edit


######## git author ben@straub.cc does not match your p4 account.
######## Use option --preserve-user to modify authorship.
######## Variable git-p4.skipUserNameCheck hides this message.
######## everything below this line is just the diff #######
--- //depot/www/live/index.html  2014-08-31 18:26:05.000000000 0000
+++ /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/index.html   2014-08-31 18:26:05.000000000 0000
@@ -60,7 +60,7 @@
 </td>
 <td valign=top>
 Source and documentation for
-<a href="http://www.perforce.com/jam/jam.html">
+<a href="jam.html">
 Jam/MR</a>,
 a software build tool.
 </td>

Esto es principalmente el mismo contenido que vería al ejecutar p4 submit, excepto las cosas al final que git-p4 ha incluido amablemente. Git-p4 intenta honrar su configuración de Git y Perforce individualmente cuando tiene que proporcionar un nombre para un commit o un conjunto de cambios, pero en algunos casos usted quiere anularla. Por ejemplo, si el commit de Git que está importando fue escrito por un colaborador que no tiene una cuenta de usuario de Perforce, es posible que aún quiera que el conjunto de cambios resultante tenga el aspecto de que lo escribió él (y no usted).

Git-p4 ha importado amablemente el mensaje de la confirmación de Git como el contenido de este conjunto de cambios de Perforce, por lo que todo lo que tenemos que hacer es guardar y salir, dos veces (una para cada confirmación). El resultante del shell será algo como esto:

$ git p4 submit
Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Synchronizing p4 checkout...
... - file(s) up-to-date.
Applying dbac45b Update link
//depot/www/live/index.html#4 - opened for edit
Change 12143 created with 1 open file(s).
Submitting change 12143.
Locking 1 files ...
edit //depot/www/live/index.html#5
Change 12143 submitted.
Applying 905ec6a Change page title
//depot/www/live/index.html#5 - opened for edit
Change 12144 created with 1 open file(s).
Submitting change 12144.
Locking 1 files ...
edit //depot/www/live/index.html#6
Change 12144 submitted.
All commits applied!
Performing incremental import into refs/remotes/p4/master git branch
Depot paths: //depot/www/live/
Import destination: refs/remotes/p4/master
Importing revision 12144 (100%)
Rebasing the current branch onto remotes/p4/master
First, rewinding head to replay your work on top of it...
$ git log --oneline --all --graph --decorate
* 775a46f (HEAD, p4/master, p4/HEAD, master) Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

El resultado es como si hubiéramos hecho un git push, que es la analogía más cercana a lo que realmente sucedió.

Tenga en cuenta que durante este proceso, cada commit de Git se convierte en un conjunto de cambios de Perforce; si desea aplastarlos en un único conjunto de cambios, puede hacerlo con una rebase interactiva antes de ejecutar git p4 submit. También tenga en cuenta que los hashes SHA-1 de todas las commits que se enviaron como conjuntos de cambios han cambiado; esto es porque git-p4 agrega una línea al final de cada confirmación que convierte:

$ git log -1
commit 775a46f630d8b46535fc9983cf3ebe6b9aa53145
Author: John Doe <john@example.com>
Date:   Sun Aug 31 10:31:44 2014 -0800

    Change page title

    [git-p4: depot-paths = "//depot/www/live/": change = 12144]

¿Qué sucede si intenta enviar una commit de fusión? Hagamos un intento. Esta es la situación en la que nos hemos metido:

$ git log --oneline --all --graph --decorate
* 3be6fd8 (HEAD, master) Correct email address
*   1dcbf21 Merge remote-tracking branch 'p4/master'
|\
| * c4689fc (p4/master, p4/HEAD) Grammar fix
* | cbacd0a Table borders: yes please
* | b4959b6 Trademark
|/
* 775a46f Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

La historia de Git y Perforce divergen después de 775a46f. El lado de Git tiene dos commits, luego un commit de fusión con la cabeza de Perforce, y luego otro commit. Vamos a tratar de enviar estos sobre un único conjunto de cambios en el lado de Perforce. Veamos qué pasaría si intentáramos enviar ahora:

$ git p4 submit -n
Perforce checkout for depot path //depot/www/live/ located at /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Would synchronize p4 checkout in /Users/ben/john_bens-mbp_8487/john_bens-mbp_8487/depot/www/live/
Would apply
  b4959b6 Trademark
  cbacd0a Table borders: yes please
  3be6fd8 Correct email address

La bandera -n es la abreviatura de --dry-run, que intenta informar qué pasaría si el comando submit se ejecutara de manera real. En este caso, parece que estaríamos creando tres conjuntos de cambios de Perforce, que corresponden a las tres confirmaciones que no se fusionan y que todavía no existen en el servidor Perforce. Eso suena exactamente como lo que queremos, veamos cómo resulta:

$ git p4 submit
[…]
$ git log --oneline --all --graph --decorate
* dadbd89 (HEAD, p4/master, p4/HEAD, master) Correct email address
* 1b79a80 Table borders: yes please
* 0097235 Trademark
* c4689fc Grammar fix
* 775a46f Change page title
* 05f1ade Update link
* 75cd059 Update copyright
* 70eaf78 Initial import of //depot/www/live/ from the state at revision #head

Nuestra historia se volvió lineal, como si hubiéramos vuelto a hacer rebase antes de enviar (que de hecho es exactamente lo que sucedió). Esto significa que puede ser libre para crear, trabajar, deshacer y fusionar ramas en el lado de Git sin miedo a que su historia se vuelva incompatible con Perforce. Si puede volver a establecerla, puede contribuir a un servidor de Perforce.

Ramificación

Si su proyecto Perforce tiene múltiples ramas, no está sin suerte; git-p4 puede manejar eso de una manera que lo haga sentir como Git. Digamos que su depósito Perforce se presenta así:

//depot
  └── project
      ├── main
      └── dev

Y digamos que tiene una rama dev, que tiene una especificación de vista que se ve así:

//depot/project/main/... //depot/project/dev/...

Git-p4 puede detectar automáticamente esa situación y hacer lo correcto:

$ git p4 clone --detect-branches //depot/project@all
Importing from //depot/project@all into project
Initialized empty Git repository in /private/tmp/project/.git/
Importing revision 20 (50%)
    Importing new branch project/dev

    Resuming with change 20
Importing revision 22 (100%)
Updated branches: main dev
$ cd project; git log --oneline --all --graph --decorate
* eae77ae (HEAD, p4/master, p4/HEAD, master) main
| * 10d55fb (p4/project/dev) dev
| * a43cfae Populate //depot/project/main/... //depot/project/dev/....
|/
* 2b83451 Project init

Tenga en cuenta el especificador “@all” en la ruta de depósito; eso le dice a git-p4 que clone no sólo el último conjunto de cambios para ese subárbol, sino todos los conjuntos de cambios que alguna vez hayan tocado esas rutas. Esto está más cerca del concepto de clon de Git, pero si está trabajando en un proyecto con una larga historia, podría llevar un tiempo.

La bandera --detect-branches le dice a git-p4 que use las especificaciones de rama de Perforce para asignar las ramas a las refs de Git. Si estas asignaciones no están presentes en el servidor Perforce (que es una forma perfectamente válida de usar Perforce), puede indicar a git-p4 cuáles son las asignaciones de bifurcación y obtendrá el mismo resultado:

$ git init project
Initialized empty Git repository in /tmp/project/.git/
$ cd project
$ git config git-p4.branchList main:dev
$ git clone --detect-branches //depot/project@all .

Estableciendo la variable de configuración git-p4.branchList en main:dev le dice a git-p4 que “main” y “dev” son ambas ramas, y la segunda es hija de la primera.

Si ahora aplicamos git checkout -b dev p4/project/dev y realizamos algunas commits, git-p4 es lo suficientemente inteligente para apuntar a la rama correcta cuando hacemos git p4 submit. Desafortunadamente, git-p4 no puede mezclar clones superficiales y ramas múltiples; si tiene un gran proyecto y quiere trabajar en más de una rama, tendrá que hacer git p4 clone una vez por cada rama a la que quiera enviar.

Para crear o integrar ramas, deberá usar un cliente Perforce. git-p4 sólo puede sincronizar y enviar a las ramas existentes, y sólo puede hacerlo sobre un conjunto de cambios lineal a la vez. Si combina dos ramas en Git e intenta enviar el nuevo conjunto de cambios, todo lo que se registrará será un conjunto de cambios de archivos; los metadatos sobre qué ramas están involucradas en la integración se perderán.

Resumen de Git y Perforce

git-p4 hace posible usar un flujo de trabajo de Git con un servidor Perforce, y es bastante bueno en eso. Sin embargo, es importante recordar que Perforce está a cargo de la fuente y que sólo está usando Git para trabajar localmente. Tenga mucho cuidado al compartir commits de Git; si tiene un control remoto que utilizan otras personas, no envíe ningun commit que aún no se haya enviado al servidor de Perforce.

Si desea mezclar libremente el uso de Perforce y Git como clientes para el control de código fuente, y puede convencer al administrador del servidor para que lo instale, Git Fusion hace que Git sea un cliente de control de versiones de primera clase para un servidor Perforce.

Git y TFS

Git se está volviendo popular entre los desarrolladores de Windows, y si estás escribiendo códigos en Windows, hay muchas posibilidades de que estés usando Team Foundation Server (TFS) de Microsoft. TFS es un paquete de colaboración que incluye seguimiento de defectos y elementos de trabajo, soporte de procesos para Scrum y otros, revisión de código y control de versiones. Hay un poco de confusión por delante: * TFS * es el servidor, que admite controlar el código fuente utilizando tanto Git como su propio VCS personalizado, al que han denominado * TFVC * (Team Foundation Version Control). El soporte de Git es una característica algo nueva para TFS (envío con la versión de 2013), por lo que todas las herramientas anteriores a eso se refieren a la porción de control de versión como ‘` TFS '’, aunque en su mayoría funcionan con TFVC.

Si te encuentras en un equipo que usa TFVC pero prefieres usar Git como su cliente de control de versiones, hay un proyecto para ti.

Cuál herramienta

De hecho, hay dos: git-tf y git-tfs.

git-tfs (alojado en https://github.com/git-tfs/git-tfs []) es un proyecto .NET, y (al momento de escribir esto) solo se ejecuta en Windows. Para trabajar con repositorios Git, utiliza los enlaces .NET para libgit2, una implementación de Git orientada a la biblioteca que es altamente eficiente y permite mucha flexibilidad con las agallas de un repositorio Git. Libgit2 no es una implementación completa de Git, por lo que para cubrir la diferencia, git-tfs realmente llamará al cliente Git de la línea de comandos para algunas operaciones, por lo que no hay límites artificiales sobre lo que puede hacer con los repositorios Git. Su compatibilidad con las características de TFVC es muy madura, ya que utiliza los ensamblados de Visual Studio para operaciones con servidores. Esto significa que necesitará acceso a esos ensamblados, lo que significa que necesita instalar una versión reciente de Visual Studio (cualquier edición desde la versión 2010, incluido Express desde la versión 2012) o el SDK de Visual Studio.

git-tf (cuyo alojamiento se encuentra en https://gittf.codeplex.com []) es un proyecto de Java y, como tal, se ejecuta en cualquier computadora con un entorno de tiempo de ejecución de Java. Interactúa con los repositorios de Git a través de JGit (una implementación JVM de Git), lo que significa que prácticamente no tiene limitaciones en términos de funciones de Git. Sin embargo, su soporte para TFVC es limitado en comparación con git-tfs. Por ejemplo, no admite ramas.

Entonces, cada herramienta tiene ventajas y desventajas, y hay muchas situaciones que favorecen a una sobre la otra. Cubriremos el uso básico de ambas en este libro.

Nota

Necesitará acceder a un repositorio basado en TFVC para seguir estas instrucciones. Estos no son tan abundantes en la naturaleza como los repositorios de Git o Subversion, por lo que puede necesitar crear uno propio. Codeplex (https://www.codeplex.com []) o Visual Studio Online (http://www.visualstudio.com []) son buenas opciones para esto.

Comenzando con: git-tf

Lo primero que haces, al igual que con cualquier proyecto de Git, es clonar. Esto es lo que parece con git-tf:

$ git tf clone https://tfs.codeplex.com:443/tfs/TFS13 $/myproject/Main project_git

El primer argumento es la URL de una colección TFVC, el segundo es de la forma $ / project / branch, y el tercero es la ruta al repositorio Git local que se va a crear (este último es opcional). git-tf sólo puede funcionar con una rama a la vez; si quieres hacer checkins en una rama diferente de TFVC, tendrás que hacer un nuevo clon desde esa rama.

Esto crea un repositorio de Git completamente funcional:

$ cd project_git
$ git log --all --oneline --decorate
512e75a (HEAD, tag: TFS_C35190, origin_tfs/tfs, master) Checkin message

Esto se denomina clon shallow, lo que significa que sólo se ha descargado el último conjunto de cambios. TFVC no está diseñado para que cada cliente tenga una copia completa del historial, por lo que git-tf usa de manera predeterminada la última versión, que es mucho más rápida.

Si tienes algo de tiempo, probablemente valga la pena clonar todo el historial del proyecto, usando la opción --deep:

$ git tf clone https://tfs.codeplex.com:443/tfs/TFS13 $/myproject/Main \
  project_git --deep
Username: domain\user
Password:
Connecting to TFS...
Cloning $/myproject into /tmp/project_git: 100%, done.
Cloned 4 changesets. Cloned last changeset 35190 as d44b17a
$ cd project_git
$ git log --all --oneline --decorate
d44b17a (HEAD, tag: TFS_C35190, origin_tfs/tfs, master) Goodbye
126aa7b (tag: TFS_C35189)
8f77431 (tag: TFS_C35178) FIRST
0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
        Team Project Creation Wizard

Observa las etiquetas con nombres como TFS_C35189; esta es una característica que te ayuda a saber qué compromisos de Git están asociados con los conjuntos de cambios de TFVC. Esta es una buena forma de representarlo, ya que puedes ver con un comando de registro simple cuál de tus confirmaciones está asociada con una instantánea que también existe en TFVC. No son necesarios (y, de hecho, puedes desactivarlos con git config git-tf.tag false) - git-tf conserva las asignaciones reales commit-changeset en el archivo .git / git-tf.

Comenzando: git-tfs

La clonación de git-tfs se comporta de forma un poco diferente. Observe:

PS> git tfs clone --with-branches \
    https://username.visualstudio.com/DefaultCollection \
    $/project/Trunk project_git
Initialized empty Git repository in C:/Users/ben/project_git/.git/
C15 = b75da1aba1ffb359d00e85c52acb261e4586b0c9
C16 = c403405f4989d73a2c3c119e79021cb2104ce44a
Tfs branches found:
- $/tfvc-test/featureA
The name of the local branch will be : featureA
C17 = d202b53f67bde32171d5078968c644e562f1c439
C18 = 44cd729d8df868a8be20438fdeeefb961958b674

Observa el indicador --with-branches. git-tfs es capaz de mapear ramas de TFVC a ramas de Git, y este indicador le dice que configure una rama local de Git para cada rama de TFVC. Esto es muy recomendable si alguna vez se ha bifurcado o se ha fusionado en TFS, pero no funcionará con un servidor anterior a TFS 2010; antes de esa versión, ‘` branches '’ eran sólo carpetas, por lo que `git-tfs no puede hacer esto con las carpetas regulares.

Echemos un vistazo al repositorio Git resultante:

PS> git log --oneline --graph --decorate --all
* 44cd729 (tfs/featureA, featureA) Goodbye
* d202b53 Branched from $/tfvc-test/Trunk
* c403405 (HEAD, tfs/default, master) Hello
* b75da1a New project
PS> git log -1
commit c403405f4989d73a2c3c119e79021cb2104ce44a
Author: Ben Straub <ben@straub.cc>
Date:   Fri Aug 1 03:41:59 2014 +0000

    Hello

    git-tfs-id: [https://username.visualstudio.com/DefaultCollection]$/myproject/Trunk;C16

Hay dos ramas locales, master y` featuresS`, que representan el punto inicial del clon (Trunk en TFVC) y una rama secundaria (` featureS` en TFVC). También puedes ver que el tfs` remote '' también tiene un par de referencias: `default y featureA, que representan las ramas de TFVC. git-tfs mapea la rama desde la que clonaste a tfs / default, y otras obtienen sus propios nombres.

Otra cosa a notar es las líneas git-tfs-id: en los mensajes de confirmación. En lugar de etiquetas, git-tfs usa estos marcadores para relacionar los conjuntos de cambios de TFVC con las confirmaciones de Git. Esto tiene la consecuencia de que tus confirmaciones de Git tendrán un hash SHA-1 diferente antes y después de que se hayan enviado a TFVC.

Git-tf [s] Flujo de trabajo

Nota

Independientemente de la herramienta que estés utilizando, debes establecer un par de valores de configuración de Git para evitar problemas.

$ git config set --local core.ignorecase=true
$ git config set --local core.autocrlf=false

Lo siguiente que querrás hacer es trabajar en el proyecto. TFVC y TFS tienen varias características que pueden agregar complejidad a tu flujo de trabajo:

  1. Las ramas de características que no están representadas en TFVC agregan un poco de complejidad. Esto tiene que ver con las * muy * diferentes formas en que TFVC y Git representan las ramas.

  2. Ten en cuenta que TFVC permite a los usuarios "verificar" los archivos del servidor, bloqueándolos para que nadie más pueda editarlos. Obviamente, esto no te impedirá editarlos en tu repositorio local, pero podría interferir cuando llegue el momento de enviar tus cambios al servidor TFVC.

  3. TFS tiene el concepto de comprobaciones "compuertas", donde un ciclo de prueba de compilación TFS debe completarse satisfactoriamente antes de permitir el registro. Utiliza la función ‘` shelve '’ en TFVC, que no cubrimos en detalle aquí. Puedes falsificar esto de forma manual con git-tf, y git-tfs proporciona el comando checkintool que es sensible a la puerta.

En aras de la brevedad, lo que trataremos aquí es el camino feliz, qué pasos laterales seguir para evitar la mayoría de estos problemas.

Flujo de trabajo del: git-tf

Digamos que has hecho algo de trabajo, has hecho un par de confirmaciones de Git en master y estás listo para compartir tu progreso en el servidor de TFVC. Aquí está nuestro repositorio de Git:

$ git log --oneline --graph --decorate --all
* 4178a82 (HEAD, master) update code
* 9df2ae3 update readme
* d44b17a (tag: TFS_C35190, origin_tfs/tfs) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
          Team Project Creation Wizard

Queremos tomar la instantánea que está en la confirmación 4178a82 y subirla al servidor TFVC. Lo primero es lo primero: veamos si alguno de nuestros compañeros de equipo hizo algo desde la última vez que nos conectamos:

$ git tf fetch
Username: domain\user
Password:
Connecting to TFS...
Fetching $/myproject at latest changeset: 100%, done.
Downloaded changeset 35320 as commit 8ef06a8. Updated FETCH_HEAD.
$ git log --oneline --graph --decorate --all
* 8ef06a8 (tag: TFS_C35320, origin_tfs/tfs) just some text
| * 4178a82 (HEAD, master) update code
| * 9df2ae3 update readme
|/
* d44b17a (tag: TFS_C35190) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
          Team Project Creation Wizard

Parece que alguien más está trabajando también, y ahora tenemos una historia divergente. Aquí es donde brilla Git, pero tenemos dos opciones de cómo proceder:

  1. Hacer una confirmación de fusión se siente natural como un usuario de Git (después de todo, eso es lo que hace git pull), y git-tf puede hacer esto por ti con un simple git tf pull. Ten en cuenta, sin embargo, que TFVC no piensa de esta manera, y si empujas la fusión se comprometerá y tu historia comenzará a verse diferente en ambos lados, lo que puede ser confuso. Sin embargo, si planeas enviar todos tus cambios como un solo conjunto de cambios, esta es probablemente la opción más fácil.

  2. Rebasing hace que nuestro historial de compromisos sea lineal, lo que significa que tenemos la opción de convertir cada una de nuestras confirmaciones de Git en un conjunto de cambios de TFVC. Como esto deja la mayoría de las opciones abiertas, te recomendamos que lo hagas de esta manera; git-tf incluso te lo facilita con git tf pull --rebase.

La decisión es tuya: Para este ejemplo, vamos a rebasar:

$ git rebase FETCH_HEAD
First, rewinding head to replay your work on top of it...
Applying: update readme
Applying: update code
$ git log --oneline --graph --decorate --all
* 5a0e25e (HEAD, master) update code
* 6eb3eb5 update readme
* 8ef06a8 (tag: TFS_C35320, origin_tfs/tfs) just some text
* d44b17a (tag: TFS_C35190) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
          Team Project Creation Wizard

Ahora estamos listos para hacer una comprobación en el servidor de TFVC. git-tf te da la opción de hacer un único conjunto de cambios que represente todos los cambios desde el último (--shallow, que es el predeterminado) y crear un nuevo conjunto de cambios para cada confirmación de Git (` --deep`). Para este ejemplo, crearemos un solo conjunto de cambios:

$ git tf checkin -m 'Updating readme and code'
Username: domain\user
Password:
Connecting to TFS...
Checking in to $/myproject: 100%, done.
Checked commit 5a0e25e in as changeset 35348
$ git log --oneline --graph --decorate --all
* 5a0e25e (HEAD, tag: TFS_C35348, origin_tfs/tfs, master) update code
* 6eb3eb5 update readme
* 8ef06a8 (tag: TFS_C35320) just some text
* d44b17a (tag: TFS_C35190) Goodbye
* 126aa7b (tag: TFS_C35189)
* 8f77431 (tag: TFS_C35178) FIRST
* 0745a25 (tag: TFS_C35177) Created team project folder $/tfvctest via the \
          Team Project Creation Wizard

Hay una nueva etiqueta TFS_C35348, que indica que TFVC está almacenando la misma instantánea exacta que la confirmación` 5a0e25e`. Es importante tener en cuenta que no todas las confirmaciones de Git deben tener una contraparte exacta en TFVC; el compromiso 6eb3eb5, por ejemplo, no existe en ninguna parte del servidor.

Ese es el flujo de trabajo principal. Hay un par de otras consideraciones que querrás tener en cuenta:

  • No hay ramificación. git-tf sólo puede crear repositorios Git de una rama TFVC a la vez.

  • Colabora con TFVC o Git, pero no con ambos. Los diferentes clones de `ngit-tf`n del mismo repositorio de TFVC pueden tener diferentes hash de confirmación de SHA-1, lo que provocará innumerables dolores de cabeza.

  • Si el flujo de trabajo de tu equipo incluye la colaboración en Git y la sincronización periódica con TFVC, solo conéctate a TFVC con uno de los repositorios de Git.

Flujo de trabajo: git-tfs

Veamos el mismo escenario usando git-tfs. Aquí están las nuevas confirmaciones que hemos realizado en la rama master en nuestro repositorio de Git:

PS> git log --oneline --graph --all --decorate
* c3bd3ae (HEAD, master) update code
* d85e5a2 update readme
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 (tfs/default) Hello
* b75da1a New project

Ahora veamos si alguien más ha hecho un trabajo mientras estábamos hackeando:

PS> git tfs fetch
C19 = aea74a0313de0a391940c999e51c5c15c381d91d
PS> git log --all --oneline --graph --decorate
* aea74a0 (tfs/default) update documentation
| * c3bd3ae (HEAD, master) update code
| * d85e5a2 update readme
|/
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

Sí, resulta que nuestro compañero de trabajo ha agregado un nuevo conjunto de cambios de TFVC, que se muestra como el nuevo compromiso aea74a0, y la rama remota tfs / default se ha movido.

Al igual que con git-tf, tenemos dos opciones fundamentales sobre cómo resolver esta historia divergente:

  1. Rebase para preservar una historia lineal.

  2. Fusión para preservar lo que realmente sucedió.

En este caso, haremos un 'checkin profundo ', donde cada confirmación de Git se convierte en un conjunto de cambios de TFVC, por lo que queremos volver a establecer la base.

PS> git rebase tfs/default
First, rewinding head to replay your work on top of it...
Applying: update readme
Applying: update code
PS> git log --all --oneline --graph --decorate
* 10a75ac (HEAD, master) update code
* 5cec4ab update readme
* aea74a0 (tfs/default) update documentation
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

Ahora estamos listos para completar nuestra contribución al registrar nuestro código en el servidor TFVC. Usaremos el comando rcheckin aquí para crear un conjunto de cambios TFVC para cada commit de Git en la ruta de HEAD a la primera rama remota tfs encontrada (el comando checkin sólo crearía un conjunto de cambios, más o menos como aplastar cuando Git se compromete).

PS> git tfs rcheckin
Working with tfs remote: default
Fetching changes from TFS to minimize possibility of late conflict...
Starting checkin of 5cec4ab4 'update readme'
 add README.md
C20 = 71a5ddce274c19f8fdc322b4f165d93d89121017
Done with 5cec4ab4b213c354341f66c80cd650ab98dcf1ed, rebasing tail onto new TFS-commit...
Rebase done successfully.
Starting checkin of b1bf0f99 'update code'
 edit .git\tfs\default\workspace\ConsoleApplication1/ConsoleApplication1/Program.cs
C21 = ff04e7c35dfbe6a8f94e782bf5e0031cee8d103b
Done with b1bf0f9977b2d48bad611ed4a03d3738df05ea5d, rebasing tail onto new TFS-commit...
Rebase done successfully.
No more to rcheckin.
PS> git log --all --oneline --graph --decorate
* ff04e7c (HEAD, tfs/default, master) update code
* 71a5ddc update readme
* aea74a0 update documentation
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

Observa cómo después de cada registro exitoso en el servidor TFVC, git-tfs vuelve a basar el trabajo restante en lo que acaba de hacer. Esto se debe a que está agregando el campo git-tfs-id al final de los mensajes de confirmación, lo que cambia los valores hash SHA-1. Esto es exactamente como se diseñó, y no hay nada de qué preocuparse, pero debes saber que está sucediendo, especialmente si compartes commits con otros.

TFS tiene muchas características que se integran con tu sistema de control de versiones, como elementos de trabajo, revisores designados, registros bloqueados, etc. Puede ser engorroso trabajar con estas características usando sólo una herramienta de línea de comandos, pero afortunadamente git-tfs te permite iniciar una herramienta gráfica de registro muy fácilmente:

PS> git tfs checkintool
PS> git tfs ct

Se parece un poco a esto:

The git-tfs checkin tool.
Figura 148. La herramienta de registro de git-tfs.

Esto resultará familiar para los usuarios de TFS, ya que es el mismo diálogo que se inicia desde Visual Studio.

git-tfs también te permite controlar ramas de TFVC desde tu repositorio de Git. Como ejemplo, creemos una:

PS> git tfs branch $/tfvc-test/featureBee
The name of the local branch will be : featureBee
C26 = 1d54865c397608c004a2cadce7296f5edc22a7e5
PS> git log --oneline --graph --decorate --all
* 1d54865 (tfs/featureBee) Creation branch $/myproject/featureBee
* ff04e7c (HEAD, tfs/default, master) update code
* 71a5ddc update readme
* aea74a0 update documentation
| * 44cd729 (tfs/featureA, featureA) Goodbye
| * d202b53 Branched from $/tfvc-test/Trunk
|/
* c403405 Hello
* b75da1a New project

Crear una rama en TFVC significa agregar un conjunto de cambios donde esa rama ahora existe, y esto se proyecta como una confirmación de Git. Ten en cuenta que git-tfs también creó la rama remota tfs / featureBee, pero` HEAD` todavía apunta a master. Si deseas trabajar en la rama recién acuñada, querrás basar tus nuevas confirmaciones en la confirmación 1d54865, tal vez creando una rama de tema a partir de esa confirmación.

Resumen de Git y TFS

git-tf y git-tfs son excelentes herramientas para interactuar con un servidor TFVC. Te permiten usar el poder de Git localmente, evitar tener que realizar un viaje de ida y vuelta al servidor central de TFVC, haciendo que tu vida como desarrollador sea mucho más fácil, sin forzar a todo tu equipo a migrar a Git. Si estás trabajando en Windows (lo cual es probable si tu equipo está usando TFS), quizás quieras usar git-tfs, ya que su conjunto de características es más completo, pero si estás trabajando en otra plataforma, puedes usar git-tf, aunque es más limitado. Al igual que con la mayoría de las herramientas de este capítulo, debes elegir uno de estos sistemas de control de versiones para que sea canónico y usar el otro de forma subordinada: Git o TFVC deberían ser el centro de colaboración, pero no ambos.

scroll-to-top