##// END OF EJS Templates
convert: export revmap to source....
Brendan Cully -
r4812:a5209b04 default
parent child Browse files
Show More
@@ -1,348 +1,349 b''
1 # convert.py Foreign SCM converter
1 # convert.py Foreign SCM converter
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, converter_source, converter_sink
8 from common import NoRepo, converter_source, converter_sink
9 from cvs import convert_cvs
9 from cvs import convert_cvs
10 from git import convert_git
10 from git import convert_git
11 from hg import convert_mercurial
11 from hg import convert_mercurial
12 from subversion import convert_svn
12 from subversion import convert_svn
13
13
14 import os, shutil
14 import os, shutil
15 from mercurial import hg, ui, util, commands
15 from mercurial import hg, ui, util, commands
16
16
17 commands.norepo += " convert"
17 commands.norepo += " convert"
18
18
19 converters = [convert_cvs, convert_git, convert_svn, convert_mercurial]
19 converters = [convert_cvs, convert_git, convert_svn, convert_mercurial]
20
20
21 def convertsource(ui, path, **opts):
21 def convertsource(ui, path, **opts):
22 for c in converters:
22 for c in converters:
23 if not hasattr(c, 'getcommit'):
23 if not hasattr(c, 'getcommit'):
24 continue
24 continue
25 try:
25 try:
26 return c(ui, path, **opts)
26 return c(ui, path, **opts)
27 except NoRepo:
27 except NoRepo:
28 pass
28 pass
29 raise util.Abort('%s: unknown repository type' % path)
29 raise util.Abort('%s: unknown repository type' % path)
30
30
31 def convertsink(ui, path):
31 def convertsink(ui, path):
32 if not os.path.isdir(path):
32 if not os.path.isdir(path):
33 raise util.Abort("%s: not a directory" % path)
33 raise util.Abort("%s: not a directory" % path)
34 for c in converters:
34 for c in converters:
35 if not hasattr(c, 'putcommit'):
35 if not hasattr(c, 'putcommit'):
36 continue
36 continue
37 try:
37 try:
38 return c(ui, path)
38 return c(ui, path)
39 except NoRepo:
39 except NoRepo:
40 pass
40 pass
41 raise util.Abort('%s: unknown repository type' % path)
41 raise util.Abort('%s: unknown repository type' % path)
42
42
43 class convert(object):
43 class convert(object):
44 def __init__(self, ui, source, dest, mapfile, opts):
44 def __init__(self, ui, source, dest, mapfile, opts):
45
45
46 self.source = source
46 self.source = source
47 self.dest = dest
47 self.dest = dest
48 self.ui = ui
48 self.ui = ui
49 self.opts = opts
49 self.opts = opts
50 self.commitcache = {}
50 self.commitcache = {}
51 self.mapfile = mapfile
51 self.mapfile = mapfile
52 self.mapfilefd = None
52 self.mapfilefd = None
53 self.authors = {}
53 self.authors = {}
54 self.authorfile = None
54 self.authorfile = None
55
55
56 self.map = {}
56 self.map = {}
57 try:
57 try:
58 origmapfile = open(self.mapfile, 'r')
58 origmapfile = open(self.mapfile, 'r')
59 for l in origmapfile:
59 for l in origmapfile:
60 sv, dv = l[:-1].split()
60 sv, dv = l[:-1].split()
61 self.map[sv] = dv
61 self.map[sv] = dv
62 origmapfile.close()
62 origmapfile.close()
63 except IOError:
63 except IOError:
64 pass
64 pass
65
65
66 # Read first the dst author map if any
66 # Read first the dst author map if any
67 authorfile = self.dest.authorfile()
67 authorfile = self.dest.authorfile()
68 if authorfile and os.path.exists(authorfile):
68 if authorfile and os.path.exists(authorfile):
69 self.readauthormap(authorfile)
69 self.readauthormap(authorfile)
70 # Extend/Override with new author map if necessary
70 # Extend/Override with new author map if necessary
71 if opts.get('authors'):
71 if opts.get('authors'):
72 self.readauthormap(opts.get('authors'))
72 self.readauthormap(opts.get('authors'))
73 self.authorfile = self.dest.authorfile()
73 self.authorfile = self.dest.authorfile()
74
74
75 def walktree(self, heads):
75 def walktree(self, heads):
76 '''Return a mapping that identifies the uncommitted parents of every
76 '''Return a mapping that identifies the uncommitted parents of every
77 uncommitted changeset.'''
77 uncommitted changeset.'''
78 visit = heads
78 visit = heads
79 known = {}
79 known = {}
80 parents = {}
80 parents = {}
81 while visit:
81 while visit:
82 n = visit.pop(0)
82 n = visit.pop(0)
83 if n in known or n in self.map: continue
83 if n in known or n in self.map: continue
84 known[n] = 1
84 known[n] = 1
85 self.commitcache[n] = self.source.getcommit(n)
85 self.commitcache[n] = self.source.getcommit(n)
86 cp = self.commitcache[n].parents
86 cp = self.commitcache[n].parents
87 parents[n] = []
87 parents[n] = []
88 for p in cp:
88 for p in cp:
89 parents[n].append(p)
89 parents[n].append(p)
90 visit.append(p)
90 visit.append(p)
91
91
92 return parents
92 return parents
93
93
94 def toposort(self, parents):
94 def toposort(self, parents):
95 '''Return an ordering such that every uncommitted changeset is
95 '''Return an ordering such that every uncommitted changeset is
96 preceeded by all its uncommitted ancestors.'''
96 preceeded by all its uncommitted ancestors.'''
97 visit = parents.keys()
97 visit = parents.keys()
98 seen = {}
98 seen = {}
99 children = {}
99 children = {}
100
100
101 while visit:
101 while visit:
102 n = visit.pop(0)
102 n = visit.pop(0)
103 if n in seen: continue
103 if n in seen: continue
104 seen[n] = 1
104 seen[n] = 1
105 # Ensure that nodes without parents are present in the 'children'
105 # Ensure that nodes without parents are present in the 'children'
106 # mapping.
106 # mapping.
107 children.setdefault(n, [])
107 children.setdefault(n, [])
108 for p in parents[n]:
108 for p in parents[n]:
109 if not p in self.map:
109 if not p in self.map:
110 visit.append(p)
110 visit.append(p)
111 children.setdefault(p, []).append(n)
111 children.setdefault(p, []).append(n)
112
112
113 s = []
113 s = []
114 removed = {}
114 removed = {}
115 visit = children.keys()
115 visit = children.keys()
116 while visit:
116 while visit:
117 n = visit.pop(0)
117 n = visit.pop(0)
118 if n in removed: continue
118 if n in removed: continue
119 dep = 0
119 dep = 0
120 if n in parents:
120 if n in parents:
121 for p in parents[n]:
121 for p in parents[n]:
122 if p in self.map: continue
122 if p in self.map: continue
123 if p not in removed:
123 if p not in removed:
124 # we're still dependent
124 # we're still dependent
125 visit.append(n)
125 visit.append(n)
126 dep = 1
126 dep = 1
127 break
127 break
128
128
129 if not dep:
129 if not dep:
130 # all n's parents are in the list
130 # all n's parents are in the list
131 removed[n] = 1
131 removed[n] = 1
132 if n not in self.map:
132 if n not in self.map:
133 s.append(n)
133 s.append(n)
134 if n in children:
134 if n in children:
135 for c in children[n]:
135 for c in children[n]:
136 visit.insert(0, c)
136 visit.insert(0, c)
137
137
138 if self.opts.get('datesort'):
138 if self.opts.get('datesort'):
139 depth = {}
139 depth = {}
140 for n in s:
140 for n in s:
141 depth[n] = 0
141 depth[n] = 0
142 pl = [p for p in self.commitcache[n].parents
142 pl = [p for p in self.commitcache[n].parents
143 if p not in self.map]
143 if p not in self.map]
144 if pl:
144 if pl:
145 depth[n] = max([depth[p] for p in pl]) + 1
145 depth[n] = max([depth[p] for p in pl]) + 1
146
146
147 s = [(depth[n], self.commitcache[n].date, n) for n in s]
147 s = [(depth[n], self.commitcache[n].date, n) for n in s]
148 s.sort()
148 s.sort()
149 s = [e[2] for e in s]
149 s = [e[2] for e in s]
150
150
151 return s
151 return s
152
152
153 def mapentry(self, src, dst):
153 def mapentry(self, src, dst):
154 if self.mapfilefd is None:
154 if self.mapfilefd is None:
155 try:
155 try:
156 self.mapfilefd = open(self.mapfile, "a")
156 self.mapfilefd = open(self.mapfile, "a")
157 except IOError, (errno, strerror):
157 except IOError, (errno, strerror):
158 raise util.Abort("Could not open map file %s: %s, %s\n" % (self.mapfile, errno, strerror))
158 raise util.Abort("Could not open map file %s: %s, %s\n" % (self.mapfile, errno, strerror))
159 self.map[src] = dst
159 self.map[src] = dst
160 self.mapfilefd.write("%s %s\n" % (src, dst))
160 self.mapfilefd.write("%s %s\n" % (src, dst))
161 self.mapfilefd.flush()
161 self.mapfilefd.flush()
162
162
163 def writeauthormap(self):
163 def writeauthormap(self):
164 authorfile = self.authorfile
164 authorfile = self.authorfile
165 if authorfile:
165 if authorfile:
166 self.ui.status('Writing author map file %s\n' % authorfile)
166 self.ui.status('Writing author map file %s\n' % authorfile)
167 ofile = open(authorfile, 'w+')
167 ofile = open(authorfile, 'w+')
168 for author in self.authors:
168 for author in self.authors:
169 ofile.write("%s=%s\n" % (author, self.authors[author]))
169 ofile.write("%s=%s\n" % (author, self.authors[author]))
170 ofile.close()
170 ofile.close()
171
171
172 def readauthormap(self, authorfile):
172 def readauthormap(self, authorfile):
173 afile = open(authorfile, 'r')
173 afile = open(authorfile, 'r')
174 for line in afile:
174 for line in afile:
175 try:
175 try:
176 srcauthor = line.split('=')[0].strip()
176 srcauthor = line.split('=')[0].strip()
177 dstauthor = line.split('=')[1].strip()
177 dstauthor = line.split('=')[1].strip()
178 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
178 if srcauthor in self.authors and dstauthor != self.authors[srcauthor]:
179 self.ui.status(
179 self.ui.status(
180 'Overriding mapping for author %s, was %s, will be %s\n'
180 'Overriding mapping for author %s, was %s, will be %s\n'
181 % (srcauthor, self.authors[srcauthor], dstauthor))
181 % (srcauthor, self.authors[srcauthor], dstauthor))
182 else:
182 else:
183 self.ui.debug('Mapping author %s to %s\n'
183 self.ui.debug('Mapping author %s to %s\n'
184 % (srcauthor, dstauthor))
184 % (srcauthor, dstauthor))
185 self.authors[srcauthor] = dstauthor
185 self.authors[srcauthor] = dstauthor
186 except IndexError:
186 except IndexError:
187 self.ui.warn(
187 self.ui.warn(
188 'Ignoring bad line in author file map %s: %s\n'
188 'Ignoring bad line in author file map %s: %s\n'
189 % (authorfile, line))
189 % (authorfile, line))
190 afile.close()
190 afile.close()
191
191
192 def copy(self, rev):
192 def copy(self, rev):
193 c = self.commitcache[rev]
193 c = self.commitcache[rev]
194 files = self.source.getchanges(rev)
194 files = self.source.getchanges(rev)
195
195
196 do_copies = (hasattr(c, 'copies') and hasattr(self.dest, 'copyfile'))
196 do_copies = (hasattr(c, 'copies') and hasattr(self.dest, 'copyfile'))
197
197
198 for f, v in files:
198 for f, v in files:
199 try:
199 try:
200 data = self.source.getfile(f, v)
200 data = self.source.getfile(f, v)
201 except IOError, inst:
201 except IOError, inst:
202 self.dest.delfile(f)
202 self.dest.delfile(f)
203 else:
203 else:
204 e = self.source.getmode(f, v)
204 e = self.source.getmode(f, v)
205 self.dest.putfile(f, e, data)
205 self.dest.putfile(f, e, data)
206 if do_copies:
206 if do_copies:
207 if f in c.copies:
207 if f in c.copies:
208 # Merely marks that a copy happened.
208 # Merely marks that a copy happened.
209 self.dest.copyfile(c.copies[f], f)
209 self.dest.copyfile(c.copies[f], f)
210
210
211
211
212 r = [self.map[v] for v in c.parents]
212 r = [self.map[v] for v in c.parents]
213 f = [f for f, v in files]
213 f = [f for f, v in files]
214 newnode = self.dest.putcommit(f, r, c)
214 newnode = self.dest.putcommit(f, r, c)
215 self.mapentry(rev, newnode)
215 self.mapentry(rev, newnode)
216
216
217 def convert(self):
217 def convert(self):
218 try:
218 try:
219 self.source.setrevmap(self.map)
219 self.ui.status("scanning source...\n")
220 self.ui.status("scanning source...\n")
220 heads = self.source.getheads()
221 heads = self.source.getheads()
221 parents = self.walktree(heads)
222 parents = self.walktree(heads)
222 self.ui.status("sorting...\n")
223 self.ui.status("sorting...\n")
223 t = self.toposort(parents)
224 t = self.toposort(parents)
224 num = len(t)
225 num = len(t)
225 c = None
226 c = None
226
227
227 self.ui.status("converting...\n")
228 self.ui.status("converting...\n")
228 for c in t:
229 for c in t:
229 num -= 1
230 num -= 1
230 desc = self.commitcache[c].desc
231 desc = self.commitcache[c].desc
231 if "\n" in desc:
232 if "\n" in desc:
232 desc = desc.splitlines()[0]
233 desc = desc.splitlines()[0]
233 author = self.commitcache[c].author
234 author = self.commitcache[c].author
234 author = self.authors.get(author, author)
235 author = self.authors.get(author, author)
235 self.commitcache[c].author = author
236 self.commitcache[c].author = author
236 self.ui.status("%d %s\n" % (num, desc))
237 self.ui.status("%d %s\n" % (num, desc))
237 self.copy(c)
238 self.copy(c)
238
239
239 tags = self.source.gettags()
240 tags = self.source.gettags()
240 ctags = {}
241 ctags = {}
241 for k in tags:
242 for k in tags:
242 v = tags[k]
243 v = tags[k]
243 if v in self.map:
244 if v in self.map:
244 ctags[k] = self.map[v]
245 ctags[k] = self.map[v]
245
246
246 if c and ctags:
247 if c and ctags:
247 nrev = self.dest.puttags(ctags)
248 nrev = self.dest.puttags(ctags)
248 # write another hash correspondence to override the previous
249 # write another hash correspondence to override the previous
249 # one so we don't end up with extra tag heads
250 # one so we don't end up with extra tag heads
250 if nrev:
251 if nrev:
251 self.mapentry(c, nrev)
252 self.mapentry(c, nrev)
252
253
253 self.writeauthormap()
254 self.writeauthormap()
254 finally:
255 finally:
255 self.cleanup()
256 self.cleanup()
256
257
257 def cleanup(self):
258 def cleanup(self):
258 if self.mapfilefd:
259 if self.mapfilefd:
259 self.mapfilefd.close()
260 self.mapfilefd.close()
260
261
261 def _convert(ui, src, dest=None, mapfile=None, **opts):
262 def _convert(ui, src, dest=None, mapfile=None, **opts):
262 '''Convert a foreign SCM repository to a Mercurial one.
263 '''Convert a foreign SCM repository to a Mercurial one.
263
264
264 Accepted source formats:
265 Accepted source formats:
265 - GIT
266 - GIT
266 - CVS
267 - CVS
267 - SVN
268 - SVN
268
269
269 Accepted destination formats:
270 Accepted destination formats:
270 - Mercurial
271 - Mercurial
271
272
272 If no revision is given, all revisions will be converted. Otherwise,
273 If no revision is given, all revisions will be converted. Otherwise,
273 convert will only import up to the named revision (given in a format
274 convert will only import up to the named revision (given in a format
274 understood by the source).
275 understood by the source).
275
276
276 If destination isn't given, a new Mercurial repo named <src>-hg will
277 If destination isn't given, a new Mercurial repo named <src>-hg will
277 be created. If <mapfile> isn't given, it will be put in a default
278 be created. If <mapfile> isn't given, it will be put in a default
278 location (<dest>/.hg/shamap by default)
279 location (<dest>/.hg/shamap by default)
279
280
280 The <mapfile> is a simple text file that maps each source commit ID to
281 The <mapfile> is a simple text file that maps each source commit ID to
281 the destination ID for that revision, like so:
282 the destination ID for that revision, like so:
282 <source ID> <destination ID>
283 <source ID> <destination ID>
283
284
284 If the file doesn't exist, it's automatically created. It's updated
285 If the file doesn't exist, it's automatically created. It's updated
285 on each commit copied, so convert-repo can be interrupted and can
286 on each commit copied, so convert-repo can be interrupted and can
286 be run repeatedly to copy new commits.
287 be run repeatedly to copy new commits.
287
288
288 The [username mapping] file is a simple text file that maps each source
289 The [username mapping] file is a simple text file that maps each source
289 commit author to a destination commit author. It is handy for source SCMs
290 commit author to a destination commit author. It is handy for source SCMs
290 that use unix logins to identify authors (eg: CVS). One line per author
291 that use unix logins to identify authors (eg: CVS). One line per author
291 mapping and the line format is:
292 mapping and the line format is:
292 srcauthor=whatever string you want
293 srcauthor=whatever string you want
293 '''
294 '''
294
295
295 if not dest:
296 if not dest:
296 dest = src + "-hg"
297 dest = src + "-hg"
297 ui.status("assuming destination %s\n" % dest)
298 ui.status("assuming destination %s\n" % dest)
298
299
299 # Try to be smart and initalize things when required
300 # Try to be smart and initalize things when required
300 created = False
301 created = False
301 if os.path.isdir(dest):
302 if os.path.isdir(dest):
302 if len(os.listdir(dest)) > 0:
303 if len(os.listdir(dest)) > 0:
303 try:
304 try:
304 hg.repository(ui, dest)
305 hg.repository(ui, dest)
305 ui.status("destination %s is a Mercurial repository\n" % dest)
306 ui.status("destination %s is a Mercurial repository\n" % dest)
306 except hg.RepoError:
307 except hg.RepoError:
307 raise util.Abort(
308 raise util.Abort(
308 "destination directory %s is not empty.\n"
309 "destination directory %s is not empty.\n"
309 "Please specify an empty directory to be initialized\n"
310 "Please specify an empty directory to be initialized\n"
310 "or an already initialized mercurial repository"
311 "or an already initialized mercurial repository"
311 % dest)
312 % dest)
312 else:
313 else:
313 ui.status("initializing destination %s repository\n" % dest)
314 ui.status("initializing destination %s repository\n" % dest)
314 hg.repository(ui, dest, create=True)
315 hg.repository(ui, dest, create=True)
315 created = True
316 created = True
316 elif os.path.exists(dest):
317 elif os.path.exists(dest):
317 raise util.Abort("destination %s exists and is not a directory" % dest)
318 raise util.Abort("destination %s exists and is not a directory" % dest)
318 else:
319 else:
319 ui.status("initializing destination %s repository\n" % dest)
320 ui.status("initializing destination %s repository\n" % dest)
320 hg.repository(ui, dest, create=True)
321 hg.repository(ui, dest, create=True)
321 created = True
322 created = True
322
323
323 destc = convertsink(ui, dest)
324 destc = convertsink(ui, dest)
324
325
325 try:
326 try:
326 srcc = convertsource(ui, src, rev=opts.get('rev'))
327 srcc = convertsource(ui, src, rev=opts.get('rev'))
327 except Exception:
328 except Exception:
328 if created:
329 if created:
329 shutil.rmtree(dest, True)
330 shutil.rmtree(dest, True)
330 raise
331 raise
331
332
332 if not mapfile:
333 if not mapfile:
333 try:
334 try:
334 mapfile = destc.mapfile()
335 mapfile = destc.mapfile()
335 except:
336 except:
336 mapfile = os.path.join(destc, "map")
337 mapfile = os.path.join(destc, "map")
337
338
338 c = convert(ui, srcc, destc, mapfile, opts)
339 c = convert(ui, srcc, destc, mapfile, opts)
339 c.convert()
340 c.convert()
340
341
341 cmdtable = {
342 cmdtable = {
342 "convert":
343 "convert":
343 (_convert,
344 (_convert,
344 [('A', 'authors', '', 'username mapping filename'),
345 [('A', 'authors', '', 'username mapping filename'),
345 ('r', 'rev', '', 'import up to target revision REV'),
346 ('r', 'rev', '', 'import up to target revision REV'),
346 ('', 'datesort', None, 'try to sort changesets by date')],
347 ('', 'datesort', None, 'try to sort changesets by date')],
347 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
348 'hg convert [OPTION]... SOURCE [DEST [MAPFILE]]'),
348 }
349 }
@@ -1,114 +1,119 b''
1 # common code for the convert extension
1 # common code for the convert extension
2
2
3 class NoRepo(Exception): pass
3 class NoRepo(Exception): pass
4
4
5 class commit(object):
5 class commit(object):
6 def __init__(self, **parts):
6 def __init__(self, **parts):
7 for x in "author date desc parents".split():
7 for x in "author date desc parents".split():
8 if not x in parts:
8 if not x in parts:
9 raise util.Abort("commit missing field %s" % x)
9 raise util.Abort("commit missing field %s" % x)
10 self.__dict__.update(parts)
10 self.__dict__.update(parts)
11 if not self.desc or self.desc.isspace():
11 if not self.desc or self.desc.isspace():
12 self.desc = '*** empty log message ***'
12 self.desc = '*** empty log message ***'
13
13
14 class converter_source(object):
14 class converter_source(object):
15 """Conversion source interface"""
15 """Conversion source interface"""
16
16
17 def __init__(self, ui, path, rev=None):
17 def __init__(self, ui, path, rev=None):
18 """Initialize conversion source (or raise NoRepo("message")
18 """Initialize conversion source (or raise NoRepo("message")
19 exception if path is not a valid repository)"""
19 exception if path is not a valid repository)"""
20 self.ui = ui
20 self.ui = ui
21 self.path = path
21 self.path = path
22 self.rev = rev
22 self.rev = rev
23
23
24 self.encoding = 'utf-8'
24 self.encoding = 'utf-8'
25 self.revmap = {}
26
27 def setrevmap(self, revmap):
28 """set the map of already-converted revisions"""
29 self.revmap = revmap
25
30
26 def getheads(self):
31 def getheads(self):
27 """Return a list of this repository's heads"""
32 """Return a list of this repository's heads"""
28 raise NotImplementedError()
33 raise NotImplementedError()
29
34
30 def getfile(self, name, rev):
35 def getfile(self, name, rev):
31 """Return file contents as a string"""
36 """Return file contents as a string"""
32 raise NotImplementedError()
37 raise NotImplementedError()
33
38
34 def getmode(self, name, rev):
39 def getmode(self, name, rev):
35 """Return file mode, eg. '', 'x', or 'l'"""
40 """Return file mode, eg. '', 'x', or 'l'"""
36 raise NotImplementedError()
41 raise NotImplementedError()
37
42
38 def getchanges(self, version):
43 def getchanges(self, version):
39 """Return sorted list of (filename, id) tuples for all files changed in rev.
44 """Return sorted list of (filename, id) tuples for all files changed in rev.
40
45
41 id just tells us which revision to return in getfile(), e.g. in
46 id just tells us which revision to return in getfile(), e.g. in
42 git it's an object hash."""
47 git it's an object hash."""
43 raise NotImplementedError()
48 raise NotImplementedError()
44
49
45 def getcommit(self, version):
50 def getcommit(self, version):
46 """Return the commit object for version"""
51 """Return the commit object for version"""
47 raise NotImplementedError()
52 raise NotImplementedError()
48
53
49 def gettags(self):
54 def gettags(self):
50 """Return the tags as a dictionary of name: revision"""
55 """Return the tags as a dictionary of name: revision"""
51 raise NotImplementedError()
56 raise NotImplementedError()
52
57
53 def recode(self, s, encoding=None):
58 def recode(self, s, encoding=None):
54 if not encoding:
59 if not encoding:
55 encoding = self.encoding or 'utf-8'
60 encoding = self.encoding or 'utf-8'
56
61
57 try:
62 try:
58 return s.decode(encoding).encode("utf-8")
63 return s.decode(encoding).encode("utf-8")
59 except:
64 except:
60 try:
65 try:
61 return s.decode("latin-1").encode("utf-8")
66 return s.decode("latin-1").encode("utf-8")
62 except:
67 except:
63 return s.decode(encoding, "replace").encode("utf-8")
68 return s.decode(encoding, "replace").encode("utf-8")
64
69
65 class converter_sink(object):
70 class converter_sink(object):
66 """Conversion sink (target) interface"""
71 """Conversion sink (target) interface"""
67
72
68 def __init__(self, ui, path):
73 def __init__(self, ui, path):
69 """Initialize conversion sink (or raise NoRepo("message")
74 """Initialize conversion sink (or raise NoRepo("message")
70 exception if path is not a valid repository)"""
75 exception if path is not a valid repository)"""
71 raise NotImplementedError()
76 raise NotImplementedError()
72
77
73 def getheads(self):
78 def getheads(self):
74 """Return a list of this repository's heads"""
79 """Return a list of this repository's heads"""
75 raise NotImplementedError()
80 raise NotImplementedError()
76
81
77 def mapfile(self):
82 def mapfile(self):
78 """Path to a file that will contain lines
83 """Path to a file that will contain lines
79 source_rev_id sink_rev_id
84 source_rev_id sink_rev_id
80 mapping equivalent revision identifiers for each system."""
85 mapping equivalent revision identifiers for each system."""
81 raise NotImplementedError()
86 raise NotImplementedError()
82
87
83 def authorfile(self):
88 def authorfile(self):
84 """Path to a file that will contain lines
89 """Path to a file that will contain lines
85 srcauthor=dstauthor
90 srcauthor=dstauthor
86 mapping equivalent authors identifiers for each system."""
91 mapping equivalent authors identifiers for each system."""
87 return None
92 return None
88
93
89 def putfile(self, f, e, data):
94 def putfile(self, f, e, data):
90 """Put file for next putcommit().
95 """Put file for next putcommit().
91 f: path to file
96 f: path to file
92 e: '', 'x', or 'l' (regular file, executable, or symlink)
97 e: '', 'x', or 'l' (regular file, executable, or symlink)
93 data: file contents"""
98 data: file contents"""
94 raise NotImplementedError()
99 raise NotImplementedError()
95
100
96 def delfile(self, f):
101 def delfile(self, f):
97 """Delete file for next putcommit().
102 """Delete file for next putcommit().
98 f: path to file"""
103 f: path to file"""
99 raise NotImplementedError()
104 raise NotImplementedError()
100
105
101 def putcommit(self, files, parents, commit):
106 def putcommit(self, files, parents, commit):
102 """Create a revision with all changed files listed in 'files'
107 """Create a revision with all changed files listed in 'files'
103 and having listed parents. 'commit' is a commit object containing
108 and having listed parents. 'commit' is a commit object containing
104 at a minimum the author, date, and message for this changeset.
109 at a minimum the author, date, and message for this changeset.
105 Called after putfile() and delfile() calls. Note that the sink
110 Called after putfile() and delfile() calls. Note that the sink
106 repository is not told to update itself to a particular revision
111 repository is not told to update itself to a particular revision
107 (or even what that revision would be) before it receives the
112 (or even what that revision would be) before it receives the
108 file data."""
113 file data."""
109 raise NotImplementedError()
114 raise NotImplementedError()
110
115
111 def puttags(self, tags):
116 def puttags(self, tags):
112 """Put tags into sink.
117 """Put tags into sink.
113 tags: {tagname: sink_rev_id, ...}"""
118 tags: {tagname: sink_rev_id, ...}"""
114 raise NotImplementedError()
119 raise NotImplementedError()
General Comments 0
You need to be logged in to leave comments. Login now