##// END OF EJS Templates
convert: allow missing tools not to stop source type detection
Patrick Mezard -
r6332:950e72fc default
parent child Browse files
Show More
@@ -1,354 +1,357
1 # common code for the convert extension
1 # common code for the convert extension
2 import base64, errno
2 import base64, errno
3 import os
3 import os
4 import cPickle as pickle
4 import cPickle as pickle
5 from mercurial import util
5 from mercurial import util
6 from mercurial.i18n import _
6 from mercurial.i18n import _
7
7
8 def encodeargs(args):
8 def encodeargs(args):
9 def encodearg(s):
9 def encodearg(s):
10 lines = base64.encodestring(s)
10 lines = base64.encodestring(s)
11 lines = [l.splitlines()[0] for l in lines]
11 lines = [l.splitlines()[0] for l in lines]
12 return ''.join(lines)
12 return ''.join(lines)
13
13
14 s = pickle.dumps(args)
14 s = pickle.dumps(args)
15 return encodearg(s)
15 return encodearg(s)
16
16
17 def decodeargs(s):
17 def decodeargs(s):
18 s = base64.decodestring(s)
18 s = base64.decodestring(s)
19 return pickle.loads(s)
19 return pickle.loads(s)
20
20
21 def checktool(exe, name=None):
21 class MissingTool(Exception): pass
22
23 def checktool(exe, name=None, abort=True):
22 name = name or exe
24 name = name or exe
23 if not util.find_exe(exe):
25 if not util.find_exe(exe):
24 raise util.Abort('cannot find required "%s" tool' % name)
26 exc = abort and util.Abort or MissingTool
27 raise exc(_('cannot find required "%s" tool') % name)
25
28
26 class NoRepo(Exception): pass
29 class NoRepo(Exception): pass
27
30
28 SKIPREV = 'SKIP'
31 SKIPREV = 'SKIP'
29
32
30 class commit(object):
33 class commit(object):
31 def __init__(self, author, date, desc, parents, branch=None, rev=None,
34 def __init__(self, author, date, desc, parents, branch=None, rev=None,
32 extra={}):
35 extra={}):
33 self.author = author or 'unknown'
36 self.author = author or 'unknown'
34 self.date = date or '0 0'
37 self.date = date or '0 0'
35 self.desc = desc
38 self.desc = desc
36 self.parents = parents
39 self.parents = parents
37 self.branch = branch
40 self.branch = branch
38 self.rev = rev
41 self.rev = rev
39 self.extra = extra
42 self.extra = extra
40
43
41 class converter_source(object):
44 class converter_source(object):
42 """Conversion source interface"""
45 """Conversion source interface"""
43
46
44 def __init__(self, ui, path=None, rev=None):
47 def __init__(self, ui, path=None, rev=None):
45 """Initialize conversion source (or raise NoRepo("message")
48 """Initialize conversion source (or raise NoRepo("message")
46 exception if path is not a valid repository)"""
49 exception if path is not a valid repository)"""
47 self.ui = ui
50 self.ui = ui
48 self.path = path
51 self.path = path
49 self.rev = rev
52 self.rev = rev
50
53
51 self.encoding = 'utf-8'
54 self.encoding = 'utf-8'
52
55
53 def before(self):
56 def before(self):
54 pass
57 pass
55
58
56 def after(self):
59 def after(self):
57 pass
60 pass
58
61
59 def setrevmap(self, revmap):
62 def setrevmap(self, revmap):
60 """set the map of already-converted revisions"""
63 """set the map of already-converted revisions"""
61 pass
64 pass
62
65
63 def getheads(self):
66 def getheads(self):
64 """Return a list of this repository's heads"""
67 """Return a list of this repository's heads"""
65 raise NotImplementedError()
68 raise NotImplementedError()
66
69
67 def getfile(self, name, rev):
70 def getfile(self, name, rev):
68 """Return file contents as a string"""
71 """Return file contents as a string"""
69 raise NotImplementedError()
72 raise NotImplementedError()
70
73
71 def getmode(self, name, rev):
74 def getmode(self, name, rev):
72 """Return file mode, eg. '', 'x', or 'l'"""
75 """Return file mode, eg. '', 'x', or 'l'"""
73 raise NotImplementedError()
76 raise NotImplementedError()
74
77
75 def getchanges(self, version):
78 def getchanges(self, version):
76 """Returns a tuple of (files, copies)
79 """Returns a tuple of (files, copies)
77 Files is a sorted list of (filename, id) tuples for all files changed
80 Files is a sorted list of (filename, id) tuples for all files changed
78 in version, where id is the source revision id of the file.
81 in version, where id is the source revision id of the file.
79
82
80 copies is a dictionary of dest: source
83 copies is a dictionary of dest: source
81 """
84 """
82 raise NotImplementedError()
85 raise NotImplementedError()
83
86
84 def getcommit(self, version):
87 def getcommit(self, version):
85 """Return the commit object for version"""
88 """Return the commit object for version"""
86 raise NotImplementedError()
89 raise NotImplementedError()
87
90
88 def gettags(self):
91 def gettags(self):
89 """Return the tags as a dictionary of name: revision"""
92 """Return the tags as a dictionary of name: revision"""
90 raise NotImplementedError()
93 raise NotImplementedError()
91
94
92 def recode(self, s, encoding=None):
95 def recode(self, s, encoding=None):
93 if not encoding:
96 if not encoding:
94 encoding = self.encoding or 'utf-8'
97 encoding = self.encoding or 'utf-8'
95
98
96 if isinstance(s, unicode):
99 if isinstance(s, unicode):
97 return s.encode("utf-8")
100 return s.encode("utf-8")
98 try:
101 try:
99 return s.decode(encoding).encode("utf-8")
102 return s.decode(encoding).encode("utf-8")
100 except:
103 except:
101 try:
104 try:
102 return s.decode("latin-1").encode("utf-8")
105 return s.decode("latin-1").encode("utf-8")
103 except:
106 except:
104 return s.decode(encoding, "replace").encode("utf-8")
107 return s.decode(encoding, "replace").encode("utf-8")
105
108
106 def getchangedfiles(self, rev, i):
109 def getchangedfiles(self, rev, i):
107 """Return the files changed by rev compared to parent[i].
110 """Return the files changed by rev compared to parent[i].
108
111
109 i is an index selecting one of the parents of rev. The return
112 i is an index selecting one of the parents of rev. The return
110 value should be the list of files that are different in rev and
113 value should be the list of files that are different in rev and
111 this parent.
114 this parent.
112
115
113 If rev has no parents, i is None.
116 If rev has no parents, i is None.
114
117
115 This function is only needed to support --filemap
118 This function is only needed to support --filemap
116 """
119 """
117 raise NotImplementedError()
120 raise NotImplementedError()
118
121
119 def converted(self, rev, sinkrev):
122 def converted(self, rev, sinkrev):
120 '''Notify the source that a revision has been converted.'''
123 '''Notify the source that a revision has been converted.'''
121 pass
124 pass
122
125
123
126
124 class converter_sink(object):
127 class converter_sink(object):
125 """Conversion sink (target) interface"""
128 """Conversion sink (target) interface"""
126
129
127 def __init__(self, ui, path):
130 def __init__(self, ui, path):
128 """Initialize conversion sink (or raise NoRepo("message")
131 """Initialize conversion sink (or raise NoRepo("message")
129 exception if path is not a valid repository)
132 exception if path is not a valid repository)
130
133
131 created is a list of paths to remove if a fatal error occurs
134 created is a list of paths to remove if a fatal error occurs
132 later"""
135 later"""
133 self.ui = ui
136 self.ui = ui
134 self.path = path
137 self.path = path
135 self.created = []
138 self.created = []
136
139
137 def getheads(self):
140 def getheads(self):
138 """Return a list of this repository's heads"""
141 """Return a list of this repository's heads"""
139 raise NotImplementedError()
142 raise NotImplementedError()
140
143
141 def revmapfile(self):
144 def revmapfile(self):
142 """Path to a file that will contain lines
145 """Path to a file that will contain lines
143 source_rev_id sink_rev_id
146 source_rev_id sink_rev_id
144 mapping equivalent revision identifiers for each system."""
147 mapping equivalent revision identifiers for each system."""
145 raise NotImplementedError()
148 raise NotImplementedError()
146
149
147 def authorfile(self):
150 def authorfile(self):
148 """Path to a file that will contain lines
151 """Path to a file that will contain lines
149 srcauthor=dstauthor
152 srcauthor=dstauthor
150 mapping equivalent authors identifiers for each system."""
153 mapping equivalent authors identifiers for each system."""
151 return None
154 return None
152
155
153 def putfile(self, f, e, data):
156 def putfile(self, f, e, data):
154 """Put file for next putcommit().
157 """Put file for next putcommit().
155 f: path to file
158 f: path to file
156 e: '', 'x', or 'l' (regular file, executable, or symlink)
159 e: '', 'x', or 'l' (regular file, executable, or symlink)
157 data: file contents"""
160 data: file contents"""
158 raise NotImplementedError()
161 raise NotImplementedError()
159
162
160 def delfile(self, f):
163 def delfile(self, f):
161 """Delete file for next putcommit().
164 """Delete file for next putcommit().
162 f: path to file"""
165 f: path to file"""
163 raise NotImplementedError()
166 raise NotImplementedError()
164
167
165 def putcommit(self, files, parents, commit):
168 def putcommit(self, files, parents, commit):
166 """Create a revision with all changed files listed in 'files'
169 """Create a revision with all changed files listed in 'files'
167 and having listed parents. 'commit' is a commit object containing
170 and having listed parents. 'commit' is a commit object containing
168 at a minimum the author, date, and message for this changeset.
171 at a minimum the author, date, and message for this changeset.
169 Called after putfile() and delfile() calls. Note that the sink
172 Called after putfile() and delfile() calls. Note that the sink
170 repository is not told to update itself to a particular revision
173 repository is not told to update itself to a particular revision
171 (or even what that revision would be) before it receives the
174 (or even what that revision would be) before it receives the
172 file data."""
175 file data."""
173 raise NotImplementedError()
176 raise NotImplementedError()
174
177
175 def puttags(self, tags):
178 def puttags(self, tags):
176 """Put tags into sink.
179 """Put tags into sink.
177 tags: {tagname: sink_rev_id, ...}"""
180 tags: {tagname: sink_rev_id, ...}"""
178 raise NotImplementedError()
181 raise NotImplementedError()
179
182
180 def setbranch(self, branch, pbranches):
183 def setbranch(self, branch, pbranches):
181 """Set the current branch name. Called before the first putfile
184 """Set the current branch name. Called before the first putfile
182 on the branch.
185 on the branch.
183 branch: branch name for subsequent commits
186 branch: branch name for subsequent commits
184 pbranches: (converted parent revision, parent branch) tuples"""
187 pbranches: (converted parent revision, parent branch) tuples"""
185 pass
188 pass
186
189
187 def setfilemapmode(self, active):
190 def setfilemapmode(self, active):
188 """Tell the destination that we're using a filemap
191 """Tell the destination that we're using a filemap
189
192
190 Some converter_sources (svn in particular) can claim that a file
193 Some converter_sources (svn in particular) can claim that a file
191 was changed in a revision, even if there was no change. This method
194 was changed in a revision, even if there was no change. This method
192 tells the destination that we're using a filemap and that it should
195 tells the destination that we're using a filemap and that it should
193 filter empty revisions.
196 filter empty revisions.
194 """
197 """
195 pass
198 pass
196
199
197 def before(self):
200 def before(self):
198 pass
201 pass
199
202
200 def after(self):
203 def after(self):
201 pass
204 pass
202
205
203
206
204 class commandline(object):
207 class commandline(object):
205 def __init__(self, ui, command):
208 def __init__(self, ui, command):
206 self.ui = ui
209 self.ui = ui
207 self.command = command
210 self.command = command
208
211
209 def prerun(self):
212 def prerun(self):
210 pass
213 pass
211
214
212 def postrun(self):
215 def postrun(self):
213 pass
216 pass
214
217
215 def _cmdline(self, cmd, *args, **kwargs):
218 def _cmdline(self, cmd, *args, **kwargs):
216 cmdline = [self.command, cmd] + list(args)
219 cmdline = [self.command, cmd] + list(args)
217 for k, v in kwargs.iteritems():
220 for k, v in kwargs.iteritems():
218 if len(k) == 1:
221 if len(k) == 1:
219 cmdline.append('-' + k)
222 cmdline.append('-' + k)
220 else:
223 else:
221 cmdline.append('--' + k.replace('_', '-'))
224 cmdline.append('--' + k.replace('_', '-'))
222 try:
225 try:
223 if len(k) == 1:
226 if len(k) == 1:
224 cmdline.append('' + v)
227 cmdline.append('' + v)
225 else:
228 else:
226 cmdline[-1] += '=' + v
229 cmdline[-1] += '=' + v
227 except TypeError:
230 except TypeError:
228 pass
231 pass
229 cmdline = [util.shellquote(arg) for arg in cmdline]
232 cmdline = [util.shellquote(arg) for arg in cmdline]
230 cmdline += ['2>', util.nulldev, '<', util.nulldev]
233 cmdline += ['2>', util.nulldev, '<', util.nulldev]
231 cmdline = ' '.join(cmdline)
234 cmdline = ' '.join(cmdline)
232 self.ui.debug(cmdline, '\n')
235 self.ui.debug(cmdline, '\n')
233 return cmdline
236 return cmdline
234
237
235 def _run(self, cmd, *args, **kwargs):
238 def _run(self, cmd, *args, **kwargs):
236 cmdline = self._cmdline(cmd, *args, **kwargs)
239 cmdline = self._cmdline(cmd, *args, **kwargs)
237 self.prerun()
240 self.prerun()
238 try:
241 try:
239 return util.popen(cmdline)
242 return util.popen(cmdline)
240 finally:
243 finally:
241 self.postrun()
244 self.postrun()
242
245
243 def run(self, cmd, *args, **kwargs):
246 def run(self, cmd, *args, **kwargs):
244 fp = self._run(cmd, *args, **kwargs)
247 fp = self._run(cmd, *args, **kwargs)
245 output = fp.read()
248 output = fp.read()
246 self.ui.debug(output)
249 self.ui.debug(output)
247 return output, fp.close()
250 return output, fp.close()
248
251
249 def runlines(self, cmd, *args, **kwargs):
252 def runlines(self, cmd, *args, **kwargs):
250 fp = self._run(cmd, *args, **kwargs)
253 fp = self._run(cmd, *args, **kwargs)
251 output = fp.readlines()
254 output = fp.readlines()
252 self.ui.debug(''.join(output))
255 self.ui.debug(''.join(output))
253 return output, fp.close()
256 return output, fp.close()
254
257
255 def checkexit(self, status, output=''):
258 def checkexit(self, status, output=''):
256 if status:
259 if status:
257 if output:
260 if output:
258 self.ui.warn(_('%s error:\n') % self.command)
261 self.ui.warn(_('%s error:\n') % self.command)
259 self.ui.warn(output)
262 self.ui.warn(output)
260 msg = util.explain_exit(status)[0]
263 msg = util.explain_exit(status)[0]
261 raise util.Abort(_('%s %s') % (self.command, msg))
264 raise util.Abort(_('%s %s') % (self.command, msg))
262
265
263 def run0(self, cmd, *args, **kwargs):
266 def run0(self, cmd, *args, **kwargs):
264 output, status = self.run(cmd, *args, **kwargs)
267 output, status = self.run(cmd, *args, **kwargs)
265 self.checkexit(status, output)
268 self.checkexit(status, output)
266 return output
269 return output
267
270
268 def runlines0(self, cmd, *args, **kwargs):
271 def runlines0(self, cmd, *args, **kwargs):
269 output, status = self.runlines(cmd, *args, **kwargs)
272 output, status = self.runlines(cmd, *args, **kwargs)
270 self.checkexit(status, ''.join(output))
273 self.checkexit(status, ''.join(output))
271 return output
274 return output
272
275
273 def getargmax(self):
276 def getargmax(self):
274 if '_argmax' in self.__dict__:
277 if '_argmax' in self.__dict__:
275 return self._argmax
278 return self._argmax
276
279
277 # POSIX requires at least 4096 bytes for ARG_MAX
280 # POSIX requires at least 4096 bytes for ARG_MAX
278 self._argmax = 4096
281 self._argmax = 4096
279 try:
282 try:
280 self._argmax = os.sysconf("SC_ARG_MAX")
283 self._argmax = os.sysconf("SC_ARG_MAX")
281 except:
284 except:
282 pass
285 pass
283
286
284 # Windows shells impose their own limits on command line length,
287 # Windows shells impose their own limits on command line length,
285 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
288 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
286 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
289 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
287 # details about cmd.exe limitations.
290 # details about cmd.exe limitations.
288
291
289 # Since ARG_MAX is for command line _and_ environment, lower our limit
292 # Since ARG_MAX is for command line _and_ environment, lower our limit
290 # (and make happy Windows shells while doing this).
293 # (and make happy Windows shells while doing this).
291
294
292 self._argmax = self._argmax/2 - 1
295 self._argmax = self._argmax/2 - 1
293 return self._argmax
296 return self._argmax
294
297
295 def limit_arglist(self, arglist, cmd, *args, **kwargs):
298 def limit_arglist(self, arglist, cmd, *args, **kwargs):
296 limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
299 limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
297 bytes = 0
300 bytes = 0
298 fl = []
301 fl = []
299 for fn in arglist:
302 for fn in arglist:
300 b = len(fn) + 3
303 b = len(fn) + 3
301 if bytes + b < limit or len(fl) == 0:
304 if bytes + b < limit or len(fl) == 0:
302 fl.append(fn)
305 fl.append(fn)
303 bytes += b
306 bytes += b
304 else:
307 else:
305 yield fl
308 yield fl
306 fl = [fn]
309 fl = [fn]
307 bytes = b
310 bytes = b
308 if fl:
311 if fl:
309 yield fl
312 yield fl
310
313
311 def xargs(self, arglist, cmd, *args, **kwargs):
314 def xargs(self, arglist, cmd, *args, **kwargs):
312 for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
315 for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
313 self.run0(cmd, *(list(args) + l), **kwargs)
316 self.run0(cmd, *(list(args) + l), **kwargs)
314
317
315 class mapfile(dict):
318 class mapfile(dict):
316 def __init__(self, ui, path):
319 def __init__(self, ui, path):
317 super(mapfile, self).__init__()
320 super(mapfile, self).__init__()
318 self.ui = ui
321 self.ui = ui
319 self.path = path
322 self.path = path
320 self.fp = None
323 self.fp = None
321 self.order = []
324 self.order = []
322 self._read()
325 self._read()
323
326
324 def _read(self):
327 def _read(self):
325 if self.path is None:
328 if self.path is None:
326 return
329 return
327 try:
330 try:
328 fp = open(self.path, 'r')
331 fp = open(self.path, 'r')
329 except IOError, err:
332 except IOError, err:
330 if err.errno != errno.ENOENT:
333 if err.errno != errno.ENOENT:
331 raise
334 raise
332 return
335 return
333 for line in fp:
336 for line in fp:
334 key, value = line[:-1].split(' ', 1)
337 key, value = line[:-1].split(' ', 1)
335 if key not in self:
338 if key not in self:
336 self.order.append(key)
339 self.order.append(key)
337 super(mapfile, self).__setitem__(key, value)
340 super(mapfile, self).__setitem__(key, value)
338 fp.close()
341 fp.close()
339
342
340 def __setitem__(self, key, value):
343 def __setitem__(self, key, value):
341 if self.fp is None:
344 if self.fp is None:
342 try:
345 try:
343 self.fp = open(self.path, 'a')
346 self.fp = open(self.path, 'a')
344 except IOError, err:
347 except IOError, err:
345 raise util.Abort(_('could not open map file %r: %s') %
348 raise util.Abort(_('could not open map file %r: %s') %
346 (self.path, err.strerror))
349 (self.path, err.strerror))
347 self.fp.write('%s %s\n' % (key, value))
350 self.fp.write('%s %s\n' % (key, value))
348 self.fp.flush()
351 self.fp.flush()
349 super(mapfile, self).__setitem__(key, value)
352 super(mapfile, self).__setitem__(key, value)
350
353
351 def close(self):
354 def close(self):
352 if self.fp:
355 if self.fp:
353 self.fp.close()
356 self.fp.close()
354 self.fp = None
357 self.fp = None
@@ -1,354 +1,354
1 # convcmd - convert extension commands definition
1 # convcmd - convert extension commands definition
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
6 # of the GNU General Public License, incorporated herein by reference.
7
7
8 from common import NoRepo, SKIPREV, mapfile
8 from common import NoRepo, MissingTool, SKIPREV, mapfile
9 from cvs import convert_cvs
9 from cvs import convert_cvs
10 from darcs import darcs_source
10 from darcs import darcs_source
11 from git import convert_git
11 from git import convert_git
12 from hg import mercurial_source, mercurial_sink
12 from hg import mercurial_source, mercurial_sink
13 from subversion import debugsvnlog, svn_source, svn_sink
13 from subversion import debugsvnlog, svn_source, svn_sink
14 from monotone import monotone_source
14 from monotone import monotone_source
15 from gnuarch import gnuarch_source
15 from gnuarch import gnuarch_source
16 import filemap
16 import filemap
17
17
18 import os, shutil
18 import os, shutil
19 from mercurial import hg, util
19 from mercurial import hg, util
20 from mercurial.i18n import _
20 from mercurial.i18n import _
21
21
22 orig_encoding = 'ascii'
22 orig_encoding = 'ascii'
23
23
24 def recode(s):
24 def recode(s):
25 if isinstance(s, unicode):
25 if isinstance(s, unicode):
26 return s.encode(orig_encoding, 'replace')
26 return s.encode(orig_encoding, 'replace')
27 else:
27 else:
28 return s.decode('utf-8').encode(orig_encoding, 'replace')
28 return s.decode('utf-8').encode(orig_encoding, 'replace')
29
29
30 source_converters = [
30 source_converters = [
31 ('cvs', convert_cvs),
31 ('cvs', convert_cvs),
32 ('git', convert_git),
32 ('git', convert_git),
33 ('svn', svn_source),
33 ('svn', svn_source),
34 ('hg', mercurial_source),
34 ('hg', mercurial_source),
35 ('darcs', darcs_source),
35 ('darcs', darcs_source),
36 ('mtn', monotone_source),
36 ('mtn', monotone_source),
37 ('gnuarch', gnuarch_source),
37 ('gnuarch', gnuarch_source),
38 ]
38 ]
39
39
40 sink_converters = [
40 sink_converters = [
41 ('hg', mercurial_sink),
41 ('hg', mercurial_sink),
42 ('svn', svn_sink),
42 ('svn', svn_sink),
43 ]
43 ]
44
44
45 def convertsource(ui, path, type, rev):
45 def convertsource(ui, path, type, rev):
46 exceptions = []
46 exceptions = []
47 for name, source in source_converters:
47 for name, source in source_converters:
48 try:
48 try:
49 if not type or name == type:
49 if not type or name == type:
50 return source(ui, path, rev)
50 return source(ui, path, rev)
51 except NoRepo, inst:
51 except (NoRepo, MissingTool), inst:
52 exceptions.append(inst)
52 exceptions.append(inst)
53 if not ui.quiet:
53 if not ui.quiet:
54 for inst in exceptions:
54 for inst in exceptions:
55 ui.write(_("%s\n") % inst)
55 ui.write(_("%s\n") % inst)
56 raise util.Abort('%s: unknown repository type' % path)
56 raise util.Abort('%s: unknown repository type' % path)
57
57
58 def convertsink(ui, path, type):
58 def convertsink(ui, path, type):
59 for name, sink in sink_converters:
59 for name, sink in sink_converters:
60 try:
60 try:
61 if not type or name == type:
61 if not type or name == type:
62 return sink(ui, path)
62 return sink(ui, path)
63 except NoRepo, inst:
63 except NoRepo, inst:
64 ui.note(_("convert: %s\n") % inst)
64 ui.note(_("convert: %s\n") % inst)
65 raise util.Abort('%s: unknown repository type' % path)
65 raise util.Abort('%s: unknown repository type' % path)
66
66
67 class converter(object):
67 class converter(object):
68 def __init__(self, ui, source, dest, revmapfile, opts):
68 def __init__(self, ui, source, dest, revmapfile, opts):
69
69
70 self.source = source
70 self.source = source
71 self.dest = dest
71 self.dest = dest
72 self.ui = ui
72 self.ui = ui
73 self.opts = opts
73 self.opts = opts
74 self.commitcache = {}
74 self.commitcache = {}
75 self.authors = {}
75 self.authors = {}
76 self.authorfile = None
76 self.authorfile = None
77
77
78 self.map = mapfile(ui, revmapfile)
78 self.map = mapfile(ui, revmapfile)
79
79
80 # Read first the dst author map if any
80 # Read first the dst author map if any
81 authorfile = self.dest.authorfile()
81 authorfile = self.dest.authorfile()
82 if authorfile and os.path.exists(authorfile):
82 if authorfile and os.path.exists(authorfile):
83 self.readauthormap(authorfile)
83 self.readauthormap(authorfile)
84 # Extend/Override with new author map if necessary
84 # Extend/Override with new author map if necessary
85 if opts.get('authors'):
85 if opts.get('authors'):
86 self.readauthormap(opts.get('authors'))
86 self.readauthormap(opts.get('authors'))
87 self.authorfile = self.dest.authorfile()
87 self.authorfile = self.dest.authorfile()
88
88
89 self.splicemap = mapfile(ui, opts.get('splicemap'))
89 self.splicemap = mapfile(ui, opts.get('splicemap'))
90
90
91 def walktree(self, heads):
91 def walktree(self, heads):
92 '''Return a mapping that identifies the uncommitted parents of every
92 '''Return a mapping that identifies the uncommitted parents of every
93 uncommitted changeset.'''
93 uncommitted changeset.'''
94 visit = heads
94 visit = heads
95 known = {}
95 known = {}
96 parents = {}
96 parents = {}
97 while visit:
97 while visit:
98 n = visit.pop(0)
98 n = visit.pop(0)
99 if n in known or n in self.map: continue
99 if n in known or n in self.map: continue
100 known[n] = 1
100 known[n] = 1
101 commit = self.cachecommit(n)
101 commit = self.cachecommit(n)
102 parents[n] = []
102 parents[n] = []
103 for p in commit.parents:
103 for p in commit.parents:
104 parents[n].append(p)
104 parents[n].append(p)
105 visit.append(p)
105 visit.append(p)
106
106
107 return parents
107 return parents
108
108
109 def toposort(self, parents):
109 def toposort(self, parents):
110 '''Return an ordering such that every uncommitted changeset is
110 '''Return an ordering such that every uncommitted changeset is
111 preceeded by all its uncommitted ancestors.'''
111 preceeded by all its uncommitted ancestors.'''
112 visit = parents.keys()
112 visit = parents.keys()
113 seen = {}
113 seen = {}
114 children = {}
114 children = {}
115 actives = []
115 actives = []
116
116
117 while visit:
117 while visit:
118 n = visit.pop(0)
118 n = visit.pop(0)
119 if n in seen: continue
119 if n in seen: continue
120 seen[n] = 1
120 seen[n] = 1
121 # Ensure that nodes without parents are present in the 'children'
121 # Ensure that nodes without parents are present in the 'children'
122 # mapping.
122 # mapping.
123 children.setdefault(n, [])
123 children.setdefault(n, [])
124 hasparent = False
124 hasparent = False
125 for p in parents[n]:
125 for p in parents[n]:
126 if not p in self.map:
126 if not p in self.map:
127 visit.append(p)
127 visit.append(p)
128 hasparent = True
128 hasparent = True
129 children.setdefault(p, []).append(n)
129 children.setdefault(p, []).append(n)
130 if not hasparent:
130 if not hasparent:
131 actives.append(n)
131 actives.append(n)
132
132
133 del seen
133 del seen
134 del visit
134 del visit
135
135
136 if self.opts.get('datesort'):
136 if self.opts.get('datesort'):
137 dates = {}
137 dates = {}
138 def getdate(n):
138 def getdate(n):
139 if n not in dates:
139 if n not in dates:
140 dates[n] = util.parsedate(self.commitcache[n].date)
140 dates[n] = util.parsedate(self.commitcache[n].date)
141 return dates[n]
141 return dates[n]
142
142
143 def picknext(nodes):
143 def picknext(nodes):
144 return min([(getdate(n), n) for n in nodes])[1]
144 return min([(getdate(n), n) for n in nodes])[1]
145 else:
145 else:
146 prev = [None]
146 prev = [None]
147 def picknext(nodes):
147 def picknext(nodes):
148 # Return the first eligible child of the previously converted
148 # Return the first eligible child of the previously converted
149 # revision, or any of them.
149 # revision, or any of them.
150 next = nodes[0]
150 next = nodes[0]
151 for n in nodes:
151 for n in nodes:
152 if prev[0] in parents[n]:
152 if prev[0] in parents[n]:
153 next = n
153 next = n
154 break
154 break
155 prev[0] = next
155 prev[0] = next
156 return next
156 return next
157
157
158 s = []
158 s = []
159 pendings = {}
159 pendings = {}
160 while actives:
160 while actives:
161 n = picknext(actives)
161 n = picknext(actives)
162 actives.remove(n)
162 actives.remove(n)
163 s.append(n)
163 s.append(n)
164
164
165 # Update dependents list
165 # Update dependents list
166 for c in children.get(n, []):
166 for c in children.get(n, []):
167 if c not in pendings:
167 if c not in pendings:
168 pendings[c] = [p for p in parents[c] if p not in self.map]
168 pendings[c] = [p for p in parents[c] if p not in self.map]
169 try:
169 try:
170 pendings[c].remove(n)
170 pendings[c].remove(n)
171 except ValueError:
171 except ValueError:
172 raise util.Abort(_('cycle detected between %s and %s')
172 raise util.Abort(_('cycle detected between %s and %s')
173 % (recode(c), recode(n)))
173 % (recode(c), recode(n)))
174 if not pendings[c]:
174 if not pendings[c]:
175 # Parents are converted, node is eligible
175 # Parents are converted, node is eligible
176 actives.insert(0, c)
176 actives.insert(0, c)
177 pendings[c] = None
177 pendings[c] = None
178
178
179 if len(s) != len(parents):
179 if len(s) != len(parents):
180 raise util.Abort(_("not all revisions were sorted"))
180 raise util.Abort(_("not all revisions were sorted"))
181
181
182 return s
182 return s
183
183
184 def writeauthormap(self):
184 def writeauthormap(self):
185 authorfile = self.authorfile
185 authorfile = self.authorfile
186 if authorfile:
186 if authorfile:
187 self.ui.status('Writing author map file %s\n' % authorfile)
187 self.ui.status('Writing author map file %s\n' % authorfile)
188 ofile = open(authorfile, 'w+')
188 ofile = open(authorfile, 'w+')
189 for author in self.authors:
189 for author in self.authors:
190 ofile.write("%s=%s\n" % (author, self.authors[author]))
190 ofile.write("%s=%s\n" % (author, self.authors[author]))
191 ofile.close()
191 ofile.close()
192
192
193 def readauthormap(self, authorfile):
193 def readauthormap(self, authorfile):
194 afile = open(authorfile, 'r')
194 afile = open(authorfile, 'r')
195 for line in afile:
195 for line in afile:
196 if line.strip() == '':
196 if line.strip() == '':
197 continue
197 continue
198 try:
198 try:
199 srcauthor, dstauthor = line.split('=', 1)
199 srcauthor, dstauthor = line.split('=', 1)
200 srcauthor = srcauthor.strip()
200 srcauthor = srcauthor.strip()
201 dstauthor = dstauthor.strip()
201 dstauthor = dstauthor.strip()
202 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
202 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
203 self.ui.status(
203 self.ui.status(
204 'Overriding mapping for author %s, was %s, will be %s\n'
204 'Overriding mapping for author %s, was %s, will be %s\n'
205 % (srcauthor, self.authors[srcauthor], dstauthor))
205 % (srcauthor, self.authors[srcauthor], dstauthor))
206 else:
206 else:
207 self.ui.debug('Mapping author %s to %s\n'
207 self.ui.debug('Mapping author %s to %s\n'
208 % (srcauthor, dstauthor))
208 % (srcauthor, dstauthor))
209 self.authors[srcauthor] = dstauthor
209 self.authors[srcauthor] = dstauthor
210 except IndexError:
210 except IndexError:
211 self.ui.warn(
211 self.ui.warn(
212 'Ignoring bad line in author map file %s: %s\n'
212 'Ignoring bad line in author map file %s: %s\n'
213 % (authorfile, line.rstrip()))
213 % (authorfile, line.rstrip()))
214 afile.close()
214 afile.close()
215
215
216 def cachecommit(self, rev):
216 def cachecommit(self, rev):
217 commit = self.source.getcommit(rev)
217 commit = self.source.getcommit(rev)
218 commit.author = self.authors.get(commit.author, commit.author)
218 commit.author = self.authors.get(commit.author, commit.author)
219 self.commitcache[rev] = commit
219 self.commitcache[rev] = commit
220 return commit
220 return commit
221
221
222 def copy(self, rev):
222 def copy(self, rev):
223 commit = self.commitcache[rev]
223 commit = self.commitcache[rev]
224 do_copies = hasattr(self.dest, 'copyfile')
224 do_copies = hasattr(self.dest, 'copyfile')
225 filenames = []
225 filenames = []
226
226
227 changes = self.source.getchanges(rev)
227 changes = self.source.getchanges(rev)
228 if isinstance(changes, basestring):
228 if isinstance(changes, basestring):
229 if changes == SKIPREV:
229 if changes == SKIPREV:
230 dest = SKIPREV
230 dest = SKIPREV
231 else:
231 else:
232 dest = self.map[changes]
232 dest = self.map[changes]
233 self.map[rev] = dest
233 self.map[rev] = dest
234 return
234 return
235 files, copies = changes
235 files, copies = changes
236 pbranches = []
236 pbranches = []
237 if commit.parents:
237 if commit.parents:
238 for prev in commit.parents:
238 for prev in commit.parents:
239 if prev not in self.commitcache:
239 if prev not in self.commitcache:
240 self.cachecommit(prev)
240 self.cachecommit(prev)
241 pbranches.append((self.map[prev],
241 pbranches.append((self.map[prev],
242 self.commitcache[prev].branch))
242 self.commitcache[prev].branch))
243 self.dest.setbranch(commit.branch, pbranches)
243 self.dest.setbranch(commit.branch, pbranches)
244 for f, v in files:
244 for f, v in files:
245 filenames.append(f)
245 filenames.append(f)
246 try:
246 try:
247 data = self.source.getfile(f, v)
247 data = self.source.getfile(f, v)
248 except IOError, inst:
248 except IOError, inst:
249 self.dest.delfile(f)
249 self.dest.delfile(f)
250 else:
250 else:
251 e = self.source.getmode(f, v)
251 e = self.source.getmode(f, v)
252 self.dest.putfile(f, e, data)
252 self.dest.putfile(f, e, data)
253 if do_copies:
253 if do_copies:
254 if f in copies:
254 if f in copies:
255 copyf = copies[f]
255 copyf = copies[f]
256 # Merely marks that a copy happened.
256 # Merely marks that a copy happened.
257 self.dest.copyfile(copyf, f)
257 self.dest.copyfile(copyf, f)
258
258
259 try:
259 try:
260 parents = self.splicemap[rev].replace(',', ' ').split()
260 parents = self.splicemap[rev].replace(',', ' ').split()
261 self.ui.status('spliced in %s as parents of %s\n' %
261 self.ui.status('spliced in %s as parents of %s\n' %
262 (parents, rev))
262 (parents, rev))
263 parents = [self.map.get(p, p) for p in parents]
263 parents = [self.map.get(p, p) for p in parents]
264 except KeyError:
264 except KeyError:
265 parents = [b[0] for b in pbranches]
265 parents = [b[0] for b in pbranches]
266 newnode = self.dest.putcommit(filenames, parents, commit)
266 newnode = self.dest.putcommit(filenames, parents, commit)
267 self.source.converted(rev, newnode)
267 self.source.converted(rev, newnode)
268 self.map[rev] = newnode
268 self.map[rev] = newnode
269
269
270 def convert(self):
270 def convert(self):
271
271
272 try:
272 try:
273 self.source.before()
273 self.source.before()
274 self.dest.before()
274 self.dest.before()
275 self.source.setrevmap(self.map)
275 self.source.setrevmap(self.map)
276 self.ui.status("scanning source...\n")
276 self.ui.status("scanning source...\n")
277 heads = self.source.getheads()
277 heads = self.source.getheads()
278 parents = self.walktree(heads)
278 parents = self.walktree(heads)
279 self.ui.status("sorting...\n")
279 self.ui.status("sorting...\n")
280 t = self.toposort(parents)
280 t = self.toposort(parents)
281 num = len(t)
281 num = len(t)
282 c = None
282 c = None
283
283
284 self.ui.status("converting...\n")
284 self.ui.status("converting...\n")
285 for c in t:
285 for c in t:
286 num -= 1
286 num -= 1
287 desc = self.commitcache[c].desc
287 desc = self.commitcache[c].desc
288 if "\n" in desc:
288 if "\n" in desc:
289 desc = desc.splitlines()[0]
289 desc = desc.splitlines()[0]
290 # convert log message to local encoding without using
290 # convert log message to local encoding without using
291 # tolocal() because util._encoding conver() use it as
291 # tolocal() because util._encoding conver() use it as
292 # 'utf-8'
292 # 'utf-8'
293 self.ui.status("%d %s\n" % (num, recode(desc)))
293 self.ui.status("%d %s\n" % (num, recode(desc)))
294 self.ui.note(_("source: %s\n" % recode(c)))
294 self.ui.note(_("source: %s\n" % recode(c)))
295 self.copy(c)
295 self.copy(c)
296
296
297 tags = self.source.gettags()
297 tags = self.source.gettags()
298 ctags = {}
298 ctags = {}
299 for k in tags:
299 for k in tags:
300 v = tags[k]
300 v = tags[k]
301 if self.map.get(v, SKIPREV) != SKIPREV:
301 if self.map.get(v, SKIPREV) != SKIPREV:
302 ctags[k] = self.map[v]
302 ctags[k] = self.map[v]
303
303
304 if c and ctags:
304 if c and ctags:
305 nrev = self.dest.puttags(ctags)
305 nrev = self.dest.puttags(ctags)
306 # write another hash correspondence to override the previous
306 # write another hash correspondence to override the previous
307 # one so we don't end up with extra tag heads
307 # one so we don't end up with extra tag heads
308 if nrev:
308 if nrev:
309 self.map[c] = nrev
309 self.map[c] = nrev
310
310
311 self.writeauthormap()
311 self.writeauthormap()
312 finally:
312 finally:
313 self.cleanup()
313 self.cleanup()
314
314
315 def cleanup(self):
315 def cleanup(self):
316 try:
316 try:
317 self.dest.after()
317 self.dest.after()
318 finally:
318 finally:
319 self.source.after()
319 self.source.after()
320 self.map.close()
320 self.map.close()
321
321
322 def convert(ui, src, dest=None, revmapfile=None, **opts):
322 def convert(ui, src, dest=None, revmapfile=None, **opts):
323 global orig_encoding
323 global orig_encoding
324 orig_encoding = util._encoding
324 orig_encoding = util._encoding
325 util._encoding = 'UTF-8'
325 util._encoding = 'UTF-8'
326
326
327 if not dest:
327 if not dest:
328 dest = hg.defaultdest(src) + "-hg"
328 dest = hg.defaultdest(src) + "-hg"
329 ui.status("assuming destination %s\n" % dest)
329 ui.status("assuming destination %s\n" % dest)
330
330
331 destc = convertsink(ui, dest, opts.get('dest_type'))
331 destc = convertsink(ui, dest, opts.get('dest_type'))
332
332
333 try:
333 try:
334 srcc = convertsource(ui, src, opts.get('source_type'),
334 srcc = convertsource(ui, src, opts.get('source_type'),
335 opts.get('rev'))
335 opts.get('rev'))
336 except Exception:
336 except Exception:
337 for path in destc.created:
337 for path in destc.created:
338 shutil.rmtree(path, True)
338 shutil.rmtree(path, True)
339 raise
339 raise
340
340
341 fmap = opts.get('filemap')
341 fmap = opts.get('filemap')
342 if fmap:
342 if fmap:
343 srcc = filemap.filemap_source(ui, srcc, fmap)
343 srcc = filemap.filemap_source(ui, srcc, fmap)
344 destc.setfilemapmode(True)
344 destc.setfilemapmode(True)
345
345
346 if not revmapfile:
346 if not revmapfile:
347 try:
347 try:
348 revmapfile = destc.revmapfile()
348 revmapfile = destc.revmapfile()
349 except:
349 except:
350 revmapfile = os.path.join(destc, "map")
350 revmapfile = os.path.join(destc, "map")
351
351
352 c = converter(ui, srcc, destc, revmapfile, opts)
352 c = converter(ui, srcc, destc, revmapfile, opts)
353 c.convert()
353 c.convert()
354
354
@@ -1,193 +1,194
1 # monotone support for the convert extension
1 # monotone support for the convert extension
2
2
3 import os, re, time
3 import os, re, time
4 from mercurial import util
4 from mercurial import util
5 from common import NoRepo, commit, converter_source, checktool, commandline
5 from common import NoRepo, MissingTool, commit, converter_source, checktool
6 from common import commandline
6 from mercurial.i18n import _
7 from mercurial.i18n import _
7
8
8 class monotone_source(converter_source, commandline):
9 class monotone_source(converter_source, commandline):
9 def __init__(self, ui, path=None, rev=None):
10 def __init__(self, ui, path=None, rev=None):
10 converter_source.__init__(self, ui, path, rev)
11 converter_source.__init__(self, ui, path, rev)
11 commandline.__init__(self, ui, 'mtn')
12 commandline.__init__(self, ui, 'mtn')
12
13
13 self.ui = ui
14 self.ui = ui
14 self.path = path
15 self.path = path
15
16
16 # regular expressions for parsing monotone output
17 # regular expressions for parsing monotone output
17 space = r'\s*'
18 space = r'\s*'
18 name = r'\s+"((?:[^"]|\\")*)"\s*'
19 name = r'\s+"((?:[^"]|\\")*)"\s*'
19 value = name
20 value = name
20 revision = r'\s+\[(\w+)\]\s*'
21 revision = r'\s+\[(\w+)\]\s*'
21 lines = r'(?:.|\n)+'
22 lines = r'(?:.|\n)+'
22
23
23 self.dir_re = re.compile(space + "dir" + name)
24 self.dir_re = re.compile(space + "dir" + name)
24 self.file_re = re.compile(space + "file" + name + "content" + revision)
25 self.file_re = re.compile(space + "file" + name + "content" + revision)
25 self.add_file_re = re.compile(space + "add_file" + name + "content" + revision)
26 self.add_file_re = re.compile(space + "add_file" + name + "content" + revision)
26 self.patch_re = re.compile(space + "patch" + name + "from" + revision + "to" + revision)
27 self.patch_re = re.compile(space + "patch" + name + "from" + revision + "to" + revision)
27 self.rename_re = re.compile(space + "rename" + name + "to" + name)
28 self.rename_re = re.compile(space + "rename" + name + "to" + name)
28 self.tag_re = re.compile(space + "tag" + name + "revision" + revision)
29 self.tag_re = re.compile(space + "tag" + name + "revision" + revision)
29 self.cert_re = re.compile(lines + space + "name" + name + "value" + value)
30 self.cert_re = re.compile(lines + space + "name" + name + "value" + value)
30
31
31 attr = space + "file" + lines + space + "attr" + space
32 attr = space + "file" + lines + space + "attr" + space
32 self.attr_execute_re = re.compile(attr + '"mtn:execute"' + space + '"true"')
33 self.attr_execute_re = re.compile(attr + '"mtn:execute"' + space + '"true"')
33
34
34 # cached data
35 # cached data
35 self.manifest_rev = None
36 self.manifest_rev = None
36 self.manifest = None
37 self.manifest = None
37 self.files = None
38 self.files = None
38 self.dirs = None
39 self.dirs = None
39
40
40 norepo = NoRepo (_("%s does not look like a monotone repo") % path)
41 norepo = NoRepo (_("%s does not look like a monotone repo") % path)
41 if not os.path.exists(path):
42 if not os.path.exists(path):
42 raise norepo
43 raise norepo
43
44
44 checktool('mtn')
45 checktool('mtn', abort=False)
45
46
46 # test if there are any revisions
47 # test if there are any revisions
47 self.rev = None
48 self.rev = None
48 try:
49 try:
49 self.getheads()
50 self.getheads()
50 except:
51 except:
51 raise norepo
52 raise norepo
52 self.rev = rev
53 self.rev = rev
53
54
54 def mtnrun(self, *args, **kwargs):
55 def mtnrun(self, *args, **kwargs):
55 kwargs['d'] = self.path
56 kwargs['d'] = self.path
56 return self.run0('automate', *args, **kwargs)
57 return self.run0('automate', *args, **kwargs)
57
58
58 def mtnloadmanifest(self, rev):
59 def mtnloadmanifest(self, rev):
59 if self.manifest_rev == rev:
60 if self.manifest_rev == rev:
60 return
61 return
61 self.manifest = self.mtnrun("get_manifest_of", rev).split("\n\n")
62 self.manifest = self.mtnrun("get_manifest_of", rev).split("\n\n")
62 self.manifest_rev = rev
63 self.manifest_rev = rev
63 self.files = {}
64 self.files = {}
64 self.dirs = {}
65 self.dirs = {}
65
66
66 for e in self.manifest:
67 for e in self.manifest:
67 m = self.file_re.match(e)
68 m = self.file_re.match(e)
68 if m:
69 if m:
69 attr = ""
70 attr = ""
70 name = m.group(1)
71 name = m.group(1)
71 node = m.group(2)
72 node = m.group(2)
72 if self.attr_execute_re.match(e):
73 if self.attr_execute_re.match(e):
73 attr += "x"
74 attr += "x"
74 self.files[name] = (node, attr)
75 self.files[name] = (node, attr)
75 m = self.dir_re.match(e)
76 m = self.dir_re.match(e)
76 if m:
77 if m:
77 self.dirs[m.group(1)] = True
78 self.dirs[m.group(1)] = True
78
79
79 def mtnisfile(self, name, rev):
80 def mtnisfile(self, name, rev):
80 # a non-file could be a directory or a deleted or renamed file
81 # a non-file could be a directory or a deleted or renamed file
81 self.mtnloadmanifest(rev)
82 self.mtnloadmanifest(rev)
82 try:
83 try:
83 self.files[name]
84 self.files[name]
84 return True
85 return True
85 except KeyError:
86 except KeyError:
86 return False
87 return False
87
88
88 def mtnisdir(self, name, rev):
89 def mtnisdir(self, name, rev):
89 self.mtnloadmanifest(rev)
90 self.mtnloadmanifest(rev)
90 try:
91 try:
91 self.dirs[name]
92 self.dirs[name]
92 return True
93 return True
93 except KeyError:
94 except KeyError:
94 return False
95 return False
95
96
96 def mtngetcerts(self, rev):
97 def mtngetcerts(self, rev):
97 certs = {"author":"<missing>", "date":"<missing>",
98 certs = {"author":"<missing>", "date":"<missing>",
98 "changelog":"<missing>", "branch":"<missing>"}
99 "changelog":"<missing>", "branch":"<missing>"}
99 cert_list = self.mtnrun("certs", rev).split("\n\n")
100 cert_list = self.mtnrun("certs", rev).split("\n\n")
100 for e in cert_list:
101 for e in cert_list:
101 m = self.cert_re.match(e)
102 m = self.cert_re.match(e)
102 if m:
103 if m:
103 certs[m.group(1)] = m.group(2)
104 certs[m.group(1)] = m.group(2)
104 return certs
105 return certs
105
106
106 def mtnrenamefiles(self, files, fromdir, todir):
107 def mtnrenamefiles(self, files, fromdir, todir):
107 renamed = {}
108 renamed = {}
108 for tofile in files:
109 for tofile in files:
109 suffix = tofile.lstrip(todir)
110 suffix = tofile.lstrip(todir)
110 if todir + suffix == tofile:
111 if todir + suffix == tofile:
111 renamed[tofile] = (fromdir + suffix).lstrip("/")
112 renamed[tofile] = (fromdir + suffix).lstrip("/")
112 return renamed
113 return renamed
113
114
114
115
115 # implement the converter_source interface:
116 # implement the converter_source interface:
116
117
117 def getheads(self):
118 def getheads(self):
118 if not self.rev:
119 if not self.rev:
119 return self.mtnrun("leaves").splitlines()
120 return self.mtnrun("leaves").splitlines()
120 else:
121 else:
121 return [self.rev]
122 return [self.rev]
122
123
123 def getchanges(self, rev):
124 def getchanges(self, rev):
124 #revision = self.mtncmd("get_revision %s" % rev).split("\n\n")
125 #revision = self.mtncmd("get_revision %s" % rev).split("\n\n")
125 revision = self.mtnrun("get_revision", rev).split("\n\n")
126 revision = self.mtnrun("get_revision", rev).split("\n\n")
126 files = {}
127 files = {}
127 copies = {}
128 copies = {}
128 for e in revision:
129 for e in revision:
129 m = self.add_file_re.match(e)
130 m = self.add_file_re.match(e)
130 if m:
131 if m:
131 files[m.group(1)] = rev
132 files[m.group(1)] = rev
132 m = self.patch_re.match(e)
133 m = self.patch_re.match(e)
133 if m:
134 if m:
134 files[m.group(1)] = rev
135 files[m.group(1)] = rev
135
136
136 # Delete/rename is handled later when the convert engine
137 # Delete/rename is handled later when the convert engine
137 # discovers an IOError exception from getfile,
138 # discovers an IOError exception from getfile,
138 # but only if we add the "from" file to the list of changes.
139 # but only if we add the "from" file to the list of changes.
139 m = self.rename_re.match(e)
140 m = self.rename_re.match(e)
140 if m:
141 if m:
141 toname = m.group(2)
142 toname = m.group(2)
142 fromname = m.group(1)
143 fromname = m.group(1)
143 if self.mtnisfile(toname, rev):
144 if self.mtnisfile(toname, rev):
144 copies[toname] = fromname
145 copies[toname] = fromname
145 files[toname] = rev
146 files[toname] = rev
146 files[fromname] = rev
147 files[fromname] = rev
147 if self.mtnisdir(toname, rev):
148 if self.mtnisdir(toname, rev):
148 renamed = self.mtnrenamefiles(self.files, fromname, toname)
149 renamed = self.mtnrenamefiles(self.files, fromname, toname)
149 for tofile, fromfile in renamed.items():
150 for tofile, fromfile in renamed.items():
150 self.ui.debug (_("copying file in renamed dir from '%s' to '%s'") % (fromfile, tofile), '\n')
151 self.ui.debug (_("copying file in renamed dir from '%s' to '%s'") % (fromfile, tofile), '\n')
151 files[tofile] = rev
152 files[tofile] = rev
152 for fromfile in renamed.values():
153 for fromfile in renamed.values():
153 files[fromfile] = rev
154 files[fromfile] = rev
154 return (files.items(), copies)
155 return (files.items(), copies)
155
156
156 def getmode(self, name, rev):
157 def getmode(self, name, rev):
157 self.mtnloadmanifest(rev)
158 self.mtnloadmanifest(rev)
158 try:
159 try:
159 node, attr = self.files[name]
160 node, attr = self.files[name]
160 return attr
161 return attr
161 except KeyError:
162 except KeyError:
162 return ""
163 return ""
163
164
164 def getfile(self, name, rev):
165 def getfile(self, name, rev):
165 if not self.mtnisfile(name, rev):
166 if not self.mtnisfile(name, rev):
166 raise IOError() # file was deleted or renamed
167 raise IOError() # file was deleted or renamed
167 try:
168 try:
168 return self.mtnrun("get_file_of", name, r=rev)
169 return self.mtnrun("get_file_of", name, r=rev)
169 except:
170 except:
170 raise IOError() # file was deleted or renamed
171 raise IOError() # file was deleted or renamed
171
172
172 def getcommit(self, rev):
173 def getcommit(self, rev):
173 certs = self.mtngetcerts(rev)
174 certs = self.mtngetcerts(rev)
174 return commit(
175 return commit(
175 author=certs["author"],
176 author=certs["author"],
176 date=util.datestr(util.strdate(certs["date"], "%Y-%m-%dT%H:%M:%S")),
177 date=util.datestr(util.strdate(certs["date"], "%Y-%m-%dT%H:%M:%S")),
177 desc=certs["changelog"],
178 desc=certs["changelog"],
178 rev=rev,
179 rev=rev,
179 parents=self.mtnrun("parents", rev).splitlines(),
180 parents=self.mtnrun("parents", rev).splitlines(),
180 branch=certs["branch"])
181 branch=certs["branch"])
181
182
182 def gettags(self):
183 def gettags(self):
183 tags = {}
184 tags = {}
184 for e in self.mtnrun("tags").split("\n\n"):
185 for e in self.mtnrun("tags").split("\n\n"):
185 m = self.tag_re.match(e)
186 m = self.tag_re.match(e)
186 if m:
187 if m:
187 tags[m.group(1)] = m.group(2)
188 tags[m.group(1)] = m.group(2)
188 return tags
189 return tags
189
190
190 def getchangedfiles(self, rev, i):
191 def getchangedfiles(self, rev, i):
191 # This function is only needed to support --filemap
192 # This function is only needed to support --filemap
192 # ... and we don't support that
193 # ... and we don't support that
193 raise NotImplementedError()
194 raise NotImplementedError()
General Comments 0
You need to be logged in to leave comments. Login now