Git e Mercurial: confronti e contrasto

Per un po ‘di tempo ho usato la sovversione per i miei progetti personali.

Sempre più continuo a sentire grandi cose su Git e Mercurial e sul DVCS in generale.

Mi piacerebbe dare un vortice a tutta la faccenda del DVC, ma non ho molta familiarità con nessuna delle due opzioni.

Quali sono alcune delle differenze tra Mercurial e Git?

Si noti che non sto cercando di scoprire quale sia il “migliore” o anche quale debba essere il primo. Sono principalmente alla ricerca di aree chiave in cui sono simili e in cui sono diversi, perché sono interessato a sapere come si differenziano in termini di implementazione e filosofia.

Dichiarazione di non responsabilità: utilizzo Git, seguo lo sviluppo di Git sulla mailing list git e persino contribuisco un po ‘a Git (principalmente gitweb). Conosco Mercurial dalla documentazione e alcuni dalla discussione sul canale IRC #revctrl su FreeNode.

Grazie a tutte le persone sul canale IRC #mercurial che hanno fornito aiuto su Mercurial per questa recensione



Sommario

Qui sarebbe bello avere qualche syntax per la tabella, qualcosa del genere in PHPMarkdown / MultiMarkdown / estensione Maruku di Markdown

  • Struttura del repository: Mercurial non consente l’unione di polpi (con più di due genitori), né il tagging di oggetti non di commit.
  • Tag: Mercurial utilizza il file .hgtags con regole speciali per i tag per repository e supporta anche i tag locali in .hg/localtags ; in Git i tag sono refs che si trovano in refs refs/tags/ namespace, e per impostazione predefinita sono autofollowed sul recupero e richiedono push esplicito.
  • Succursali: in Mercurial il stream di lavoro di base è basato su teste anonime ; Git utilizza rami con nome leggero e ha un tipo speciale di rami (rami di monitoraggio remoto ) che seguono le diramazioni nel repository remoto.
  • Denominazione e intervalli di revisione : Mercurial fornisce numeri di revisione , da locale a repository e basa le relative revisioni (contando da tip, ovvero ramo corrente) e intervalli di revisione su questa numerazione locale ; Git fornisce un modo per fare riferimento alla revisione relativa alla punta della branca e gli intervalli di revisione sono topologici (basati sul grafico delle revisioni)
  • Mercurial usa il rinominare il tracciamento , mentre Git usa il rilevamento del nome per rinominare i file
  • Rete: Mercurial supporta i protocolli “intelligenti” SSH e HTTP e il protocollo HTTP statico; Git moderno supporta i protocolli “intelligenti” SSH, HTTP e GIT e il protocollo “stupido” HTTP (S). Entrambi hanno il supporto per i file bundle per il trasporto off-line.
  • Mercurial utilizza estensioni (plug-in) e API stabilite; Git ha scripting e formati stabiliti.

Ci sono alcune cose che distingue Mercurial da Git, ma ci sono altre cose che le rendono simili. Entrambi i progetti prendono in prestito idee l’uno dall’altro. Per esempio il comando hg bisect in Mercurial (ex bisect extension ) è stato ispirato dal comando git bisect in Git, mentre l’idea di git bundle stata ispirata da hg bundle .

Struttura del repository, memorizzazione delle revisioni

In Git ci sono quattro tipi di oggetti nel suo database degli oggetti: oggetti blob che contengono il contenuto di un file, oggetti gerarchici che memorizzano la struttura delle directory, inclusi i nomi dei file e le parti rilevanti dei permessi dei file (permesso eseguibile per i file, essendo un collegamento simbolico) , commit object che contiene informazioni di authorship, puntatore all’istantanea dello stato del repository alla revisione rappresentata da un commit (tramite un object tree della top directory del progetto) e riferimenti a zero o più commit padre e tag oggetti che fanno riferimento ad altri oggetti e possono essere firmato usando PGP / GPG.

Git utilizza due metodi per archiviare gli oggetti: formato allentato , in cui ogni object è memorizzato in un file separato (quei file vengono scritti una volta e mai modificati) e in un formato compresso in cui molti oggetti sono archiviati delta-compressi in un singolo file. L’atomicità delle operazioni è fornita dal fatto che il riferimento a un nuovo object è scritto (atomicamente, usando il trucco di creazione + rinominare) dopo aver scritto un object.

I repository Git richiedono manutenzione periodica con git gc (per ridurre lo spazio su disco e migliorare le prestazioni), anche se oggigiorno Git lo fa automaticamente. (Questo metodo fornisce una migliore compressione dei repository).

Mercurial (per quanto ho capito) memorizza la cronologia di un file in un filelog (insieme, penso, con metadati aggiuntivi come rintracciare il rintracciamento e alcune informazioni di supporto); utilizza una struttura piatta chiamata manifest per memorizzare la struttura di directory e la struttura chiamata changelog che memorizza le informazioni sui changeset (revisioni), inclusi il messaggio di commit e zero, uno o due genitori.

