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