threads / discuss / 6340

Mercurial to git converter.

Subject: Mercurial to git converter.

## tl;dr

15 messages between Jan 11, 2007 and Jun 17, 2008.

replies: 14people: 7as markdown or json

Stelian Pop· Jan 11, 2007, 17:49 UTC · lore
Hi all,

I switched recently from Mercurial to git for some of my projects, and needed a conversion tool with branch support (unlike tailor). I didn't find any, so I ended up writing my own one.

hg-to-git.py can probably be greatly improved (it's a rather crude
combination of shell and python) but it does already work quite well for
me. Features:
	- supports incremental conversion (keep a git repo in sync with a hg
one)
	- supports hg branches
	- converts hg tags 

Feel free to use it, improve it or even include it in mainline git (like git-svn and friends).

Stelian.
#! /usr/bin/python
""" hg-to-svn.py - A Mercurial to GIT converter
    Copyright (C)2007 Stelian Pop <stelian@popies.net>
    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2, or (at your option)
    any later version.
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
"""

import os, os.path, sys import tempfile, popen2, pickle, getopt import re

# Maps hg version -> git version hgvers = {} # List of children for each hg revision hgchildren = {} # Current branch for each hg revision hgbranch = {}

#------------------------------------------------------------------------------
def usage():
        print """\
%s: [OPTIONS] <hgprj>
options:
    -s, --gitstate=FILE: name of the state to be saved/read
                         for incrementals
required:
    hgprj:  name of the HG project to import (directory)
""" % sys.argv[0]
#------------------------------------------------------------------------------
def getgitenv(user, date):
    env = ''
    elems = re.compile('(.*?)\s+<(.*)>').match(user)
    if elems:
        env += 'export GIT_AUTHOR_NAME="%s" ;' % elems.group(1)
        env += 'export GIT_COMMITER_NAME="%s" ;' % elems.group(1)
        env += 'export GIT_AUTHOR_EMAIL="%s" ;' % elems.group(2)
        env += 'export GIT_COMMITER_EMAIL="%s" ;' % elems.group(2)
    else:
        env += 'export GIT_AUTHOR_NAME="%s" ;' % user
        env += 'export GIT_COMMITER_NAME="%s" ;' % user
        env += 'export GIT_AUTHOR_EMAIL= ;'
        env += 'export GIT_COMMITER_EMAIL= ;'
    env += 'export GIT_AUTHOR_DATE="%s" ;' % date
    env += 'export GIT_COMMITTER_DATE="%s" ;' % date
    return env
#------------------------------------------------------------------------------
state = ''
try:
    opts, args = getopt.getopt(sys.argv[1:], 's:t:', ['gitstate=', 'tempdir='])
    for o, a in opts:
        if o in ('-s', '--gitstate'):
            state = a
            state = os.path.abspath(state)
    if len(args) != 1:
        raise('params')
except:
    usage()
    sys.exit(1)

hgprj = args[0] os.chdir(hgprj)

if state:
    if os.path.exists(state):
        print 'State does exist, reading'
        f = open(state, 'r')
        hgvers = pickle.load(f)
    else:
        print 'State does not exist, first run'

tip = os.popen('hg tip | head -1 | cut -f 2 -d :').read().strip() print 'tip is', tip

# Calculate the branches
print 'analysing the branches...'
hgchildren["0"] = ()
hgbranch["0"] = "master"
for cset in range(1, int(tip) + 1):
    hgchildren[str(cset)] = ()
    prnts = os.popen('hg log -r %d | grep ^parent: | cut -f 2 -d :' % cset).readlines()
    if len(prnts) > 0:
        parent = prnts[0].strip()
    else:
        parent = str(cset - 1)
    hgchildren[parent] += ( str(cset), )
    if len(prnts) > 1:
        mparent = prnts[1].strip()
        hgchildren[mparent] += ( str(cset), )
    else:
        mparent = None
    if mparent:
        # For merge changesets, take either one, preferably the 'master' branch
        if hgbranch[mparent] == 'master':
            hgbranch[str(cset)] = 'master'
        else:
            hgbranch[str(cset)] = hgbranch[parent]
    else:
        # Normal changesets
        # For first children, take the parent branch, for the others create a new branch
        if hgchildren[parent][0] == str(cset):
            hgbranch[str(cset)] = hgbranch[parent]
        else:
            hgbranch[str(cset)] = "branch-" + str(cset)
