##// END OF EJS Templates
convert: don't use multi-argument set.update...
Mads Kiilerich -
r22360:3e483253 default
parent child Browse files
Show More
@@ -1,476 +1,477
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 24 from mercurial import hg, util, context, bookmarks, error, scmutil
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 116 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
117 117 self.before()
118 118
119 119 def _rewritetags(self, source, revmap, data):
120 120 fp = cStringIO.StringIO()
121 121 for line in data.splitlines():
122 122 s = line.split(' ', 1)
123 123 if len(s) != 2:
124 124 continue
125 125 revid = revmap.get(source.lookuprev(s[0]))
126 126 if not revid:
127 127 continue
128 128 fp.write('%s %s\n' % (revid, s[1]))
129 129 return fp.getvalue()
130 130
131 131 def putcommit(self, files, copies, parents, commit, source, revmap, full):
132 132 files = dict(files)
133 133 def getfilectx(repo, memctx, f):
134 134 try:
135 135 v = files[f]
136 136 except KeyError:
137 137 return None
138 138 data, mode = source.getfile(f, v)
139 139 if data is None:
140 140 return None
141 141 if f == '.hgtags':
142 142 data = self._rewritetags(source, revmap, data)
143 143 return context.memfilectx(self.repo, f, data, 'l' in mode,
144 144 'x' in mode, copies.get(f))
145 145
146 146 pl = []
147 147 for p in parents:
148 148 if p not in pl:
149 149 pl.append(p)
150 150 parents = pl
151 151 nparents = len(parents)
152 152 if self.filemapmode and nparents == 1:
153 153 m1node = self.repo.changelog.read(bin(parents[0]))[0]
154 154 parent = parents[0]
155 155
156 156 if len(parents) < 2:
157 157 parents.append(nullid)
158 158 if len(parents) < 2:
159 159 parents.append(nullid)
160 160 p2 = parents.pop(0)
161 161
162 162 text = commit.desc
163 163
164 164 sha1s = re.findall(sha1re, text)
165 165 for sha1 in sha1s:
166 166 oldrev = source.lookuprev(sha1)
167 167 newrev = revmap.get(oldrev)
168 168 if newrev is not None:
169 169 text = text.replace(sha1, newrev[:len(sha1)])
170 170
171 171 extra = commit.extra.copy()
172 172
173 173 for label in ('source', 'transplant_source', 'rebase_source'):
174 174 node = extra.get(label)
175 175
176 176 if node is None:
177 177 continue
178 178
179 179 # Only transplant stores its reference in binary
180 180 if label == 'transplant_source':
181 181 node = hex(node)
182 182
183 183 newrev = revmap.get(node)
184 184 if newrev is not None:
185 185 if label == 'transplant_source':
186 186 newrev = bin(newrev)
187 187
188 188 extra[label] = newrev
189 189
190 190 if self.branchnames and commit.branch:
191 191 extra['branch'] = commit.branch
192 192 if commit.rev:
193 193 extra['convert_revision'] = commit.rev
194 194
195 195 while parents:
196 196 p1 = p2
197 197 p2 = parents.pop(0)
198 198 fileset = set(files)
199 199 if full:
200 fileset.update(self.repo[p1], self.repo[p2])
200 fileset.update(self.repo[p1])
201 fileset.update(self.repo[p2])
201 202 ctx = context.memctx(self.repo, (p1, p2), text, fileset,
202 203 getfilectx, commit.author, commit.date, extra)
203 204 self.repo.commitctx(ctx)
204 205 text = "(octopus merge fixup)\n"
205 206 p2 = hex(self.repo.changelog.tip())
206 207
207 208 if self.filemapmode and nparents == 1:
208 209 man = self.repo.manifest
209 210 mnode = self.repo.changelog.read(bin(p2))[0]
210 211 closed = 'close' in commit.extra
211 212 if not closed and not man.cmp(m1node, man.revision(mnode)):
212 213 self.ui.status(_("filtering out empty revision\n"))
213 214 self.repo.rollback(force=True)
214 215 return parent
215 216 return p2
216 217
217 218 def puttags(self, tags):
218 219 try:
219 220 parentctx = self.repo[self.tagsbranch]
220 221 tagparent = parentctx.node()
221 222 except error.RepoError:
222 223 parentctx = None
223 224 tagparent = nullid
224 225
225 226 oldlines = set()
226 227 for branch, heads in self.repo.branchmap().iteritems():
227 228 for h in heads:
228 229 if '.hgtags' in self.repo[h]:
229 230 oldlines.update(
230 231 set(self.repo[h]['.hgtags'].data().splitlines(True)))
231 232 oldlines = sorted(list(oldlines))
232 233
233 234 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
234 235 if newlines == oldlines:
235 236 return None, None
236 237
237 238 # if the old and new tags match, then there is nothing to update
238 239 oldtags = set()
239 240 newtags = set()
240 241 for line in oldlines:
241 242 s = line.strip().split(' ', 1)
242 243 if len(s) != 2:
243 244 continue
244 245 oldtags.add(s[1])
245 246 for line in newlines:
246 247 s = line.strip().split(' ', 1)
247 248 if len(s) != 2:
248 249 continue
249 250 if s[1] not in oldtags:
250 251 newtags.add(s[1].strip())
251 252
252 253 if not newtags:
253 254 return None, None
254 255
255 256 data = "".join(newlines)
256 257 def getfilectx(repo, memctx, f):
257 258 return context.memfilectx(repo, f, data, False, False, None)
258 259
259 260 self.ui.status(_("updating tags\n"))
260 261 date = "%s 0" % int(time.mktime(time.gmtime()))
261 262 extra = {'branch': self.tagsbranch}
262 263 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
263 264 [".hgtags"], getfilectx, "convert-repo", date,
264 265 extra)
265 266 self.repo.commitctx(ctx)
266 267 return hex(self.repo.changelog.tip()), hex(tagparent)
267 268
268 269 def setfilemapmode(self, active):
269 270 self.filemapmode = active
270 271
271 272 def putbookmarks(self, updatedbookmark):
272 273 if not len(updatedbookmark):
273 274 return
274 275
275 276 self.ui.status(_("updating bookmarks\n"))
276 277 destmarks = self.repo._bookmarks
277 278 for bookmark in updatedbookmark:
278 279 destmarks[bookmark] = bin(updatedbookmark[bookmark])
279 280 destmarks.write()
280 281
281 282 def hascommitfrommap(self, rev):
282 283 # the exact semantics of clonebranches is unclear so we can't say no
283 284 return rev in self.repo or self.clonebranches
284 285
285 286 def hascommitforsplicemap(self, rev):
286 287 if rev not in self.repo and self.clonebranches:
287 288 raise util.Abort(_('revision %s not found in destination '
288 289 'repository (lookups with clonebranches=true '
289 290 'are not implemented)') % rev)
290 291 return rev in self.repo
291 292
292 293 class mercurial_source(converter_source):
293 294 def __init__(self, ui, path, rev=None):
294 295 converter_source.__init__(self, ui, path, rev)
295 296 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
296 297 self.ignored = set()
297 298 self.saverev = ui.configbool('convert', 'hg.saverev', False)
298 299 try:
299 300 self.repo = hg.repository(self.ui, path)
300 301 # try to provoke an exception if this isn't really a hg
301 302 # repo, but some other bogus compatible-looking url
302 303 if not self.repo.local():
303 304 raise error.RepoError
304 305 except error.RepoError:
305 306 ui.traceback()
306 307 raise NoRepo(_("%s is not a local Mercurial repository") % path)
307 308 self.lastrev = None
308 309 self.lastctx = None
309 310 self._changescache = None, None
310 311 self.convertfp = None
311 312 # Restrict converted revisions to startrev descendants
312 313 startnode = ui.config('convert', 'hg.startrev')
313 314 hgrevs = ui.config('convert', 'hg.revs')
314 315 if hgrevs is None:
315 316 if startnode is not None:
316 317 try:
317 318 startnode = self.repo.lookup(startnode)
318 319 except error.RepoError:
319 320 raise util.Abort(_('%s is not a valid start revision')
320 321 % startnode)
321 322 startrev = self.repo.changelog.rev(startnode)
322 323 children = {startnode: 1}
323 324 for r in self.repo.changelog.descendants([startrev]):
324 325 children[self.repo.changelog.node(r)] = 1
325 326 self.keep = children.__contains__
326 327 else:
327 328 self.keep = util.always
328 329 if rev:
329 330 self._heads = [self.repo[rev].node()]
330 331 else:
331 332 self._heads = self.repo.heads()
332 333 else:
333 334 if rev or startnode is not None:
334 335 raise util.Abort(_('hg.revs cannot be combined with '
335 336 'hg.startrev or --rev'))
336 337 nodes = set()
337 338 parents = set()
338 339 for r in scmutil.revrange(self.repo, [hgrevs]):
339 340 ctx = self.repo[r]
340 341 nodes.add(ctx.node())
341 342 parents.update(p.node() for p in ctx.parents())
342 343 self.keep = nodes.__contains__
343 344 self._heads = nodes - parents
344 345
345 346 def changectx(self, rev):
346 347 if self.lastrev != rev:
347 348 self.lastctx = self.repo[rev]
348 349 self.lastrev = rev
349 350 return self.lastctx
350 351
351 352 def parents(self, ctx):
352 353 return [p for p in ctx.parents() if p and self.keep(p.node())]
353 354
354 355 def getheads(self):
355 356 return [hex(h) for h in self._heads if self.keep(h)]
356 357
357 358 def getfile(self, name, rev):
358 359 try:
359 360 fctx = self.changectx(rev)[name]
360 361 return fctx.data(), fctx.flags()
361 362 except error.LookupError:
362 363 return None, None
363 364
364 365 def getchanges(self, rev, full):
365 366 ctx = self.changectx(rev)
366 367 parents = self.parents(ctx)
367 368 if full or not parents:
368 369 files = copyfiles = ctx.manifest()
369 370 if parents:
370 371 if self._changescache[0] == rev:
371 372 m, a, r = self._changescache[1]
372 373 else:
373 374 m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
374 375 if not full:
375 376 files = m + a + r
376 377 copyfiles = m + a
377 378 # getcopies() is also run for roots and before filtering so missing
378 379 # revlogs are detected early
379 380 copies = self.getcopies(ctx, parents, copyfiles)
380 381 changes = [(f, rev) for f in files if f not in self.ignored]
381 382 changes.sort()
382 383 return changes, copies
383 384
384 385 def getcopies(self, ctx, parents, files):
385 386 copies = {}
386 387 for name in files:
387 388 if name in self.ignored:
388 389 continue
389 390 try:
390 391 copysource, _copynode = ctx.filectx(name).renamed()
391 392 if copysource in self.ignored:
392 393 continue
393 394 # Ignore copy sources not in parent revisions
394 395 found = False
395 396 for p in parents:
396 397 if copysource in p:
397 398 found = True
398 399 break
399 400 if not found:
400 401 continue
401 402 copies[name] = copysource
402 403 except TypeError:
403 404 pass
404 405 except error.LookupError, e:
405 406 if not self.ignoreerrors:
406 407 raise
407 408 self.ignored.add(name)
408 409 self.ui.warn(_('ignoring: %s\n') % e)
409 410 return copies
410 411
411 412 def getcommit(self, rev):
412 413 ctx = self.changectx(rev)
413 414 parents = [p.hex() for p in self.parents(ctx)]
414 415 if self.saverev:
415 416 crev = rev
416 417 else:
417 418 crev = None
418 419 return commit(author=ctx.user(),
419 420 date=util.datestr(ctx.date(), '%Y-%m-%d %H:%M:%S %1%2'),
420 421 desc=ctx.description(), rev=crev, parents=parents,
421 422 branch=ctx.branch(), extra=ctx.extra(),
422 423 sortkey=ctx.rev())
423 424
424 425 def gettags(self):
425 426 # This will get written to .hgtags, filter non global tags out.
426 427 tags = [t for t in self.repo.tagslist()
427 428 if self.repo.tagtype(t[0]) == 'global']
428 429 return dict([(name, hex(node)) for name, node in tags
429 430 if self.keep(node)])
430 431
431 432 def getchangedfiles(self, rev, i):
432 433 ctx = self.changectx(rev)
433 434 parents = self.parents(ctx)
434 435 if not parents and i is None:
435 436 i = 0
436 437 changes = [], ctx.manifest().keys(), []
437 438 else:
438 439 i = i or 0
439 440 changes = self.repo.status(parents[i].node(), ctx.node())[:3]
440 441 changes = [[f for f in l if f not in self.ignored] for l in changes]
441 442
442 443 if i == 0:
443 444 self._changescache = (rev, changes)
444 445
445 446 return changes[0] + changes[1] + changes[2]
446 447
447 448 def converted(self, rev, destrev):
448 449 if self.convertfp is None:
449 450 self.convertfp = open(self.repo.join('shamap'), 'a')
450 451 self.convertfp.write('%s %s\n' % (destrev, rev))
451 452 self.convertfp.flush()
452 453
453 454 def before(self):
454 455 self.ui.debug('run hg source pre-conversion action\n')
455 456
456 457 def after(self):
457 458 self.ui.debug('run hg source post-conversion action\n')
458 459
459 460 def hasnativeorder(self):
460 461 return True
461 462
462 463 def hasnativeclose(self):
463 464 return True
464 465
465 466 def lookuprev(self, rev):
466 467 try:
467 468 return hex(self.repo.lookup(rev))
468 469 except error.RepoError:
469 470 return None
470 471
471 472 def getbookmarks(self):
472 473 return bookmarks.listbookmarks(self.repo)
473 474
474 475 def checkrevformat(self, revstr, mapname='splicemap'):
475 476 """ Mercurial, revision string is a 40 byte hex """
476 477 self.checkhexformat(revstr, mapname)
General Comments 0
You need to be logged in to leave comments. Login now