##// END OF EJS Templates
convert: avoid updating tags when there is nothing new...
Sean Farley -
r20377:5842d63c default
parent child Browse files
Show More
@@ -1,431 +1,450
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 getheads(self):
82 82 h = self.repo.changelog.heads()
83 83 return [hex(x) for x in h]
84 84
85 85 def setbranch(self, branch, pbranches):
86 86 if not self.clonebranches:
87 87 return
88 88
89 89 setbranch = (branch != self.lastbranch)
90 90 self.lastbranch = branch
91 91 if not branch:
92 92 branch = 'default'
93 93 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
94 94 pbranch = pbranches and pbranches[0][1] or 'default'
95 95
96 96 branchpath = os.path.join(self.path, branch)
97 97 if setbranch:
98 98 self.after()
99 99 try:
100 100 self.repo = hg.repository(self.ui, branchpath)
101 101 except Exception:
102 102 self.repo = hg.repository(self.ui, branchpath, create=True)
103 103 self.before()
104 104
105 105 # pbranches may bring revisions from other branches (merge parents)
106 106 # Make sure we have them, or pull them.
107 107 missings = {}
108 108 for b in pbranches:
109 109 try:
110 110 self.repo.lookup(b[0])
111 111 except Exception:
112 112 missings.setdefault(b[1], []).append(b[0])
113 113
114 114 if missings:
115 115 self.after()
116 116 for pbranch, heads in sorted(missings.iteritems()):
117 117 pbranchpath = os.path.join(self.path, pbranch)
118 118 prepo = hg.peer(self.ui, {}, pbranchpath)
119 119 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
120 120 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
121 121 self.before()
122 122
123 123 def _rewritetags(self, source, revmap, data):
124 124 fp = cStringIO.StringIO()
125 125 for line in data.splitlines():
126 126 s = line.split(' ', 1)
127 127 if len(s) != 2:
128 128 continue
129 129 revid = revmap.get(source.lookuprev(s[0]))
130 130 if not revid:
131 131 continue
132 132 fp.write('%s %s\n' % (revid, s[1]))
133 133 return fp.getvalue()
134 134
135 135 def putcommit(self, files, copies, parents, commit, source, revmap):
136 136
137 137 files = dict(files)
138 138 def getfilectx(repo, memctx, f):
139 139 v = files[f]
140 140 data, mode = source.getfile(f, v)
141 141 if f == '.hgtags':
142 142 data = self._rewritetags(source, revmap, data)
143 143 return context.memfilectx(f, data, 'l' in mode, 'x' in mode,
144 144 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 if self.branchnames and commit.branch:
173 173 extra['branch'] = commit.branch
174 174 if commit.rev:
175 175 extra['convert_revision'] = commit.rev
176 176
177 177 while parents:
178 178 p1 = p2
179 179 p2 = parents.pop(0)
180 180 ctx = context.memctx(self.repo, (p1, p2), text, files.keys(),
181 181 getfilectx, commit.author, commit.date, extra)
182 182 self.repo.commitctx(ctx)
183 183 text = "(octopus merge fixup)\n"
184 184 p2 = hex(self.repo.changelog.tip())
185 185
186 186 if self.filemapmode and nparents == 1:
187 187 man = self.repo.manifest
188 188 mnode = self.repo.changelog.read(bin(p2))[0]
189 189 closed = 'close' in commit.extra
190 190 if not closed and not man.cmp(m1node, man.revision(mnode)):
191 191 self.ui.status(_("filtering out empty revision\n"))
192 192 self.repo.rollback(force=True)
193 193 return parent
194 194 return p2
195 195
196 196 def puttags(self, tags):
197 197 try:
198 198 parentctx = self.repo[self.tagsbranch]
199 199 tagparent = parentctx.node()
200 200 except error.RepoError:
201 201 parentctx = None
202 202 tagparent = nullid
203 203
204 204 oldlines = set()
205 205 for branch, heads in self.repo.branchmap().iteritems():
206 206 for h in heads:
207 207 if '.hgtags' in self.repo[h]:
208 208 oldlines.update(
209 209 set(self.repo[h]['.hgtags'].data().splitlines(True)))
210 210 oldlines = sorted(list(oldlines))
211 211
212 212 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
213 213 if newlines == oldlines:
214 214 return None, None
215
216 # if the old and new tags match, then there is nothing to update
217 oldtags = set()
218 newtags = set()
219 for line in oldlines:
220 s = line.strip().split(' ', 1)
221 if len(s) != 2:
222 continue
223 oldtags.add(s[1])
224 for line in newlines:
225 s = line.strip().split(' ', 1)
226 if len(s) != 2:
227 continue
228 if s[1] not in oldtags:
229 newtags.add(s[1].strip())
230
231 if not newtags:
232 return None, None
233
215 234 data = "".join(newlines)
216 235 def getfilectx(repo, memctx, f):
217 236 return context.memfilectx(f, data, False, False, None)
218 237
219 238 self.ui.status(_("updating tags\n"))
220 239 date = "%s 0" % int(time.mktime(time.gmtime()))
221 240 extra = {'branch': self.tagsbranch}
222 241 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
223 242 [".hgtags"], getfilectx, "convert-repo", date,
224 243 extra)
225 244 self.repo.commitctx(ctx)
226 245 return hex(self.repo.changelog.tip()), hex(tagparent)
227 246
228 247 def setfilemapmode(self, active):
229 248 self.filemapmode = active
230 249
231 250 def putbookmarks(self, updatedbookmark):
232 251 if not len(updatedbookmark):
233 252 return
234 253
235 254 self.ui.status(_("updating bookmarks\n"))
236 255 destmarks = self.repo._bookmarks
237 256 for bookmark in updatedbookmark:
238 257 destmarks[bookmark] = bin(updatedbookmark[bookmark])
239 258 destmarks.write()
240 259
241 260 def hascommit(self, rev):
242 261 if rev not in self.repo and self.clonebranches:
243 262 raise util.Abort(_('revision %s not found in destination '
244 263 'repository (lookups with clonebranches=true '
245 264 'are not implemented)') % rev)
246 265 return rev in self.repo
247 266
248 267 class mercurial_source(converter_source):
249 268 def __init__(self, ui, path, rev=None):
250 269 converter_source.__init__(self, ui, path, rev)
251 270 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
252 271 self.ignored = set()
253 272 self.saverev = ui.configbool('convert', 'hg.saverev', False)
254 273 try:
255 274 self.repo = hg.repository(self.ui, path)
256 275 # try to provoke an exception if this isn't really a hg
257 276 # repo, but some other bogus compatible-looking url
258 277 if not self.repo.local():
259 278 raise error.RepoError
260 279 except error.RepoError:
261 280 ui.traceback()
262 281 raise NoRepo(_("%s is not a local Mercurial repository") % path)
263 282 self.lastrev = None
264 283 self.lastctx = None
265 284 self._changescache = None
266 285 self.convertfp = None
267 286 # Restrict converted revisions to startrev descendants
268 287 startnode = ui.config('convert', 'hg.startrev')
269 288 hgrevs = ui.config('convert', 'hg.revs')
270 289 if hgrevs is None:
271 290 if startnode is not None:
272 291 try:
273 292 startnode = self.repo.lookup(startnode)
274 293 except error.RepoError:
275 294 raise util.Abort(_('%s is not a valid start revision')
276 295 % startnode)
277 296 startrev = self.repo.changelog.rev(startnode)
278 297 children = {startnode: 1}
279 298 for r in self.repo.changelog.descendants([startrev]):
280 299 children[self.repo.changelog.node(r)] = 1
281 300 self.keep = children.__contains__
282 301 else:
283 302 self.keep = util.always
284 303 if rev:
285 304 self._heads = [self.repo[rev].node()]
286 305 else:
287 306 self._heads = self.repo.heads()
288 307 else:
289 308 if rev or startnode is not None:
290 309 raise util.Abort(_('hg.revs cannot be combined with '
291 310 'hg.startrev or --rev'))
292 311 nodes = set()
293 312 parents = set()
294 313 for r in scmutil.revrange(self.repo, [hgrevs]):
295 314 ctx = self.repo[r]
296 315 nodes.add(ctx.node())
297 316 parents.update(p.node() for p in ctx.parents())
298 317 self.keep = nodes.__contains__
299 318 self._heads = nodes - parents
300 319
301 320 def changectx(self, rev):
302 321 if self.lastrev != rev:
303 322 self.lastctx = self.repo[rev]
304 323 self.lastrev = rev
305 324 return self.lastctx
306 325
307 326 def parents(self, ctx):
308 327 return [p for p in ctx.parents() if p and self.keep(p.node())]
309 328
310 329 def getheads(self):
311 330 return [hex(h) for h in self._heads if self.keep(h)]
312 331
313 332 def getfile(self, name, rev):
314 333 try:
315 334 fctx = self.changectx(rev)[name]
316 335 return fctx.data(), fctx.flags()
317 336 except error.LookupError, err:
318 337 raise IOError(err)
319 338
320 339 def getchanges(self, rev):
321 340 ctx = self.changectx(rev)
322 341 parents = self.parents(ctx)
323 342 if not parents:
324 343 files = sorted(ctx.manifest())
325 344 # getcopies() is not needed for roots, but it is a simple way to
326 345 # detect missing revlogs and abort on errors or populate
327 346 # self.ignored
328 347 self.getcopies(ctx, parents, files)
329 348 return [(f, rev) for f in files if f not in self.ignored], {}
330 349 if self._changescache and self._changescache[0] == rev:
331 350 m, a, r = self._changescache[1]
332 351 else:
333 352 m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
334 353 # getcopies() detects missing revlogs early, run it before
335 354 # filtering the changes.
336 355 copies = self.getcopies(ctx, parents, m + a)
337 356 changes = [(name, rev) for name in m + a + r
338 357 if name not in self.ignored]
339 358 return sorted(changes), copies
340 359
341 360 def getcopies(self, ctx, parents, files):
342 361 copies = {}
343 362 for name in files:
344 363 if name in self.ignored:
345 364 continue
346 365 try:
347 366 copysource, _copynode = ctx.filectx(name).renamed()
348 367 if copysource in self.ignored:
349 368 continue
350 369 # Ignore copy sources not in parent revisions
351 370 found = False
352 371 for p in parents:
353 372 if copysource in p:
354 373 found = True
355 374 break
356 375 if not found:
357 376 continue
358 377 copies[name] = copysource
359 378 except TypeError:
360 379 pass
361 380 except error.LookupError, e:
362 381 if not self.ignoreerrors:
363 382 raise
364 383 self.ignored.add(name)
365 384 self.ui.warn(_('ignoring: %s\n') % e)
366 385 return copies
367 386
368 387 def getcommit(self, rev):
369 388 ctx = self.changectx(rev)
370 389 parents = [p.hex() for p in self.parents(ctx)]
371 390 if self.saverev:
372 391 crev = rev
373 392 else:
374 393 crev = None
375 394 return commit(author=ctx.user(),
376 395 date=util.datestr(ctx.date(), '%Y-%m-%d %H:%M:%S %1%2'),
377 396 desc=ctx.description(), rev=crev, parents=parents,
378 397 branch=ctx.branch(), extra=ctx.extra(),
379 398 sortkey=ctx.rev())
380 399
381 400 def gettags(self):
382 401 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
383 402 return dict([(name, hex(node)) for name, node in tags
384 403 if self.keep(node)])
385 404
386 405 def getchangedfiles(self, rev, i):
387 406 ctx = self.changectx(rev)
388 407 parents = self.parents(ctx)
389 408 if not parents and i is None:
390 409 i = 0
391 410 changes = [], ctx.manifest().keys(), []
392 411 else:
393 412 i = i or 0
394 413 changes = self.repo.status(parents[i].node(), ctx.node())[:3]
395 414 changes = [[f for f in l if f not in self.ignored] for l in changes]
396 415
397 416 if i == 0:
398 417 self._changescache = (rev, changes)
399 418
400 419 return changes[0] + changes[1] + changes[2]
401 420
402 421 def converted(self, rev, destrev):
403 422 if self.convertfp is None:
404 423 self.convertfp = open(self.repo.join('shamap'), 'a')
405 424 self.convertfp.write('%s %s\n' % (destrev, rev))
406 425 self.convertfp.flush()
407 426
408 427 def before(self):
409 428 self.ui.debug('run hg source pre-conversion action\n')
410 429
411 430 def after(self):
412 431 self.ui.debug('run hg source post-conversion action\n')
413 432
414 433 def hasnativeorder(self):
415 434 return True
416 435
417 436 def hasnativeclose(self):
418 437 return True
419 438
420 439 def lookuprev(self, rev):
421 440 try:
422 441 return hex(self.repo.lookup(rev))
423 442 except error.RepoError:
424 443 return None
425 444
426 445 def getbookmarks(self):
427 446 return bookmarks.listbookmarks(self.repo)
428 447
429 448 def checkrevformat(self, revstr, mapname='splicemap'):
430 449 """ Mercurial, revision string is a 40 byte hex """
431 450 self.checkhexformat(revstr, mapname)
General Comments 0
You need to be logged in to leave comments. Login now