if not hgvers.has_key("0"):
    print 'creating repository'
    os.system('git-init-db')

# loop through every hg changeset for cset in range(int(tip) + 1):

    # incremental, already seen
    if hgvers.has_key(str(cset)):
        continue
    # get info
    prnts = os.popen('hg log -r %d | grep ^parent: | cut -f 2 -d :' % cset).readlines()
    if len(prnts) > 0:
        parent = prnts[0].strip()
    else:
        parent = str(cset - 1)
    if len(prnts) > 1:
        mparent = prnts[1].strip()
    else:
        mparent = None
    (fdcomment, filecomment) = tempfile.mkstemp()
    csetcomment = os.popen('hg log -r %d -v | grep -v ^changeset: | grep -v ^parent: | grep -v ^user: | grep -v ^date | grep -v ^files: | grep -v ^description: | grep -v ^tag:' % cset).read().strip()
    os.write(fdcomment, csetcomment)
    os.close(fdcomment)
    date = os.popen('hg log -r %d | grep ^date: | cut -f 2- -d :' % cset).read().strip()
    tag = os.popen('hg log -r %d | grep ^tag: | cut -f 2- -d :' % cset).read().strip()
    user = os.popen('hg log -r %d | grep ^user: | cut -f 2- -d :' % cset).read().strip()
    print '-----------------------------------------'
    print 'cset:', cset
    print 'branch:', hgbranch[str(cset)]
    print 'user:', user
    print 'date:', date
    print 'comment:', csetcomment
    print 'parent:', parent
    if mparent:
        print 'mparent:', mparent
    if tag:
        print 'tag:', tag
    print '-----------------------------------------'
    # checkout the parent if necessary
    if cset != 0:
        if hgbranch[str(cset)] == "branch-" + str(cset):
            print 'creating new branch', hgbranch[str(cset)]
            os.system('git-checkout -b %s %s' % (hgbranch[str(cset)], hgvers[parent]))
        else:
            print 'checking out branch', hgbranch[str(cset)]
            os.system('git-checkout %s' % hgbranch[str(cset)])
    # merge
    if mparent:
        if hgbranch[parent] == hgbranch[str(cset)]:
            otherbranch = hgbranch[mparent]
        else:
            otherbranch = hgbranch[parent]
        print 'merging', otherbranch, 'into', hgbranch[str(cset)]
        os.system(getgitenv(user, date) + 'git-merge --no-commit -s ours "" %s %s' % (hgbranch[str(cset)], otherbranch))
    # remove everything except .git and .hg directories
    os.system('find . \( -path "./.hg" -o -path "./.git" \) -prune -o ! -name "." -print | xargs rm -rf')
    # repopulate with checkouted files
    os.system('hg update -C %d' % cset)
    # add new files
    os.system('git-ls-files -x .hg --others | git-update-index --add --stdin')
    # delete removed files
    os.system('git-ls-files -x .hg --deleted | git-update-index --remove --stdin')
    # commit
    os.system(getgitenv(user, date) + 'git-commit -a -F %s' % filecomment)
    os.unlink(filecomment)
    # tag
    if tag and tag != 'tip':
        os.system(getgitenv(user, date) + 'git-tag %s' % tag)
    # delete branch if not used anymore...
    if mparent and len(hgchildren[str(cset)]):
        print "Deleting unused branch:", otherbranch
        os.system('git-branch -d %s' % otherbranch)
    # retrieve and record the version
    vvv = os.popen('git-show | head -1').read()
    vvv = vvv[vvv.index(' ') + 1 : ].strip()
    print 'record', cset, '->', vvv
    hgvers[str(cset)] = vvv
os.system('git-repack -a -d')
# write the state for incrementals
if state:
    print 'Writing state'
    f = open(state, 'w')
    pickle.dump(hgvers, f)
# vim: et ts=8 sw=4 sts=4
-- 
Stelian Pop <stelian@popies.net>
Jakub Narebski· Jan 12, 2007, 19:28 UTC · re: Stelian Pop · lore

Re: Mercurial to git converter.

