Documentation/git-diff: A..B and A...B cannot take tree-ishes
[alt-git.git] / contrib / hg-to-git / hg-to-git.py
blob37337ff01fa56783cadeb3df685580101f92554c
1 #! /usr/bin/python
3 """ hg-to-svn.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, popen2, pickle, getopt
24 import re
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 = {}
33 #------------------------------------------------------------------------------
35 def usage():
37 print """\
38 %s: [OPTIONS] <hgprj>
40 options:
41 -s, --gitstate=FILE: name of the state to be saved/read
42 for incrementals
44 required:
45 hgprj: name of the HG project to import (directory)
46 """ % sys.argv[0]
48 #------------------------------------------------------------------------------
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= ;'
64 env += 'export GIT_AUTHOR_DATE="%s" ;' % date
65 env += 'export GIT_COMMITTER_DATE="%s" ;' % date
66 return env
68 #------------------------------------------------------------------------------
70 state = ''
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)
79 if len(args) != 1:
80 raise('params')
81 except:
82 usage()
83 sys.exit(1)
85 hgprj = args[0]
86 os.chdir(hgprj)
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'
96 tip = os.popen('hg tip | head -1 | cut -f 2 -d :').read().strip()
97 print 'tip is', tip
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
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)
131 if not hgvers.has_key("0"):
132 print 'creating repository'
133 os.system('git-init-db')
135 # loop through every hg changeset
136 for cset in range(int(tip) + 1):
138 # incremental, already seen
139 if hgvers.has_key(str(cset)):
140 continue
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
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)
158 date = os.popen('hg log -r %d | grep ^date: | cut -f 2- -d :' % cset).read().strip()
160 tag = os.popen('hg log -r %d | grep ^tag: | cut -f 2- -d :' % cset).read().strip()
162 user = os.popen('hg log -r %d | grep ^user: | cut -f 2- -d :' % cset).read().strip()
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 '-----------------------------------------'
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)])
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))
195 # remove everything except .git and .hg directories
196 os.system('find . \( -path "./.hg" -o -path "./.git" \) -prune -o ! -name "." -print | xargs rm -rf')
198 # repopulate with checkouted files
199 os.system('hg update -C %d' % cset)
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')
206 # commit
207 os.system(getgitenv(user, date) + 'git-commit -a -F %s' % filecomment)
208 os.unlink(filecomment)
210 # tag
211 if tag and tag != 'tip':
212 os.system(getgitenv(user, date) + 'git-tag %s' % tag)
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)
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
225 os.system('git-repack -a -d')
227 # write the state for incrementals
228 if state:
229 print 'Writing state'
230 f = open(state, 'w')
231 pickle.dump(hgvers, f)
233 # vim: et ts=8 sw=4 sts=4