contrib / hg-to-git / hg-to-git.pyon commit Merge branch 'maint' (08e1812)
   1#! /usr/bin/python
   2
   3""" hg-to-svn.py - A Mercurial to GIT converter
   4
   5    Copyright (C)2007 Stelian Pop <stelian@popies.net>
   6
   7    This program is free software; you can redistribute it and/or modify
   8    it under the terms of the GNU General Public License as published by
   9    the Free Software Foundation; either version 2, or (at your option)
  10    any later version.
  11
  12    This program is distributed in the hope that it will be useful,
  13    but WITHOUT ANY WARRANTY; without even the implied warranty of
  14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15    GNU General Public License for more details.
  16
  17    You should have received a copy of the GNU General Public License
  18    along with this program; if not, write to the Free Software
  19    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  20"""
  21
  22import os, os.path, sys
  23import tempfile, popen2, pickle, getopt
  24import re
  25
  26# Maps hg version -> git version
  27hgvers = {}
  28# List of children for each hg revision
  29hgchildren = {}
  30# Current branch for each hg revision
  31hgbranch = {}
  32# Number of new changesets converted from hg
  33hgnewcsets = 0
  34
  35#------------------------------------------------------------------------------
  36
  37def usage():
  38
  39        print """\
  40%s: [OPTIONS] <hgprj>
  41
  42options:
  43    -s, --gitstate=FILE: name of the state to be saved/read
  44                         for incrementals
  45    -n, --nrepack=INT:   number of changesets that will trigger
  46                         a repack (default=0, -1 to deactivate)
  47
  48required:
  49    hgprj:  name of the HG project to import (directory)
  50""" % sys.argv[0]
  51
  52#------------------------------------------------------------------------------
  53
  54def getgitenv(user, date):
  55    env = ''
  56    elems = re.compile('(.*?)\s+<(.*)>').match(user)
  57    if elems:
  58        env += 'export GIT_AUTHOR_NAME="%s" ;' % elems.group(1)
  59        env += 'export GIT_COMMITER_NAME="%s" ;' % elems.group(1)
  60        env += 'export GIT_AUTHOR_EMAIL="%s" ;' % elems.group(2)
  61        env += 'export GIT_COMMITER_EMAIL="%s" ;' % elems.group(2)
  62    else:
  63        env += 'export GIT_AUTHOR_NAME="%s" ;' % user
  64        env += 'export GIT_COMMITER_NAME="%s" ;' % user
  65        env += 'export GIT_AUTHOR_EMAIL= ;'
  66        env += 'export GIT_COMMITER_EMAIL= ;'
  67
  68    env += 'export GIT_AUTHOR_DATE="%s" ;' % date
  69    env += 'export GIT_COMMITTER_DATE="%s" ;' % date
  70    return env
  71
  72#------------------------------------------------------------------------------
  73
  74state = ''
  75opt_nrepack = 0
  76
  77try:
  78    opts, args = getopt.getopt(sys.argv[1:], 's:t:n:', ['gitstate=', 'tempdir=', 'nrepack='])
  79    for o, a in opts:
  80        if o in ('-s', '--gitstate'):
  81            state = a
  82            state = os.path.abspath(state)
  83        if o in ('-n', '--nrepack'):
  84            opt_nrepack = int(a)
  85    if len(args) != 1:
  86        raise('params')
  87except:
  88    usage()
  89    sys.exit(1)
  90
  91hgprj = args[0]
  92os.chdir(hgprj)
  93
  94if state:
  95    if os.path.exists(state):
  96        print 'State does exist, reading'
  97        f = open(state, 'r')
  98        hgvers = pickle.load(f)
  99    else:
 100        print 'State does not exist, first run'
 101
 102tip = os.popen('hg tip | head -1 | cut -f 2 -d :').read().strip()
 103print 'tip is', tip
 104
 105# Calculate the branches
 106print 'analysing the branches...'
 107hgchildren["0"] = ()
 108hgbranch["0"] = "master"
 109for cset in range(1, int(tip) + 1):
 110    hgchildren[str(cset)] = ()
 111    prnts = os.popen('hg log -r %d | grep ^parent: | cut -f 2 -d :' % cset).readlines()
 112    if len(prnts) > 0:
 113        parent = prnts[0].strip()
 114    else:
 115        parent = str(cset - 1)
 116    hgchildren[parent] += ( str(cset), )
 117    if len(prnts) > 1:
 118        mparent = prnts[1].strip()
 119        hgchildren[mparent] += ( str(cset), )
 120    else:
 121        mparent = None
 122
 123    if mparent:
 124        # For merge changesets, take either one, preferably the 'master' branch
 125        if hgbranch[mparent] == 'master':
 126            hgbranch[str(cset)] = 'master'
 127        else:
 128            hgbranch[str(cset)] = hgbranch[parent]
 129    else:
 130        # Normal changesets
 131        # For first children, take the parent branch, for the others create a new branch
 132        if hgchildren[parent][0] == str(cset):
 133            hgbranch[str(cset)] = hgbranch[parent]
 134        else:
 135            hgbranch[str(cset)] = "branch-" + str(cset)
 136
 137if not hgvers.has_key("0"):
 138    print 'creating repository'
 139    os.system('git-init-db')
 140
 141# loop through every hg changeset
 142for cset in range(int(tip) + 1):
 143
 144    # incremental, already seen
 145    if hgvers.has_key(str(cset)):
 146        continue
 147    hgnewcsets += 1
 148
 149    # get info
 150    prnts = os.popen('hg log -r %d | grep ^parent: | cut -f 2 -d :' % cset).readlines()
 151    if len(prnts) > 0:
 152        parent = prnts[0].strip()
 153    else:
 154        parent = str(cset - 1)
 155    if len(prnts) > 1:
 156        mparent = prnts[1].strip()
 157    else:
 158        mparent = None
 159
 160    (fdcomment, filecomment) = tempfile.mkstemp()
 161    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()
 162    os.write(fdcomment, csetcomment)
 163    os.close(fdcomment)
 164
 165    date = os.popen('hg log -r %d | grep ^date: | cut -f 2- -d :' % cset).read().strip()
 166
 167    tag = os.popen('hg log -r %d | grep ^tag: | cut -f 2- -d :' % cset).read().strip()
 168
 169    user = os.popen('hg log -r %d | grep ^user: | cut -f 2- -d :' % cset).read().strip()
 170
 171    print '-----------------------------------------'
 172    print 'cset:', cset
 173    print 'branch:', hgbranch[str(cset)]
 174    print 'user:', user
 175    print 'date:', date
 176    print 'comment:', csetcomment
 177    print 'parent:', parent
 178    if mparent:
 179        print 'mparent:', mparent
 180    if tag:
 181        print 'tag:', tag
 182    print '-----------------------------------------'
 183
 184    # checkout the parent if necessary
 185    if cset != 0:
 186        if hgbranch[str(cset)] == "branch-" + str(cset):
 187            print 'creating new branch', hgbranch[str(cset)]
 188            os.system('git-checkout -b %s %s' % (hgbranch[str(cset)], hgvers[parent]))
 189        else:
 190            print 'checking out branch', hgbranch[str(cset)]
 191            os.system('git-checkout %s' % hgbranch[str(cset)])
 192
 193    # merge
 194    if mparent:
 195        if hgbranch[parent] == hgbranch[str(cset)]:
 196            otherbranch = hgbranch[mparent]
 197        else:
 198            otherbranch = hgbranch[parent]
 199        print 'merging', otherbranch, 'into', hgbranch[str(cset)]
 200        os.system(getgitenv(user, date) + 'git-merge --no-commit -s ours "" %s %s' % (hgbranch[str(cset)], otherbranch))
 201
 202    # remove everything except .git and .hg directories
 203    os.system('find . \( -path "./.hg" -o -path "./.git" \) -prune -o ! -name "." -print | xargs rm -rf')
 204
 205    # repopulate with checkouted files
 206    os.system('hg update -C %d' % cset)
 207
 208    # add new files
 209    os.system('git-ls-files -x .hg --others | git-update-index --add --stdin')
 210    # delete removed files
 211    os.system('git-ls-files -x .hg --deleted | git-update-index --remove --stdin')
 212
 213    # commit
 214    os.system(getgitenv(user, date) + 'git commit --allow-empty -a -F %s' % filecomment)
 215    os.unlink(filecomment)
 216
 217    # tag
 218    if tag and tag != 'tip':
 219        os.system(getgitenv(user, date) + 'git-tag %s' % tag)
 220
 221    # delete branch if not used anymore...
 222    if mparent and len(hgchildren[str(cset)]):
 223        print "Deleting unused branch:", otherbranch
 224        os.system('git-branch -d %s' % otherbranch)
 225
 226    # retrieve and record the version
 227    vvv = os.popen('git-show | head -1').read()
 228    vvv = vvv[vvv.index(' ') + 1 : ].strip()
 229    print 'record', cset, '->', vvv
 230    hgvers[str(cset)] = vvv
 231
 232if hgnewcsets >= opt_nrepack and opt_nrepack != -1:
 233    os.system('git-repack -a -d')
 234
 235# write the state for incrementals
 236if state:
 237    print 'Writing state'
 238    f = open(state, 'w')
 239    pickle.dump(hgvers, f)
 240
 241# vim: et ts=8 sw=4 sts=4