##// END OF EJS Templates
convert: drop unused getheads from sinks
Mads Kiilerich -
r20397:d7e78e6d default
parent child Browse files
Show More
@@ -1,448 +1,444 b''
1 1 # common.py - common code for the 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 import base64, errno, subprocess, os, datetime, re
9 9 import cPickle as pickle
10 10 from mercurial import util
11 11 from mercurial.i18n import _
12 12
13 13 propertycache = util.propertycache
14 14
15 15 def encodeargs(args):
16 16 def encodearg(s):
17 17 lines = base64.encodestring(s)
18 18 lines = [l.splitlines()[0] for l in lines]
19 19 return ''.join(lines)
20 20
21 21 s = pickle.dumps(args)
22 22 return encodearg(s)
23 23
24 24 def decodeargs(s):
25 25 s = base64.decodestring(s)
26 26 return pickle.loads(s)
27 27
28 28 class MissingTool(Exception):
29 29 pass
30 30
31 31 def checktool(exe, name=None, abort=True):
32 32 name = name or exe
33 33 if not util.findexe(exe):
34 34 exc = abort and util.Abort or MissingTool
35 35 raise exc(_('cannot find required "%s" tool') % name)
36 36
37 37 class NoRepo(Exception):
38 38 pass
39 39
40 40 SKIPREV = 'SKIP'
41 41
42 42 class commit(object):
43 43 def __init__(self, author, date, desc, parents, branch=None, rev=None,
44 44 extra={}, sortkey=None):
45 45 self.author = author or 'unknown'
46 46 self.date = date or '0 0'
47 47 self.desc = desc
48 48 self.parents = parents
49 49 self.branch = branch
50 50 self.rev = rev
51 51 self.extra = extra
52 52 self.sortkey = sortkey
53 53
54 54 class converter_source(object):
55 55 """Conversion source interface"""
56 56
57 57 def __init__(self, ui, path=None, rev=None):
58 58 """Initialize conversion source (or raise NoRepo("message")
59 59 exception if path is not a valid repository)"""
60 60 self.ui = ui
61 61 self.path = path
62 62 self.rev = rev
63 63
64 64 self.encoding = 'utf-8'
65 65
66 66 def checkhexformat(self, revstr, mapname='splicemap'):
67 67 """ fails if revstr is not a 40 byte hex. mercurial and git both uses
68 68 such format for their revision numbering
69 69 """
70 70 if not re.match(r'[0-9a-fA-F]{40,40}$', revstr):
71 71 raise util.Abort(_('%s entry %s is not a valid revision'
72 72 ' identifier') % (mapname, revstr))
73 73
74 74 def before(self):
75 75 pass
76 76
77 77 def after(self):
78 78 pass
79 79
80 80 def setrevmap(self, revmap):
81 81 """set the map of already-converted revisions"""
82 82 pass
83 83
84 84 def getheads(self):
85 85 """Return a list of this repository's heads"""
86 86 raise NotImplementedError
87 87
88 88 def getfile(self, name, rev):
89 89 """Return a pair (data, mode) where data is the file content
90 90 as a string and mode one of '', 'x' or 'l'. rev is the
91 91 identifier returned by a previous call to getchanges(). Raise
92 92 IOError to indicate that name was deleted in rev.
93 93 """
94 94 raise NotImplementedError
95 95
96 96 def getchanges(self, version):
97 97 """Returns a tuple of (files, copies).
98 98
99 99 files is a sorted list of (filename, id) tuples for all files
100 100 changed between version and its first parent returned by
101 101 getcommit(). id is the source revision id of the file.
102 102
103 103 copies is a dictionary of dest: source
104 104 """
105 105 raise NotImplementedError
106 106
107 107 def getcommit(self, version):
108 108 """Return the commit object for version"""
109 109 raise NotImplementedError
110 110
111 111 def gettags(self):
112 112 """Return the tags as a dictionary of name: revision
113 113
114 114 Tag names must be UTF-8 strings.
115 115 """
116 116 raise NotImplementedError
117 117
118 118 def recode(self, s, encoding=None):
119 119 if not encoding:
120 120 encoding = self.encoding or 'utf-8'
121 121
122 122 if isinstance(s, unicode):
123 123 return s.encode("utf-8")
124 124 try:
125 125 return s.decode(encoding).encode("utf-8")
126 126 except UnicodeError:
127 127 try:
128 128 return s.decode("latin-1").encode("utf-8")
129 129 except UnicodeError:
130 130 return s.decode(encoding, "replace").encode("utf-8")
131 131
132 132 def getchangedfiles(self, rev, i):
133 133 """Return the files changed by rev compared to parent[i].
134 134
135 135 i is an index selecting one of the parents of rev. The return
136 136 value should be the list of files that are different in rev and
137 137 this parent.
138 138
139 139 If rev has no parents, i is None.
140 140
141 141 This function is only needed to support --filemap
142 142 """
143 143 raise NotImplementedError
144 144
145 145 def converted(self, rev, sinkrev):
146 146 '''Notify the source that a revision has been converted.'''
147 147 pass
148 148
149 149 def hasnativeorder(self):
150 150 """Return true if this source has a meaningful, native revision
151 151 order. For instance, Mercurial revisions are store sequentially
152 152 while there is no such global ordering with Darcs.
153 153 """
154 154 return False
155 155
156 156 def hasnativeclose(self):
157 157 """Return true if this source has ability to close branch.
158 158 """
159 159 return False
160 160
161 161 def lookuprev(self, rev):
162 162 """If rev is a meaningful revision reference in source, return
163 163 the referenced identifier in the same format used by getcommit().
164 164 return None otherwise.
165 165 """
166 166 return None
167 167
168 168 def getbookmarks(self):
169 169 """Return the bookmarks as a dictionary of name: revision
170 170
171 171 Bookmark names are to be UTF-8 strings.
172 172 """
173 173 return {}
174 174
175 175 def checkrevformat(self, revstr, mapname='splicemap'):
176 176 """revstr is a string that describes a revision in the given
177 177 source control system. Return true if revstr has correct
178 178 format.
179 179 """
180 180 return True
181 181
182 182 class converter_sink(object):
183 183 """Conversion sink (target) interface"""
184 184
185 185 def __init__(self, ui, path):
186 186 """Initialize conversion sink (or raise NoRepo("message")
187 187 exception if path is not a valid repository)
188 188
189 189 created is a list of paths to remove if a fatal error occurs
190 190 later"""
191 191 self.ui = ui
192 192 self.path = path
193 193 self.created = []
194 194
195 def getheads(self):
196 """Return a list of this repository's heads"""
197 raise NotImplementedError
198
199 195 def revmapfile(self):
200 196 """Path to a file that will contain lines
201 197 source_rev_id sink_rev_id
202 198 mapping equivalent revision identifiers for each system."""
203 199 raise NotImplementedError
204 200
205 201 def authorfile(self):
206 202 """Path to a file that will contain lines
207 203 srcauthor=dstauthor
208 204 mapping equivalent authors identifiers for each system."""
209 205 return None
210 206
211 207 def putcommit(self, files, copies, parents, commit, source,
212 208 revmap, tagmap):
213 209 """Create a revision with all changed files listed in 'files'
214 210 and having listed parents. 'commit' is a commit object
215 211 containing at a minimum the author, date, and message for this
216 212 changeset. 'files' is a list of (path, version) tuples,
217 213 'copies' is a dictionary mapping destinations to sources,
218 214 'source' is the source repository, and 'revmap' is a mapfile
219 215 of source revisions to converted revisions. Only getfile() and
220 216 lookuprev() should be called on 'source'.
221 217
222 218 Note that the sink repository is not told to update itself to
223 219 a particular revision (or even what that revision would be)
224 220 before it receives the file data.
225 221 """
226 222 raise NotImplementedError
227 223
228 224 def puttags(self, tags):
229 225 """Put tags into sink.
230 226
231 227 tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
232 228 Return a pair (tag_revision, tag_parent_revision), or (None, None)
233 229 if nothing was changed.
234 230 """
235 231 raise NotImplementedError
236 232
237 233 def setbranch(self, branch, pbranches):
238 234 """Set the current branch name. Called before the first putcommit
239 235 on the branch.
240 236 branch: branch name for subsequent commits
241 237 pbranches: (converted parent revision, parent branch) tuples"""
242 238 pass
243 239
244 240 def setfilemapmode(self, active):
245 241 """Tell the destination that we're using a filemap
246 242
247 243 Some converter_sources (svn in particular) can claim that a file
248 244 was changed in a revision, even if there was no change. This method
249 245 tells the destination that we're using a filemap and that it should
250 246 filter empty revisions.
251 247 """
252 248 pass
253 249
254 250 def before(self):
255 251 pass
256 252
257 253 def after(self):
258 254 pass
259 255
260 256 def putbookmarks(self, bookmarks):
261 257 """Put bookmarks into sink.
262 258
263 259 bookmarks: {bookmarkname: sink_rev_id, ...}
264 260 where bookmarkname is an UTF-8 string.
265 261 """
266 262 pass
267 263
268 264 def hascommit(self, rev):
269 265 """Return True if the sink contains rev"""
270 266 raise NotImplementedError
271 267
272 268 class commandline(object):
273 269 def __init__(self, ui, command):
274 270 self.ui = ui
275 271 self.command = command
276 272
277 273 def prerun(self):
278 274 pass
279 275
280 276 def postrun(self):
281 277 pass
282 278
283 279 def _cmdline(self, cmd, *args, **kwargs):
284 280 cmdline = [self.command, cmd] + list(args)
285 281 for k, v in kwargs.iteritems():
286 282 if len(k) == 1:
287 283 cmdline.append('-' + k)
288 284 else:
289 285 cmdline.append('--' + k.replace('_', '-'))
290 286 try:
291 287 if len(k) == 1:
292 288 cmdline.append('' + v)
293 289 else:
294 290 cmdline[-1] += '=' + v
295 291 except TypeError:
296 292 pass
297 293 cmdline = [util.shellquote(arg) for arg in cmdline]
298 294 if not self.ui.debugflag:
299 295 cmdline += ['2>', os.devnull]
300 296 cmdline = ' '.join(cmdline)
301 297 return cmdline
302 298
303 299 def _run(self, cmd, *args, **kwargs):
304 300 def popen(cmdline):
305 301 p = subprocess.Popen(cmdline, shell=True, bufsize=-1,
306 302 close_fds=util.closefds,
307 303 stdout=subprocess.PIPE)
308 304 return p
309 305 return self._dorun(popen, cmd, *args, **kwargs)
310 306
311 307 def _run2(self, cmd, *args, **kwargs):
312 308 return self._dorun(util.popen2, cmd, *args, **kwargs)
313 309
314 310 def _dorun(self, openfunc, cmd, *args, **kwargs):
315 311 cmdline = self._cmdline(cmd, *args, **kwargs)
316 312 self.ui.debug('running: %s\n' % (cmdline,))
317 313 self.prerun()
318 314 try:
319 315 return openfunc(cmdline)
320 316 finally:
321 317 self.postrun()
322 318
323 319 def run(self, cmd, *args, **kwargs):
324 320 p = self._run(cmd, *args, **kwargs)
325 321 output = p.communicate()[0]
326 322 self.ui.debug(output)
327 323 return output, p.returncode
328 324
329 325 def runlines(self, cmd, *args, **kwargs):
330 326 p = self._run(cmd, *args, **kwargs)
331 327 output = p.stdout.readlines()
332 328 p.wait()
333 329 self.ui.debug(''.join(output))
334 330 return output, p.returncode
335 331
336 332 def checkexit(self, status, output=''):
337 333 if status:
338 334 if output:
339 335 self.ui.warn(_('%s error:\n') % self.command)
340 336 self.ui.warn(output)
341 337 msg = util.explainexit(status)[0]
342 338 raise util.Abort('%s %s' % (self.command, msg))
343 339
344 340 def run0(self, cmd, *args, **kwargs):
345 341 output, status = self.run(cmd, *args, **kwargs)
346 342 self.checkexit(status, output)
347 343 return output
348 344
349 345 def runlines0(self, cmd, *args, **kwargs):
350 346 output, status = self.runlines(cmd, *args, **kwargs)
351 347 self.checkexit(status, ''.join(output))
352 348 return output
353 349
354 350 @propertycache
355 351 def argmax(self):
356 352 # POSIX requires at least 4096 bytes for ARG_MAX
357 353 argmax = 4096
358 354 try:
359 355 argmax = os.sysconf("SC_ARG_MAX")
360 356 except (AttributeError, ValueError):
361 357 pass
362 358
363 359 # Windows shells impose their own limits on command line length,
364 360 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
365 361 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
366 362 # details about cmd.exe limitations.
367 363
368 364 # Since ARG_MAX is for command line _and_ environment, lower our limit
369 365 # (and make happy Windows shells while doing this).
370 366 return argmax // 2 - 1
371 367
372 368 def _limit_arglist(self, arglist, cmd, *args, **kwargs):
373 369 cmdlen = len(self._cmdline(cmd, *args, **kwargs))
374 370 limit = self.argmax - cmdlen
375 371 bytes = 0
376 372 fl = []
377 373 for fn in arglist:
378 374 b = len(fn) + 3
379 375 if bytes + b < limit or len(fl) == 0:
380 376 fl.append(fn)
381 377 bytes += b
382 378 else:
383 379 yield fl
384 380 fl = [fn]
385 381 bytes = b
386 382 if fl:
387 383 yield fl
388 384
389 385 def xargs(self, arglist, cmd, *args, **kwargs):
390 386 for l in self._limit_arglist(arglist, cmd, *args, **kwargs):
391 387 self.run0(cmd, *(list(args) + l), **kwargs)
392 388
393 389 class mapfile(dict):
394 390 def __init__(self, ui, path):
395 391 super(mapfile, self).__init__()
396 392 self.ui = ui
397 393 self.path = path
398 394 self.fp = None
399 395 self.order = []
400 396 self._read()
401 397
402 398 def _read(self):
403 399 if not self.path:
404 400 return
405 401 try:
406 402 fp = open(self.path, 'r')
407 403 except IOError, err:
408 404 if err.errno != errno.ENOENT:
409 405 raise
410 406 return
411 407 for i, line in enumerate(fp):
412 408 line = line.splitlines()[0].rstrip()
413 409 if not line:
414 410 # Ignore blank lines
415 411 continue
416 412 try:
417 413 key, value = line.rsplit(' ', 1)
418 414 except ValueError:
419 415 raise util.Abort(
420 416 _('syntax error in %s(%d): key/value pair expected')
421 417 % (self.path, i + 1))
422 418 if key not in self:
423 419 self.order.append(key)
424 420 super(mapfile, self).__setitem__(key, value)
425 421 fp.close()
426 422
427 423 def __setitem__(self, key, value):
428 424 if self.fp is None:
429 425 try:
430 426 self.fp = open(self.path, 'a')
431 427 except IOError, err:
432 428 raise util.Abort(_('could not open map file %r: %s') %
433 429 (self.path, err.strerror))
434 430 self.fp.write('%s %s\n' % (key, value))
435 431 self.fp.flush()
436 432 super(mapfile, self).__setitem__(key, value)
437 433
438 434 def close(self):
439 435 if self.fp:
440 436 self.fp.close()
441 437 self.fp = None
442 438
443 439 def makedatetimestamp(t):
444 440 """Like util.makedate() but for time t instead of current time"""
445 441 delta = (datetime.datetime.utcfromtimestamp(t) -
446 442 datetime.datetime.fromtimestamp(t))
447 443 tz = delta.days * 86400 + delta.seconds
448 444 return t, tz
@@ -1,451 +1,447 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 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 def getheads(self):
82 h = self.repo.changelog.heads()
83 return [hex(x) for x in h]
84
85 81 def setbranch(self, branch, pbranches):
86 82 if not self.clonebranches:
87 83 return
88 84
89 85 setbranch = (branch != self.lastbranch)
90 86 self.lastbranch = branch
91 87 if not branch:
92 88 branch = 'default'
93 89 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
94 90 pbranch = pbranches and pbranches[0][1] or 'default'
95 91
96 92 branchpath = os.path.join(self.path, branch)
97 93 if setbranch:
98 94 self.after()
99 95 try:
100 96 self.repo = hg.repository(self.ui, branchpath)
101 97 except Exception:
102 98 self.repo = hg.repository(self.ui, branchpath, create=True)
103 99 self.before()
104 100
105 101 # pbranches may bring revisions from other branches (merge parents)
106 102 # Make sure we have them, or pull them.
107 103 missings = {}
108 104 for b in pbranches:
109 105 try:
110 106 self.repo.lookup(b[0])
111 107 except Exception:
112 108 missings.setdefault(b[1], []).append(b[0])
113 109
114 110 if missings:
115 111 self.after()
116 112 for pbranch, heads in sorted(missings.iteritems()):
117 113 pbranchpath = os.path.join(self.path, pbranch)
118 114 prepo = hg.peer(self.ui, {}, pbranchpath)
119 115 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
120 116 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
121 117 self.before()
122 118
123 119 def _rewritetags(self, source, revmap, tagmap, data):
124 120 fp = cStringIO.StringIO()
125 121 for line in data.splitlines():
126 122 s = line.split(' ', 1)
127 123 if len(s) != 2:
128 124 continue
129 125 revid = revmap.get(source.lookuprev(s[0]))
130 126 if not revid:
131 127 continue
132 128 fp.write('%s %s\n' % (revid, tagmap.get(s[1], s[1])))
133 129 return fp.getvalue()
134 130
135 131 def putcommit(self, files, copies, parents, commit, source,
136 132 revmap, tagmap):
137 133
138 134 files = dict(files)
139 135 def getfilectx(repo, memctx, f):
140 136 v = files[f]
141 137 data, mode = source.getfile(f, v)
142 138 if f == '.hgtags':
143 139 data = self._rewritetags(source, revmap, tagmap, data)
144 140 return context.memfilectx(f, data, 'l' in mode, 'x' in mode,
145 141 copies.get(f))
146 142
147 143 pl = []
148 144 for p in parents:
149 145 if p not in pl:
150 146 pl.append(p)
151 147 parents = pl
152 148 nparents = len(parents)
153 149 if self.filemapmode and nparents == 1:
154 150 m1node = self.repo.changelog.read(bin(parents[0]))[0]
155 151 parent = parents[0]
156 152
157 153 if len(parents) < 2:
158 154 parents.append(nullid)
159 155 if len(parents) < 2:
160 156 parents.append(nullid)
161 157 p2 = parents.pop(0)
162 158
163 159 text = commit.desc
164 160
165 161 sha1s = re.findall(sha1re, text)
166 162 for sha1 in sha1s:
167 163 oldrev = source.lookuprev(sha1)
168 164 newrev = revmap.get(oldrev)
169 165 if newrev is not None:
170 166 text = text.replace(sha1, newrev[:len(sha1)])
171 167
172 168 extra = commit.extra.copy()
173 169 if self.branchnames and commit.branch:
174 170 extra['branch'] = commit.branch
175 171 if commit.rev:
176 172 extra['convert_revision'] = commit.rev
177 173
178 174 while parents:
179 175 p1 = p2
180 176 p2 = parents.pop(0)
181 177 ctx = context.memctx(self.repo, (p1, p2), text, files.keys(),
182 178 getfilectx, commit.author, commit.date, extra)
183 179 self.repo.commitctx(ctx)
184 180 text = "(octopus merge fixup)\n"
185 181 p2 = hex(self.repo.changelog.tip())
186 182
187 183 if self.filemapmode and nparents == 1:
188 184 man = self.repo.manifest
189 185 mnode = self.repo.changelog.read(bin(p2))[0]
190 186 closed = 'close' in commit.extra
191 187 if not closed and not man.cmp(m1node, man.revision(mnode)):
192 188 self.ui.status(_("filtering out empty revision\n"))
193 189 self.repo.rollback(force=True)
194 190 return parent
195 191 return p2
196 192
197 193 def puttags(self, tags):
198 194 try:
199 195 parentctx = self.repo[self.tagsbranch]
200 196 tagparent = parentctx.node()
201 197 except error.RepoError:
202 198 parentctx = None
203 199 tagparent = nullid
204 200
205 201 oldlines = set()
206 202 for branch, heads in self.repo.branchmap().iteritems():
207 203 for h in heads:
208 204 if '.hgtags' in self.repo[h]:
209 205 oldlines.update(
210 206 set(self.repo[h]['.hgtags'].data().splitlines(True)))
211 207 oldlines = sorted(list(oldlines))
212 208
213 209 newlines = sorted([("%s %s\n" % (tags[tag], tag)) for tag in tags])
214 210 if newlines == oldlines:
215 211 return None, None
216 212
217 213 # if the old and new tags match, then there is nothing to update
218 214 oldtags = set()
219 215 newtags = set()
220 216 for line in oldlines:
221 217 s = line.strip().split(' ', 1)
222 218 if len(s) != 2:
223 219 continue
224 220 oldtags.add(s[1])
225 221 for line in newlines:
226 222 s = line.strip().split(' ', 1)
227 223 if len(s) != 2:
228 224 continue
229 225 if s[1] not in oldtags:
230 226 newtags.add(s[1].strip())
231 227
232 228 if not newtags:
233 229 return None, None
234 230
235 231 data = "".join(newlines)
236 232 def getfilectx(repo, memctx, f):
237 233 return context.memfilectx(f, data, False, False, None)
238 234
239 235 self.ui.status(_("updating tags\n"))
240 236 date = "%s 0" % int(time.mktime(time.gmtime()))
241 237 extra = {'branch': self.tagsbranch}
242 238 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
243 239 [".hgtags"], getfilectx, "convert-repo", date,
244 240 extra)
245 241 self.repo.commitctx(ctx)
246 242 return hex(self.repo.changelog.tip()), hex(tagparent)
247 243
248 244 def setfilemapmode(self, active):
249 245 self.filemapmode = active
250 246
251 247 def putbookmarks(self, updatedbookmark):
252 248 if not len(updatedbookmark):
253 249 return
254 250
255 251 self.ui.status(_("updating bookmarks\n"))
256 252 destmarks = self.repo._bookmarks
257 253 for bookmark in updatedbookmark:
258 254 destmarks[bookmark] = bin(updatedbookmark[bookmark])
259 255 destmarks.write()
260 256
261 257 def hascommit(self, rev):
262 258 if rev not in self.repo and self.clonebranches:
263 259 raise util.Abort(_('revision %s not found in destination '
264 260 'repository (lookups with clonebranches=true '
265 261 'are not implemented)') % rev)
266 262 return rev in self.repo
267 263
268 264 class mercurial_source(converter_source):
269 265 def __init__(self, ui, path, rev=None):
270 266 converter_source.__init__(self, ui, path, rev)
271 267 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
272 268 self.ignored = set()
273 269 self.saverev = ui.configbool('convert', 'hg.saverev', False)
274 270 try:
275 271 self.repo = hg.repository(self.ui, path)
276 272 # try to provoke an exception if this isn't really a hg
277 273 # repo, but some other bogus compatible-looking url
278 274 if not self.repo.local():
279 275 raise error.RepoError
280 276 except error.RepoError:
281 277 ui.traceback()
282 278 raise NoRepo(_("%s is not a local Mercurial repository") % path)
283 279 self.lastrev = None
284 280 self.lastctx = None
285 281 self._changescache = None
286 282 self.convertfp = None
287 283 # Restrict converted revisions to startrev descendants
288 284 startnode = ui.config('convert', 'hg.startrev')
289 285 hgrevs = ui.config('convert', 'hg.revs')
290 286 if hgrevs is None:
291 287 if startnode is not None:
292 288 try:
293 289 startnode = self.repo.lookup(startnode)
294 290 except error.RepoError:
295 291 raise util.Abort(_('%s is not a valid start revision')
296 292 % startnode)
297 293 startrev = self.repo.changelog.rev(startnode)
298 294 children = {startnode: 1}
299 295 for r in self.repo.changelog.descendants([startrev]):
300 296 children[self.repo.changelog.node(r)] = 1
301 297 self.keep = children.__contains__
302 298 else:
303 299 self.keep = util.always
304 300 if rev:
305 301 self._heads = [self.repo[rev].node()]
306 302 else:
307 303 self._heads = self.repo.heads()
308 304 else:
309 305 if rev or startnode is not None:
310 306 raise util.Abort(_('hg.revs cannot be combined with '
311 307 'hg.startrev or --rev'))
312 308 nodes = set()
313 309 parents = set()
314 310 for r in scmutil.revrange(self.repo, [hgrevs]):
315 311 ctx = self.repo[r]
316 312 nodes.add(ctx.node())
317 313 parents.update(p.node() for p in ctx.parents())
318 314 self.keep = nodes.__contains__
319 315 self._heads = nodes - parents
320 316
321 317 def changectx(self, rev):
322 318 if self.lastrev != rev:
323 319 self.lastctx = self.repo[rev]
324 320 self.lastrev = rev
325 321 return self.lastctx
326 322
327 323 def parents(self, ctx):
328 324 return [p for p in ctx.parents() if p and self.keep(p.node())]
329 325
330 326 def getheads(self):
331 327 return [hex(h) for h in self._heads if self.keep(h)]
332 328
333 329 def getfile(self, name, rev):
334 330 try:
335 331 fctx = self.changectx(rev)[name]
336 332 return fctx.data(), fctx.flags()
337 333 except error.LookupError, err:
338 334 raise IOError(err)
339 335
340 336 def getchanges(self, rev):
341 337 ctx = self.changectx(rev)
342 338 parents = self.parents(ctx)
343 339 if not parents:
344 340 files = sorted(ctx.manifest())
345 341 # getcopies() is not needed for roots, but it is a simple way to
346 342 # detect missing revlogs and abort on errors or populate
347 343 # self.ignored
348 344 self.getcopies(ctx, parents, files)
349 345 return [(f, rev) for f in files if f not in self.ignored], {}
350 346 if self._changescache and self._changescache[0] == rev:
351 347 m, a, r = self._changescache[1]
352 348 else:
353 349 m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
354 350 # getcopies() detects missing revlogs early, run it before
355 351 # filtering the changes.
356 352 copies = self.getcopies(ctx, parents, m + a)
357 353 changes = [(name, rev) for name in m + a + r
358 354 if name not in self.ignored]
359 355 return sorted(changes), copies
360 356
361 357 def getcopies(self, ctx, parents, files):
362 358 copies = {}
363 359 for name in files:
364 360 if name in self.ignored:
365 361 continue
366 362 try:
367 363 copysource, _copynode = ctx.filectx(name).renamed()
368 364 if copysource in self.ignored:
369 365 continue
370 366 # Ignore copy sources not in parent revisions
371 367 found = False
372 368 for p in parents:
373 369 if copysource in p:
374 370 found = True
375 371 break
376 372 if not found:
377 373 continue
378 374 copies[name] = copysource
379 375 except TypeError:
380 376 pass
381 377 except error.LookupError, e:
382 378 if not self.ignoreerrors:
383 379 raise
384 380 self.ignored.add(name)
385 381 self.ui.warn(_('ignoring: %s\n') % e)
386 382 return copies
387 383
388 384 def getcommit(self, rev):
389 385 ctx = self.changectx(rev)
390 386 parents = [p.hex() for p in self.parents(ctx)]
391 387 if self.saverev:
392 388 crev = rev
393 389 else:
394 390 crev = None
395 391 return commit(author=ctx.user(),
396 392 date=util.datestr(ctx.date(), '%Y-%m-%d %H:%M:%S %1%2'),
397 393 desc=ctx.description(), rev=crev, parents=parents,
398 394 branch=ctx.branch(), extra=ctx.extra(),
399 395 sortkey=ctx.rev())
400 396
401 397 def gettags(self):
402 398 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
403 399 return dict([(name, hex(node)) for name, node in tags
404 400 if self.keep(node)])
405 401
406 402 def getchangedfiles(self, rev, i):
407 403 ctx = self.changectx(rev)
408 404 parents = self.parents(ctx)
409 405 if not parents and i is None:
410 406 i = 0
411 407 changes = [], ctx.manifest().keys(), []
412 408 else:
413 409 i = i or 0
414 410 changes = self.repo.status(parents[i].node(), ctx.node())[:3]
415 411 changes = [[f for f in l if f not in self.ignored] for l in changes]
416 412
417 413 if i == 0:
418 414 self._changescache = (rev, changes)
419 415
420 416 return changes[0] + changes[1] + changes[2]
421 417
422 418 def converted(self, rev, destrev):
423 419 if self.convertfp is None:
424 420 self.convertfp = open(self.repo.join('shamap'), 'a')
425 421 self.convertfp.write('%s %s\n' % (destrev, rev))
426 422 self.convertfp.flush()
427 423
428 424 def before(self):
429 425 self.ui.debug('run hg source pre-conversion action\n')
430 426
431 427 def after(self):
432 428 self.ui.debug('run hg source post-conversion action\n')
433 429
434 430 def hasnativeorder(self):
435 431 return True
436 432
437 433 def hasnativeclose(self):
438 434 return True
439 435
440 436 def lookuprev(self, rev):
441 437 try:
442 438 return hex(self.repo.lookup(rev))
443 439 except error.RepoError:
444 440 return None
445 441
446 442 def getbookmarks(self):
447 443 return bookmarks.listbookmarks(self.repo)
448 444
449 445 def checkrevformat(self, revstr, mapname='splicemap'):
450 446 """ Mercurial, revision string is a 40 byte hex """
451 447 self.checkhexformat(revstr, mapname)
General Comments 0
You need to be logged in to leave comments. Login now