Stelian Pop wrote:
Show 14 quoted lines
> I switched recently from Mercurial to git for some of my projects, and
> needed a conversion tool with branch support (unlike tailor). I didn't
> find any, so I ended up writing my own one. 
> 
> hg-to-git.py can probably be greatly improved (it's a rather crude
> combination of shell and python) but it does already work quite well for
> me. Features:
>       - supports incremental conversion (keep a git repo in sync 
>         with a hg repository)
>       - supports hg branches
>       - converts hg tags 
> 
> Feel free to use it, improve it or even include it in mainline git (like
> git-svn and friends).
Thanks. Added to
  http://git.or.cz/gitwiki/InterfacesFrontendsAndTools#hg-to-git

(Perhaps you should send it as git patch adding file contrib/hg-to-git.py or contrib/git-hgimport/hg-to-git.py ?)

-- 
Jakub Narebski
Warsaw, Poland
ShadeHawk on #git
Stelian Pop· Jan 12, 2007, 20:14 UTC · re: Jakub Narebski · lore

Re: Mercurial to git converter.

Le vendredi 12 janvier 2007 à 20:28 +0100, Jakub Narebski a écrit :
Show 19 quoted lines
> Stelian Pop wrote:
> 
> > I switched recently from Mercurial to git for some of my projects, and
> > needed a conversion tool with branch support (unlike tailor). I didn't
> > find any, so I ended up writing my own one. 
> > 
> > hg-to-git.py can probably be greatly improved (it's a rather crude
> > combination of shell and python) but it does already work quite well for
> > me. Features:
> >       - supports incremental conversion (keep a git repo in sync 
> >         with a hg repository)
> >       - supports hg branches
> >       - converts hg tags 
> > 
> > Feel free to use it, improve it or even include it in mainline git (like
> > git-svn and friends).
> 
> Thanks. Added to
>   http://git.or.cz/gitwiki/InterfacesFrontendsAndTools#hg-to-git
Thanks.
> (Perhaps you should send it as git patch adding file contrib/hg-to-git.py
> or contrib/git-hgimport/hg-to-git.py ?)
Sure, I can do that (patch against 'master', right ?)

Do I put this in contrib/ as you suggest or directly in / (where friends like git-p4import.py live) ? What is most likely to be accepted ?

Stelian.
-- 
Stelian Pop <stelian@popies.net>
Stelian Pop· Jan 12, 2007, 21:57 UTC · re: Stelian Pop · lore

[PATCH] Add hg-to-git conversion utility.

hg-to-git.py is able to convert a Mercurial repository into a git one, and preserves the branches in the process (unlike tailor)

hg-to-git.py can probably be greatly improved (it's a rather crude
combination of shell and python) but it does already work quite well for
me. Features:
	- supports incremental conversion
	  (for keeping a git repo in sync with a hg one)
	- supports hg branches
	- converts hg tags
Signed-off-by: Stelian Pop <stelian@popies.net>
---
 Documentation/git-tools.txt     |    7 +
 contrib/hg-to-git/hg-to-git.py  |  233 +++++++++++++++++++++++++++++++++++++++
 contrib/hg-to-git/hg-to-git.txt |   22 ++++
 3 files changed, 262 insertions(+), 0 deletions(-)
diff --git a/Documentation/git-tools.txt b/Documentation/git-tools.txt
index 0914cbb..4d245d5 100644
--- a/Documentation/git-tools.txt
+++ b/Documentation/git-tools.txt
@@ -68,6 +68,13 @@ History Viewers
 Foreign SCM interface
 ---------------------
 
+   - *hg-to-git* (contrib/)
+
+   hg-to-git converts a Mercurial repository into a git one, and
+   preserves the full branch history in the process. hg-to-git can
+   also be used in an incremental way to keep the git repository
+   in sync with the master Mercurial repository.
+
    - *git-svn* (contrib/)
 
    git-svn is a simple conduit for changesets between a single Subversion