Mercurial utilizza il diario delle transazioni per fornire l’atomicità delle operazioni e si affida al troncamento dei file per ripulire dopo un’operazione fallita o interrotta. I revolog sono solo append.

Guardando la struttura del repository in Git rispetto a Mercurial, si può vedere che Git è più simile al database degli oggetti (o al file system indirizzato al contenuto), e Mercurial è più simile al tradizionale database relazionale a campo fisso.

differenze:
In Git gli oggetti dell’albero formano una struttura gerarchica ; nel file manifest di Mercurial è una struttura piatta . In Git blob object store una versione di un contenuto di un file; in Mercurial filelog memorizza l’ intera cronologia di un singolo file (se non teniamo conto di eventuali complicazioni con i nomi). Ciò significa che ci sono diverse aree di operazioni in cui Git sarebbe più veloce di Mercurial, tutte le altre cose considerate uguali (come le fusioni o la cronologia di un progetto) e le aree in cui Mercurial sarebbe più veloce di Git (come applicare le patch o mostrare storia di un singolo file). Questo problema potrebbe non essere importante per l’utente finale.

A causa della struttura a record fisso della struttura del changelog di Mercurial, i commit in Mercurial possono avere solo un massimo di due genitori ; commit in Git può avere più di due genitori (i cosiddetti “polpo si fondono”). Mentre è ansible (in teoria) sostituire l’unione di polpo con una serie di fusioni di due genitori, questo potrebbe causare complicazioni durante la conversione tra repository Mercurial e Git.

Per quanto ne so, Mercurial non ha l’equivalente di tag annotati (oggetti tag) da Git. Un caso speciale di tag annotati sono tag firmati (con firma PGP / GPG); l’equivalente in Mercurial può essere fatto usando GpgExtension , la cui estensione viene distribuita insieme a Mercurial. Non è ansible taggare l’object non commit in Mercurial come in Git, ma non è molto importante, penso (alcuni repository git utilizzano blob taggato per distribuire la chiave PGP pubblica da utilizzare per verificare i tag firmati).

Riferimenti: rami e tag

Nei riferimenti Git (rami, rami e tag di tracciamento remoto) risiedono al di fuori dei DAG dei commit (come dovrebbero). I riferimenti in refs/heads/ namespace ( filiali locali ) puntano a commit, e sono solitamente aggiornati da “git commit”; puntano alla punta (testa) del ramo, ecco perché tale nome. Riferimenti in refs/remotes// namespace ( rami di remote-tracking ) puntano al commit, seguono i rami nel repository remoto e sono aggiornati da “git fetch” o equivalente. I riferimenti in refs/tags/ namespace ( tag ) puntano di solito a commit (tag leggeri) o tag object (tag annotati e firmati) e non sono destinati a cambiare.

tag

In Mercurial puoi dare un nome persistente alla revisione usando il tag ; i tag sono memorizzati in modo simile ai pattern di ignorare. Significa che i tag visibili a livello globale sono memorizzati nel file .hgtags controllato di .hgtags nel tuo repository. Ciò comporta due conseguenze: in primo luogo, Mercurial deve utilizzare regole speciali per questo file per ottenere l’elenco corrente di tutti i tag e per aggiornare tale file (ad esempio, legge la revisione del file più recente, attualmente non ritirata); in secondo luogo, devi commutare le modifiche a questo file in modo che il nuovo tag sia visibile ad altri utenti / altri repository (a quanto ho capito).

Mercurial supporta anche tag locali , memorizzati in hg/localtags , che non sono visibili ad altri (e ovviamente non sono trasferibili)

In Git i tag sono fissi (costanti) con riferimenti ad altri oggetti (solitamente taggano oggetti, che a loro volta puntano a commit) memorizzati in refs/tags/ namespace. Di default quando recupera o spinge un insieme di revisioni, git recupera o spinge automaticamente i tag che puntano a revisioni che vengono recuperate o inviate. Tuttavia è ansible controllare in una certa misura quali tag vengono recuperati o spinti.

Git tratta tag leggeri (che puntano direttamente ai commit) e tag annotati (che puntano agli oggetti tag, che contengono tag message che include opzionalmente la firma PGP, che a loro volta puntano al commit) in modo leggermente diverso, ad esempio per impostazione predefinita considera solo i tag annotati durante la descrizione commette usando “git describe”.

Git non ha un equivalente rigoroso di tag locali in Mercurial. Ciononostante, le best practice di Git consigliano di installare un repository pubblico separato, nel quale si apportano modifiche pronte e da cui altri clonano e recuperano. Ciò significa che i tag (e i rami) che non vengono spinti sono privati ​​del tuo repository. D’altra parte è ansible utilizzare anche namespace diversi da heads , remotes o tags , ad esempio local-tags locali per tag locali.

Opinione personale: a mio parere, i tag dovrebbero risiedere al di fuori del grafico di revisione, poiché sono esterni (sono indicatori nel grafico delle revisioni). I tag dovrebbero essere non versione, ma trasferibili. La scelta di Mercurial di utilizzare un meccanismo simile a quello per ignorare i file, significa che deve trattare .hgtags appositamente (il file in-tree è trasferibile, ma ordinario è versionato), o ha tag che sono solo locali ( .hg/localtags è non versione, ma non .hg/localtags ).

