Code

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