Merge branch 'maint'
[git/spearce.git] / contrib / hg-to-git / hg-to-git.py
blob2a6839d81ee970cb799859c7a833f8f8cb780864
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, 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 Exception('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 sock = os.popen('hg tip --template "{rev}"')
110 tip = sock.read()
111 if sock.close():
112 sys.exit(1)
113 if verbose:
114 print 'tip is', tip
116 # Calculate the branches
117 if verbose:
118 print 'analysing the branches...'
119 hgchildren["0"] = ()
120 hgparents["0"] = (None, None)
121 hgbranch["0"] = "master"
122 for cset in range(1, int(tip) + 1):
123 hgchildren[str(cset)] = ()
124 prnts = os.popen('hg log -r %d --template "{parents}"' % cset).read().strip().split(' ')
125 prnts = map(lambda x: x[:x.find(':')], prnts)
126 if prnts[0] != '':
127 parent = prnts[0].strip()
128 else:
129 parent = str(cset - 1)
130 hgchildren[parent] += ( str(cset), )
131 if len(prnts) > 1:
132 mparent = prnts[1].strip()
133 hgchildren[mparent] += ( str(cset), )
134 else:
135 mparent = None
137 hgparents[str(cset)] = (parent, mparent)
139 if mparent:
140 # For merge changesets, take either one, preferably the 'master' branch
141 if hgbranch[mparent] == 'master':
142 hgbranch[str(cset)] = 'master'
143 else:
144 hgbranch[str(cset)] = hgbranch[parent]
145 else:
146 # Normal changesets
147 # For first children, take the parent branch, for the others create a new branch
148 if hgchildren[parent][0] == str(cset):
149 hgbranch[str(cset)] = hgbranch[parent]
150 else:
151 hgbranch[str(cset)] = "branch-" + str(cset)
153 if not hgvers.has_key("0"):
154 print 'creating repository'
155 os.system('git init')
157 # loop through every hg changeset
158 for cset in range(int(tip) + 1):
160 # incremental, already seen
161 if hgvers.has_key(str(cset)):
162 continue
163 hgnewcsets += 1
165 # get info
166 log_data = os.popen('hg log -r %d --template "{tags}\n{date|date}\n{author}\n"' % cset).readlines()
167 tag = log_data[0].strip()
168 date = log_data[1].strip()
169 user = log_data[2].strip()
170 parent = hgparents[str(cset)][0]
171 mparent = hgparents[str(cset)][1]
173 #get comment
174 (fdcomment, filecomment) = tempfile.mkstemp()
175 csetcomment = os.popen('hg log -r %d --template "{desc}"' % cset).read().strip()
176 os.write(fdcomment, csetcomment)
177 os.close(fdcomment)
179 print '-----------------------------------------'
180 print 'cset:', cset
181 print 'branch:', hgbranch[str(cset)]
182 print 'user:', user
183 print 'date:', date
184 print 'comment:', csetcomment
185 if parent:
186 print 'parent:', parent
187 if mparent:
188 print 'mparent:', mparent
189 if tag:
190 print 'tag:', tag
191 print '-----------------------------------------'
193 # checkout the parent if necessary
194 if cset != 0:
195 if hgbranch[str(cset)] == "branch-" + str(cset):
196 print 'creating new branch', hgbranch[str(cset)]
197 os.system('git checkout -b %s %s' % (hgbranch[str(cset)], hgvers[parent]))
198 else:
199 print 'checking out branch', hgbranch[str(cset)]
200 os.system('git checkout %s' % hgbranch[str(cset)])
202 # merge
203 if mparent:
204 if hgbranch[parent] == hgbranch[str(cset)]:
205 otherbranch = hgbranch[mparent]
206 else:
207 otherbranch = hgbranch[parent]
208 print 'merging', otherbranch, 'into', hgbranch[str(cset)]
209 os.system(getgitenv(user, date) + 'git merge --no-commit -s ours "" %s %s' % (hgbranch[str(cset)], otherbranch))
211 # remove everything except .git and .hg directories
212 os.system('find . \( -path "./.hg" -o -path "./.git" \) -prune -o ! -name "." -print | xargs rm -rf')
214 # repopulate with checkouted files
215 os.system('hg update -C %d' % cset)
217 # add new files
218 os.system('git ls-files -x .hg --others | git update-index --add --stdin')
219 # delete removed files
220 os.system('git ls-files -x .hg --deleted | git update-index --remove --stdin')
222 # commit
223 os.system(getgitenv(user, date) + 'git commit --allow-empty -a -F %s' % filecomment)
224 os.unlink(filecomment)
226 # tag
227 if tag and tag != 'tip':
228 os.system(getgitenv(user, date) + 'git tag %s' % tag)
230 # delete branch if not used anymore...
231 if mparent and len(hgchildren[str(cset)]):
232 print "Deleting unused branch:", otherbranch
233 os.system('git branch -d %s' % otherbranch)
235 # retrieve and record the version
236 vvv = os.popen('git show --quiet --pretty=format:%H').read()
237 print 'record', cset, '->', vvv
238 hgvers[str(cset)] = vvv
240 if hgnewcsets >= opt_nrepack and opt_nrepack != -1:
241 os.system('git repack -a -d')
243 # write the state for incrementals
244 if state:
245 if verbose:
246 print 'Writing state'
247 f = open(state, 'w')
248 pickle.dump(hgvers, f)
250 # vim: et ts=8 sw=4 sts=4