diff --git a/contrib/hg-to-git/hg-to-git.py b/contrib/hg-to-git/hg-to-git.py
new file mode 100755
index 0000000..37337ff
--- /dev/null
+++ b/contrib/hg-to-git/hg-to-git.py
@@ -0,0 +1,233 @@
+#! /usr/bin/python
+
+""" hg-to-svn.py - A Mercurial to GIT converter
+
+    Copyright (C)2007 Stelian Pop <stelian@popies.net>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2, or (at your option)
+    any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+"""
+
+import os, os.path, sys
+import tempfile, popen2, pickle, getopt
+import re
+
+# Maps hg version -> git version
+hgvers = {}
+# List of children for each hg revision
+hgchildren = {}
+# Current branch for each hg revision
+hgbranch = {}
+
+#------------------------------------------------------------------------------
+
+def usage():
+
+        print """\
+%s: [OPTIONS] <hgprj>
+
+options:
+    -s, --gitstate=FILE: name of the state to be saved/read
+                         for incrementals
+
+required:
+    hgprj:  name of the HG project to import (directory)
+""" % sys.argv[0]
+
+#------------------------------------------------------------------------------
+
+def getgitenv(user, date):
+    env = ''
+    elems = re.compile('(.*?)\s+<(.*)>').match(user)
+    if elems:
+        env += 'export GIT_AUTHOR_NAME="%s" ;' % elems.group(1)
+        env += 'export GIT_COMMITER_NAME="%s" ;' % elems.group(1)
+        env += 'export GIT_AUTHOR_EMAIL="%s" ;' % elems.group(2)
+        env += 'export GIT_COMMITER_EMAIL="%s" ;' % elems.group(2)
+    else:
+        env += 'export GIT_AUTHOR_NAME="%s" ;' % user
+        env += 'export GIT_COMMITER_NAME="%s" ;' % user
+        env += 'export GIT_AUTHOR_EMAIL= ;'
+        env += 'export GIT_COMMITER_EMAIL= ;'
+
+    env += 'export GIT_AUTHOR_DATE="%s" ;' % date
+    env += 'export GIT_COMMITTER_DATE="%s" ;' % date
+    return env
+
+#------------------------------------------------------------------------------
+
+state = ''
+
+try:
+    opts, args = getopt.getopt(sys.argv[1:], 's:t:', ['gitstate=', 'tempdir='])
+    for o, a in opts:
+        if o in ('-s', '--gitstate'):
+            state = a
+            state = os.path.abspath(state)
+
+    if len(args) != 1:
+        raise('params')
+except:
+    usage()
+    sys.exit(1)
+
+hgprj = args[0]
+os.chdir(hgprj)
+
+if state:
+    if os.path.exists(state):
+        print 'State does exist, reading'
+        f = open(state, 'r')
+        hgvers = pickle.load(f)
+    else:
+        print 'State does not exist, first run'
+
+tip = os.popen('hg tip | head -1 | cut -f 2 -d :').read().strip()
+print 'tip is', tip
+
+# Calculate the branches
+print 'analysing the branches...'
+hgchildren["0"] = ()
+hgbranch["0"] = "master"
+for cset in range(1, int(tip) + 1):
+    hgchildren[str(cset)] = ()
+    prnts = os.popen('hg log -r %d | grep ^parent: | cut -f 2 -d :' % cset).readlines()
+    if len(prnts) > 0:
+        parent = prnts[0].strip()
+    else:
+        parent = str(cset - 1)
+    hgchildren[parent] += ( str(cset), )
+    if len(prnts) > 1:
+        mparent = prnts[1].strip()
+        hgchildren[mparent] += ( str(cset), )
+    else:
+        mparent = None
+
+    if mparent:
+        # For merge changesets, take either one, preferably the 'master' branch
+        if hgbranch[mparent] == 'master':
+            hgbranch[str(cset)] = 'master'
+        else:
+            hgbranch[str(cset)] = hgbranch[parent]
+    else:
+        # Normal changesets
+        # For first children, take the parent branch, for the others create a new branch
+        if hgchildren[parent][0] == str(cset):
+            hgbranch[str(cset)] = hgbranch[parent]
+        else:
+            hgbranch[str(cset)] = "branch-" + str(cset)
+
+if not hgvers.has_key("0"):
+    print 'creating repository'
+    os.system('git-init-db')
+
+# loop through every hg changeset
+for cset in range(int(tip) + 1):
+
+    # incremental, already seen
+    if hgvers.has_key(str(cset)):
+        continue
+
+    # get info
+    prnts = os.popen('hg log -r %d | grep ^parent: | cut -f 2 -d :' % cset).readlines()
+    if len(prnts) > 0:
+        parent = prnts[0].strip()
+    else:
+        parent = str(cset - 1)
+    if len(prnts) > 1:
+        mparent = prnts[1].strip()
+    else:
+        mparent = None
+
+    (fdcomment, filecomment) = tempfile.mkstemp()
+    csetcomment = os.popen('hg log -r %d -v | grep -v ^changeset: | grep -v ^parent: | grep -v ^user: | grep -v ^date | grep -v ^files: | grep -v ^description: | grep -v ^tag:' % cset).read().strip()
+    os.write(fdcomment, csetcomment)
+    os.close(fdcomment)
+
+    date = os.popen('hg log -r %d | grep ^date: | cut -f 2- -d :' % cset).read().strip()
+
+    tag = os.popen('hg log -r %d | grep ^tag: | cut -f 2- -d :' % cset).read().strip()
+
+    user = os.popen('hg log -r %d | grep ^user: | cut -f 2- -d :' % cset).read().strip()
+
+    print '-----------------------------------------'
+    print 'cset:', cset
+    print 'branch:', hgbranch[str(cset)]
+    print 'user:', user
+    print 'date:', date
+    print 'comment:', csetcomment
+    print 'parent:', parent
+    if mparent:
+        print 'mparent:', mparent
+    if tag:
+        print 'tag:', tag
+    print '-----------------------------------------'
+
+    # checkout the parent if necessary
+    if cset != 0:
+        if hgbranch[str(cset)] == "branch-" + str(cset):
+            print 'creating new branch', hgbranch[str(cset)]
+            os.system('git-checkout -b %s %s' % (hgbranch[str(cset)], hgvers[parent]))
+        else:
+            print 'checking out branch', hgbranch[str(cset)]
+            os.system('git-checkout %s' % hgbranch[str(cset)])
+
+    # merge
+    if mparent:
+        if hgbranch[parent] == hgbranch[str(cset)]:
+            otherbranch = hgbranch[mparent]
+        else:
+            otherbranch = hgbranch[parent]
+        print 'merging', otherbranch, 'into', hgbranch[str(cset)]
+        os.system(getgitenv(user, date) + 'git-merge --no-commit -s ours "" %s %s' % (hgbranch[str(cset)], otherbranch))
+
+    # remove everything except .git and .hg directories
+    os.system('find . \( -path "./.hg" -o -path "./.git" \) -prune -o ! -name "." -print | xargs rm -rf')
+
+    # repopulate with checkouted files
+    os.system('hg update -C %d' % cset)
+
+    # add new files
+    os.system('git-ls-files -x .hg --others | git-update-index --add --stdin')
+    # delete removed files
+    os.system('git-ls-files -x .hg --deleted | git-update-index --remove --stdin')
+
+    # commit
+    os.system(getgitenv(user, date) + 'git-commit -a -F %s' % filecomment)
+    os.unlink(filecomment)
+
+    # tag
+    if tag and tag != 'tip':
+        os.system(getgitenv(user, date) + 'git-tag %s' % tag)
+
+    # delete branch if not used anymore...
+    if mparent and len(hgchildren[str(cset)]):
+        print "Deleting unused branch:", otherbranch
+        os.system('git-branch -d %s' % otherbranch)
+
+    # retrieve and record the version
+    vvv = os.popen('git-show | head -1').read()
+    vvv = vvv[vvv.index(' ') + 1 : ].strip()
+    print 'record', cset, '->', vvv
+    hgvers[str(cset)] = vvv
+
+os.system('git-repack -a -d')
+
+# write the state for incrementals
+if state:
+    print 'Writing state'
+    f = open(state, 'w')
+    pickle.dump(hgvers, f)
+
+# vim: et ts=8 sw=4 sts=4
diff --git a/contrib/hg-to-git/hg-to-git.txt b/contrib/hg-to-git/hg-to-git.txt
new file mode 100644
index 0000000..cf1c735
--- /dev/null
+++ b/contrib/hg-to-git/hg-to-git.txt
@@ -0,0 +1,22 @@
+hg-to-git.py is able to convert a Mercurial repository into a git one,
+and preserves the branches in the process (unlike tailor)
+
+hg-to-git.py can probably be greatly improved (it's a rather crude
+combination of shell and python) but it does already work quite well for
+me. Features:
+	- supports incremental conversion
+	  (for keeping a git repo in sync with a hg one)
+        - supports hg branches
+        - converts hg tags 
+
+Note that the git repository will be created 'in place' (at the same
+location as the source hg repo). You will have to manually remove the
+'.hg' directory after the conversion.
+
+Also note that the incremental conversion uses 'simple' hg changesets
+identifiers (ordinals, as opposed to SHA-1 ids), and since these ids
+are not stable across different repositories the hg-to-git.py state file
+is forever tied to one hg repository.
+
+-- 
+Stelian Pop <stelian@popies.net>
-- 
1.4.4.4
Shawn O. Pearce· Jan 12, 2007, 22:15 UTC · re: Stelian Pop · lore

