##// END OF EJS Templates
initial version of monotone source for convert extension
Mikkel Fahnøe Jørgensen -
r6306:2f9de4aa default
parent child Browse files
Show More
@@ -0,0 +1,214
1 # monotone support for the convert extension
2
3 import os
4 import re
5 import time
6 from mercurial import util
7
8 from common import NoRepo, commit, converter_source, checktool
9
10 class monotone_source(converter_source):
11 def __init__(self, ui, path=None, rev=None):
12 converter_source.__init__(self, ui, path, rev)
13
14 self.ui = ui
15 self.path = path
16
17
18 # regular expressions for parsing monotone output
19
20 space = r'\s*'
21 name = r'\s+"((?:[^"]|\\")*)"\s*'
22 value = name
23 revision = r'\s+\[(\w+)\]\s*'
24 lines = r'(?:.|\n)+'
25
26 self.dir_re = re.compile(space + "dir" + name)
27 self.file_re = re.compile(space + "file" + name + "content" + revision)
28 self.add_file_re = re.compile(space + "add_file" + name + "content" + revision)
29 self.patch_re = re.compile(space + "patch" + name + "from" + revision + "to" + revision)
30 self.rename_re = re.compile(space + "rename" + name + "to" + name)
31 self.tag_re = re.compile(space + "tag" + name + "revision" + revision)
32 self.cert_re = re.compile(lines + space + "name" + name + "value" + value)
33
34 attr = space + "file" + lines + space + "attr" + space
35 self.attr_execute_re = re.compile(attr + '"mtn:execute"' + space + '"true"')
36
37 # cached data
38
39 self.manifest_rev = None
40 self.manifest = None
41 self.files = None
42 self.dirs = None
43
44 norepo = NoRepo("%s does not look like a monotone repo" % path)
45 if not os.path.exists(path):
46 raise norepo
47
48 checktool('mtn')
49
50 # test if there are are any revisions
51 self.rev = None
52 try :
53 self.getheads()
54 except :
55 raise norepo
56
57 self.rev = rev
58
59
60 def mtncmd(self, arg):
61 cmdline = "mtn -d %s automate %s" % (util.shellquote(self.path), arg)
62 self.ui.debug(cmdline, '\n')
63 p = util.popen(cmdline)
64 result = p.read()
65 if p.close():
66 raise IOError()
67 return result
68
69 def mtnloadmanifest(self, rev):
70 if self.manifest_rev == rev:
71 return
72 self.manifest_rev = rev
73 self.manifest = self.mtncmd("get_manifest_of %s" % rev).split("\n\n")
74
75 manifest = self.manifest
76 files = {}
77 dirs = {}
78
79 for e in manifest:
80 m = self.file_re.match(e)
81 if m:
82 attr = ""
83 name = m.group(1)
84 node = m.group(2)
85 if self.attr_execute_re.match(e):
86 attr += "x"
87 files[name] = (node, attr)
88 m = self.dir_re.match(e)
89 if m:
90 dirs[m.group(1)] = True
91
92 self.files = files
93 self.dirs = dirs
94
95 def mtnisfile(self, name, rev):
96 # a non-file could be a directory or a deleted or renamed file
97 self.mtnloadmanifest(rev)
98 try :
99 self.files[name]
100 return True
101 except KeyError:
102 return False
103
104 def mtnisdir(self, name, rev):
105 self.mtnloadmanifest(rev)
106 try :
107 self.dirs[name]
108 return True
109 except KeyError:
110 return False
111
112 def mtngetcerts(self, rev):
113 certs = {"author":"<missing>", "date":"<missing>",
114 "changelog":"<missing>", "branch":"<missing>"}
115 cert_list = self.mtncmd("certs %s" % rev).split("\n\n")
116 for e in cert_list:
117 m = self.cert_re.match(e)
118 if m:
119 certs[m.group(1)] = m.group(2)
120 return certs
121
122 def mtngetparents(self, rev):
123 parents = self.mtncmd("parents %s" % rev).strip("\n").split("\n")
124 p = []
125 for x in parents:
126 if len(x) >= 40: # blank revs have been seen otherwise
127 p.append(x)
128 return p
129
130 def mtnrenamefiles(self, files, fromdir, todir):
131 renamed = {}
132 for tofile in files:
133 suffix = tofile.lstrip(todir)
134 if todir + suffix == tofile:
135 renamed[tofile] = (fromdir + suffix).lstrip("/")
136 return renamed
137
138
139 # implement the converter_source interface:
140
141 def getheads(self):
142 if not self.rev or self.rev == "":
143 return self.mtncmd("leaves").splitlines()
144 else:
145 return [self.rev]
146
147 def getchanges(self, rev):
148 revision = self.mtncmd("get_revision %s" % rev).split("\n\n")
149 files = {}
150 copies = {}
151 for e in revision:
152 m = self.add_file_re.match(e)
153 if m:
154 files[m.group(1)] = rev
155 m = self.patch_re.match(e)
156 if m:
157 files[m.group(1)] = rev
158
159 # Delete/rename is handled later when the convert engine
160 # discovers an IOError exception from getfile,
161 # but only if we add the "from" file to the list of changes.
162 m = self.rename_re.match(e)
163 if m:
164 toname = m.group(2)
165 fromname = m.group(1)
166 if self.mtnisfile(toname, rev):
167 copies[toname] = fromname
168 files[toname] = rev
169 files[fromname] = rev
170 if self.mtnisdir(toname, rev):
171 renamed = self.mtnrenamefiles(self.files, fromname, toname)
172 for tofile, fromfile in renamed.items():
173 self.ui.debug (("copying file in renamed dir from '%s' to '%s'" % (fromfile, tofile)), "\n")
174 files[tofile] = rev
175 for fromfile in renamed.values():
176 files[fromfile] = rev
177
178 return (files.items(), copies)
179
180 def getmode(self, name, rev):
181 self.mtnloadmanifest(rev)
182 try :
183 node, attr = self.files[name]
184 return attr
185 except KeyError:
186 return ""
187
188 def getfile(self, name, rev):
189 if not self.mtnisfile(name, rev):
190 raise IOError() # file was deleted or renamed
191 return self.mtncmd("get_file_of %s -r %s" % (util.shellquote(name), rev))
192
193 def getcommit(self, rev):
194 certs = self.mtngetcerts(rev)
195 return commit(
196 author=certs["author"],
197 date=util.datestr(util.strdate(certs["date"], "%Y-%m-%dT%H:%M:%S")),
198 desc=certs["changelog"],
199 rev=rev,
200 parents=self.mtngetparents(rev),
201 branch=certs["branch"])
202
203 def gettags(self):
204 tags = {}
205 for e in self.mtncmd("tags").split("\n\n"):
206 m = self.tag_re.match(e)
207 if m:
208 tags[m.group(1)] = m.group(2)
209 return tags
210
211 def getchangedfiles(self, rev, i):
212 # This function is only needed to support --filemap
213 # ... and we don't support that
214 raise NotImplementedError()
@@ -1,148 +1,149
1 1 # convert.py Foreign SCM converter
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import convcmd
9 9 from mercurial import commands
10 10
11 11 # Commands definition was moved elsewhere to ease demandload job.
12 12
13 13 def convert(ui, src, dest=None, revmapfile=None, **opts):
14 14 """Convert a foreign SCM repository to a Mercurial one.
15 15
16 16 Accepted source formats:
17 17 - Mercurial
18 18 - CVS
19 19 - Darcs
20 20 - git
21 21 - Subversion
22 - Monotone
22 23 - GNU Arch
23 24
24 25 Accepted destination formats:
25 26 - Mercurial
26 27 - Subversion (history on branches is not preserved)
27 28
28 29 If no revision is given, all revisions will be converted. Otherwise,
29 30 convert will only import up to the named revision (given in a format
30 31 understood by the source).
31 32
32 33 If no destination directory name is specified, it defaults to the
33 34 basename of the source with '-hg' appended. If the destination
34 35 repository doesn't exist, it will be created.
35 36
36 37 If <REVMAP> isn't given, it will be put in a default location
37 38 (<dest>/.hg/shamap by default). The <REVMAP> is a simple text
38 39 file that maps each source commit ID to the destination ID for
39 40 that revision, like so:
40 41 <source ID> <destination ID>
41 42
42 43 If the file doesn't exist, it's automatically created. It's updated
43 44 on each commit copied, so convert-repo can be interrupted and can
44 45 be run repeatedly to copy new commits.
45 46
46 47 The [username mapping] file is a simple text file that maps each source
47 48 commit author to a destination commit author. It is handy for source SCMs
48 49 that use unix logins to identify authors (eg: CVS). One line per author
49 50 mapping and the line format is:
50 51 srcauthor=whatever string you want
51 52
52 53 The filemap is a file that allows filtering and remapping of files
53 54 and directories. Comment lines start with '#'. Each line can
54 55 contain one of the following directives:
55 56
56 57 include path/to/file
57 58
58 59 exclude path/to/file
59 60
60 61 rename from/file to/file
61 62
62 63 The 'include' directive causes a file, or all files under a
63 64 directory, to be included in the destination repository, and the
64 65 exclusion of all other files and dirs not explicitely included.
65 66 The 'exclude' directive causes files or directories to be omitted.
66 67 The 'rename' directive renames a file or directory. To rename from a
67 68 subdirectory into the root of the repository, use '.' as the path to
68 69 rename to.
69 70
70 71 The splicemap is a file that allows insertion of synthetic
71 72 history, letting you specify the parents of a revision. This is
72 73 useful if you want to e.g. give a Subversion merge two parents, or
73 74 graft two disconnected series of history together. Each entry
74 75 contains a key, followed by a space, followed by one or two
75 76 values, separated by spaces. The key is the revision ID in the
76 77 source revision control system whose parents should be modified
77 78 (same format as a key in .hg/shamap). The values are the revision
78 79 IDs (in either the source or destination revision control system)
79 80 that should be used as the new parents for that node.
80 81
81 82 Mercurial Source
82 83 -----------------
83 84
84 85 --config convert.hg.saverev=True (boolean)
85 86 allow target to preserve source revision ID
86 87
87 88 Subversion Source
88 89 -----------------
89 90
90 91 Subversion source detects classical trunk/branches/tags layouts.
91 92 By default, the supplied "svn://repo/path/" source URL is
92 93 converted as a single branch. If "svn://repo/path/trunk" exists
93 94 it replaces the default branch. If "svn://repo/path/branches"
94 95 exists, its subdirectories are listed as possible branches. If
95 96 "svn://repo/path/tags" exists, it is looked for tags referencing
96 97 converted branches. Default "trunk", "branches" and "tags" values
97 98 can be overriden with following options. Set them to paths
98 99 relative to the source URL, or leave them blank to disable
99 100 autodetection.
100 101
101 102 --config convert.svn.branches=branches (directory name)
102 103 specify the directory containing branches
103 104 --config convert.svn.tags=tags (directory name)
104 105 specify the directory containing tags
105 106 --config convert.svn.trunk=trunk (directory name)
106 107 specify the name of the trunk branch
107 108
108 109 Source history can be retrieved starting at a specific revision,
109 110 instead of being integrally converted. Only single branch
110 111 conversions are supported.
111 112
112 113 --config convert.svn.startrev=0 (svn revision number)
113 114 specify start Subversion revision.
114 115
115 116 Mercurial Destination
116 117 ---------------------
117 118
118 119 --config convert.hg.clonebranches=False (boolean)
119 120 dispatch source branches in separate clones.
120 121 --config convert.hg.tagsbranch=default (branch name)
121 122 tag revisions branch name
122 123 --config convert.hg.usebranchnames=True (boolean)
123 124 preserve branch names
124 125
125 126 """
126 127 return convcmd.convert(ui, src, dest, revmapfile, **opts)
127 128
128 129 def debugsvnlog(ui, **opts):
129 130 return convcmd.debugsvnlog(ui, **opts)
130 131
131 132 commands.norepo += " convert debugsvnlog"
132 133
133 134 cmdtable = {
134 135 "convert":
135 136 (convert,
136 137 [('A', 'authors', '', 'username mapping filename'),
137 138 ('d', 'dest-type', '', 'destination repository type'),
138 139 ('', 'filemap', '', 'remap file names using contents of file'),
139 140 ('r', 'rev', '', 'import up to target revision REV'),
140 141 ('s', 'source-type', '', 'source repository type'),
141 142 ('', 'splicemap', '', 'splice synthesized history into place'),
142 143 ('', 'datesort', None, 'try to sort changesets by date')],
143 144 'hg convert [OPTION]... SOURCE [DEST [REVMAP]]'),
144 145 "debugsvnlog":
145 146 (debugsvnlog,
146 147 [],
147 148 'hg debugsvnlog'),
148 149 }
@@ -1,352 +1,354
1 1 # convcmd - convert extension commands definition
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from common import NoRepo, SKIPREV, mapfile
9 9 from cvs import convert_cvs
10 10 from darcs import darcs_source
11 11 from git import convert_git
12 12 from hg import mercurial_source, mercurial_sink
13 13 from subversion import debugsvnlog, svn_source, svn_sink
14 from monotone import monotone_source
14 15 from gnuarch import gnuarch_source
15 16 import filemap
16 17
17 18 import os, shutil
18 19 from mercurial import hg, util
19 20 from mercurial.i18n import _
20 21
21 22 orig_encoding = 'ascii'
22 23
23 24 def recode(s):
24 25 if isinstance(s, unicode):
25 26 return s.encode(orig_encoding, 'replace')
26 27 else:
27 28 return s.decode('utf-8').encode(orig_encoding, 'replace')
28 29
29 30 source_converters = [
30 31 ('cvs', convert_cvs),
31 32 ('git', convert_git),
32 33 ('svn', svn_source),
33 34 ('hg', mercurial_source),
34 35 ('darcs', darcs_source),
36 ('mtn', monotone_source),
35 37 ('gnuarch', gnuarch_source),
36 38 ]
37 39
38 40 sink_converters = [
39 41 ('hg', mercurial_sink),
40 42 ('svn', svn_sink),
41 43 ]
42 44
43 45 def convertsource(ui, path, type, rev):
44 46 exceptions = []
45 47 for name, source in source_converters:
46 48 try:
47 49 if not type or name == type:
48 50 return source(ui, path, rev)
49 51 except NoRepo, inst:
50 52 exceptions.append(inst)
51 53 if not ui.quiet:
52 54 for inst in exceptions:
53 55 ui.write(_("%s\n") % inst)
54 56 raise util.Abort('%s: unknown repository type' % path)
55 57
56 58 def convertsink(ui, path, type):
57 59 for name, sink in sink_converters:
58 60 try:
59 61 if not type or name == type:
60 62 return sink(ui, path)
61 63 except NoRepo, inst:
62 64 ui.note(_("convert: %s\n") % inst)
63 65 raise util.Abort('%s: unknown repository type' % path)
64 66
65 67 class converter(object):
66 68 def __init__(self, ui, source, dest, revmapfile, opts):
67 69
68 70 self.source = source
69 71 self.dest = dest
70 72 self.ui = ui
71 73 self.opts = opts
72 74 self.commitcache = {}
73 75 self.authors = {}
74 76 self.authorfile = None
75 77
76 78 self.map = mapfile(ui, revmapfile)
77 79
78 80 # Read first the dst author map if any
79 81 authorfile = self.dest.authorfile()
80 82 if authorfile and os.path.exists(authorfile):
81 83 self.readauthormap(authorfile)
82 84 # Extend/Override with new author map if necessary
83 85 if opts.get('authors'):
84 86 self.readauthormap(opts.get('authors'))
85 87 self.authorfile = self.dest.authorfile()
86 88
87 89 self.splicemap = mapfile(ui, opts.get('splicemap'))
88 90
89 91 def walktree(self, heads):
90 92 '''Return a mapping that identifies the uncommitted parents of every
91 93 uncommitted changeset.'''
92 94 visit = heads
93 95 known = {}
94 96 parents = {}
95 97 while visit:
96 98 n = visit.pop(0)
97 99 if n in known or n in self.map: continue
98 100 known[n] = 1
99 101 commit = self.cachecommit(n)
100 102 parents[n] = []
101 103 for p in commit.parents:
102 104 parents[n].append(p)
103 105 visit.append(p)
104 106
105 107 return parents
106 108
107 109 def toposort(self, parents):
108 110 '''Return an ordering such that every uncommitted changeset is
109 111 preceeded by all its uncommitted ancestors.'''
110 112 visit = parents.keys()
111 113 seen = {}
112 114 children = {}
113 115 actives = []
114 116
115 117 while visit:
116 118 n = visit.pop(0)
117 119 if n in seen: continue
118 120 seen[n] = 1
119 121 # Ensure that nodes without parents are present in the 'children'
120 122 # mapping.
121 123 children.setdefault(n, [])
122 124 hasparent = False
123 125 for p in parents[n]:
124 126 if not p in self.map:
125 127 visit.append(p)
126 128 hasparent = True
127 129 children.setdefault(p, []).append(n)
128 130 if not hasparent:
129 131 actives.append(n)
130 132
131 133 del seen
132 134 del visit
133 135
134 136 if self.opts.get('datesort'):
135 137 dates = {}
136 138 def getdate(n):
137 139 if n not in dates:
138 140 dates[n] = util.parsedate(self.commitcache[n].date)
139 141 return dates[n]
140 142
141 143 def picknext(nodes):
142 144 return min([(getdate(n), n) for n in nodes])[1]
143 145 else:
144 146 prev = [None]
145 147 def picknext(nodes):
146 148 # Return the first eligible child of the previously converted
147 149 # revision, or any of them.
148 150 next = nodes[0]
149 151 for n in nodes:
150 152 if prev[0] in parents[n]:
151 153 next = n
152 154 break
153 155 prev[0] = next
154 156 return next
155 157
156 158 s = []
157 159 pendings = {}
158 160 while actives:
159 161 n = picknext(actives)
160 162 actives.remove(n)
161 163 s.append(n)
162 164
163 165 # Update dependents list
164 166 for c in children.get(n, []):
165 167 if c not in pendings:
166 168 pendings[c] = [p for p in parents[c] if p not in self.map]
167 169 try:
168 170 pendings[c].remove(n)
169 171 except ValueError:
170 172 raise util.Abort(_('cycle detected between %s and %s')
171 173 % (recode(c), recode(n)))
172 174 if not pendings[c]:
173 175 # Parents are converted, node is eligible
174 176 actives.insert(0, c)
175 177 pendings[c] = None
176 178
177 179 if len(s) != len(parents):
178 180 raise util.Abort(_("not all revisions were sorted"))
179 181
180 182 return s
181 183
182 184 def writeauthormap(self):
183 185 authorfile = self.authorfile
184 186 if authorfile:
185 187 self.ui.status('Writing author map file %s\n' % authorfile)
186 188 ofile = open(authorfile, 'w+')
187 189 for author in self.authors:
188 190 ofile.write("%s=%s\n" % (author, self.authors[author]))
189 191 ofile.close()
190 192
191 193 def readauthormap(self, authorfile):
192 194 afile = open(authorfile, 'r')
193 195 for line in afile:
194 196 if line.strip() == '':
195 197 continue
196 198 try:
197 199 srcauthor, dstauthor = line.split('=', 1)
198 200 srcauthor = srcauthor.strip()
199 201 dstauthor = dstauthor.strip()
200 202 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
201 203 self.ui.status(
202 204 'Overriding mapping for author %s, was %s, will be %s\n'
203 205 % (srcauthor, self.authors[srcauthor], dstauthor))
204 206 else:
205 207 self.ui.debug('Mapping author %s to %s\n'
206 208 % (srcauthor, dstauthor))
207 209 self.authors[srcauthor] = dstauthor
208 210 except IndexError:
209 211 self.ui.warn(
210 212 'Ignoring bad line in author map file %s: %s\n'
211 213 % (authorfile, line.rstrip()))
212 214 afile.close()
213 215
214 216 def cachecommit(self, rev):
215 217 commit = self.source.getcommit(rev)
216 218 commit.author = self.authors.get(commit.author, commit.author)
217 219 self.commitcache[rev] = commit
218 220 return commit
219 221
220 222 def copy(self, rev):
221 223 commit = self.commitcache[rev]
222 224 do_copies = hasattr(self.dest, 'copyfile')
223 225 filenames = []
224 226
225 227 changes = self.source.getchanges(rev)
226 228 if isinstance(changes, basestring):
227 229 if changes == SKIPREV:
228 230 dest = SKIPREV
229 231 else:
230 232 dest = self.map[changes]
231 233 self.map[rev] = dest
232 234 return
233 235 files, copies = changes
234 236 pbranches = []
235 237 if commit.parents:
236 238 for prev in commit.parents:
237 239 if prev not in self.commitcache:
238 240 self.cachecommit(prev)
239 241 pbranches.append((self.map[prev],
240 242 self.commitcache[prev].branch))
241 243 self.dest.setbranch(commit.branch, pbranches)
242 244 for f, v in files:
243 245 filenames.append(f)
244 246 try:
245 247 data = self.source.getfile(f, v)
246 248 except IOError, inst:
247 249 self.dest.delfile(f)
248 250 else:
249 251 e = self.source.getmode(f, v)
250 252 self.dest.putfile(f, e, data)
251 253 if do_copies:
252 254 if f in copies:
253 255 copyf = copies[f]
254 256 # Merely marks that a copy happened.
255 257 self.dest.copyfile(copyf, f)
256 258
257 259 try:
258 260 parents = self.splicemap[rev].replace(',', ' ').split()
259 261 self.ui.status('spliced in %s as parents of %s\n' %
260 262 (parents, rev))
261 263 parents = [self.map.get(p, p) for p in parents]
262 264 except KeyError:
263 265 parents = [b[0] for b in pbranches]
264 266 newnode = self.dest.putcommit(filenames, parents, commit)
265 267 self.source.converted(rev, newnode)
266 268 self.map[rev] = newnode
267 269
268 270 def convert(self):
269 271
270 272 try:
271 273 self.source.before()
272 274 self.dest.before()
273 275 self.source.setrevmap(self.map)
274 276 self.ui.status("scanning source...\n")
275 277 heads = self.source.getheads()
276 278 parents = self.walktree(heads)
277 279 self.ui.status("sorting...\n")
278 280 t = self.toposort(parents)
279 281 num = len(t)
280 282 c = None
281 283
282 284 self.ui.status("converting...\n")
283 285 for c in t:
284 286 num -= 1
285 287 desc = self.commitcache[c].desc
286 288 if "\n" in desc:
287 289 desc = desc.splitlines()[0]
288 290 # convert log message to local encoding without using
289 291 # tolocal() because util._encoding conver() use it as
290 292 # 'utf-8'
291 293 self.ui.status("%d %s\n" % (num, recode(desc)))
292 294 self.ui.note(_("source: %s\n" % recode(c)))
293 295 self.copy(c)
294 296
295 297 tags = self.source.gettags()
296 298 ctags = {}
297 299 for k in tags:
298 300 v = tags[k]
299 301 if self.map.get(v, SKIPREV) != SKIPREV:
300 302 ctags[k] = self.map[v]
301 303
302 304 if c and ctags:
303 305 nrev = self.dest.puttags(ctags)
304 306 # write another hash correspondence to override the previous
305 307 # one so we don't end up with extra tag heads
306 308 if nrev:
307 309 self.map[c] = nrev
308 310
309 311 self.writeauthormap()
310 312 finally:
311 313 self.cleanup()
312 314
313 315 def cleanup(self):
314 316 try:
315 317 self.dest.after()
316 318 finally:
317 319 self.source.after()
318 320 self.map.close()
319 321
320 322 def convert(ui, src, dest=None, revmapfile=None, **opts):
321 323 global orig_encoding
322 324 orig_encoding = util._encoding
323 325 util._encoding = 'UTF-8'
324 326
325 327 if not dest:
326 328 dest = hg.defaultdest(src) + "-hg"
327 329 ui.status("assuming destination %s\n" % dest)
328 330
329 331 destc = convertsink(ui, dest, opts.get('dest_type'))
330 332
331 333 try:
332 334 srcc = convertsource(ui, src, opts.get('source_type'),
333 335 opts.get('rev'))
334 336 except Exception:
335 337 for path in destc.created:
336 338 shutil.rmtree(path, True)
337 339 raise
338 340
339 341 fmap = opts.get('filemap')
340 342 if fmap:
341 343 srcc = filemap.filemap_source(ui, srcc, fmap)
342 344 destc.setfilemapmode(True)
343 345
344 346 if not revmapfile:
345 347 try:
346 348 revmapfile = destc.revmapfile()
347 349 except:
348 350 revmapfile = os.path.join(destc, "map")
349 351
350 352 c = converter(ui, srcc, destc, revmapfile, opts)
351 353 c.convert()
352 354
General Comments 0
You need to be logged in to leave comments. Login now