Windows: Wrap execve so that shell scripts can be invoked.
[git/vmiklos.git] / contrib / hg-to-git / hg-to-git.py
blobf68ef725d42cdb1a6082574f9ef3a45d2346c296
1 #! /usr/bin/python
3 """ hg-to-git.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 # List of parents for each hg revision
31 hgparents = {}
32 # Current branch for each hg revision
33 hgbranch = {}
34 # Number of new changesets converted from hg
35 hgnewcsets = 0
37 #------------------------------------------------------------------------------
39 def usage():
41 print """\
42 %s: [OPTIONS] <hgprj>
44 options:
45 -s, --gitstate=FILE: name of the state to be saved/read
46 for incrementals
47 -n, --nrepack=INT: number of changesets that will trigger
48 a repack (default=0, -1 to deactivate)
49 -v, --verbose: be verbose
51 required:
52 hgprj: name of the HG project to import (directory)
53 """ % sys.argv[0]
55 #------------------------------------------------------------------------------
57 def getgitenv(user, date):
58 env = ''
59 elems = re.compile('(.*?)\s+<(.*)>').match(user)
60 if elems:
61 env += 'export GIT_AUTHOR_NAME="%s" ;' % elems.group(1)
62 env += 'export GIT_COMMITER_NAME="%s" ;' % elems.group(1)
63 env += 'export GIT_AUTHOR_EMAIL="%s" ;' % elems.group(2)
64 env += 'export GIT_COMMITER_EMAIL="%s" ;' % elems.group(2)
65 else:
66 env += 'export GIT_AUTHOR_NAME="%s" ;' % user
67 env += 'export GIT_COMMITER_NAME="%s" ;' % user
68 env += 'export GIT_AUTHOR_EMAIL= ;'
69 env += 'export GIT_COMMITER_EMAIL= ;'
71 env += 'export GIT_AUTHOR_DATE="%s" ;' % date
72 env += 'export GIT_COMMITTER_DATE="%s" ;' % date
73 return env
75 #------------------------------------------------------------------------------
77 state = ''
78 opt_nrepack = 0
79 verbose = False
81 try:
82 opts, args = getopt.getopt(sys.argv[1:], 's:t:n:v', ['gitstate=', 'tempdir=', 'nrepack=', 'verbose'])
83 for o, a in opts:
84 if o in ('-s', '--gitstate'):
85 state = a
86 state = os.path.abspath(state)
87 if o in ('-n', '--nrepack'):
88 opt_nrepack = int(a)
89 if o in ('-v', '--verbose'):
90 verbose = True
91 if len(args) != 1:
92 raise('params')
93 except:
94 usage()
95 sys.exit(1)
97 hgprj = args[0]
98 os.chdir(hgprj)
100 if state:
101 if os.path.exists(state):
102 if verbose:
103 print 'State does exist, reading'
104 f = open(state, 'r')
105 hgvers = pickle.load(f)
106 else:
107 print 'State does not exist, first run'
109 tip = os.popen('hg tip --template "{rev}"').read()
110 if verbose:
111 print 'tip is', tip
113 # Calculate the branches
114 if verbose:
115 print 'analysing the branches...'
116 hgchildren["0"] = ()
117 hgparents["0"] = (None, None)
118 hgbranch["0"] = "master"
119 for cset in range(1, int(tip) + 1):
120 hgchildren[str(cset)] = ()
121 prnts = os.popen('hg log -r %d --template "{parents}"' % cset).read().strip().split(' ')
122 prnts = map(lambda x: x[:x.find(':')], prnts)
123 if prnts[0] != '':
124 parent = prnts[0].strip()
125 else:
126 parent = str(cset - 1)
127 hgchildren[parent] += ( str(cset), )
128 if len(prnts) > 1:
129 mparent = prnts[1].strip()
130 hgchildren[mparent] += ( str(cset), )
131 else:
132 mparent = None
134 hgparents[str(cset)] = (parent, mparent)
136 if mparent:
137 # For merge changesets, take either one, preferably the 'master' branch
138 if hgbranch[mparent] == 'master':
139 hgbranch[str(cset)] = 'master'
140 else:
141 hgbranch[str(cset)] = hgbranch[parent]
142 else:
143 # Normal changesets
144 # For first children, take the parent branch, for the others create a new branch
145 if hgchildren[parent][0] == str(cset):
146 hgbranch[str(cset)] = hgbranch[parent]
147 else:
148 hgbranch[str(cset)] = "branch-" + str(cset)
150 if not hgvers.has_key("0"):
151 print 'creating repository'
152 os.system('git-init-db')
154 # loop through every hg changeset
155 for cset in range(int(tip) + 1):
157 # incremental, already seen
158 if hgvers.has_key(str(cset)):
159 continue
160 hgnewcsets += 1
162 # get info
163 log_data = os.popen('hg log -r %d --template "{tags}\n{date|date}\n{author}\n"' % cset).readlines()
164 tag = log_data[0].strip()
165 date = log_data[1].strip()
166 user = log_data[2].strip()
167 parent = hgparents[str(cset)][0]
168 mparent = hgparents[str(cset)][1]
170 #get comment
171 (fdcomment, filecomment) = tempfile.mkstemp()
172 csetcomment = os.popen('hg log -r %d --template "{desc}"' % cset).read().strip()
173 os.write(fdcomment, csetcomment)
174 os.close(fdcomment)
176 print '-----------------------------------------'
177 print 'cset:', cset
178 print 'branch:', hgbranch[str(cset)]
179 print 'user:', user
180 print 'date:', date
181 print 'comment:', csetcomment
182 if parent:
183 print 'parent:', parent
184 if mparent:
185 print 'mparent:', mparent
186 if tag:
187 print 'tag:', tag
188 print '-----------------------------------------'
190 # checkout the parent if necessary
191 if cset != 0:
192 if hgbranch[str(cset)] == "branch-" + str(cset):
193 print 'creating new branch', hgbranch[str(cset)]
194 os.system('git-checkout -b %s %s' % (hgbranch[str(cset)], hgvers[parent]))
195 else:
196 print 'checking out branch', hgbranch[str(cset)]
197 os.system('git-checkout %s' % hgbranch[str(cset)])
199 # merge
200 if mparent:
201 if hgbranch[parent] == hgbranch[str(cset)]:
202 otherbranch = hgbranch[mparent]
203 else:
204 otherbranch = hgbranch[parent]
205 print 'merging', otherbranch, 'into', hgbranch[str(cset)]
206 os.system(getgitenv(user, date) + 'git-merge --no-commit -s ours "" %s %s' % (hgbranch[str(cset)], otherbranch))
208 # remove everything except .git and .hg directories
209 os.system('find . \( -path "./.hg" -o -path "./.git" \) -prune -o ! -name "." -print | xargs rm -rf')
211 # repopulate with checkouted files
212 os.system('hg update -C %d' % cset)
214 # add new files
215 os.system('git-ls-files -x .hg --others | git-update-index --add --stdin')
216 # delete removed files
217 os.system('git-ls-files -x .hg --deleted | git-update-index --remove --stdin')
219 # commit
220 os.system(getgitenv(user, date) + 'git commit --allow-empty -a -F %s' % filecomment)
221 os.unlink(filecomment)
223 # tag
224 if tag and tag != 'tip':
225 os.system(getgitenv(user, date) + 'git-tag %s' % tag)
227 # delete branch if not used anymore...
228 if mparent and len(hgchildren[str(cset)]):
229 print "Deleting unused branch:", otherbranch
230 os.system('git-branch -d %s' % otherbranch)
232 # retrieve and record the version
233 vvv = os.popen('git-show --quiet --pretty=format:%H').read()
234 print 'record', cset, '->', vvv
235 hgvers[str(cset)] = vvv
237 if hgnewcsets >= opt_nrepack and opt_nrepack != -1:
238 os.system('git-repack -a -d')
240 # write the state for incrementals
241 if state:
242 if verbose:
243 print 'Writing state'
244 f = open(state, 'w')
245 pickle.dump(hgvers, f)
247 # vim: et ts=8 sw=4 sts=4