Re: [PATCH] Add hg-to-git conversion utility.

Stelian Pop <stelian@popies.net> wrote:
> hg-to-git.py  is able to convert a Mercurial repository into a git one,
> and preserves the branches in the process (unlike tailor)
Cool!

You've already written the converter, but you may want to look at using git-fast-import as the backend interface to Git. I think your code may come out shorter, be clearer, and will run faster.

For example Simon Schubert's cvs -> git converter was written in one night (last night) in Ruby using git-fast-import as the backend (Simon already had the heavy lifting CVS portions done). I peeked at his source and its quite readable, though maybe that's just the Ruby talking. ;-) Simon is apparently trying to import FreeBSD's CVS repository to Git. A non-trivial task.

Chris Lee also has an svn -> git converter that uses gfi as its backend. Its only 150 or so lines of Python and is also quite readable. Chris is apparently trying to import KDE to Git. An equally non-trivial task.

I think the common thread between the two implementations is that there's less process forking and less manipulation of ugly environment variables. So the import runs faster and its a little easier to organize the code.

The gitweb (and mirror URLs) for gfi is available here:
  http://repo.or.cz/w/git/fastimport.git

Post 1.5.0 I'm going to nudge Junio to get gfi added to the main git.git repository. (Right now I don't want to distract him from the release.)

