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