##// END OF EJS Templates
convert: make convertsource option handling transparent
Brendan Cully -
r4780:169fe1e6 default
parent child Browse files
Show More
@@ -1,348 +1,348 b''
1 1 # convert.py Foreign SCM converter
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from common import NoRepo, converter_source, converter_sink
9 9 from cvs import convert_cvs
10 10 from git import convert_git
11 11 from hg import convert_mercurial
12 12 from subversion import convert_svn
13 13
14 14 import os, shutil
15 15 from mercurial import hg, ui, util, commands
16 16
17 17 commands.norepo += " convert"
18 18
19 19 converters = [convert_cvs, convert_git, convert_svn, convert_mercurial]
20 20
21 def convertsource(ui, path, rev=None):
21 def convertsource(ui, path, **opts):
22 22 for c in converters:
23 23 if not hasattr(c, 'getcommit'):
24 24 continue
25 25 try:
26 return c(ui, path, rev=rev)
26 return c(ui, path, **opts)
27 27 except NoRepo:
28 28 pass
29 29 raise util.Abort('%s: unknown repository type' % path)
30 30
31 31 def convertsink(ui, path):
32 32 if not os.path.isdir(path):
33 33 raise util.Abort("%s: not a directory" % path)
34 34 for c in converters:
35 35 if not hasattr(c, 'putcommit'):
36 36 continue
37 37 try:
38 38 return c(ui, path)
39 39 except NoRepo:
40 40 pass
41 41 raise util.Abort('%s: unknown repository type' % path)
42 42
43 43 class convert(object):
44 44 def __init__(self, ui, source, dest, mapfile, opts):
45 45
46 46 self.source = source
47 47 self.dest = dest
48 48 self.ui = ui
49 49 self.opts = opts
50 50 self.commitcache = {}
51 51 self.mapfile = mapfile
52 52 self.mapfilefd = None
53 53 self.authors = {}
54 54 self.authorfile = None
55 55
56 56 self.map = {}
57 57 try:
58 58 origmapfile = open(self.mapfile, 'r')
59 59 for l in origmapfile:
60 60 sv, dv = l[:-1].split()
61 61 self.map[sv] = dv
62 62 origmapfile.close()
63 63 except IOError:
64 64 pass
65 65
66 66 # Read first the dst author map if any
67 67 authorfile = self.dest.authorfile()
68 68 if authorfile and os.path.exists(authorfile):
69 69 self.readauthormap(authorfile)
70 70 # Extend/Override with new author map if necessary
71 71 if opts.get('authors'):
72 72 self.readauthormap(opts.get('authors'))
73 73 self.authorfile = self.dest.authorfile()
74 74
75 75 def walktree(self, heads):
76 76 '''Return a mapping that identifies the uncommitted parents of every
77 77 uncommitted changeset.'''
78 78 visit = heads
79 79 known = {}
80 80 parents = {}
81 81 while visit:
82 82 n = visit.pop(0)
83 83 if n in known or n in self.map: continue
84 84 known[n] = 1
85 85 self.commitcache[n] = self.source.getcommit(n)
86 86 cp = self.commitcache[n].parents
87 87 parents[n] = []
88 88 for p in cp:
89 89 parents[n].append(p)
90 90 visit.append(p)
91 91
92 92 return parents
93 93
94 94 def toposort(self, parents):
95 95 '''Return an ordering such that every uncommitted changeset is
96 96 preceeded by all its uncommitted ancestors.'''
97 97 visit = parents.keys()
98 98 seen = {}
99 99 children = {}
100 100
101 101 while visit:
102 102 n = visit.pop(0)
103 103 if n in seen: continue
104 104 seen[n] = 1
105 105 # Ensure that nodes without parents are present in the 'children'
106 106 # mapping.
107 107 children.setdefault(n, [])
108 108 for p in parents[n]:
109 109 if not p in self.map:
110 110 visit.append(p)
111 111 children.setdefault(p, []).append(n)
112 112
113 113 s = []
114 114 removed = {}
115 115 visit = children.keys()
116 116 while visit:
117 117 n = visit.pop(0)
118 118 if n in removed: continue
119 119 dep = 0
120 120 if n in parents:
121 121 for p in parents[n]:
122 122 if p in self.map: continue
123 123 if p not in removed:
124 124 # we're still dependent
125 125 visit.append(n)
126 126 dep = 1
127 127 break
128 128
129 129 if not dep:
130 130 # all n's parents are in the list
131 131 removed[n] = 1
132 132 if n not in self.map:
133 133 s.append(n)
134 134 if n in children:
135 135 for c in children[n]:
136 136 visit.insert(0, c)
137 137
138 138 if self.opts.get('datesort'):
139 139 depth = {}
140 140 for n in s:
141 141 depth[n] = 0
142 142 pl = [p for p in self.commitcache[n].parents
143 143 if p not in self.map]
144 144 if pl:
145 145 depth[n] = max([depth[p] for p in pl]) + 1
146 146
147 147 s = [(depth[n], self.commitcache[n].date, n) for n in s]
148 148 s.sort()
149 149 s = [e[2] for e in s]
150 150
151 151 return s
152 152
153 153 def mapentry(self, src, dst):
154 154 if self.mapfilefd is None:
155 155 try:
156 156 self.mapfilefd = open(self.mapfile, "a")
157 157 except IOError, (errno, strerror):
158 158 raise util.Abort("Could not open map file %s: %s, %s\n" % (self.mapfile, errno, strerror))
159 159 self.map[src] = dst
160 160 self.mapfilefd.write("%s %s\n" % (src, dst))
161 161 self.mapfilefd.flush()
162 162
163 163 def writeauthormap(self):
164 164 authorfile = self.authorfile
165 165 if authorfile:
166 166 self.ui.status('Writing author map file %s\n' % authorfile)
167 167 ofile = open(authorfile, 'w+')
168 168 for author in self.authors:
169 169 ofile.write("%s=%s\n" % (author, self.authors[author]))
170 170 ofile.close()
171 171
172 172 def readauthormap(self, authorfile):
173 173 afile = open(authorfile, 'r')
174 174 for line in afile:
175 175 try:
176 176 srcauthor = line.split('=')[0].strip()
177 177 dstauthor = line.split('=')[1].strip()
178 178 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
179 179 self.ui.status(
180 180 'Overriding mapping for author %s, was %s, will be %s\n'
181 181 % (srcauthor, self.authors[srcauthor], dstauthor))
182 182 else:
183 183 self.ui.debug('Mapping author %s to %s\n'
184 184 % (srcauthor, dstauthor))
185 185 self.authors[srcauthor] = dstauthor
186 186 except IndexError:
187 187 self.ui.warn(
188 188 'Ignoring bad line in author file map %s: %s\n'
189 189 % (authorfile, line))
190 190 afile.close()
191 191
192 192 def copy(self, rev):
193 193 c = self.commitcache[rev]
194 194 files = self.source.getchanges(rev)
195 195
196 196 do_copies = (hasattr(c, 'copies') and hasattr(self.dest, 'copyfile'))
197 197
198 198 for f, v in files:
199 199 try:
200 200 data = self.source.getfile(f, v)
201 201 except IOError, inst:
202 202 self.dest.delfile(f)
203 203 else:
204 204 e = self.source.getmode(f, v)
205 205 self.dest.putfile(f, e, data)
206 206 if do_copies:
207 207 if f in c.copies:
208 208 # Merely marks that a copy happened.
209 209 self.dest.copyfile(c.copies[f], f)
210 210
211 211
212 212 r = [self.map[v] for v in c.parents]
213 213 f = [f for f, v in files]
214 214 newnode = self.dest.putcommit(f, r, c)
215 215 self.mapentry(rev, newnode)
216 216
217 217 def convert(self):
218 218 try:
219 219 self.ui.status("scanning source...\n")
220 220 heads = self.source.getheads()
221 221 parents = self.walktree(heads)
222 222 self.ui.status("sorting...\n")
223 223 t = self.toposort(parents)
224 224 num = len(t)
225 225 c = None
226 226
227 227 self.ui.status("converting...\n")
228 228 for c in t:
229 229 num -= 1
230 230 desc = self.commitcache[c].desc
231 231 if "\n" in desc:
232 232 desc = desc.splitlines()[0]
233 233 author = self.commitcache[c].author
234 234 author = self.authors.get(author, author)
235 235 self.commitcache[c].author = author
236 236 self.ui.status("%d %s\n" % (num, desc))
237 237 self.copy(c)
238 238
239 239 tags = self.source.gettags()
240 240 ctags = {}
241 241 for k in tags:
242 242 v = tags[k]
243 243 if v in self.map:
244 244 ctags[k] = self.map[v]
245 245
246 246 if c and ctags:
247 247 nrev = self.dest.puttags(ctags)
248 248 # write another hash correspondence to override the previous
249 249 # one so we don't end up with extra tag heads
250 250 if nrev:
251 251 self.mapentry(c, nrev)
252 252
253 253 self.writeauthormap()
254 254 finally:
255 255 self.cleanup()
256 256
257 257 def cleanup(self):
258 258 if self.mapfilefd:
259 259 self.mapfilefd.close()
260 260
261 261 def _convert(ui, src, dest=None, mapfile=None, **opts):
262 262 '''Convert a foreign SCM repository to a Mercurial one.
263 263
264 264 Accepted source formats:
265 265 - GIT
266 266 - CVS
267 267 - SVN
268 268
269 269 Accepted destination formats:
270 270 - Mercurial
271 271
272 272 If no revision is given, all revisions will be converted. Otherwise,
273 273 convert will only import up to the named revision (given in a format
274 274 understood by the source).
275 275
276 276 If destination isn't given, a new Mercurial repo named <src>-hg will
277 277 be created. If <mapfile> isn't given, it will be put in a default
278 278 location (<dest>/.hg/shamap by default)
279 279
280 280 The <mapfile> is a simple text file that maps each source commit ID to
281 281 the destination ID for that revision, like so:
282 282 <source ID> <destination ID>
283 283
284 284 If the file doesn't exist, it's automatically created. It's updated
285 285 on each commit copied, so convert-repo can be interrupted and can
286 286 be run repeatedly to copy new commits.
287 287
288 288 The [username mapping] file is a simple text file that maps each source
289 289 commit author to a destination commit author. It is handy for source SCMs
290 290 that use unix logins to identify authors (eg: CVS). One line per author
291 291 mapping and the line format is:
292 292 srcauthor=whatever string you want
293 293 '''
294 294
295 295 if not dest:
296 296 dest = src + "-hg"
297 297 ui.status("assuming destination %s\n" % dest)
298 298
299 299 # Try to be smart and initalize things when required
300 300 created = False
301 301 if os.path.isdir(dest):
302 302 if len(os.listdir(dest)) > 0:
303 303 try:
304 304 hg.repository(ui, dest)
305 305 ui.status("destination %s is a Mercurial repository\n" % dest)
306 306 except hg.RepoError:
307 307 raise util.Abort(
308 308 "destination directory %s is not empty.\n"
309 309 "Please specify an empty directory to be initialized\n"
310 310 "or an already initialized mercurial repository"
311 311 % dest)
312 312 else:
313 313 ui.status("initializing destination %s repository\n" % dest)
314 314 hg.repository(ui, dest, create=True)
315 315 created = True
316 316 elif os.path.exists(dest):
317 317 raise util.Abort("destination %s exists and is not a directory" % dest)
318 318 else:
319 319 ui.status("initializing destination %s repository\n" % dest)
320 320 hg.repository(ui, dest, create=True)
321 321 created = True
322 322
323 323 destc = convertsink(ui, dest)
324 324
325 325 try:
326 326 srcc = convertsource(ui, src, rev=opts.get('rev'))
327 327 except Exception:
328 328 if created:
329 329 shutil.rmtree(dest, True)
330 330 raise
331 331
332 332 if not mapfile:
333 333 try:
334 334 mapfile = destc.mapfile()
335 335 except:
336 336 mapfile = os.path.join(destc, "map")
337 337
338 338 c = convert(ui, srcc, destc, mapfile, opts)
339 339 c.convert()
340 340
341 341 cmdtable = {
342 342 "convert":
343 343 (_convert,
344 344 [('A', 'authors', '', 'username mapping filename'),
345 345 ('r', 'rev', '', 'import up to target revision REV'),
346 346 ('', 'datesort', None, 'try to sort changesets by date')],
347 347 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
348 348 }
General Comments 0
You need to be logged in to leave comments. Login now