-- 
Shawn.
Stelian Pop· Jan 12, 2007, 23:00 UTC · re: Shawn O. Pearce · lore

Re: [PATCH] Add hg-to-git conversion utility.

Shawn O. Pearce a écrit :
Show 9 quoted lines
> Stelian Pop <stelian@popies.net> wrote:
>> hg-to-git.py  is able to convert a Mercurial repository into a git one,
>> and preserves the branches in the process (unlike tailor)
> 
> Cool!
> 
> You've already written the converter, but you may want to look at
> using git-fast-import as the backend interface to Git.  I think
> your code may come out shorter, be clearer, and will run faster.
[...]
> Post 1.5.0 I'm going to nudge Junio to get gfi added to the main
> git.git repository.  (Right now I don't want to distract him from
> the release.)

Rebasing hg-to-git.py on gfi will probably be a very good idea once gfi is in the tree.

But I wrote this since I had a need for it, and once my repositories converted to git I'm not sure I'll need it again :) So don't count too much on me to do this rebasing :)

But who knows, on a cold rainy day...
-- 
Stelian Pop <stelian@popies.net>
Junio C Hamano· Jan 12, 2007, 23:14 UTC · re: Stelian Pop · lore

Re: [PATCH] Add hg-to-git conversion utility.

Stelian Pop <stelian@popies.net> writes:
> But I wrote this since I had a need for it, and once my repositories
> converted to git I'm not sure I'll need it again :)

It is not a good strategy to say upfront that you are not going to maintain it, when you are submitting something for inclusion.

Stelian Pop· Jan 12, 2007, 23:18 UTC · re: Junio C Hamano · lore

Re: [PATCH] Add hg-to-git conversion utility.

Junio C Hamano a écrit :
Show 7 quoted lines
> Stelian Pop <stelian@popies.net> writes:
> 
>> But I wrote this since I had a need for it, and once my repositories
>> converted to git I'm not sure I'll need it again :)
> 
> It is not a good strategy to say upfront that you are not going
> to maintain it, when you are submitting something for inclusion.
Hey, I didn't say I won't fix it if it breaks !

