7a1c3e497f00fd886a0602551bfa933931a995be
1 #! /usr/bin/python
3 """ hg-to-svn.py - A Mercurial to GIT converter
5 Copyright (C)2007 Stelian Pop <stelian@popies.net>
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.
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.
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 """
22 import os, os.path, sys
23 import tempfile, popen2, pickle, getopt
24 import re
26 # Maps hg version -> git version
27 hgvers = {}
28 # List of children for each hg revision
29 hgchildren = {}
30 # Current branch for each hg revision
31 hgbranch = {}
32 # Number of new changesets converted from hg
33 hgnewcsets = 0
35 #------------------------------------------------------------------------------
37 def usage():
39 print """\
40 %s: [OPTIONS] <hgprj>
42 options:
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)
48 required:
49 hgprj: name of the HG project to import (directory)
50 """ % sys.argv[0]
52 #------------------------------------------------------------------------------
54 def 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= ;'
68 env += 'export GIT_AUTHOR_DATE="%s" ;' % date
69 env += 'export GIT_COMMITTER_DATE="%s" ;' % date
70 return env
72 #------------------------------------------------------------------------------
74 state = ''
75 opt_nrepack = 0
77 try:
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')
87 except:
88 usage()
89 sys.exit(1)
91 hgprj = args[0]
92 os.chdir(hgprj)
94 if 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'
102 tip = os.popen('hg tip | head -1 | cut -f 2 -d :').read().strip()
103 print 'tip is', tip
105 # Calculate the branches
106 print 'analysing the branches...'
107 hgchildren["0"] = ()
108 hgbranch["0"] = "master"
109 for 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
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)
137 if not hgvers.has_key("0"):
138 print 'creating repository'
139 os.system('git-init-db')
141 # loop through every hg changeset
142 for cset in range(int(tip) + 1):
144 # incremental, already seen
145 if hgvers.has_key(str(cset)):
146 continue
147 hgnewcsets += 1
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
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)
165 date = os.popen('hg log -r %d | grep ^date: | cut -f 2- -d :' % cset).read().strip()
167 tag = os.popen('hg log -r %d | grep ^tag: | cut -f 2- -d :' % cset).read().strip()
169 user = os.popen('hg log -r %d | grep ^user: | cut -f 2- -d :' % cset).read().strip()
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 '-----------------------------------------'
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)])
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))
202 # remove everything except .git and .hg directories
203 os.system('find . \( -path "./.hg" -o -path "./.git" \) -prune -o ! -name "." -print | xargs rm -rf')
205 # repopulate with checkouted files
206 os.system('hg update -C %d' % cset)
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')
213 # commit
214 os.system(getgitenv(user, date) + 'git-commit -a -F %s' % filecomment)
215 os.unlink(filecomment)
217 # tag
218 if tag and tag != 'tip':
219 os.system(getgitenv(user, date) + 'git-tag %s' % tag)
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)
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
232 if hgnewcsets >= opt_nrepack and opt_nrepack != -1:
233 os.system('git-repack -a -d')
235 # write the state for incrementals
236 if state:
237 print 'Writing state'
238 f = open(state, 'w')
239 pickle.dump(hgvers, f)
241 # vim: et ts=8 sw=4 sts=4