##// END OF EJS Templates
convert: add bookmark support to the hg sink
Edouard Gomez -
r13746:d80b7685 default
parent child Browse files
Show More
@@ -1,376 +1,386 b''
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, error
24 from mercurial import hg, util, context, bookmarks, error
25 25
26 26 from common import NoRepo, commit, converter_source, converter_sink
27 27
28 28 class mercurial_sink(converter_sink):
29 29 def __init__(self, ui, path):
30 30 converter_sink.__init__(self, ui, path)
31 31 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
32 32 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
33 33 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
34 34 self.lastbranch = None
35 35 if os.path.isdir(path) and len(os.listdir(path)) > 0:
36 36 try:
37 37 self.repo = hg.repository(self.ui, path)
38 38 if not self.repo.local():
39 39 raise NoRepo(_('%s is not a local Mercurial repository')
40 40 % path)
41 41 except error.RepoError, err:
42 42 ui.traceback()
43 43 raise NoRepo(err.args[0])
44 44 else:
45 45 try:
46 46 ui.status(_('initializing destination %s repository\n') % path)
47 47 self.repo = hg.repository(self.ui, path, create=True)
48 48 if not self.repo.local():
49 49 raise NoRepo(_('%s is not a local Mercurial repository')
50 50 % path)
51 51 self.created.append(path)
52 52 except error.RepoError:
53 53 ui.traceback()
54 54 raise NoRepo(_("could not create hg repository %s as sink")
55 55 % path)
56 56 self.lock = None
57 57 self.wlock = None
58 58 self.filemapmode = False
59 59
60 60 def before(self):
61 61 self.ui.debug('run hg sink pre-conversion action\n')
62 62 self.wlock = self.repo.wlock()
63 63 self.lock = self.repo.lock()
64 64
65 65 def after(self):
66 66 self.ui.debug('run hg sink post-conversion action\n')
67 67 if self.lock:
68 68 self.lock.release()
69 69 if self.wlock:
70 70 self.wlock.release()
71 71
72 72 def revmapfile(self):
73 73 return os.path.join(self.path, ".hg", "shamap")
74 74
75 75 def authorfile(self):
76 76 return os.path.join(self.path, ".hg", "authormap")
77 77
78 78 def getheads(self):
79 79 h = self.repo.changelog.heads()
80 80 return [hex(x) for x in h]
81 81
82 82 def setbranch(self, branch, pbranches):
83 83 if not self.clonebranches:
84 84 return
85 85
86 86 setbranch = (branch != self.lastbranch)
87 87 self.lastbranch = branch
88 88 if not branch:
89 89 branch = 'default'
90 90 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
91 91 pbranch = pbranches and pbranches[0][1] or 'default'
92 92
93 93 branchpath = os.path.join(self.path, branch)
94 94 if setbranch:
95 95 self.after()
96 96 try:
97 97 self.repo = hg.repository(self.ui, branchpath)
98 98 except:
99 99 self.repo = hg.repository(self.ui, branchpath, create=True)
100 100 self.before()
101 101
102 102 # pbranches may bring revisions from other branches (merge parents)
103 103 # Make sure we have them, or pull them.
104 104 missings = {}
105 105 for b in pbranches:
106 106 try:
107 107 self.repo.lookup(b[0])
108 108 except:
109 109 missings.setdefault(b[1], []).append(b[0])
110 110
111 111 if missings:
112 112 self.after()
113 113 for pbranch, heads in missings.iteritems():
114 114 pbranchpath = os.path.join(self.path, pbranch)
115 115 prepo = hg.repository(self.ui, pbranchpath)
116 116 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
117 117 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
118 118 self.before()
119 119
120 120 def _rewritetags(self, source, revmap, data):
121 121 fp = cStringIO.StringIO()
122 122 for line in data.splitlines():
123 123 s = line.split(' ', 1)
124 124 if len(s) != 2:
125 125 continue
126 126 revid = revmap.get(source.lookuprev(s[0]))
127 127 if not revid:
128 128 continue
129 129 fp.write('%s %s\n' % (revid, s[1]))
130 130 return fp.getvalue()
131 131
132 132 def putcommit(self, files, copies, parents, commit, source, revmap):
133 133
134 134 files = dict(files)
135 135 def getfilectx(repo, memctx, f):
136 136 v = files[f]
137 137 data, mode = source.getfile(f, v)
138 138 if f == '.hgtags':
139 139 data = self._rewritetags(source, revmap, data)
140 140 return context.memfilectx(f, data, 'l' in mode, 'x' in mode,
141 141 copies.get(f))
142 142
143 143 pl = []
144 144 for p in parents:
145 145 if p not in pl:
146 146 pl.append(p)
147 147 parents = pl
148 148 nparents = len(parents)
149 149 if self.filemapmode and nparents == 1:
150 150 m1node = self.repo.changelog.read(bin(parents[0]))[0]
151 151 parent = parents[0]
152 152
153 153 if len(parents) < 2:
154 154 parents.append(nullid)
155 155 if len(parents) < 2:
156 156 parents.append(nullid)
157 157 p2 = parents.pop(0)
158 158
159 159 text = commit.desc
160 160 extra = commit.extra.copy()
161 161 if self.branchnames and commit.branch:
162 162 extra['branch'] = commit.branch
163 163 if commit.rev:
164 164 extra['convert_revision'] = commit.rev
165 165
166 166 while parents:
167 167 p1 = p2
168 168 p2 = parents.pop(0)
169 169 ctx = context.memctx(self.repo, (p1, p2), text, files.keys(),
170 170 getfilectx, commit.author, commit.date, extra)
171 171 self.repo.commitctx(ctx)
172 172 text = "(octopus merge fixup)\n"
173 173 p2 = hex(self.repo.changelog.tip())
174 174
175 175 if self.filemapmode and nparents == 1:
176 176 man = self.repo.manifest
177 177 mnode = self.repo.changelog.read(bin(p2))[0]
178 178 closed = 'close' in commit.extra
179 179 if not closed and not man.cmp(m1node, man.revision(mnode)):
180 180 self.ui.status(_("filtering out empty revision\n"))
181 181 self.repo.rollback()
182 182 return parent
183 183 return p2
184 184
185 185 def puttags(self, tags):
186 186 try:
187 187 parentctx = self.repo[self.tagsbranch]
188 188 tagparent = parentctx.node()
189 189 except error.RepoError:
190 190 parentctx = None
191 191 tagparent = nullid
192 192
193 193 try:
194 194 oldlines = sorted(parentctx['.hgtags'].data().splitlines(True))
195 195 except:
196 196 oldlines = []
197 197
198 198 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
199 199 if newlines == oldlines:
200 200 return None, None
201 201 data = "".join(newlines)
202 202 def getfilectx(repo, memctx, f):
203 203 return context.memfilectx(f, data, False, False, None)
204 204
205 205 self.ui.status(_("updating tags\n"))
206 206 date = "%s 0" % int(time.mktime(time.gmtime()))
207 207 extra = {'branch': self.tagsbranch}
208 208 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
209 209 [".hgtags"], getfilectx, "convert-repo", date,
210 210 extra)
211 211 self.repo.commitctx(ctx)
212 212 return hex(self.repo.changelog.tip()), hex(tagparent)
213 213
214 214 def setfilemapmode(self, active):
215 215 self.filemapmode = active
216 216
217 def putbookmarks(self, updatedbookmark):
218 if not len(updatedbookmark):
219 return
220
221 self.ui.status(_("updating bookmarks\n"))
222 for bookmark in updatedbookmark:
223 self.repo._bookmarks[bookmark] = bin(updatedbookmark[bookmark])
224 bookmarks.write(self.repo)
225
226
217 227 class mercurial_source(converter_source):
218 228 def __init__(self, ui, path, rev=None):
219 229 converter_source.__init__(self, ui, path, rev)
220 230 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
221 231 self.ignored = set()
222 232 self.saverev = ui.configbool('convert', 'hg.saverev', False)
223 233 try:
224 234 self.repo = hg.repository(self.ui, path)
225 235 # try to provoke an exception if this isn't really a hg
226 236 # repo, but some other bogus compatible-looking url
227 237 if not self.repo.local():
228 238 raise error.RepoError()
229 239 except error.RepoError:
230 240 ui.traceback()
231 241 raise NoRepo(_("%s is not a local Mercurial repository") % path)
232 242 self.lastrev = None
233 243 self.lastctx = None
234 244 self._changescache = None
235 245 self.convertfp = None
236 246 # Restrict converted revisions to startrev descendants
237 247 startnode = ui.config('convert', 'hg.startrev')
238 248 if startnode is not None:
239 249 try:
240 250 startnode = self.repo.lookup(startnode)
241 251 except error.RepoError:
242 252 raise util.Abort(_('%s is not a valid start revision')
243 253 % startnode)
244 254 startrev = self.repo.changelog.rev(startnode)
245 255 children = {startnode: 1}
246 256 for rev in self.repo.changelog.descendants(startrev):
247 257 children[self.repo.changelog.node(rev)] = 1
248 258 self.keep = children.__contains__
249 259 else:
250 260 self.keep = util.always
251 261
252 262 def changectx(self, rev):
253 263 if self.lastrev != rev:
254 264 self.lastctx = self.repo[rev]
255 265 self.lastrev = rev
256 266 return self.lastctx
257 267
258 268 def parents(self, ctx):
259 269 return [p for p in ctx.parents() if p and self.keep(p.node())]
260 270
261 271 def getheads(self):
262 272 if self.rev:
263 273 heads = [self.repo[self.rev].node()]
264 274 else:
265 275 heads = self.repo.heads()
266 276 return [hex(h) for h in heads if self.keep(h)]
267 277
268 278 def getfile(self, name, rev):
269 279 try:
270 280 fctx = self.changectx(rev)[name]
271 281 return fctx.data(), fctx.flags()
272 282 except error.LookupError, err:
273 283 raise IOError(err)
274 284
275 285 def getchanges(self, rev):
276 286 ctx = self.changectx(rev)
277 287 parents = self.parents(ctx)
278 288 if not parents:
279 289 files = sorted(ctx.manifest())
280 290 if self.ignoreerrors:
281 291 # calling getcopies() is a simple way to detect missing
282 292 # revlogs and populate self.ignored
283 293 self.getcopies(ctx, parents, files)
284 294 return [(f, rev) for f in files if f not in self.ignored], {}
285 295 if self._changescache and self._changescache[0] == rev:
286 296 m, a, r = self._changescache[1]
287 297 else:
288 298 m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
289 299 # getcopies() detects missing revlogs early, run it before
290 300 # filtering the changes.
291 301 copies = self.getcopies(ctx, parents, m + a)
292 302 changes = [(name, rev) for name in m + a + r
293 303 if name not in self.ignored]
294 304 return sorted(changes), copies
295 305
296 306 def getcopies(self, ctx, parents, files):
297 307 copies = {}
298 308 for name in files:
299 309 if name in self.ignored:
300 310 continue
301 311 try:
302 312 copysource, copynode = ctx.filectx(name).renamed()
303 313 if copysource in self.ignored or not self.keep(copynode):
304 314 continue
305 315 # Ignore copy sources not in parent revisions
306 316 found = False
307 317 for p in parents:
308 318 if copysource in p:
309 319 found = True
310 320 break
311 321 if not found:
312 322 continue
313 323 copies[name] = copysource
314 324 except TypeError:
315 325 pass
316 326 except error.LookupError, e:
317 327 if not self.ignoreerrors:
318 328 raise
319 329 self.ignored.add(name)
320 330 self.ui.warn(_('ignoring: %s\n') % e)
321 331 return copies
322 332
323 333 def getcommit(self, rev):
324 334 ctx = self.changectx(rev)
325 335 parents = [p.hex() for p in self.parents(ctx)]
326 336 if self.saverev:
327 337 crev = rev
328 338 else:
329 339 crev = None
330 340 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
331 341 desc=ctx.description(), rev=crev, parents=parents,
332 342 branch=ctx.branch(), extra=ctx.extra(),
333 343 sortkey=ctx.rev())
334 344
335 345 def gettags(self):
336 346 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
337 347 return dict([(name, hex(node)) for name, node in tags
338 348 if self.keep(node)])
339 349
340 350 def getchangedfiles(self, rev, i):
341 351 ctx = self.changectx(rev)
342 352 parents = self.parents(ctx)
343 353 if not parents and i is None:
344 354 i = 0
345 355 changes = [], ctx.manifest().keys(), []
346 356 else:
347 357 i = i or 0
348 358 changes = self.repo.status(parents[i].node(), ctx.node())[:3]
349 359 changes = [[f for f in l if f not in self.ignored] for l in changes]
350 360
351 361 if i == 0:
352 362 self._changescache = (rev, changes)
353 363
354 364 return changes[0] + changes[1] + changes[2]
355 365
356 366 def converted(self, rev, destrev):
357 367 if self.convertfp is None:
358 368 self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'),
359 369 'a')
360 370 self.convertfp.write('%s %s\n' % (destrev, rev))
361 371 self.convertfp.flush()
362 372
363 373 def before(self):
364 374 self.ui.debug('run hg source pre-conversion action\n')
365 375
366 376 def after(self):
367 377 self.ui.debug('run hg source post-conversion action\n')
368 378
369 379 def hasnativeorder(self):
370 380 return True
371 381
372 382 def lookuprev(self, rev):
373 383 try:
374 384 return hex(self.repo.lookup(rev))
375 385 except error.RepoError:
376 386 return None
General Comments 0
You need to be logged in to leave comments. Login now