filiali

Nel ramo locale di Git (punta di diramazione o diramazione) è un riferimento con nome a un commit, in cui è ansible far crescere nuovi commit. Il ramo può anche significare una linea di sviluppo triggers, cioè tutti i commit raggiungibili dalla punta del ramo. I rami locali risiedono in refs/heads/ namespace, quindi ad es. Il nome completo del ramo ‘master’ è ‘refs / heads / master’.

Il ramo corrente in Git (che significa ramo estratto e ramo dove andrà il nuovo commit) è il ramo a cui fa riferimento l’HEAD ref. Si può avere HEAD che punta direttamente a un commit, piuttosto che essere un riferimento simbolico; questa situazione di essere su un ramo anonimo anonimo è chiamata HEAD distaccato (“git branch” indica che sei su “(nessun ramo)”).

In Mercurial ci sono filiali anonime (diramazioni) e si possono usare segnalibri (tramite estensione segnalibro ). Tali diramazioni di segnalibri sono puramente locali e tali nomi erano (fino alla versione 1.6) non trasferibili con Mercurial. È ansible utilizzare rsync o scp per copiare il file .hg/bookmarks in un repository remoto. Puoi anche utilizzare hg id -r per ottenere l’id di revisione di un suggerimento corrente di un segnalibro.

