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