I may not have the time and need to make it better than it already is though.

-- 
Stelian Pop <stelian@popies.net>
Nick Andrew· Jun 14, 2008, 10:23 UTC · re: Stelian Pop · lore

Re: Mercurial to git converter.

Stelian Pop <stelian <at> popies.net> writes:
Show 7 quoted lines
> I switched recently from Mercurial to git for some of my projects, and
> needed a conversion tool with branch support (unlike tailor). I didn't
> find any, so I ended up writing my own one. 
> 
> hg-to-git.py can probably be greatly improved (it's a rather crude
> combination of shell and python) but it does already work quite well for
> me. [...]

Thanks for that. One small problem is that it does not get the changelog right if a commit is made on a local branch. It thinks the "branch:" line is part of the changelog.

Here's a patch to fix that:

diff -u -r1.1 hg-to-git.py --- hg-to-git.py +++ hg-to-git.py

@@ -151,7 +151,7 @@
         mparent = None

     (fdcomment, filecomment) = tempfile.mkstemp()
-    csetcomment = os.popen('hg log -r %d -v | grep -v ^changeset: | grep -v 
^parent: | grep -v ^user: | grep -v ^date | grep -v ^files: | grep -v 
^description: | grep -v ^tag:' % cset).read().strip()
+    csetcomment = os.popen('hg log -r %d -v | egrep -v "^(changeset|parent|
user|date|files|description|tag|branch):" ' % cset).read().strip()
     os.write(fdcomment, csetcomment)
     os.close(fdcomment)


Nick.
Johannes Schindelin· Jun 14, 2008, 15:53 UTC · re: Nick Andrew · lore

Re: Mercurial to git converter.

Hi,
On Sat, 14 Jun 2008, Nick Andrew wrote:
Show 13 quoted lines
> Stelian Pop <stelian <at> popies.net> writes:
> 
> > I switched recently from Mercurial to git for some of my projects, and
> > needed a conversion tool with branch support (unlike tailor). I didn't
> > find any, so I ended up writing my own one. 
> > 
> > hg-to-git.py can probably be greatly improved (it's a rather crude 
> > combination of shell and python) but it does already work quite well 
> > for me. [...]
> 
> Thanks for that. One small problem is that it does not get the changelog 
> right if a commit is made on a local branch. It thinks the "branch:" 
> line is part of the changelog.

FWIW I switched my local mercurial2git conversion to the hg-fast-export solution from

	git://repo.or.cz/fast-export.git

Even if it's documentation leaves almost completely to be desired, after figuring out how to operate it by reading the source, it works pretty nicely, and fast, because it uses fast-import.

Originally, I did not try it because I thought it cannot operate in incremental mode, but it can.

Ciao, Dscho

Jakub Narebski· Jun 14, 2008, 16:48 UTC · re: Johannes Schindelin · lore

Re: Mercurial to git converter.

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
Show 6 quoted lines
> On Sat, 14 Jun 2008, Nick Andrew wrote: 
>> Stelian Pop <stelian <at> popies.net> writes:
>> 
>>> hg-to-git.py can probably be greatly improved (it's a rather crude 
>>> combination of shell and python) but it does already work quite well 
>>> for me. [...]
[...]
 
Show 5 quoted lines
> FWIW I switched my local mercurial2git conversion to the hg-fast-export 
> solution from 
> 
> 	git://repo.or.cz/fast-export.git
>
[...] 
> Originally, I did not try it because I thought it cannot operate in 
> incremental mode, but it can.

It can, or it can recently thanks to --export-marks / --import-marks work?

It is nice that SCM-to-SCM converters seems to standarize on using fast-import language as intermediary.

IIRC fast-import by Shawn O. Pearce started as fast backend for first cvs2git (unfortunately fork and not extension of cvs2svn) by Jon Smirl (?) around time when Mozilla looked for SCM to move to from CVS. (In the end they choose Mercurial, mainly because of better Windows support, and I think also better Windows GUI; I wonder how it works for them now?).

-- 
Jakub Narebski
Poland
ShadeHawk on #git
Shawn O. Pearce· Jun 16, 2008, 02:24 UTC · re: Jakub Narebski · lore

Re: Mercurial to git converter.

Jakub Narebski <jnareb@gmail.com> wrote:
Show 12 quoted lines
> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> > FWIW I switched my local mercurial2git conversion to the hg-fast-export 
> > solution from 
> > 
> > 	git://repo.or.cz/fast-export.git
> >
> [...] 
> > Originally, I did not try it because I thought it cannot operate in 
> > incremental mode, but it can.
> 
> It can, or it can recently thanks to --export-marks / --import-marks
> work?
The recent --export-marks/import-marks work you speak of was adding
these options to git-fast-export, making it easier to perform an
incremental _export_ from Git to another SCM.  I believe Dscho
is speaking about an Hg->Git conversion, which would rely on
git-fast-import's previously existing --export-marks/import-marks
feature.
 
> It is nice that SCM-to-SCM converters seems to standarize on using
> fast-import language as intermediary.  

Turns out the little format we knocked together for this is able to represent most conversions for SCMs. I'm just happy users are easily able to convert into Git.

Show 6 quoted lines
> IIRC fast-import by Shawn O. Pearce started as fast backend for first
> cvs2git (unfortunately fork and not extension of cvs2svn) by Jon Smirl
> (?) around time when Mozilla looked for SCM to move to from CVS.  (In
> the end they choose Mercurial, mainly because of better Windows
> support, and I think also better Windows GUI; I wonder how it works
> for them now?).

Your history is correct. The current version of cvs2svn also supports cvs2git, and may also support cvs2bzr by using the git-fast-import backend and bzr-fast-import as the parser.

-- 
Shawn.
Nick Andrew· Jun 16, 2008, 08:21 UTC · re: Johannes Schindelin · lore

Re: Mercurial to git converter.

On Sat, Jun 14, 2008 at 04:53:04PM +0100, Johannes Schindelin wrote:
> FWIW I switched my local mercurial2git conversion to the hg-fast-export 
> solution from 
> 
> 	git://repo.or.cz/fast-export.git
Yep.

In my case I needed to convert only a single repository with only a few tens of commits, so the slow script was adequate for my use ... except for that problem with the changelog messages - which was trivially fixed, so I posted to the list only to document the fix.

Nick.
-- 
PGP Key ID = 0x418487E7                      http://www.nick-andrew.net/
PGP Key fingerprint = B3ED 6894 8E49 1770 C24A  67E3 6266 6EB9 4184 87E7
Mike Hommey· Jun 16, 2008, 08:26 UTC · re: Johannes Schindelin · lore

Re: Mercurial to git converter.

On Sat, Jun 14, 2008 at 04:53:04PM +0100, Johannes Schindelin wrote:
Show 22 quoted lines
> Hi,
> 
> On Sat, 14 Jun 2008, Nick Andrew wrote:
> 
> > Stelian Pop <stelian <at> popies.net> writes:
> > 
> > > I switched recently from Mercurial to git for some of my projects, and
> > > needed a conversion tool with branch support (unlike tailor). I didn't
> > > find any, so I ended up writing my own one. 
> > > 
> > > hg-to-git.py can probably be greatly improved (it's a rather crude 
> > > combination of shell and python) but it does already work quite well 
> > > for me. [...]
> > 
> > Thanks for that. One small problem is that it does not get the changelog 
> > right if a commit is made on a local branch. It thinks the "branch:" 
> > line is part of the changelog.
> 
> FWIW I switched my local mercurial2git conversion to the hg-fast-export 
> solution from 
> 
> 	git://repo.or.cz/fast-export.git

Would you happen to have a clonable mirror or this ? repo.or.cz doesn't respond.

Mike
Johannes Schindelin· Jun 17, 2008, 09:12 UTC · re: Mike Hommey · lore

Re: Mercurial to git converter.

Hi,
On Mon, 16 Jun 2008, Mike Hommey wrote:
> Would you happen to have a clonable mirror or this ? repo.or.cz doesn't 
> respond.

I'm sorry, I was travelling all day yesterday. repo.or.cz is back, but I have a mirror for fast-export.git. However, a few months ago, mugwump and me set up a mirror that should work (but AFAICT is http:// only): http://git.utsl.gen.nz/mirror/repo.or.cz/.

Ciao, Dscho

← back to recent threads