Dal momento che 1,6 segnalibri possono essere spinti / tirati. La pagina BookmarksExtension ha una sezione su Lavorare con i repository remoti . C’è una differenza in quanto i nomi dei segnalibri Mercurial sono globali , mentre la definizione di “remote” in Git descrive anche la mapping dei nomi dei rami dai nomi nel repository remoto ai nomi dei rami locali di localizzazione remota; ad esempio refs/heads/*:refs/remotes/origin/* mapping significa che si può trovare lo stato del ramo ‘master’ (‘refs / heads / master’) nel repository remoto nel remote-tracking ‘origine / master’ ramo (‘refs / remotes / origin / master’).

Mercurial ha anche i cosiddetti rami denominati , in cui il nome del ramo è incorporato in un commit (in un changeset). Tale nome è globale (trasferito su fetch). Questi nomi di rami sono registrati in modo permanente come parte dei metadati del changeset. Con il moderno Mercurial puoi chiudere “named branch” e interrompere la registrazione del nome del ramo. In questo meccanismo le punte dei rami sono calcolate al volo.

I “rami nominati” di Mercurial dovrebbero, a mio parere, essere chiamati etichette di commit , perché è ciò che sono. Esistono situazioni in cui “il ramo nominato” può avere più suggerimenti (più commit senza figli) e può anche consistere in diverse parti disgiunte del grafico delle revisioni.

Non esiste un equivalente di quei “rami incorporati” Mercurial in Git; inoltre, la filosofia di Git è che, mentre si può affermare che il ramo include alcuni commit, non significa che un commit appartenga a qualche ramo.

Si noti che la documentazione di Mercurial propone ancora di utilizzare cloni separati (repository separati) almeno per rami longevi (stream singolo per stream di lavoro repository), alias ramificazione tramite clonazione .

Rami nella spinta

Mercurial di default spinge tutte le teste . Se si desidera premere un singolo ramo ( testa singola ), è necessario specificare la revisione del tip del ramo che si desidera premere. È ansible specificare un suggerimento per il numero di revisione (da locale a repository), dall’identificatore di revisione, dal nome del segnalibro (locale al repository, non trasferito) o dal nome del ramo incorporato (ramo denominato).

Per quanto ne so, se si spinge un intervallo di revisioni che contiene commit contrassegnati come in un “ramo con nome” in gergo Mercurial, si avrà questo “ramo chiamato” nel repository che si preme. Ciò significa che i nomi di tali rami incorporati (“rami nominati”) sono globali (rispetto ai cloni di un determinato repository / progetto).

Per impostazione predefinita (sobject alla variabile di configurazione push.default ) “git push” o “git push < remote >” Git spingerebbe le branch corrispondenti , cioè solo quelle filiali locali che hanno il loro equivalente già presente nel repository remoto in cui viene inserito. Puoi usare l’opzione --all per git-push (“git push –all”) per spingere tutti i rami , puoi usare “git push < remote > < branch >” per spingere un singolo ramo dato , e puoi usare ” spingere < telecomando > HEAD “per spingere il ramo corrente .

Tutto quanto sopra presuppone che Git non sia configurato su quali rami eseguire il push tramite variabili remote..push .

Rami nel recupero

Nota: qui utilizzo la terminologia Git dove “fetch” significa scaricare le modifiche dal repository remoto senza integrare tali modifiche con il lavoro locale. Questo è ciò che fa ” git fetch ” e ” hg pull “.

Se ho capito bene, per impostazione predefinita Mercurial recupera tutte le teste dal repository remoto, ma puoi specificare il ramo da recuperare tramite ” hg pull --rev ” o ” hg pull # ” per Ottieni un singolo ramo . È ansible specificare usando l’identificatore di revisione, il nome “branch name” (ramo incorporato in changelog) o il nome del segnalibro. Il nome del segnalibro tuttavia (almeno attualmente) non viene trasferito. Tutte le revisioni denominate “succursali” che ricevi appartengono per essere trasferite. “hg pull” memorizza le punte dei rami recuperati come teste anonime e anonime.

In Git di default (per il telecomando “origine” creato da “git clone”, e per i remoti creati usando “git remote add”) ” git fetch ” (o ” git fetch “) ottiene tutte le derivazioni dal repository remoto (da refs/heads/ namespace), e li memorizza in refs/remotes/ namespace. Ciò significa, ad esempio, che il ramo denominato “master” (nome completo: “refs / heads / master”) in “origine” remota verrà memorizzato (salvato) come ramo di rilevamento remoto “origine / master” (nome completo: “refs / telecomandi / origin / master ‘).

Puoi recuperare un singolo ramo in Git usando git fetch – Git memorizzerebbe i branch richiesti in FETCH_HEAD, che è qualcosa di simile alle teste senza nome Mercurial.

Questi sono solo esempi di casi predefiniti di potente refspec Sintassi di Git: con refspec puoi specificare e / o configurare quali rami vuoi recuperare e dove archiviarli. Ad esempio, il caso predefinito “recupera tutti i rami” è rappresentato da “+ refs / heads / *: refs / remotes / origin / *” refspec con caratteri jolly e “fetch single branch” è una scorciatoia per “refs / heads / :” . I Refspec sono usati per mappare i nomi dei rami (ref) nel repository remoto ai nomi dei refs locali. Ma non è necessario sapere (molto) su refspecs per essere in grado di lavorare efficacemente con Git (grazie soprattutto al comando “git remote”).

Opinione personale: Personalmente ritengo che “i rami nominati” (con nomi di rami incorporati nei metadati del changeset) in Mercurial siano design erroneo con il suo spazio dei nomi globale, specialmente per un sistema di controllo delle versioni distribuito . Ad esempio, prendiamo in considerazione il caso in cui sia Alice che Bob hanno “named branch” chiamato “for-joe” nei loro repository, rami che non hanno nulla in comune. Nel repository di Joe, tuttavia, questi due rami verrebbero maltrattati come un singolo ramo. Quindi, in qualche modo, hai ideato una convenzione che protegga da scontri tra nomi di filiali. Questo non è un problema con Git, dove nel repository di Joe ‘for-joe’ il ramo di Alice sarebbe ‘alice / for-joe’, e da Bob sarebbe ‘bob / for-joe’. Vedi anche Separazione del nome del ramo dal problema dell’identity framework del ramo generato su Mercurial wiki.

I “rami segnalibri” di Mercurial sono attualmente privi di un meccanismo di distribuzione intrinseco.

differenze:
Questa zona è una delle principali differenze tra Mercurial e Git, come hanno detto nelle loro risposte James Woodyatt e Steve Losh . Mercurial, per impostazione predefinita, utilizza le codeline leggere anonime, che nella sua terminologia sono chiamate “teste”. Git utilizza rami denominati leggeri, con mapping iniettiva per mappare i nomi dei rami nel repository remoto ai nomi dei rami di tracciamento remoto. Git “ti obbliga” a nominare i rami (beh, ad eccezione del singolo ramo senza nome, situazione chiamata HEAD distaccato), ma penso che funzioni meglio con flussi di lavoro complessi come il stream di lavoro di argomenti, ovvero più rami nel paradigma di un singolo repository.

Revisioni dei nomi

In Git ci sono molti modi di nominare le revisioni (descritte ad esempio nella pagina man di git rev-parse ):

  • Il nome completo SHA1 dell’object (stringa esadecimale da 40 byte) o una sottostringa di tale che è univoca all’interno del repository
  • Un nome di riferimento simbolico, ad esempio “master” (che si riferisce al ramo “master”) o “v1.5.0” (che fa riferimento al tag) o “origine / successiva” (riferito al ramo di monitoraggio remoto)
  • Un suffisso ^ al parametro di revisione indica il primo genitore di un object commit, ^n significa n-es principale di un commit merge. Un suffisso ~n al parametro di revisione significa n-esimo antenato di un commit in linea retta primo genitore. Questi suffissi possono essere combinati, per formare un identificatore di revisione seguendo il percorso da un riferimento simbolico, ad esempio ‘pu ~ 3 ^ 2 ~ 3’
  • Output di “git describe”, ovvero un tag più vicino, opzionalmente seguito da un trattino e un numero di commit, seguito da un trattino, una ‘g’ e un nome di object abbreviato, ad esempio ‘v1.6.5.1-75- g5bf8097’ .

Ci sono anche specificatori di revisione che coinvolgono Reflog, non menzionati qui. In Git ogni object, sia esso commit, tag, tree o blob ha il suo identificatore SHA-1; esiste una syntax speciale come ad es. ‘next: Documentation’ o ‘next: README’ per fare riferimento a tree (directory) o blob (contenuto del file) alla revisione specificata.

Mercurial ha anche molti modi di nominare i changeset (descritti ad esempio nella manpage hg ):

  • Un intero semplice viene trattato come un numero di revisione. È necessario ricordare che i numeri di revisione sono locali per un determinato repository ; in altri repository possono essere diversi.
  • Gli interi negativi sono trattati come offset sequenziali dalla punta, con -1 che denota la punta, -2 che denota la revisione prima della punta e così via. Sono anche locali al repository.
  • Un identificatore di revisione univoco (stringa esadecimale a 40 cifre) o il suo prefisso univoco.
  • Un nome di tag (nome simbolico associato alla revisione data) o un nome di segnalibro (con estensione: nome simbolico associato a testa data, locale a repository) o “ramo denominato” (etichetta di commit; la revisione data da “ramo denominato” è tip (commit senza figli) di tutti i commit con un’etichetta di commit data, con il numero di revisione più grande se ce ne sono più di uno)
  • Il nome riservato “tip” è un tag speciale che identifica sempre la revisione più recente.
  • Il nome riservato “null” indica la revisione nulla.
  • Il nome riservato “.” indica il genitore della directory di lavoro.

differenze
Come puoi vedere confrontando le liste sopra elencate, Mercurial offre numeri di revisione, da locale a repository, mentre Git no. D’altra parte Mercurial offre compensazioni relative solo da “tip” (ramo attuale), che sono locali al repository (almeno senza ParentrevspecExtension ), mentre Git consente di specificare qualsiasi commit che segue da qualsiasi suggerimento.

La revisione più recente è denominata HEAD in Git e “tip” in Mercurial; non c’è una revisione nulla in Git. Sia Mercurial che Git possono avere molte radici (possono avere più di un commit senza genitori, questo di solito è il risultato di progetti precedentemente separati).

Vedi anche: Molti diversi tipi di articoli con le specifiche di revisione sul blog di Elijah (newren’s).

Opinione personale: Penso che i numeri di revisione siano sopravvalutati (almeno per lo sviluppo distribuito e / o la storia non lineare / ramificata). In primo luogo, per un sistema di controllo della versione distribuito devono essere locali al repository, o richiedere il trattamento di alcuni repository in modo speciale come autorità di numerazione centrale. In secondo luogo, i progetti più grandi, con una cronologia più lunga, possono avere numero di revisioni nell’intervallo di 5 cifre, quindi offrono solo un leggero vantaggio rispetto agli identificatori di revisione di 6-7 caratteri e implicano un ordine rigoroso mentre le revisioni sono solo parzialmente ordinate (intendo le revisioni n e n + 1 non devono essere genitore e figlio).

Intervalli di revisione

Gli intervalli di revisione Git sono topologici . Sintassi A..B comunemente vista, che per storia lineare significa intervallo di revisione che inizia da A (ma esclude A) e termina con B (cioè intervallo è aperto dal basso ), è abbreviazione (“zucchero sintattico”) per ^AB , che per i comandi di attraversamento della storia si intendono tutti i commit raggiungibili da B, esclusi quelli raggiungibili da A. Ciò significa che il comportamento dell’intervallo A..B è del tutto prevedibile (e abbastanza utile) anche se A non è l’antenato di B: A..B significa quindi gamma di revisioni da antenato comune di A e B (fusione di base) a revisione B.

In Mercurial gli intervalli di revisione sono basati sull’intervallo di numeri di revisione . L’intervallo è specificato usando la syntax A:B , e contrariamente all’intervallo Git agisce come un intervallo chiuso . Anche l’intervallo B: A è l’intervallo A: B in ordine inverso, che non è il caso in Git (ma vedi la nota seguente sulla syntax A...B ). Ma tale semplicità ha un prezzo: l’intervallo di revisione A: B ha senso solo se A è l’antenato di B o viceversa, cioè con la storia lineare; altrimenti (suppongo che) l’intervallo sia imprevedibile e il risultato è locale al repository (perché i numeri di revisione sono locali al repository).

Questo problema è stato risolto con Mercurial 1.6, che ha un nuovo intervallo di revisione topologico , dove ‘A..B’ (o ‘A :: B’) è inteso come l’insieme di changeset che sono entrambi discendenti di X e antenati di Y. Questo è , Suppongo, equivalente a ‘–estrada-percorso A..B’ in Git.

Git ha anche la notazione A...B per la differenza simmetrica delle revisioni; significa AB --not $(git merge-base AB) , il che significa che tutti i commit sono raggiungibili da A o B, ma escludendo tutti i commit raggiungibili da entrambi (raggiungibile dai comuni antenati).

rinomina

Mercurial utilizza il rinominare il tracciamento per gestire la rinomina dei file. Ciò significa che l’informazione sul fatto che un file è stato rinominato viene salvata al momento del commit; in Mercurial questa informazione viene salvata nella forma “diff avanzata” nei metadati filelog (file revlog). La conseguenza di ciò è che devi usare hg rename / hg mv … o devi ricordare di eseguire hg addremove per fare il rilevamento della rinominazione basato sulla similarità.

Git è univoco tra i sistemi di controllo delle versioni in quanto utilizza il rilevamento del nome per gestire la rinomina dei file. Ciò significa che il fatto che il file è stato rinominato viene rilevato al momento necessario: quando si esegue un’unione o quando si mostra un diff (se richiesto / configurato). Questo ha il vantaggio che l’algoritmo di rinominazione può essere migliorato e non congelato al momento del commit.

Sia Git che Mercurial richiedono l’uso dell’opzione --follow per seguire i --follow quando si mostra la cronologia di un singolo file. Entrambi possono seguire i nomi quando mostrano la cronologia delle linee di un file in git blame / hg annotate .

In Git il comando git blame è in grado di seguire il movimento del codice, anche spostando (o copiando) il codice da un file all’altro, anche se il movimento del codice non fa parte di un rinomato file rinominato. Per quanto ne so, questa caratteristica è unica per Git (al momento della stesura di questo articolo, ottobre 2009).

Protocolli di rete

Sia Mercurial che Git supportano il recupero e il push dei repository sullo stesso file system, dove l’URL del repository è solo un percorso del file system nel repository. Entrambi hanno anche il supporto per il recupero da file bundle .

Supporto Mercurial che recupera e spinge tramite SSH e tramite protocolli HTTP. Per SSH è necessario un account shell accessibile sul computer di destinazione e una copia di hg installata / disponibile. Per l’accesso HTTP è richiesto lo script hg-serve o Mercurial CGI in esecuzione e Mercurial deve essere installato sulla macchina server.

Git supporta due tipi di protocolli utilizzati per accedere al repository remoto:

  • i protocolli “intelligenti” , che includono l’accesso tramite SSH e il protocollo personalizzato git: // (da git-daemon ), richiedono l’installazione di git sul server. Lo scambio in questi protocolli consiste in client e server che negoziano su quali oggetti hanno in comune e quindi generano e inviano un file pack. Modern Git include il supporto per il protocollo HTTP “intelligente”.
  • protocolli “stupidi” , che includono HTTP e FTP (solo per il recupero) e HTTPS (per l’invio tramite WebDAV), non richiedono git installato sul server, ma richiedono che il repository contenga informazioni aggiuntive generate da git update-server-info (di solito viene eseguito da un hook). Lo scambio consiste in client che camminano sulla catena di commit e scaricano oggetti sciolti e file pack secondo necessità. Il rovescio della medaglia è che scarica più del necessario (per esempio nel caso in cui ci sia solo un singolo pacchetto che verrebbe scaricato completamente anche quando si recuperano solo alcune revisioni) e che potrebbe richiedere il completamento di molte connessioni.

Estensione: scriptability vs extensions (plugins)

Mercurial è implementato in Python , con alcuni codici core scritti in C per le prestazioni. Fornisce API per scrivere estensioni (plug-in) come metodo per aggiungere funzionalità extra. Alcune funzionalità, come “segnalibri” o revisioni di firma, sono fornite in estensioni distribuite con Mercurial e richiedono l’triggerszione.

Git è implementato in C , Perl e script di shell . Git fornisce molti comandi di basso livello ( plumbing ) adatti per l’uso negli script. Il solito modo di introdurre una nuova funzionalità è scriverlo come Perl o script di shell, e quando l’interfaccia utente si stabilizza riscriverlo in C per prestazioni, portabilità e, nel caso di script di shell, evitando casi angolari (questa procedura è chiamata builtinification ).

Git si basa e si basa su formati [repository] e protocolli [di rete]. Invece dei binding di linguaggio ci sono reimplementazioni (parziali o complete) di Git in altri linguaggi (alcuni di questi sono parzialmente reimplementati e parzialmente wrapper attorno ai comandi git): JGit (Java, usato da EGit, Eclipse Git Plugin), Grit (Ruby) , Dulwich (Python), git # (C #).


TL; DR

Penso che tu possa avere la sensazione di ciò che questi sistemi sono simili o diversi da whatching questi due video:

Linus Torvalds su Git ( http://www.youtube.com/watch?v=4XpnKHJAok8 )
Bryan O’Sullivan su Mercurial ( http://www.youtube.com/watch?v=JExtkqzEoHY )

Entrambi sono molto simili nel design ma molto diversi nelle implementazioni.

Io uso Mercurial. Per quanto ne so Git, una cosa importante che git è diverso è che tiene traccia dei contenuti dei file invece dei file stessi. Linus dice che se si sposta una funzione da un file all’altro, Git ti dirà la cronologia di quella singola funzione attraverso lo spostamento.

Dicono anche che git è più lento su HTTP ma ha il proprio protocollo di rete e server.

Git funziona meglio come client thick SVN rispetto a Mercurial. Puoi tirare e spingere contro un server SVN. Questa funzionalità è ancora in fase di sviluppo in Mercurial

Sia Mercurial che Git hanno a disposizione soluzioni di hosting Web molto interessanti (BitBucket e GitHub), ma Google Code supporta solo Mercurial. A proposito, hanno un confronto molto dettagliato di Mercurial e Git che hanno fatto per decidere quale supportare ( http://code.google.com/p/support/wiki/DVCSAnalysis ). Ha molte buone informazioni.

Ho scritto un post sul blog sui modelli di ramificazione di Mercurial qualche tempo fa, e ho incluso confronti con il modello di branching di git. Forse lo troverai interessante: http://stevelosh.com/blog/entry/2009/8/30/a-guide-to-branching-in-mercurial/

I use both quite regularly. The major functional difference is in the way Git and Mercurial name branches within repositories. With Mercurial, branch names are cloned and pulled along with their changesets. When you add changes to a new branch in Mercurial and push to another repository, the branch name is pushed at the same time. So, branch names are more-or-less global in Mercurial, and you have to use the Bookmark extension to have local-only lightweight names (if you want them; Mercurial, by default, uses anonymous lightweight codelines, which in its terminology are called “heads”). In Git, branch names and their injective mapping to remote branches are stored locally and you must manage them explicitly, which means knowing how to do that. This is pretty much where Git gets its reputation for being harder to learn and use than Mercurial.

As others will note here, there are lots and lots of minor differences. The thing with the branches is the big differentiator.

Take a look at Git vs. Mercurial: Please Relax blog post by Patrick Thomson, where he writes:
Git is MacGyver , Mercurial is James Bond

Note that this blog post is from August 7, 2008, and both SCM improved much since.

Mercurial is almost fully written in python. Git’s core is written in C (and should be faster, than Mercurial’s) and tools written in sh, perl, tcl and uses standard GNU utils. Thus it needs to bring all these utils and interpreters with it to system that doesn’t contain them (eg Windows).

Both support work with SVN, although AFAIK svn support is broken for git on Windows (may be I am just unlucky/lame, who knows). There’re also extensions which allow to interoperate between git and Mercurial.

Mercurial has nice Visual Studio integration . Last time I checked, plugin for Git was working but extremely slow.

They basic command sets are very similar(init, clone, add, status, commit, push, pull etc.). So, basic workflow will be the same. Also, there’s TortoiseSVN-like client for both.

Extensions for Mercurial can be written in python (no surprise!) and for git they can be written in any executable form (executable binary, shell script etc). Some extensions are crazy powerful, like git bisect .

If you need good Windows support, you might prefer Mercurial. TortoiseHg (Windows explorer plugin) manages to offer a simple to use graphical interface to a rather complex tool. As state here, you will also have a Visual Studio plugin . However, last time I tried, the SVN interface didn’t work that well on Windows.

If you don’t mind the command line interface, I would recommend Git. Not for technical reason but for a strategical one. The adoption rate of git is much higher. Just see how many famous open source projects are switching from cvs/svn to Mercurial and how many are switching to Git. See how many code/project hosting providers you can find with git support compared to Mercurial hosting.

After reading all over that Mercurial is easier (which I still believe it is, after all the internet community is of the opinion), when I started working with Git and Mercurial I felt Git is relatively simpler for me to adapt to (I started off with Mercurial with TortoiseHg) when working from the command line, mainly because the git commands were named appropriately according to me and are fewer in number. Mercurial has different naming for each command that does a distinct job, while Git commands can be multipurpose according to situation (for eg, checkout ). While Git was harder back then, now the difference is hardly substantial. YMMV.. With a good GUI client like TortoiseHg, true it was much easier to work with Mercurial and I did not have to remember the slightly confusing commands. I’m not going into detail how every command for the same action varied, but here are two comprehensive lists: 1 from Mercurial’s own site and 2nd from wikivs .

 ╔═════════════════════════════╦════════════════════════════════════════════════════════════════════════════════════════════════╗ ║ Git ║ Mercurial ║ ╠═════════════════════════════╬════════════════════════════════════════════════════════════════════════════════════════════════╣ ║ git pull ║ hg pull -u ║ ║ git fetch ║ hg pull ║ ║ git reset --hard ║ hg up -C ║ ║ git revert  ║ hg backout  ║ ║ git add  ║ hg add  (Only equivalent when  is not tracked.) ║ ║ git add  ║ 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  (Requires the HisteditExtension.) ║ ║ git stash ║ hg shelve (Requires the ShelveExtension or the AtticExtension.) ║ ║ git merge ║ hg merge ║ ║ git cherry-pick  ║ hg graft  ║ ║ git rebase  ║ hg rebase -d  (Requires the RebaseExtension.) ║ ║ git format-patch  ║ hg email -r  (Requires the PatchbombExtension.) ║ ║ and git send-mail ║ ║ ║ git am  ║ hg mimport -m  (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 saves a record of every version of committed files internally, while Hg saves just the changesets which can have a smaller footprint. Git makes it easier to change the history compared to Hg, but then again its a hate-it-or-love-it feature. I like Hg for formsr and Git for latter.

What I miss in Hg is the submodule feature of Git. Hg has subrepos but that’s not exactly Git submodule.

Ecosystem around the two can also influence one’s choice: Git has to be more popular (but that’s trivial), Git has GitHub while Mercurial has BitBucket , Mercurial has TortoiseHg for which I haven’t seen an equivalent as good for Git.

Each has its advantages and disadvantages, with either of them you’re not going to lose.

Check out Scott Chacon’s post from a while back.

I think git has a reputation for being “more complicated”, though in my experience it’s not more complicated than it needs to be. IMO, the git model is way easier to understand (tags contain commits (and pointers to zero or more parent commits) contain trees contain blobs and other trees… done).

It’s not just my experience that git is not more confusing than mercurial. I’d recommend again reading this blog post from Scott Chacon on the matter.

I’ve used Git for a little over a year at my present job, and prior to that, used Mercurial for a little over a year at my previous job. I’m going to provide an evaluation from a user’s perspective.

First, both are distributed version control systems. Distributed version control systems require a change in mindset from traditional version control systems, but actually work much better in many ways once one understands them. For this reason, I consider both Git and Mercurial much superior to Subversion, Perforce, etc. The difference between distributed version control systems and traditional version control systems is much larger than the difference between Git and Mercurial.

However, there are also significant differences between Git and Mercurial that make each better suited to its own subset of use cases.

Mercurial is simpler to learn. I got to the point where I rarely had to refer to documentation or notes after a few weeks of using Mercurial; I still have to refer to my notes regularly with Git, even after using it for a year. Git is considerably more complicated.

This is partly because Mercurial is just plain cleaner. You rarely have to branch manually in Mercurial; Mercurial will create an anonymous branch automatically for you if and when you need it. Mercurial nomenclature is more intuitive; you don’t have to worry about the difference between “fetch” and “pull” as you do with Git. Mercurial is a bit less buggy. There are file name case sensitivity issues that used to cause problems when pushing projects across platforms with both Git and Mercurial; this were fixed in Mercurial some time ago while they hadn’t been fixed in Git last I checked. You can tell Mercurial about file renames; with Git, if it doesn’t detect the rename automatically – a very hit or miss proposition in my experience – the rename can’t be tracked at all.

The other reason for Git’s additional complication, however, is that much of it is needed to support additional features and power. Yes, it’s more complicated to handle branching in Git – but on the other hand, once you have the branches, it’s not too difficult to do things with those branches that are virtually impossible in Mercurial. Rebasing branches is one of these things: you can move your branch so that its base, instead of being the state of the trunk when you branched, is the state of the trunk now; this greatly simplifies version history when there are many people working on the same code base, since each of the pushes to trunk can be made to appear sequential, rather than intertwined. Similarly, it’s much easier to collapse multiple commits on your branch into a single commit, which can again help in keeping the version control history clean: ideally, all the work on a feature can appear as a single commit in trunk, replacing all the minor commits and subbranches that the developer may have made while developing the feature.

Ultimately I think the choice between Mercurial and Git should depend on how large your version control projects are, measured in terms of the number of people working on them simultaneously. If you have a group of a dozen or more working on a single monolithic web application, for example, Git’s more powerful branch management tools will make it a much better fit for your project. On the other hand, if your team is developing a heterogeneous distributed system, with only one or two developers working on any one component at any one time, using a Mercurial repository for each of the component projects will allow development to proceed more smoothly with less repository management overhead.

Bottom line: if you have a big team developing a single huge application, use Git; if your individual applications are small, with any scale coming from the number rather than the size of such applications, use Mercurial.

One difference totally unrelated to the DVCSs themselves:

Git seems to be very popular with C developers. Git is the de-facto repository for the Linux Kernel and this may be the reason why it is so popular with C developers. This is especially true for those that have the luxury of only working in the Linux/Unix world.

Java developers seem to favor Mercurial over Git. There are possibly two reasons for that: One is that a number of very large Java projects are hosted on Mercurial, including the JDK itself. Another is that the structure and clean documentation of Mercurial appeals to people coming from the Java camp whereas such people find Git inconsistent wrt command naming and lacking in documentation. I’m not saying that is actually true, I’m saying people have got used to something from their usual habitat and then they tend to choose DVCS from that.

Python developers almost exclusively favor Mercurial, I would assume. There’s actually no rational reason for that other than the fact that Mercurial is based on Python. (I use Mercurial too and I really don’t understand why people make a fuss about the implementation language of the DVCS. I don’t understand a word of Python and if it wasn’t for the fact that it is listed somewhere that it is based on Python then I wouldn’t have known).

I don’t think you can say that one DVCS fits a language better than another, so you shouldn’t choose from that. But in reality people choose (partly) based on which DVCS they get most exposed to as part of their community.

(nope, I don’t have usage statistics to back up my claims above .. it is all based on my own subjectivity)