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