]>
Commit | Line | Data |
---|---|---|
44211e8c | 1 | #!/usr/bin/env python |
98d47d4c | 2 | |
1bc7c13a | 3 | """ hg-to-git.py - A Mercurial to GIT converter |
98d47d4c SP |
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 | |
48425792 | 18 | along with this program; if not, see <http://www.gnu.org/licenses/>. |
98d47d4c SP |
19 | """ |
20 | ||
21 | import os, os.path, sys | |
b0c051d1 | 22 | import tempfile, pickle, getopt |
98d47d4c SP |
23 | import re |
24 | ||
a33faf28 ER |
25 | if sys.hexversion < 0x02030000: |
26 | # The behavior of the pickle module changed significantly in 2.3 | |
27 | sys.stderr.write("hg-to-git.py: requires Python 2.3 or later.\n") | |
28 | sys.exit(1) | |
29 | ||
98d47d4c SP |
30 | # Maps hg version -> git version |
31 | hgvers = {} | |
32 | # List of children for each hg revision | |
33 | hgchildren = {} | |
1bc7c13a MD |
34 | # List of parents for each hg revision |
35 | hgparents = {} | |
98d47d4c SP |
36 | # Current branch for each hg revision |
37 | hgbranch = {} | |
7c0d741a MG |
38 | # Number of new changesets converted from hg |
39 | hgnewcsets = 0 | |
98d47d4c SP |
40 | |
41 | #------------------------------------------------------------------------------ | |
42 | ||
43 | def usage(): | |
44 | ||
d17ae00c | 45 | print("""\ |
98d47d4c SP |
46 | %s: [OPTIONS] <hgprj> |
47 | ||
48 | options: | |
49 | -s, --gitstate=FILE: name of the state to be saved/read | |
50 | for incrementals | |
7c0d741a MG |
51 | -n, --nrepack=INT: number of changesets that will trigger |
52 | a repack (default=0, -1 to deactivate) | |
37a12dda | 53 | -v, --verbose: be verbose |
98d47d4c SP |
54 | |
55 | required: | |
56 | hgprj: name of the HG project to import (directory) | |
d17ae00c | 57 | """ % sys.argv[0]) |
98d47d4c SP |
58 | |
59 | #------------------------------------------------------------------------------ | |
60 | ||
61 | def getgitenv(user, date): | |
62 | env = '' | |
63 | elems = re.compile('(.*?)\s+<(.*)>').match(user) | |
64 | if elems: | |
65 | env += 'export GIT_AUTHOR_NAME="%s" ;' % elems.group(1) | |
0def5b6e | 66 | env += 'export GIT_COMMITTER_NAME="%s" ;' % elems.group(1) |
98d47d4c | 67 | env += 'export GIT_AUTHOR_EMAIL="%s" ;' % elems.group(2) |
0def5b6e | 68 | env += 'export GIT_COMMITTER_EMAIL="%s" ;' % elems.group(2) |
98d47d4c SP |
69 | else: |
70 | env += 'export GIT_AUTHOR_NAME="%s" ;' % user | |
0def5b6e | 71 | env += 'export GIT_COMMITTER_NAME="%s" ;' % user |
98d47d4c | 72 | env += 'export GIT_AUTHOR_EMAIL= ;' |
0def5b6e | 73 | env += 'export GIT_COMMITTER_EMAIL= ;' |
98d47d4c SP |
74 | |
75 | env += 'export GIT_AUTHOR_DATE="%s" ;' % date | |
76 | env += 'export GIT_COMMITTER_DATE="%s" ;' % date | |
77 | return env | |
78 | ||
79 | #------------------------------------------------------------------------------ | |
80 | ||
81 | state = '' | |
7c0d741a | 82 | opt_nrepack = 0 |
37a12dda | 83 | verbose = False |
98d47d4c SP |
84 | |
85 | try: | |
37a12dda | 86 | opts, args = getopt.getopt(sys.argv[1:], 's:t:n:v', ['gitstate=', 'tempdir=', 'nrepack=', 'verbose']) |
98d47d4c SP |
87 | for o, a in opts: |
88 | if o in ('-s', '--gitstate'): | |
89 | state = a | |
90 | state = os.path.abspath(state) | |
7c0d741a MG |
91 | if o in ('-n', '--nrepack'): |
92 | opt_nrepack = int(a) | |
37a12dda JS |
93 | if o in ('-v', '--verbose'): |
94 | verbose = True | |
98d47d4c | 95 | if len(args) != 1: |
6376cffa | 96 | raise Exception('params') |
98d47d4c SP |
97 | except: |
98 | usage() | |
99 | sys.exit(1) | |
100 | ||
101 | hgprj = args[0] | |
102 | os.chdir(hgprj) | |
103 | ||
104 | if state: | |
105 | if os.path.exists(state): | |
37a12dda | 106 | if verbose: |
d17ae00c | 107 | print('State does exist, reading') |
98d47d4c SP |
108 | f = open(state, 'r') |
109 | hgvers = pickle.load(f) | |
110 | else: | |
d17ae00c | 111 | print('State does not exist, first run') |
98d47d4c | 112 | |
2553ede5 MV |
113 | sock = os.popen('hg tip --template "{rev}"') |
114 | tip = sock.read() | |
115 | if sock.close(): | |
116 | sys.exit(1) | |
37a12dda | 117 | if verbose: |
d17ae00c | 118 | print('tip is', tip) |
98d47d4c SP |
119 | |
120 | # Calculate the branches | |
37a12dda | 121 | if verbose: |
d17ae00c | 122 | print('analysing the branches...') |
98d47d4c | 123 | hgchildren["0"] = () |
1bc7c13a | 124 | hgparents["0"] = (None, None) |
98d47d4c SP |
125 | hgbranch["0"] = "master" |
126 | for cset in range(1, int(tip) + 1): | |
127 | hgchildren[str(cset)] = () | |
13bf1a99 | 128 | prnts = os.popen('hg log -r %d --template "{parents}"' % cset).read().strip().split(' ') |
1bc7c13a MD |
129 | prnts = map(lambda x: x[:x.find(':')], prnts) |
130 | if prnts[0] != '': | |
98d47d4c SP |
131 | parent = prnts[0].strip() |
132 | else: | |
133 | parent = str(cset - 1) | |
134 | hgchildren[parent] += ( str(cset), ) | |
135 | if len(prnts) > 1: | |
136 | mparent = prnts[1].strip() | |
137 | hgchildren[mparent] += ( str(cset), ) | |
138 | else: | |
139 | mparent = None | |
140 | ||
1bc7c13a MD |
141 | hgparents[str(cset)] = (parent, mparent) |
142 | ||
98d47d4c SP |
143 | if mparent: |
144 | # For merge changesets, take either one, preferably the 'master' branch | |
145 | if hgbranch[mparent] == 'master': | |
146 | hgbranch[str(cset)] = 'master' | |
147 | else: | |
148 | hgbranch[str(cset)] = hgbranch[parent] | |
149 | else: | |
150 | # Normal changesets | |
151 | # For first children, take the parent branch, for the others create a new branch | |
152 | if hgchildren[parent][0] == str(cset): | |
153 | hgbranch[str(cset)] = hgbranch[parent] | |
154 | else: | |
155 | hgbranch[str(cset)] = "branch-" + str(cset) | |
156 | ||
d17ae00c HB |
157 | if "0" not in hgvers: |
158 | print('creating repository') | |
af9a01e1 | 159 | os.system('git init') |
98d47d4c SP |
160 | |
161 | # loop through every hg changeset | |
162 | for cset in range(int(tip) + 1): | |
163 | ||
164 | # incremental, already seen | |
d17ae00c | 165 | if str(cset) in hgvers: |
98d47d4c | 166 | continue |
7c0d741a | 167 | hgnewcsets += 1 |
98d47d4c SP |
168 | |
169 | # get info | |
1bc7c13a MD |
170 | log_data = os.popen('hg log -r %d --template "{tags}\n{date|date}\n{author}\n"' % cset).readlines() |
171 | tag = log_data[0].strip() | |
172 | date = log_data[1].strip() | |
173 | user = log_data[2].strip() | |
174 | parent = hgparents[str(cset)][0] | |
175 | mparent = hgparents[str(cset)][1] | |
176 | ||
177 | #get comment | |
98d47d4c | 178 | (fdcomment, filecomment) = tempfile.mkstemp() |
1bc7c13a | 179 | csetcomment = os.popen('hg log -r %d --template "{desc}"' % cset).read().strip() |
98d47d4c SP |
180 | os.write(fdcomment, csetcomment) |
181 | os.close(fdcomment) | |
182 | ||
d17ae00c HB |
183 | print('-----------------------------------------') |
184 | print('cset:', cset) | |
185 | print('branch:', hgbranch[str(cset)]) | |
186 | print('user:', user) | |
187 | print('date:', date) | |
188 | print('comment:', csetcomment) | |
1bc7c13a | 189 | if parent: |
d17ae00c | 190 | print('parent:', parent) |
98d47d4c | 191 | if mparent: |
d17ae00c | 192 | print('mparent:', mparent) |
98d47d4c | 193 | if tag: |
d17ae00c HB |
194 | print('tag:', tag) |
195 | print('-----------------------------------------') | |
98d47d4c SP |
196 | |
197 | # checkout the parent if necessary | |
198 | if cset != 0: | |
199 | if hgbranch[str(cset)] == "branch-" + str(cset): | |
d17ae00c | 200 | print('creating new branch', hgbranch[str(cset)]) |
96f23959 | 201 | os.system('git checkout -b %s %s' % (hgbranch[str(cset)], hgvers[parent])) |
98d47d4c | 202 | else: |
d17ae00c | 203 | print('checking out branch', hgbranch[str(cset)]) |
96f23959 | 204 | os.system('git checkout %s' % hgbranch[str(cset)]) |
98d47d4c SP |
205 | |
206 | # merge | |
207 | if mparent: | |
208 | if hgbranch[parent] == hgbranch[str(cset)]: | |
209 | otherbranch = hgbranch[mparent] | |
210 | else: | |
211 | otherbranch = hgbranch[parent] | |
d17ae00c | 212 | print('merging', otherbranch, 'into', hgbranch[str(cset)]) |
96f23959 | 213 | os.system(getgitenv(user, date) + 'git merge --no-commit -s ours "" %s %s' % (hgbranch[str(cset)], otherbranch)) |
98d47d4c SP |
214 | |
215 | # remove everything except .git and .hg directories | |
216 | os.system('find . \( -path "./.hg" -o -path "./.git" \) -prune -o ! -name "." -print | xargs rm -rf') | |
217 | ||
218 | # repopulate with checkouted files | |
219 | os.system('hg update -C %d' % cset) | |
220 | ||
221 | # add new files | |
96f23959 | 222 | os.system('git ls-files -x .hg --others | git update-index --add --stdin') |
98d47d4c | 223 | # delete removed files |
96f23959 | 224 | os.system('git ls-files -x .hg --deleted | git update-index --remove --stdin') |
98d47d4c SP |
225 | |
226 | # commit | |
1611eed6 | 227 | os.system(getgitenv(user, date) + 'git commit --allow-empty --allow-empty-message -a -F %s' % filecomment) |
98d47d4c SP |
228 | os.unlink(filecomment) |
229 | ||
230 | # tag | |
231 | if tag and tag != 'tip': | |
96f23959 | 232 | os.system(getgitenv(user, date) + 'git tag %s' % tag) |
98d47d4c SP |
233 | |
234 | # delete branch if not used anymore... | |
235 | if mparent and len(hgchildren[str(cset)]): | |
d17ae00c | 236 | print("Deleting unused branch:", otherbranch) |
96f23959 | 237 | os.system('git branch -d %s' % otherbranch) |
98d47d4c SP |
238 | |
239 | # retrieve and record the version | |
96f23959 | 240 | vvv = os.popen('git show --quiet --pretty=format:%H').read() |
d17ae00c | 241 | print('record', cset, '->', vvv) |
98d47d4c SP |
242 | hgvers[str(cset)] = vvv |
243 | ||
7c0d741a | 244 | if hgnewcsets >= opt_nrepack and opt_nrepack != -1: |
96f23959 | 245 | os.system('git repack -a -d') |
98d47d4c SP |
246 | |
247 | # write the state for incrementals | |
248 | if state: | |
37a12dda | 249 | if verbose: |
d17ae00c | 250 | print('Writing state') |
98d47d4c SP |
251 | f = open(state, 'w') |
252 | pickle.dump(hgvers, f) | |
253 | ||
254 | # vim: et ts=8 sw=4 sts=4 |