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