##// END OF EJS Templates
convert-hg: use localrepo.pull...
Pierre-Yves David -
r22698:32a8ad78 default
parent child Browse files
Show More
@@ -1,477 +1,478
1 1 # hg.py - hg backend for convert extension
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 # Notes for hg->hg conversion:
9 9 #
10 10 # * Old versions of Mercurial didn't trim the whitespace from the ends
11 11 # of commit messages, but new versions do. Changesets created by
12 12 # those older versions, then converted, may thus have different
13 13 # hashes for changesets that are otherwise identical.
14 14 #
15 15 # * Using "--config convert.hg.saverev=true" will make the source
16 16 # identifier to be stored in the converted revision. This will cause
17 17 # the converted revision to have a different identity than the
18 18 # source.
19 19
20 20
21 21 import os, time, cStringIO
22 22 from mercurial.i18n import _
23 23 from mercurial.node import bin, hex, nullid
24 from mercurial import hg, util, context, bookmarks, error, scmutil
24 from mercurial import hg, util, context, bookmarks, error, scmutil, exchange
25 25
26 26 from common import NoRepo, commit, converter_source, converter_sink
27 27
28 28 import re
29 29 sha1re = re.compile(r'\b[0-9a-f]{6,40}\b')
30 30
31 31 class mercurial_sink(converter_sink):
32 32 def __init__(self, ui, path):
33 33 converter_sink.__init__(self, ui, path)
34 34 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
35 35 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
36 36 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
37 37 self.lastbranch = None
38 38 if os.path.isdir(path) and len(os.listdir(path)) > 0:
39 39 try:
40 40 self.repo = hg.repository(self.ui, path)
41 41 if not self.repo.local():
42 42 raise NoRepo(_('%s is not a local Mercurial repository')
43 43 % path)
44 44 except error.RepoError, err:
45 45 ui.traceback()
46 46 raise NoRepo(err.args[0])
47 47 else:
48 48 try:
49 49 ui.status(_('initializing destination %s repository\n') % path)
50 50 self.repo = hg.repository(self.ui, path, create=True)
51 51 if not self.repo.local():
52 52 raise NoRepo(_('%s is not a local Mercurial repository')
53 53 % path)
54 54 self.created.append(path)
55 55 except error.RepoError:
56 56 ui.traceback()
57 57 raise NoRepo(_("could not create hg repository %s as sink")
58 58 % path)
59 59 self.lock = None
60 60 self.wlock = None
61 61 self.filemapmode = False
62 62
63 63 def before(self):
64 64 self.ui.debug('run hg sink pre-conversion action\n')
65 65 self.wlock = self.repo.wlock()
66 66 self.lock = self.repo.lock()
67 67
68 68 def after(self):
69 69 self.ui.debug('run hg sink post-conversion action\n')
70 70 if self.lock:
71 71 self.lock.release()
72 72 if self.wlock:
73 73 self.wlock.release()
74 74
75 75 def revmapfile(self):
76 76 return self.repo.join("shamap")
77 77
78 78 def authorfile(self):
79 79 return self.repo.join("authormap")
80 80
81 81 def setbranch(self, branch, pbranches):
82 82 if not self.clonebranches:
83 83 return
84 84
85 85 setbranch = (branch != self.lastbranch)
86 86 self.lastbranch = branch
87 87 if not branch:
88 88 branch = 'default'
89 89 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
90 90 pbranch = pbranches and pbranches[0][1] or 'default'
91 91
92 92 branchpath = os.path.join(self.path, branch)
93 93 if setbranch:
94 94 self.after()
95 95 try:
96 96 self.repo = hg.repository(self.ui, branchpath)
97 97 except Exception:
98 98 self.repo = hg.repository(self.ui, branchpath, create=True)
99 99 self.before()
100 100
101 101 # pbranches may bring revisions from other branches (merge parents)
102 102 # Make sure we have them, or pull them.
103 103 missings = {}
104 104 for b in pbranches:
105 105 try:
106 106 self.repo.lookup(b[0])
107 107 except Exception:
108 108 missings.setdefault(b[1], []).append(b[0])
109 109
110 110 if missings:
111 111 self.after()
112 112 for pbranch, heads in sorted(missings.iteritems()):
113 113 pbranchpath = os.path.join(self.path, pbranch)
114 114 prepo = hg.peer(self.ui, {}, pbranchpath)
115 115 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
116 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
116 exchange.pull(self.repo, prepo,
117 [prepo.lookup(h) for h in heads])
117 118 self.before()
118 119
119 120 def _rewritetags(self, source, revmap, data):
120 121 fp = cStringIO.StringIO()
121 122 for line in data.splitlines():
122 123 s = line.split(' ', 1)
123 124 if len(s) != 2:
124 125 continue
125 126 revid = revmap.get(source.lookuprev(s[0]))
126 127 if not revid:
127 128 continue
128 129 fp.write('%s %s\n' % (revid, s[1]))
129 130 return fp.getvalue()
130 131
131 132 def putcommit(self, files, copies, parents, commit, source, revmap, full):
132 133 files = dict(files)
133 134 def getfilectx(repo, memctx, f):
134 135 try:
135 136 v = files[f]
136 137 except KeyError:
137 138 return None
138 139 data, mode = source.getfile(f, v)
139 140 if data is None:
140 141 return None
141 142 if f == '.hgtags':
142 143 data = self._rewritetags(source, revmap, data)
143 144 return context.memfilectx(self.repo, f, data, 'l' in mode,
144 145 'x' in mode, copies.get(f))
145 146
146 147 pl = []
147 148 for p in parents:
148 149 if p not in pl:
149 150 pl.append(p)
150 151 parents = pl
151 152 nparents = len(parents)
152 153 if self.filemapmode and nparents == 1:
153 154 m1node = self.repo.changelog.read(bin(parents[0]))[0]
154 155 parent = parents[0]
155 156
156 157 if len(parents) < 2:
157 158 parents.append(nullid)
158 159 if len(parents) < 2:
159 160 parents.append(nullid)
160 161 p2 = parents.pop(0)
161 162
162 163 text = commit.desc
163 164
164 165 sha1s = re.findall(sha1re, text)
165 166 for sha1 in sha1s:
166 167 oldrev = source.lookuprev(sha1)
167 168 newrev = revmap.get(oldrev)
168 169 if newrev is not None:
169 170 text = text.replace(sha1, newrev[:len(sha1)])
170 171
171 172 extra = commit.extra.copy()
172 173
173 174 for label in ('source', 'transplant_source', 'rebase_source'):
174 175 node = extra.get(label)
175 176
176 177 if node is None:
177 178 continue
178 179
179 180 # Only transplant stores its reference in binary
180 181 if label == 'transplant_source':
181 182 node = hex(node)
182 183
183 184 newrev = revmap.get(node)
184 185 if newrev is not None:
185 186 if label == 'transplant_source':
186 187 newrev = bin(newrev)
187 188
188 189 extra[label] = newrev
189 190
190 191 if self.branchnames and commit.branch:
191 192 extra['branch'] = commit.branch
192 193 if commit.rev:
193 194 extra['convert_revision'] = commit.rev
194 195
195 196 while parents:
196 197 p1 = p2
197 198 p2 = parents.pop(0)
198 199 fileset = set(files)
199 200 if full:
200 201 fileset.update(self.repo[p1])
201 202 fileset.update(self.repo[p2])
202 203 ctx = context.memctx(self.repo, (p1, p2), text, fileset,
203 204 getfilectx, commit.author, commit.date, extra)
204 205 self.repo.commitctx(ctx)
205 206 text = "(octopus merge fixup)\n"
206 207 p2 = hex(self.repo.changelog.tip())
207 208
208 209 if self.filemapmode and nparents == 1:
209 210 man = self.repo.manifest
210 211 mnode = self.repo.changelog.read(bin(p2))[0]
211 212 closed = 'close' in commit.extra
212 213 if not closed and not man.cmp(m1node, man.revision(mnode)):
213 214 self.ui.status(_("filtering out empty revision\n"))
214 215 self.repo.rollback(force=True)
215 216 return parent
216 217 return p2
217 218
218 219 def puttags(self, tags):
219 220 try:
220 221 parentctx = self.repo[self.tagsbranch]
221 222 tagparent = parentctx.node()
222 223 except error.RepoError:
223 224 parentctx = None
224 225 tagparent = nullid
225 226
226 227 oldlines = set()
227 228 for branch, heads in self.repo.branchmap().iteritems():
228 229 for h in heads:
229 230 if '.hgtags' in self.repo[h]:
230 231 oldlines.update(
231 232 set(self.repo[h]['.hgtags'].data().splitlines(True)))
232 233 oldlines = sorted(list(oldlines))
233 234
234 235 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
235 236 if newlines == oldlines:
236 237 return None, None
237 238
238 239 # if the old and new tags match, then there is nothing to update
239 240 oldtags = set()
240 241 newtags = set()
241 242 for line in oldlines:
242 243 s = line.strip().split(' ', 1)
243 244 if len(s) != 2:
244 245 continue
245 246 oldtags.add(s[1])
246 247 for line in newlines:
247 248 s = line.strip().split(' ', 1)
248 249 if len(s) != 2:
249 250 continue
250 251 if s[1] not in oldtags:
251 252 newtags.add(s[1].strip())
252 253
253 254 if not newtags:
254 255 return None, None
255 256
256 257 data = "".join(newlines)
257 258 def getfilectx(repo, memctx, f):
258 259 return context.memfilectx(repo, f, data, False, False, None)
259 260
260 261 self.ui.status(_("updating tags\n"))
261 262 date = "%s 0" % int(time.mktime(time.gmtime()))
262 263 extra = {'branch': self.tagsbranch}
263 264 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
264 265 [".hgtags"], getfilectx, "convert-repo", date,
265 266 extra)
266 267 self.repo.commitctx(ctx)
267 268 return hex(self.repo.changelog.tip()), hex(tagparent)
268 269
269 270 def setfilemapmode(self, active):
270 271 self.filemapmode = active
271 272
272 273 def putbookmarks(self, updatedbookmark):
273 274 if not len(updatedbookmark):
274 275 return
275 276
276 277 self.ui.status(_("updating bookmarks\n"))
277 278 destmarks = self.repo._bookmarks
278 279 for bookmark in updatedbookmark:
279 280 destmarks[bookmark] = bin(updatedbookmark[bookmark])
280 281 destmarks.write()
281 282
282 283 def hascommitfrommap(self, rev):
283 284 # the exact semantics of clonebranches is unclear so we can't say no
284 285 return rev in self.repo or self.clonebranches
285 286
286 287 def hascommitforsplicemap(self, rev):
287 288 if rev not in self.repo and self.clonebranches:
288 289 raise util.Abort(_('revision %s not found in destination '
289 290 'repository (lookups with clonebranches=true '
290 291 'are not implemented)') % rev)
291 292 return rev in self.repo
292 293
293 294 class mercurial_source(converter_source):
294 295 def __init__(self, ui, path, rev=None):
295 296 converter_source.__init__(self, ui, path, rev)
296 297 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
297 298 self.ignored = set()
298 299 self.saverev = ui.configbool('convert', 'hg.saverev', False)
299 300 try:
300 301 self.repo = hg.repository(self.ui, path)
301 302 # try to provoke an exception if this isn't really a hg
302 303 # repo, but some other bogus compatible-looking url
303 304 if not self.repo.local():
304 305 raise error.RepoError
305 306 except error.RepoError:
306 307 ui.traceback()
307 308 raise NoRepo(_("%s is not a local Mercurial repository") % path)
308 309 self.lastrev = None
309 310 self.lastctx = None
310 311 self._changescache = None, None
311 312 self.convertfp = None
312 313 # Restrict converted revisions to startrev descendants
313 314 startnode = ui.config('convert', 'hg.startrev')
314 315 hgrevs = ui.config('convert', 'hg.revs')
315 316 if hgrevs is None:
316 317 if startnode is not None:
317 318 try:
318 319 startnode = self.repo.lookup(startnode)
319 320 except error.RepoError:
320 321 raise util.Abort(_('%s is not a valid start revision')
321 322 % startnode)
322 323 startrev = self.repo.changelog.rev(startnode)
323 324 children = {startnode: 1}
324 325 for r in self.repo.changelog.descendants([startrev]):
325 326 children[self.repo.changelog.node(r)] = 1
326 327 self.keep = children.__contains__
327 328 else:
328 329 self.keep = util.always
329 330 if rev:
330 331 self._heads = [self.repo[rev].node()]
331 332 else:
332 333 self._heads = self.repo.heads()
333 334 else:
334 335 if rev or startnode is not None:
335 336 raise util.Abort(_('hg.revs cannot be combined with '
336 337 'hg.startrev or --rev'))
337 338 nodes = set()
338 339 parents = set()
339 340 for r in scmutil.revrange(self.repo, [hgrevs]):
340 341 ctx = self.repo[r]
341 342 nodes.add(ctx.node())
342 343 parents.update(p.node() for p in ctx.parents())
343 344 self.keep = nodes.__contains__
344 345 self._heads = nodes - parents
345 346
346 347 def changectx(self, rev):
347 348 if self.lastrev != rev:
348 349 self.lastctx = self.repo[rev]
349 350 self.lastrev = rev
350 351 return self.lastctx
351 352
352 353 def parents(self, ctx):
353 354 return [p for p in ctx.parents() if p and self.keep(p.node())]
354 355
355 356 def getheads(self):
356 357 return [hex(h) for h in self._heads if self.keep(h)]
357 358
358 359 def getfile(self, name, rev):
359 360 try:
360 361 fctx = self.changectx(rev)[name]
361 362 return fctx.data(), fctx.flags()
362 363 except error.LookupError:
363 364 return None, None
364 365
365 366 def getchanges(self, rev, full):
366 367 ctx = self.changectx(rev)
367 368 parents = self.parents(ctx)
368 369 if full or not parents:
369 370 files = copyfiles = ctx.manifest()
370 371 if parents:
371 372 if self._changescache[0] == rev:
372 373 m, a, r = self._changescache[1]
373 374 else:
374 375 m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
375 376 if not full:
376 377 files = m + a + r
377 378 copyfiles = m + a
378 379 # getcopies() is also run for roots and before filtering so missing
379 380 # revlogs are detected early
380 381 copies = self.getcopies(ctx, parents, copyfiles)
381 382 changes = [(f, rev) for f in files if f not in self.ignored]
382 383 changes.sort()
383 384 return changes, copies
384 385
385 386 def getcopies(self, ctx, parents, files):
386 387 copies = {}
387 388 for name in files:
388 389 if name in self.ignored:
389 390 continue
390 391 try:
391 392 copysource, _copynode = ctx.filectx(name).renamed()
392 393 if copysource in self.ignored:
393 394 continue
394 395 # Ignore copy sources not in parent revisions
395 396 found = False
396 397 for p in parents:
397 398 if copysource in p:
398 399 found = True
399 400 break
400 401 if not found:
401 402 continue
402 403 copies[name] = copysource
403 404 except TypeError:
404 405 pass
405 406 except error.LookupError, e:
406 407 if not self.ignoreerrors:
407 408 raise
408 409 self.ignored.add(name)
409 410 self.ui.warn(_('ignoring: %s\n') % e)
410 411 return copies
411 412
412 413 def getcommit(self, rev):
413 414 ctx = self.changectx(rev)
414 415 parents = [p.hex() for p in self.parents(ctx)]
415 416 if self.saverev:
416 417 crev = rev
417 418 else:
418 419 crev = None
419 420 return commit(author=ctx.user(),
420 421 date=util.datestr(ctx.date(), '%Y-%m-%d %H:%M:%S %1%2'),
421 422 desc=ctx.description(), rev=crev, parents=parents,
422 423 branch=ctx.branch(), extra=ctx.extra(),
423 424 sortkey=ctx.rev())
424 425
425 426 def gettags(self):
426 427 # This will get written to .hgtags, filter non global tags out.
427 428 tags = [t for t in self.repo.tagslist()
428 429 if self.repo.tagtype(t[0]) == 'global']
429 430 return dict([(name, hex(node)) for name, node in tags
430 431 if self.keep(node)])
431 432
432 433 def getchangedfiles(self, rev, i):
433 434 ctx = self.changectx(rev)
434 435 parents = self.parents(ctx)
435 436 if not parents and i is None:
436 437 i = 0
437 438 changes = [], ctx.manifest().keys(), []
438 439 else:
439 440 i = i or 0
440 441 changes = self.repo.status(parents[i].node(), ctx.node())[:3]
441 442 changes = [[f for f in l if f not in self.ignored] for l in changes]
442 443
443 444 if i == 0:
444 445 self._changescache = (rev, changes)
445 446
446 447 return changes[0] + changes[1] + changes[2]
447 448
448 449 def converted(self, rev, destrev):
449 450 if self.convertfp is None:
450 451 self.convertfp = open(self.repo.join('shamap'), 'a')
451 452 self.convertfp.write('%s %s\n' % (destrev, rev))
452 453 self.convertfp.flush()
453 454
454 455 def before(self):
455 456 self.ui.debug('run hg source pre-conversion action\n')
456 457
457 458 def after(self):
458 459 self.ui.debug('run hg source post-conversion action\n')
459 460
460 461 def hasnativeorder(self):
461 462 return True
462 463
463 464 def hasnativeclose(self):
464 465 return True
465 466
466 467 def lookuprev(self, rev):
467 468 try:
468 469 return hex(self.repo.lookup(rev))
469 470 except error.RepoError:
470 471 return None
471 472
472 473 def getbookmarks(self):
473 474 return bookmarks.listbookmarks(self.repo)
474 475
475 476 def checkrevformat(self, revstr, mapname='splicemap'):
476 477 """ Mercurial, revision string is a 40 byte hex """
477 478 self.checkhexformat(revstr, mapname)
General Comments 0
You need to be logged in to leave comments. Login now