Git y Mercurial: comparar y contrastar
Desde hace un tiempo uso subversion para mis proyectos personales.
Cada vez escucho más cosas maravillosas sobre Git y Mercurial, y DVCS en general.
Me gustaría probar todo el asunto de DVCS, pero no estoy muy familiarizado con ninguna de las opciones.
¿Cuáles son algunas de las diferencias entre Mercurial y Git?
Nota: No estoy tratando de descubrir cuál es "mejor" o incluso con cuál debería empezar. Principalmente busco áreas clave en las que son similares y en las que son diferentes, porque me interesa saber en qué se diferencian en términos de implementación y filosofía.
Descargo de responsabilidad: uso Git, sigo el desarrollo de Git en la lista de correo de git e incluso contribuyo un poco a Git (principalmente gitweb). Conozco Mercurial por la documentación y algo por la discusión en el canal IRC #revctrl en FreeNode.
Gracias a todas las personas del canal IRC #mercurial que brindaron ayuda sobre Mercurial para este artículo.
Resumen
Aquí sería bueno tener alguna sintaxis para la tabla, algo así como en PHPMarkdown/MultiMarkdown/Maruku extensión de Markdown
- Estructura del repositorio: Mercurial no permite fusiones de octopus (con más de dos padres), ni etiquetar objetos que no sean de compromiso.
- Etiquetas: Mercurial utiliza
.hgtags
archivos versionados con reglas especiales para etiquetas por repositorio y también admite etiquetas locales en.hg/localtags
; En Git, las etiquetas son referencias que residen enrefs/tags/
el espacio de nombres y, de forma predeterminada, se siguen automáticamente al recuperarlas y requieren una inserción explícita. - Ramas: En Mercurial el flujo de trabajo básico se basa en cabezas anónimas ; Git utiliza ramas ligeras con nombre y tiene un tipo especial de ramas ( ramas de seguimiento remoto ) que siguen a las ramas en el repositorio remoto.
- Nombres y rangos de revisiones: Mercurial proporciona números de revisión , locales al repositorio, y basa las revisiones relativas (contando desde la punta, es decir, la rama actual) y los rangos de revisión en esta numeración local ; Git proporciona una manera de hacer referencia a la revisión en relación con la punta de la rama, y los rangos de revisión son topológicos (basados en un gráfico de revisiones)
- Mercurial usa seguimiento de cambios de nombre , mientras que Git usa detección de cambios de nombre para manejar los cambios de nombre de archivos
- Red: Mercurial admite protocolos "inteligentes" SSH y HTTP, y protocolo HTTP estático; El Git moderno admite los protocolos "inteligentes" SSH, HTTP y GIT, y el protocolo "tonto" HTTP(S). Ambos tienen soporte para archivos de paquetes para transporte fuera de línea.
- Mercurial utiliza extensiones (complementos) y API establecidas; Git tiene capacidad de programación y formatos establecidos.
Hay algunas cosas que diferencian a Mercurial de Git, pero hay otras que los hacen similares. Ambos proyectos toman ideas mutuas. Por ejemplo, hg bisect
el comando en Mercurial (anteriormente extensión bisect ) se inspiró en git bisect
el comando en Git, mientras que la idea de git bundle
se inspiró en hg bundle
.
Estructura del repositorio, almacenamiento de revisiones.
En Git hay cuatro tipos de objetos en su base de datos de objetos: objetos blob que contienen el contenido de un archivo, objetos de árbol jerárquico que almacenan la estructura de directorios, incluidos los nombres de los archivos y las partes relevantes de los permisos de los archivos (el permiso ejecutable para los archivos es un enlace simbólico). , objeto de confirmación que contiene información de autoría, puntero a una instantánea del estado del repositorio en la revisión representada por una confirmación (a través de un objeto de árbol del directorio superior del proyecto) y referencias a cero o más confirmaciones principales, y etiquetar objetos que hacen referencia a otros objetos y pueden firmarse mediante PGP/GPG.
Git usa dos formas de almacenar objetos: formato suelto , donde cada objeto se almacena en un archivo separado (esos archivos se escriben una vez y nunca se modifican), y formato empaquetado donde muchos objetos se almacenan comprimidos en delta en un solo archivo. La atomicidad de las operaciones la proporciona el hecho de que la referencia a un nuevo objeto se escribe (atómicamente, usando el truco crear + cambiar nombre) después de escribir un objeto.
Los repositorios de Git requieren un mantenimiento periódico git gc
(para reducir el espacio en disco y mejorar el rendimiento), aunque hoy en día Git lo hace automáticamente. (Este método proporciona una mejor compresión de los repositorios).
Mercurial (hasta donde yo lo entiendo) almacena el historial de un archivo en un registro de archivos (junto, creo, con metadatos adicionales como el seguimiento de cambios de nombre y alguna información de ayuda); utiliza una estructura plana llamada manifiesto para almacenar la estructura del directorio y una estructura llamada registro de cambios que almacena información sobre conjuntos de cambios (revisiones), incluido el mensaje de confirmación y cero, uno o dos padres.
Mercurial utiliza el diario de transacciones para proporcionar atomicidad de las operaciones y se basa en truncar archivos para limpiarlos después de una operación fallida o interrumpida. Los revlogs solo se pueden agregar.
Al observar la estructura del repositorio en Git versus Mercurial, se puede ver que Git se parece más a una base de datos de objetos (o un sistema de archivos dirigido a contenido), y Mercurial se parece más a una base de datos relacional de campo fijo tradicional.
Diferencias:
En Git los objetos del árbol forman una estructura jerárquica ; en el archivo de manifiesto Mercurial es una estructura plana . En Git blob object almacena una versión del contenido de un archivo; en Mercurial filelog almacena el historial completo de un solo archivo (si no tomamos en cuenta aquí las complicaciones con los cambios de nombre). Esto significa que hay diferentes áreas de operaciones donde Git sería más rápido que Mercurial, todas las demás cosas se consideran iguales (como fusiones o mostrar el historial de un proyecto) y áreas donde Mercurial sería más rápido que Git (como aplicar parches o mostrar historial de un solo archivo). Es posible que este problema no sea importante para el usuario final.
Debido a la estructura de registro fijo de la estructura del registro de cambios de Mercurial , las confirmaciones en Mercurial sólo pueden tener hasta dos padres ; Las confirmaciones en Git pueden tener más de dos padres (lo que se denomina "fusión de pulpo"). Si bien (en teoría) puedes reemplazar la combinación de octopus por una serie de combinaciones de dos padres, esto podría causar complicaciones al realizar la conversión entre repositorios Mercurial y Git.
Hasta donde yo sé, Mercurial no tiene equivalentes de etiquetas anotadas (objetos de etiqueta) de Git. Un caso especial de etiquetas anotadas son las etiquetas firmadas (con firma PGP/GPG); El equivalente en Mercurial se puede hacer usando GpgExtension , cuya extensión se distribuye junto con Mercurial. No se pueden etiquetar objetos no comprometidos en Mercurial como se puede hacer en Git, pero creo que eso no es muy importante (algunos repositorios de git usan blob etiquetados para distribuir la clave PGP pública para verificar las etiquetas firmadas).
Referencias: ramas y etiquetas.
En Git, las referencias (ramas, ramas de seguimiento remoto y etiquetas) residen fuera del DAG de las confirmaciones (como deberían). Las referencias en refs/heads/
el espacio de nombres ( ramas locales ) apuntan a confirmaciones y, por lo general, se actualizan mediante "git commit"; apuntan a la punta (cabeza) de la rama, de ahí ese nombre. Las referencias en refs/remotes/<remotename>/
el espacio de nombres ( ramas de seguimiento remoto ) apuntan a la confirmación, siguen las ramas en el repositorio remoto <remotename>
y se actualizan mediante "git fetch" o equivalente. Las referencias en refs/tags/
el espacio de nombres ( etiquetas ) generalmente apuntan a confirmaciones (etiquetas livianas) u objetos de etiquetas (etiquetas anotadas y firmadas) y no están destinadas a cambios.
Etiquetas
En Mercurial puedes dar un nombre persistente a la revisión usando la etiqueta ; Las etiquetas se almacenan de manera similar a los patrones de ignorar. Significa que las etiquetas visibles globalmente se almacenan en .hgtags
un archivo controlado por revisión en su repositorio. Esto tiene dos consecuencias: primero, Mercurial tiene que usar reglas especiales para este archivo para obtener la lista actual de todas las etiquetas y actualizar dicho archivo (por ejemplo, lee la revisión confirmada más reciente del archivo, la versión no desprotegida actualmente); en segundo lugar, debe confirmar los cambios en este archivo para que la nueva etiqueta sea visible para otros usuarios/otros repositorios (hasta donde tengo entendido).
Mercurial también admite etiquetas locales , almacenadas en hg/localtags
, que no son visibles para otros (y, por supuesto, no son transferibles)
En Git, las etiquetas son referencias con nombre fijas (constantes) a otros objetos (normalmente objetos de etiquetas, que a su vez apuntan a confirmaciones) almacenados en el refs/tags/
espacio de nombres. De forma predeterminada, al buscar o enviar un conjunto de revisiones, git automáticamente busca o envía etiquetas que apuntan a revisiones que se están recuperando o enviando. Sin embargo, puedes controlar hasta cierto punto qué etiquetas se obtienen o se envían.
Git trata las etiquetas ligeras (que apuntan directamente a las confirmaciones) y las etiquetas anotadas (que apuntan a objetos de etiquetas, que contienen mensajes de etiquetas que incluyen opcionalmente la firma PGP, que a su vez apuntan a las confirmaciones) de manera ligeramente diferente; por ejemplo, de forma predeterminada, solo considera las etiquetas anotadas cuando describe se confirma usando "git describe".
Git no tiene un equivalente estricto de etiquetas locales en Mercurial. Sin embargo, las mejores prácticas de git recomiendan configurar un repositorio público separado, en el que se insertan los cambios listos y desde el cual otros clonan y recuperan. Esto significa que las etiquetas (y ramas) que no envía son privadas para su repositorio. Por otro lado, también puede utilizar espacios de nombres distintos de heads
, remotes
o tags
, por ejemplo, local-tags
para etiquetas locales.
Opinión personal: en mi opinión, las etiquetas deberían residir fuera del gráfico de revisión, ya que son externas a él (son punteros al gráfico de revisiones). Las etiquetas no deben tener versiones, pero deben ser transferibles. La elección de Mercurial de utilizar un mecanismo similar al de ignorar archivos significa que tiene que tratarlo .hgtags
de manera especial (el archivo en el árbol es transferible, pero normal tiene una versión), o tener etiquetas que son solo locales ( .hg/localtags
no tiene versión, pero intransferible).
Sucursales
En Git, la rama local (punta de rama o encabezado de rama) es una referencia con nombre a una confirmación, donde se pueden generar nuevas confirmaciones. Rama también puede significar una línea activa de desarrollo, es decir, todas las confirmaciones accesibles desde la punta de la rama. Las sucursales locales residen en refs/heads/
el espacio de nombres, por lo que, por ejemplo, el nombre completo de la sucursal 'maestra' es 'refs/heads/master'.
La rama actual en Git (es decir, la rama extraída y la rama donde irá la nueva confirmación) es la rama a la que hace referencia la referencia HEAD. Se puede hacer que HEAD apunte directamente a una confirmación, en lugar de ser una referencia simbólica; esta situación de estar en una rama anónima sin nombre se llama HEAD separado ("rama git" muestra que estás en '(sin rama)').
En Mercurial hay ramas anónimas (cabezas de rama) y se pueden usar marcadores (a través de la extensión de marcador ). Dichas ramas de marcadores son puramente locales y esos nombres (hasta la versión 1.6) no eran transferibles usando Mercurial. Puede utilizar rsync o scp para copiar el .hg/bookmarks
archivo a un repositorio remoto. También puede utilizar hg id -r <bookmark> <url>
para obtener la identificación de revisión de una sugerencia actual de un marcador.
Desde 1.6 los marcadores se pueden empujar/tirar. La página BookmarksExtension tiene una sección sobre Trabajar con repositorios remotos . Hay una diferencia en que en Mercurial los nombres de los marcadores son globales , mientras que la definición de 'remoto' en Git describe también el mapeo de nombres de sucursales desde los nombres en el repositorio remoto a los nombres de las sucursales locales de seguimiento remoto; por ejemplo, refs/heads/*:refs/remotes/origin/*
el mapeo significa que se puede encontrar el estado de la rama 'maestra' ('refs/heads/master') en el repositorio remoto en la rama de seguimiento remoto 'origin/master' ('refs/remotes/origin/master').
Mercurial también tiene las llamadas ramas con nombre , donde el nombre de la rama está incrustado en una confirmación (en un conjunto de cambios). Dicho nombre es global (transferido al recuperarlo). Esos nombres de sucursales se registran permanentemente como parte de los metadatos del conjunto de cambios. Con Mercurial moderno puedes cerrar la "rama nombrada" y dejar de registrar el nombre de la rama. En este mecanismo las puntas de las ramas se calculan sobre la marcha.
En mi opinión, las "ramas con nombre" de Mercurial deberían llamarse etiquetas de confirmación , porque es lo que son. Hay situaciones en las que la "rama con nombre" puede tener varios consejos (múltiples confirmaciones sin hijos) y también puede constar de varias partes separadas del gráfico de revisiones.
No existe un equivalente de esas "ramas integradas" de Mercurial en Git; Además, la filosofía de Git es que, si bien se puede decir que una rama incluye alguna confirmación, eso no significa que una confirmación pertenezca a alguna rama.
Tenga en cuenta que la documentación de Mercurial todavía propone usar clones separados (repositorios separados) al menos para ramas de larga duración (una sola rama por flujo de trabajo de repositorio), también conocida como ramificación por clonación .
Ramas en empuje
Mercurial por defecto empuja todas las cabezas . Si desea empujar una sola rama ( cabeza única ), debe especificar la revisión de la punta de la rama que desea empujar. Puede especificar la sugerencia de rama por su número de revisión (local al repositorio), por identificador de revisión, por nombre de marcador (local al repositorio, no se transfiere) o por nombre de rama incrustada (rama con nombre).
Hasta donde yo lo entiendo, si envía una variedad de revisiones que contienen confirmaciones marcadas como en alguna "rama con nombre" en el lenguaje Mercurial, tendrá esta "rama con nombre" en el repositorio al que envía. Esto significa que los nombres de dichas ramas integradas ("ramas con nombre") son globales (con respecto a los clones de un repositorio/proyecto determinado).
De forma predeterminada (sujeto a push.default
la variable de configuración) "git push" o "git push <remote> " Git enviaría ramas coincidentes , es decir, solo aquellas ramas locales que ya tienen su equivalente presente en el repositorio remoto al que ingresa. Puede usar --all
la opción git-push ("git push --all") para enviar todas las ramas , puede usar "git push <remoto> <rama> " para enviar una sola rama determinada y puede usar "git push < remoto > HEAD" para empujar la rama actual .
Todo lo anterior supone que Git no está configurado qué ramas enviar mediante remote.<remotename>.push
variables de configuración.
Ramas en la búsqueda
Nota: aquí uso la terminología de Git donde "buscar" significa descargar cambios desde un repositorio remoto sin integrar esos cambios con el trabajo local. Esto es lo que hacen " git fetch
" y " hg pull
".
Si lo entiendo correctamente, de forma predeterminada, Mercurial recupera todos los encabezados del repositorio remoto, pero puede especificar la rama a buscar mediante " hg pull --rev <rev> <url>
" o " hg pull <url>#<rev>
" para obtener una sola rama . Puede especificar <rev> utilizando el identificador de revisión, el nombre de "rama nombrada" (rama incrustada en el registro de cambios) o el nombre del marcador. Sin embargo, el nombre del marcador (al menos actualmente) no se transfiere. Todas las revisiones de "ramas nombradas" que reciba deben transferirse. "hg pull" almacena puntas de ramas que recuperó como cabezas anónimas y sin nombre.
En Git de forma predeterminada (para el control remoto 'origen' creado por "git clone" y para los controles remotos creados usando "git remote add") " " git fetch
(o " git fetch <remote>
") obtiene todas las ramas del repositorio remoto (desde el espacio de refs/heads/
nombres) y las almacena en refs/remotes/
espacio de nombres. Esto significa, por ejemplo, que la rama denominada 'maestro' (nombre completo: 'refs/heads/master') en el 'origen' remoto se almacenaría (guardaría) como rama de seguimiento remoto 'origen/maestro' (nombre completo: 'refs/ remotos/origen/maestro').
Puede recuperar una sola rama en Git usando git fetch <remote> <branch>
: Git almacenaría las ramas solicitadas en FETCH_HEAD, que es algo similar a las cabezas sin nombre de Mercurial.
Esos son solo ejemplos de casos predeterminados de poderosa sintaxis Git de refspec : con refspecs puedes especificar y/o configurar qué ramas quieres recuperar y dónde almacenarlas. Por ejemplo, el caso predeterminado "buscar todas las ramas" está representado por la especificación de referencia comodín '+refs/heads/*:refs/remotes/origin/*', y "buscar una sola rama" es una abreviatura de 'refs/heads/<branch>:' . Las referencias se utilizan para asignar nombres de sucursales (referencias) en un repositorio remoto a nombres de referencias locales. Pero no necesitas saber (mucho) sobre refspecs para poder trabajar eficazmente con Git (gracias principalmente al comando "git remoto").
Opinión personal: personalmente creo que las "ramas con nombre" (con nombres de ramas incrustados en los metadatos del conjunto de cambios) en Mercurial son un diseño equivocado con su espacio de nombres global, especialmente para un sistema de control de versiones distribuido . Por ejemplo, tomemos el caso en el que tanto Alice como Bob tienen una "rama con nombre" llamada 'for-joe' en sus repositorios, ramas que no tienen nada en común. Sin embargo, en el repositorio de Joe esas dos ramas serían maltratadas como una sola rama. Entonces, de alguna manera se te ocurrió una convención que protege contra conflictos de nombres de sucursales. Esto no es un problema con Git, donde en el repositorio de Joe la rama 'for-joe' de Alice sería 'alice/for-joe', y de Bob sería 'bob/for-joe'. Consulte también el problema de separación del nombre de la sucursal de la identidad de la sucursal planteado en la wiki de Mercurial.
Las "ramas de marcadores" de Mercurial actualmente carecen de un mecanismo de distribución interno.
Diferencias:
esta área es una de las principales diferencias entre Mercurial y Git, como dijeron James Woodyatt y Steve Losh en sus respuestas. Mercurial, por defecto, utiliza líneas de código ligeras y anónimas, que en su terminología se denominan "cabezas". Git utiliza ramas con nombre ligeras, con mapeo inyectivo para asignar nombres de ramas en un repositorio remoto a nombres de ramas de seguimiento remoto. Git te "obliga" a nombrar ramas (bueno, con excepción de una sola rama sin nombre, situación llamada HEAD separada), pero creo que esto funciona mejor con flujos de trabajo con muchas ramas, como el flujo de trabajo de rama temática, es decir, múltiples ramas en un paradigma de repositorio único.
Revisiones de nombres
En Git hay muchas formas de nombrar las revisiones (descritas, por ejemplo, en la página de manual de git rev-parse ):
- El nombre completo del objeto SHA1 (cadena hexadecimal de 40 bytes), o una subcadena del mismo que sea única dentro del repositorio.
- Un nombre de referencia simbólico, por ejemplo, "maestro" (que se refiere a la rama "maestra"), o "v1.5.0" (que se refiere a la etiqueta) o "origen/siguiente" (que se refiere a la rama de seguimiento remoto)
- Un sufijo
^
al parámetro de revisión significa el primer padre de un objeto de confirmación,^n
significa el enésimo padre de una confirmación de fusión. Un sufijo~n
al parámetro de revisión significa el n-ésimo ancestro de una confirmación en la primera línea principal. Esos sufijos se pueden combinar para formar un especificador de revisión siguiendo la ruta de una referencia simbólica, por ejemplo, 'pu~3^2~3'. - Salida de "git describe", es decir, una etiqueta más cercana, seguida opcionalmente de un guión y una cantidad de confirmaciones, seguida de un guión, una 'g' y un nombre de objeto abreviado, por ejemplo, 'v1.6.5.1-75- g5bf8097'.
También hay especificadores de revisión que involucran reflog, que no se mencionan aquí. En Git cada objeto, ya sea confirmación, etiqueta, árbol o blob, tiene su identificador SHA-1; existe una sintaxis especial como, por ejemplo, 'siguiente:Documentación' o 'siguiente:README' para hacer referencia al árbol (directorio) o al blob (contenido del archivo) en una revisión especificada.
Mercurial también tiene muchas formas de nombrar conjuntos de cambios (descritas, por ejemplo, en la página de manual de hg ):
- Un número entero simple se trata como un número de revisión. Es necesario recordar que los números de revisión son locales para el repositorio determinado ; en otro repositorio pueden ser diferentes.
- Los números enteros negativos se tratan como desplazamientos secuenciales desde la punta, donde -1 indica la punta, -2 indica la revisión antes de la punta, y así sucesivamente. También son locales para el repositorio.
- Un identificador de revisión único (cadena hexadecimal de 40 dígitos) o su prefijo único.
- Un nombre de etiqueta (nombre simbólico asociado con una revisión dada), o un nombre de marcador (con extensión: nombre simbólico asociado con un encabezado dado, local al repositorio), o una "rama con nombre" (etiqueta de confirmación; la revisión dada por "rama con nombre" es sugerencia (confirmación sin hijos) de todas las confirmaciones con una etiqueta de confirmación determinada, con el número de revisión más grande si hay más de una sugerencia de este tipo)
- El nombre reservado "tip" es una etiqueta especial que siempre identifica la revisión más reciente.
- El nombre reservado "null" indica la revisión nula.
- El nombre reservado "." indica el directorio de trabajo principal.
Diferencias
Como puede ver al comparar las listas anteriores, Mercurial ofrece números de revisión, locales al repositorio, mientras que Git no. Por otro lado, Mercurial ofrece compensaciones relativas solo desde 'tip' (rama actual), que son locales para el repositorio (al menos sin ParentrevspecExtension ), mientras que Git permite especificar cualquier confirmación siguiente desde cualquier sugerencia.
La revisión más reciente se denomina HEAD en Git y "tip" en Mercurial; no hay ninguna revisión nula en Git. Tanto Mercurial como Git pueden tener muchas raíces (pueden tener más de una confirmación sin padres; esto generalmente es el resultado de la unión de proyectos anteriormente separados).
Ver también: Artículo sobre muchos tipos diferentes de especificadores de revisión en el Blog de Elijah (newren's).
Opinión personal: Creo que los números de revisión están sobrevalorados (al menos para el desarrollo distribuido y/o la historia no lineal/ramificada). Primero, para un sistema de control de versiones distribuido, tienen que ser locales en el repositorio o requerir tratar algún repositorio de una manera especial como una autoridad de numeración central. En segundo lugar, los proyectos más grandes, con una historia más larga, pueden tener un número de revisiones en un rango de 5 dígitos, por lo que ofrecen solo una ligera ventaja sobre los identificadores de revisión acortados a 6-7 caracteres, e implican un orden estricto, mientras que las revisiones solo están ordenadas parcialmente (aquí quiero decir que las revisiones n y n+1 no necesitan ser padre e hijo).
Rangos de revisión
En Git los rangos de revisión son topológicos . La sintaxis comúnmente vista A..B
, que para el historial lineal significa un rango de revisión que comienza en A (pero excluyendo A) y termina en B (es decir, el rango está abierto desde abajo ), es una abreviatura ("azúcar sintáctico") para ^A B
, que para los comandos que atraviesan el historial significa todos confirmaciones accesibles desde B, excluyendo aquellas accesibles desde A. Esto significa que el comportamiento del A..B
rango es completamente predecible (y bastante útil) incluso si A no es ancestro de B: A..B
significa entonces rango de revisiones del ancestro común de A y B (fusionar base ) a la revisión B.
En Mercurial, los rangos de revisión se basan en el rango de números de revisión . El rango se especifica mediante A:B
sintaxis y, a diferencia de Git, el rango actúa como un intervalo cerrado . Además, el rango B:A es el rango A:B en orden inverso, lo cual no es el caso en Git (pero consulte la nota a continuación sobre A...B
sintaxis). Pero tal simplicidad tiene un precio: el rango de revisión A:B sólo tiene sentido si A es antepasado de B o viceversa, es decir, con una historia lineal; de lo contrario (supongo que) el rango es impredecible y el resultado es local para el repositorio (porque los números de revisión son locales para el repositorio).
Esto se solucionó con Mercurial 1.6, que tiene un nuevo rango de revisión topológica , donde 'A..B' (o 'A::B') se entiende como el conjunto de conjuntos de cambios que son a la vez descendientes de X y ancestros de Y. Esto es , supongo, equivalente a '--ancestry-path A..B' en Git.
Git también tiene notación A...B
para diferencias simétricas de revisiones; significa A B --not $(git merge-base A B)
, lo que significa todas las confirmaciones accesibles desde A o B, pero excluyendo todas las confirmaciones accesibles desde ambos (accesibles desde ancestros comunes).
Cambia el nombre
Mercurial utiliza el seguimiento de cambios de nombre para gestionar los cambios de nombre de archivos. Esto significa que la información sobre el hecho de que se cambió el nombre de un archivo se guarda en el momento de la confirmación; en Mercurial, esta información se guarda en el formulario "diff mejorado" en los metadatos de filelog (file revlog). La consecuencia de esto es que debe usar hg rename
/ hg mv
... o debe recordar ejecutar hg addremove
para realizar una detección de cambio de nombre basada en similitudes.
Git es único entre los sistemas de control de versiones porque utiliza la detección de cambios de nombre para gestionar los cambios de nombre de los archivos. Esto significa que el hecho de que se cambió el nombre del archivo se detecta en el momento en que es necesario: al realizar una fusión o al mostrar una diferencia (si se solicita/configura). Esto tiene la ventaja de que el algoritmo de detección de cambio de nombre se puede mejorar y no se congela en el momento de la confirmación.
Tanto Git como Mercurial requieren el uso --follow
de la opción para seguir los cambios de nombre al mostrar el historial de un solo archivo. Ambos pueden seguir cambios de nombre cuando se muestra el historial de líneas de un archivo en git blame
/ hg annotate
.
En Git, el git blame
comando puede seguir el movimiento del código y también mover (o copiar) el código de un archivo a otro, incluso si el movimiento del código no es parte de un cambio de nombre completo del archivo. Hasta donde yo sé, esta característica es exclusiva de Git (en el momento de escribir este artículo, octubre de 2009).
Protocolos de red
Tanto Mercurial como Git tienen soporte para buscar y enviar a repositorios en el mismo sistema de archivos, donde la URL del repositorio es solo una ruta del sistema de archivos al repositorio. Ambos también admiten la recuperación de archivos de paquetes .
Mercurial admite la búsqueda y envío a través de SSH y protocolos HTTP. Para SSH se necesita una cuenta shell accesible en la máquina de destino y una copia de hg instalada/disponible. Para el acceso HTTP hg-serve
, se requiere la ejecución del script Mercurial CGI y Mercurial debe estar instalado en la máquina del servidor.
Git admite dos tipos de protocolos utilizados para acceder al repositorio remoto:
- Los protocolos "inteligentes" , que incluyen el acceso a través de SSH y mediante el protocolo git:// personalizado (por
git-daemon
), requieren tener instalado git en el servidor. El intercambio en esos protocolos consiste en que el cliente y el servidor negocian qué objetos tienen en común y luego generan y envían un archivo empaquetado. Modern Git incluye soporte para el protocolo HTTP "inteligente". - Los protocolos "tontos" , que incluyen HTTP y FTP (solo para buscar) y HTTPS (para enviar a través de WebDAV), no requieren que git esté instalado en el servidor, pero sí requieren que el repositorio contenga información adicional generada por
git update-server-info
(generalmente ejecutado desde un enlace). ). El intercambio consiste en que el cliente recorra la cadena de confirmación y descargue objetos sueltos y archivos empaquetados según sea necesario. La desventaja es que descarga más de lo estrictamente necesario (por ejemplo, en el caso de que solo haya un único archivo de paquete, se descargará completo incluso cuando solo se obtengan unas pocas revisiones) y que puede requerir muchas conexiones para finalizar.
Ampliación: capacidad de secuencias de comandos frente a extensiones (complementos)
Mercurial está implementado en Python , con parte del código central escrito en C para mejorar el rendimiento. Proporciona API para escribir extensiones (complementos) como una forma de agregar funciones adicionales. Algunas funciones, como "ramas de marcadores" o revisiones de firma, se proporcionan en extensiones distribuidas con Mercurial y requieren activarlas.
Git se implementa en C , Perl y scripts de shell . Git proporciona muchos comandos de bajo nivel ( plomería ) adecuados para usar en scripts. La forma habitual de introducir una nueva característica es escribirla como Perl o script de shell, y cuando la interfaz de usuario se estabilice, reescribirla en C para mejorar el rendimiento, la portabilidad y, en el caso del script de shell, evitar casos extremos (este procedimiento se llama incorporación ).
Git se basa y se basa en formatos [de repositorio] y protocolos [de red]. En lugar de enlaces de idiomas, hay reimplementaciones (parciales o completas) de Git en otros idiomas (algunas de ellas son parcialmente reimplementaciones y parcialmente envoltorios de comandos de git): JGit (Java, usado por EGit, Eclipse Git Plugin), Grit (Ruby) , Dulwich (Python), git# (C#).
TL;DR
Creo que puedes hacerte una idea de en qué se parecen o se diferencian esos sistemas viendo esos dos vídeos:
Linus Torvalds en Git ( http://www.youtube.com/watch?v=4XpnKHJAok8 )
Bryan O'Sullivan en Mercurial ( http://www.youtube.com/watch?v=JExtkqzEoHY )
Ambos son muy similares en diseño pero muy diferentes en implementaciones.
Yo uso Mercurial. Hasta donde yo entiendo a Git, una de las principales diferencias de Git es que rastrea el contenido de los archivos en lugar de los archivos en sí. Linus dice que si mueves una función de un archivo a otro, Git te dirá el historial de esa función única durante el movimiento.
También dicen que git es más lento a través de HTTP pero tiene su propio protocolo de red y servidor.
Git funciona mejor como cliente pesado SVN que Mercurial. Puede tirar y empujar contra un servidor SVN. Esta funcionalidad aún está en desarrollo en Mercurial.
Tanto Mercurial como Git tienen muy buenas soluciones de alojamiento web disponibles (BitBucket y GitHub), pero Google Code solo admite Mercurial. Por cierto, tienen una comparación muy detallada de Mercurial y Git que hicieron para decidir cuál admitir ( http://code.google.com/p/support/wiki/DVCSAnalysis ). Tiene mucha buena información.
Utilizo ambos con bastante regularidad. La principal diferencia funcional está en la forma en que los nombres de Git y Mercurial se ramifican dentro de los repositorios. Con Mercurial, los nombres de las ramas se clonan y se extraen junto con sus conjuntos de cambios. Cuando agrega cambios a una nueva rama en Mercurial y los envía a otro repositorio, el nombre de la rama se envía al mismo tiempo. Por lo tanto, los nombres de las sucursales son más o menos globales en Mercurial, y debe usar la extensión Bookmark para tener nombres ligeros solo locales (si los desea; Mercurial, de forma predeterminada, usa líneas de código ligeras anónimas, que en su terminología son llamados "cabezas"). En Git, los nombres de las ramas y su mapeo inyectivo a ramas remotas se almacenan localmente y debes administrarlos explícitamente, lo que significa saber cómo hacerlo. De aquí es de donde Git obtiene su reputación de ser más difícil de aprender y usar que Mercurial.
Como otros notarán aquí, hay muchísimas diferencias menores. Lo de las ramas es el gran diferenciador.
Después de leer todo el tiempo que Mercurial es más fácil (lo cual sigo creyendo que lo es, después de todo, la comunidad de Internet es de la opinión), cuando comencé a trabajar con Git y Mercurial sentí que me resultaba relativamente más sencillo adaptarme a Git (comencé con Mercurial con TortoiseHg) cuando se trabaja desde la línea de comandos, principalmente porque los comandos de git fueron nombrados apropiadamente según mi opinión y son menos numerosos. Mercurial tiene nombres diferentes para cada comando que realiza un trabajo distinto, mientras que los comandos de Git pueden ser multipropósito según la situación (por ejemplo, checkout
). Si bien Git era más difícil en aquel entonces, ahora la diferencia no es sustancial. YMMV... Con un buen cliente GUI como TortoiseHg, es cierto que era mucho más fácil trabajar con Mercurial y no tenía que recordar los comandos un poco confusos. No voy a entrar en detalles sobre cómo varía cada comando para la misma acción, pero aquí hay dos listas completas: una del propio sitio de Mercurial y la segunda de wikivs .
╔═════════════════════════════╦════════════════════════════════════════════════════════════════════════════════════════════════╗
║ Git ║ Mercurial ║
╠═════════════════════════════╬════════════════════════════════════════════════════════════════════════════════════════════════╣
║ git pull ║ hg pull -u ║
║ git fetch ║ hg pull ║
║ git reset --hard ║ hg up -C ║
║ git revert <commit> ║ hg backout <cset> ║
║ git add <new_file> ║ hg add <new_file> (Only equivalent when <new_file> is not tracked.) ║
║ git add <file> ║ Not necessary in Mercurial. ║
║ git add -i ║ hg record ║
║ git commit -a ║ hg commit ║
║ git commit --amend ║ hg commit --amend ║
║ git blame ║ hg blame or hg annotate ║
║ git blame -C ║ (closest equivalent): hg grep --all ║
║ git bisect ║ hg bisect ║
║ git rebase --interactive ║ hg histedit <base cset> (Requires the HisteditExtension.) ║
║ git stash ║ hg shelve (Requires the ShelveExtension or the AtticExtension.) ║
║ git merge ║ hg merge ║
║ git cherry-pick <commit> ║ hg graft <cset> ║
║ git rebase <upstream> ║ hg rebase -d <cset> (Requires the RebaseExtension.) ║
║ git format-patch <commits> ║ hg email -r <csets> (Requires the PatchbombExtension.) ║
║ and git send-mail ║ ║
║ git am <mbox> ║ hg mimport -m <mbox> (Requires the MboxExtension and the MqExtension. Imports patches to mq.) ║
║ git checkout HEAD ║ hg update ║
║ git log -n ║ hg log --limit n ║
║ git push ║ hg push ║
╚═════════════════════════════╩════════════════════════════════════════════════════════════════════════════════════════════════╝
Git guarda internamente un registro de cada versión de los archivos comprometidos, mientras que Hg guarda solo los conjuntos de cambios que pueden ocupar menos espacio. Git hace que sea más fácil cambiar el historial en comparación con Hg, pero nuevamente es una característica que lo odias o lo amas. Me gusta Hg para el primero y Git para el segundo.
Lo que extraño en Hg es la función de submódulo de Git. Hg tiene subrepos pero ese no es exactamente un submódulo de Git.
El ecosistema alrededor de los dos también puede influir en la elección: Git tiene que ser más popular (pero eso es trivial), Git tiene GitHub mientras que Mercurial tiene BitBucket , Mercurial tiene TortoiseHg, para el cual no he visto un equivalente tan bueno para Git.
Cada uno tiene sus ventajas y desventajas, con cualquiera de ellos no vas a perder.
Mercurial está casi completamente escrito en Python. El núcleo de Git está escrito en C (y debería ser más rápido que el de Mercurial) y las herramientas están escritas en sh, perl, tcl y utiliza utilidades estándar de GNU. Por lo tanto, necesita llevar todas estas utilidades e intérpretes al sistema que no las contiene (por ejemplo, Windows).
Ambos soportes funcionan con SVN, aunque AFAIK el soporte de svn no funciona para git en Windows (tal vez tenga mala suerte o sea aburrido, quién sabe). También hay extensiones que permiten interoperar entre git y Mercurial.
Mercurial tiene una buena integración con Visual Studio . La última vez que lo comprobé, el complemento para Git funcionaba pero era extremadamente lento.
Los conjuntos de comandos básicos son muy similares (init, clone, add, status, commit, push, pull, etc.). Entonces, el flujo de trabajo básico será el mismo. Además, existe un cliente tipo TortoiseSVN para ambos.
Las extensiones para Mercurial se pueden escribir en Python (¡no es de extrañar!) y para git se pueden escribir en cualquier forma ejecutable (binario ejecutable, script de shell, etc.). Algunas extensiones son increíblemente poderosas, como git bisect
.