Merge branch 'jc/spht'
[git] / contrib / hg-to-git / hg-to-git.py
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
22 import os, os.path, sys
23 import tempfile, popen2, pickle, getopt
24 import re
25
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
34
35 #------------------------------------------------------------------------------
36
37 def usage():
38
39         print """\
40 %s: [OPTIONS] <hgprj>
41
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)
47
48 required:
49     hgprj:  name of the HG project to import (directory)
50 """ % sys.argv[0]
51
52 #------------------------------------------------------------------------------
53
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= ;'
67
68     env += 'export GIT_AUTHOR_DATE="%s" ;' % date
69     env += 'export GIT_COMMITTER_DATE="%s" ;' % date
70     return env
71
72 #------------------------------------------------------------------------------
73
74 state = ''
75 opt_nrepack = 0
76
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)
90
91 hgprj = args[0]
92 os.chdir(hgprj)
93
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'
101
102 tip = os.popen('hg tip | head -1 | cut -f 2 -d :').read().strip()
103 print 'tip is', tip
104
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
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
137 if not hgvers.has_key("0"):
138     print 'creating repository'
139     os.system('git-init-db')
140
141 # loop through every hg changeset
142 for 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
232 if hgnewcsets >= opt_nrepack and opt_nrepack != -1:
233     os.system('git-repack -a -d')
234
235 # write the state for incrementals
236 if 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