##// END OF EJS Templates
convert: migrate to util.iterfile
Jun Wu -
r30400:d1a0a64f default
parent child Browse files
Show More
@@ -1,494 +1,494 b''
1 # common.py - common code for the convert extension
1 # common.py - common code for the convert extension
2 #
2 #
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import base64
9 import base64
10 import datetime
10 import datetime
11 import errno
11 import errno
12 import os
12 import os
13 import re
13 import re
14 import subprocess
14 import subprocess
15
15
16 from mercurial.i18n import _
16 from mercurial.i18n import _
17 from mercurial import (
17 from mercurial import (
18 error,
18 error,
19 phases,
19 phases,
20 util,
20 util,
21 )
21 )
22
22
23 pickle = util.pickle
23 pickle = util.pickle
24 propertycache = util.propertycache
24 propertycache = util.propertycache
25
25
26 def encodeargs(args):
26 def encodeargs(args):
27 def encodearg(s):
27 def encodearg(s):
28 lines = base64.encodestring(s)
28 lines = base64.encodestring(s)
29 lines = [l.splitlines()[0] for l in lines]
29 lines = [l.splitlines()[0] for l in lines]
30 return ''.join(lines)
30 return ''.join(lines)
31
31
32 s = pickle.dumps(args)
32 s = pickle.dumps(args)
33 return encodearg(s)
33 return encodearg(s)
34
34
35 def decodeargs(s):
35 def decodeargs(s):
36 s = base64.decodestring(s)
36 s = base64.decodestring(s)
37 return pickle.loads(s)
37 return pickle.loads(s)
38
38
39 class MissingTool(Exception):
39 class MissingTool(Exception):
40 pass
40 pass
41
41
42 def checktool(exe, name=None, abort=True):
42 def checktool(exe, name=None, abort=True):
43 name = name or exe
43 name = name or exe
44 if not util.findexe(exe):
44 if not util.findexe(exe):
45 if abort:
45 if abort:
46 exc = error.Abort
46 exc = error.Abort
47 else:
47 else:
48 exc = MissingTool
48 exc = MissingTool
49 raise exc(_('cannot find required "%s" tool') % name)
49 raise exc(_('cannot find required "%s" tool') % name)
50
50
51 class NoRepo(Exception):
51 class NoRepo(Exception):
52 pass
52 pass
53
53
54 SKIPREV = 'SKIP'
54 SKIPREV = 'SKIP'
55
55
56 class commit(object):
56 class commit(object):
57 def __init__(self, author, date, desc, parents, branch=None, rev=None,
57 def __init__(self, author, date, desc, parents, branch=None, rev=None,
58 extra={}, sortkey=None, saverev=True, phase=phases.draft,
58 extra={}, sortkey=None, saverev=True, phase=phases.draft,
59 optparents=None):
59 optparents=None):
60 self.author = author or 'unknown'
60 self.author = author or 'unknown'
61 self.date = date or '0 0'
61 self.date = date or '0 0'
62 self.desc = desc
62 self.desc = desc
63 self.parents = parents # will be converted and used as parents
63 self.parents = parents # will be converted and used as parents
64 self.optparents = optparents or [] # will be used if already converted
64 self.optparents = optparents or [] # will be used if already converted
65 self.branch = branch
65 self.branch = branch
66 self.rev = rev
66 self.rev = rev
67 self.extra = extra
67 self.extra = extra
68 self.sortkey = sortkey
68 self.sortkey = sortkey
69 self.saverev = saverev
69 self.saverev = saverev
70 self.phase = phase
70 self.phase = phase
71
71
72 class converter_source(object):
72 class converter_source(object):
73 """Conversion source interface"""
73 """Conversion source interface"""
74
74
75 def __init__(self, ui, path=None, revs=None):
75 def __init__(self, ui, path=None, revs=None):
76 """Initialize conversion source (or raise NoRepo("message")
76 """Initialize conversion source (or raise NoRepo("message")
77 exception if path is not a valid repository)"""
77 exception if path is not a valid repository)"""
78 self.ui = ui
78 self.ui = ui
79 self.path = path
79 self.path = path
80 self.revs = revs
80 self.revs = revs
81
81
82 self.encoding = 'utf-8'
82 self.encoding = 'utf-8'
83
83
84 def checkhexformat(self, revstr, mapname='splicemap'):
84 def checkhexformat(self, revstr, mapname='splicemap'):
85 """ fails if revstr is not a 40 byte hex. mercurial and git both uses
85 """ fails if revstr is not a 40 byte hex. mercurial and git both uses
86 such format for their revision numbering
86 such format for their revision numbering
87 """
87 """
88 if not re.match(r'[0-9a-fA-F]{40,40}$', revstr):
88 if not re.match(r'[0-9a-fA-F]{40,40}$', revstr):
89 raise error.Abort(_('%s entry %s is not a valid revision'
89 raise error.Abort(_('%s entry %s is not a valid revision'
90 ' identifier') % (mapname, revstr))
90 ' identifier') % (mapname, revstr))
91
91
92 def before(self):
92 def before(self):
93 pass
93 pass
94
94
95 def after(self):
95 def after(self):
96 pass
96 pass
97
97
98 def targetfilebelongstosource(self, targetfilename):
98 def targetfilebelongstosource(self, targetfilename):
99 """Returns true if the given targetfile belongs to the source repo. This
99 """Returns true if the given targetfile belongs to the source repo. This
100 is useful when only a subdirectory of the target belongs to the source
100 is useful when only a subdirectory of the target belongs to the source
101 repo."""
101 repo."""
102 # For normal full repo converts, this is always True.
102 # For normal full repo converts, this is always True.
103 return True
103 return True
104
104
105 def setrevmap(self, revmap):
105 def setrevmap(self, revmap):
106 """set the map of already-converted revisions"""
106 """set the map of already-converted revisions"""
107 pass
107 pass
108
108
109 def getheads(self):
109 def getheads(self):
110 """Return a list of this repository's heads"""
110 """Return a list of this repository's heads"""
111 raise NotImplementedError
111 raise NotImplementedError
112
112
113 def getfile(self, name, rev):
113 def getfile(self, name, rev):
114 """Return a pair (data, mode) where data is the file content
114 """Return a pair (data, mode) where data is the file content
115 as a string and mode one of '', 'x' or 'l'. rev is the
115 as a string and mode one of '', 'x' or 'l'. rev is the
116 identifier returned by a previous call to getchanges().
116 identifier returned by a previous call to getchanges().
117 Data is None if file is missing/deleted in rev.
117 Data is None if file is missing/deleted in rev.
118 """
118 """
119 raise NotImplementedError
119 raise NotImplementedError
120
120
121 def getchanges(self, version, full):
121 def getchanges(self, version, full):
122 """Returns a tuple of (files, copies, cleanp2).
122 """Returns a tuple of (files, copies, cleanp2).
123
123
124 files is a sorted list of (filename, id) tuples for all files
124 files is a sorted list of (filename, id) tuples for all files
125 changed between version and its first parent returned by
125 changed between version and its first parent returned by
126 getcommit(). If full, all files in that revision is returned.
126 getcommit(). If full, all files in that revision is returned.
127 id is the source revision id of the file.
127 id is the source revision id of the file.
128
128
129 copies is a dictionary of dest: source
129 copies is a dictionary of dest: source
130
130
131 cleanp2 is the set of files filenames that are clean against p2.
131 cleanp2 is the set of files filenames that are clean against p2.
132 (Files that are clean against p1 are already not in files (unless
132 (Files that are clean against p1 are already not in files (unless
133 full). This makes it possible to handle p2 clean files similarly.)
133 full). This makes it possible to handle p2 clean files similarly.)
134 """
134 """
135 raise NotImplementedError
135 raise NotImplementedError
136
136
137 def getcommit(self, version):
137 def getcommit(self, version):
138 """Return the commit object for version"""
138 """Return the commit object for version"""
139 raise NotImplementedError
139 raise NotImplementedError
140
140
141 def numcommits(self):
141 def numcommits(self):
142 """Return the number of commits in this source.
142 """Return the number of commits in this source.
143
143
144 If unknown, return None.
144 If unknown, return None.
145 """
145 """
146 return None
146 return None
147
147
148 def gettags(self):
148 def gettags(self):
149 """Return the tags as a dictionary of name: revision
149 """Return the tags as a dictionary of name: revision
150
150
151 Tag names must be UTF-8 strings.
151 Tag names must be UTF-8 strings.
152 """
152 """
153 raise NotImplementedError
153 raise NotImplementedError
154
154
155 def recode(self, s, encoding=None):
155 def recode(self, s, encoding=None):
156 if not encoding:
156 if not encoding:
157 encoding = self.encoding or 'utf-8'
157 encoding = self.encoding or 'utf-8'
158
158
159 if isinstance(s, unicode):
159 if isinstance(s, unicode):
160 return s.encode("utf-8")
160 return s.encode("utf-8")
161 try:
161 try:
162 return s.decode(encoding).encode("utf-8")
162 return s.decode(encoding).encode("utf-8")
163 except UnicodeError:
163 except UnicodeError:
164 try:
164 try:
165 return s.decode("latin-1").encode("utf-8")
165 return s.decode("latin-1").encode("utf-8")
166 except UnicodeError:
166 except UnicodeError:
167 return s.decode(encoding, "replace").encode("utf-8")
167 return s.decode(encoding, "replace").encode("utf-8")
168
168
169 def getchangedfiles(self, rev, i):
169 def getchangedfiles(self, rev, i):
170 """Return the files changed by rev compared to parent[i].
170 """Return the files changed by rev compared to parent[i].
171
171
172 i is an index selecting one of the parents of rev. The return
172 i is an index selecting one of the parents of rev. The return
173 value should be the list of files that are different in rev and
173 value should be the list of files that are different in rev and
174 this parent.
174 this parent.
175
175
176 If rev has no parents, i is None.
176 If rev has no parents, i is None.
177
177
178 This function is only needed to support --filemap
178 This function is only needed to support --filemap
179 """
179 """
180 raise NotImplementedError
180 raise NotImplementedError
181
181
182 def converted(self, rev, sinkrev):
182 def converted(self, rev, sinkrev):
183 '''Notify the source that a revision has been converted.'''
183 '''Notify the source that a revision has been converted.'''
184 pass
184 pass
185
185
186 def hasnativeorder(self):
186 def hasnativeorder(self):
187 """Return true if this source has a meaningful, native revision
187 """Return true if this source has a meaningful, native revision
188 order. For instance, Mercurial revisions are store sequentially
188 order. For instance, Mercurial revisions are store sequentially
189 while there is no such global ordering with Darcs.
189 while there is no such global ordering with Darcs.
190 """
190 """
191 return False
191 return False
192
192
193 def hasnativeclose(self):
193 def hasnativeclose(self):
194 """Return true if this source has ability to close branch.
194 """Return true if this source has ability to close branch.
195 """
195 """
196 return False
196 return False
197
197
198 def lookuprev(self, rev):
198 def lookuprev(self, rev):
199 """If rev is a meaningful revision reference in source, return
199 """If rev is a meaningful revision reference in source, return
200 the referenced identifier in the same format used by getcommit().
200 the referenced identifier in the same format used by getcommit().
201 return None otherwise.
201 return None otherwise.
202 """
202 """
203 return None
203 return None
204
204
205 def getbookmarks(self):
205 def getbookmarks(self):
206 """Return the bookmarks as a dictionary of name: revision
206 """Return the bookmarks as a dictionary of name: revision
207
207
208 Bookmark names are to be UTF-8 strings.
208 Bookmark names are to be UTF-8 strings.
209 """
209 """
210 return {}
210 return {}
211
211
212 def checkrevformat(self, revstr, mapname='splicemap'):
212 def checkrevformat(self, revstr, mapname='splicemap'):
213 """revstr is a string that describes a revision in the given
213 """revstr is a string that describes a revision in the given
214 source control system. Return true if revstr has correct
214 source control system. Return true if revstr has correct
215 format.
215 format.
216 """
216 """
217 return True
217 return True
218
218
219 class converter_sink(object):
219 class converter_sink(object):
220 """Conversion sink (target) interface"""
220 """Conversion sink (target) interface"""
221
221
222 def __init__(self, ui, path):
222 def __init__(self, ui, path):
223 """Initialize conversion sink (or raise NoRepo("message")
223 """Initialize conversion sink (or raise NoRepo("message")
224 exception if path is not a valid repository)
224 exception if path is not a valid repository)
225
225
226 created is a list of paths to remove if a fatal error occurs
226 created is a list of paths to remove if a fatal error occurs
227 later"""
227 later"""
228 self.ui = ui
228 self.ui = ui
229 self.path = path
229 self.path = path
230 self.created = []
230 self.created = []
231
231
232 def revmapfile(self):
232 def revmapfile(self):
233 """Path to a file that will contain lines
233 """Path to a file that will contain lines
234 source_rev_id sink_rev_id
234 source_rev_id sink_rev_id
235 mapping equivalent revision identifiers for each system."""
235 mapping equivalent revision identifiers for each system."""
236 raise NotImplementedError
236 raise NotImplementedError
237
237
238 def authorfile(self):
238 def authorfile(self):
239 """Path to a file that will contain lines
239 """Path to a file that will contain lines
240 srcauthor=dstauthor
240 srcauthor=dstauthor
241 mapping equivalent authors identifiers for each system."""
241 mapping equivalent authors identifiers for each system."""
242 return None
242 return None
243
243
244 def putcommit(self, files, copies, parents, commit, source, revmap, full,
244 def putcommit(self, files, copies, parents, commit, source, revmap, full,
245 cleanp2):
245 cleanp2):
246 """Create a revision with all changed files listed in 'files'
246 """Create a revision with all changed files listed in 'files'
247 and having listed parents. 'commit' is a commit object
247 and having listed parents. 'commit' is a commit object
248 containing at a minimum the author, date, and message for this
248 containing at a minimum the author, date, and message for this
249 changeset. 'files' is a list of (path, version) tuples,
249 changeset. 'files' is a list of (path, version) tuples,
250 'copies' is a dictionary mapping destinations to sources,
250 'copies' is a dictionary mapping destinations to sources,
251 'source' is the source repository, and 'revmap' is a mapfile
251 'source' is the source repository, and 'revmap' is a mapfile
252 of source revisions to converted revisions. Only getfile() and
252 of source revisions to converted revisions. Only getfile() and
253 lookuprev() should be called on 'source'. 'full' means that 'files'
253 lookuprev() should be called on 'source'. 'full' means that 'files'
254 is complete and all other files should be removed.
254 is complete and all other files should be removed.
255 'cleanp2' is a set of the filenames that are unchanged from p2
255 'cleanp2' is a set of the filenames that are unchanged from p2
256 (only in the common merge case where there two parents).
256 (only in the common merge case where there two parents).
257
257
258 Note that the sink repository is not told to update itself to
258 Note that the sink repository is not told to update itself to
259 a particular revision (or even what that revision would be)
259 a particular revision (or even what that revision would be)
260 before it receives the file data.
260 before it receives the file data.
261 """
261 """
262 raise NotImplementedError
262 raise NotImplementedError
263
263
264 def puttags(self, tags):
264 def puttags(self, tags):
265 """Put tags into sink.
265 """Put tags into sink.
266
266
267 tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
267 tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
268 Return a pair (tag_revision, tag_parent_revision), or (None, None)
268 Return a pair (tag_revision, tag_parent_revision), or (None, None)
269 if nothing was changed.
269 if nothing was changed.
270 """
270 """
271 raise NotImplementedError
271 raise NotImplementedError
272
272
273 def setbranch(self, branch, pbranches):
273 def setbranch(self, branch, pbranches):
274 """Set the current branch name. Called before the first putcommit
274 """Set the current branch name. Called before the first putcommit
275 on the branch.
275 on the branch.
276 branch: branch name for subsequent commits
276 branch: branch name for subsequent commits
277 pbranches: (converted parent revision, parent branch) tuples"""
277 pbranches: (converted parent revision, parent branch) tuples"""
278 pass
278 pass
279
279
280 def setfilemapmode(self, active):
280 def setfilemapmode(self, active):
281 """Tell the destination that we're using a filemap
281 """Tell the destination that we're using a filemap
282
282
283 Some converter_sources (svn in particular) can claim that a file
283 Some converter_sources (svn in particular) can claim that a file
284 was changed in a revision, even if there was no change. This method
284 was changed in a revision, even if there was no change. This method
285 tells the destination that we're using a filemap and that it should
285 tells the destination that we're using a filemap and that it should
286 filter empty revisions.
286 filter empty revisions.
287 """
287 """
288 pass
288 pass
289
289
290 def before(self):
290 def before(self):
291 pass
291 pass
292
292
293 def after(self):
293 def after(self):
294 pass
294 pass
295
295
296 def putbookmarks(self, bookmarks):
296 def putbookmarks(self, bookmarks):
297 """Put bookmarks into sink.
297 """Put bookmarks into sink.
298
298
299 bookmarks: {bookmarkname: sink_rev_id, ...}
299 bookmarks: {bookmarkname: sink_rev_id, ...}
300 where bookmarkname is an UTF-8 string.
300 where bookmarkname is an UTF-8 string.
301 """
301 """
302 pass
302 pass
303
303
304 def hascommitfrommap(self, rev):
304 def hascommitfrommap(self, rev):
305 """Return False if a rev mentioned in a filemap is known to not be
305 """Return False if a rev mentioned in a filemap is known to not be
306 present."""
306 present."""
307 raise NotImplementedError
307 raise NotImplementedError
308
308
309 def hascommitforsplicemap(self, rev):
309 def hascommitforsplicemap(self, rev):
310 """This method is for the special needs for splicemap handling and not
310 """This method is for the special needs for splicemap handling and not
311 for general use. Returns True if the sink contains rev, aborts on some
311 for general use. Returns True if the sink contains rev, aborts on some
312 special cases."""
312 special cases."""
313 raise NotImplementedError
313 raise NotImplementedError
314
314
315 class commandline(object):
315 class commandline(object):
316 def __init__(self, ui, command):
316 def __init__(self, ui, command):
317 self.ui = ui
317 self.ui = ui
318 self.command = command
318 self.command = command
319
319
320 def prerun(self):
320 def prerun(self):
321 pass
321 pass
322
322
323 def postrun(self):
323 def postrun(self):
324 pass
324 pass
325
325
326 def _cmdline(self, cmd, *args, **kwargs):
326 def _cmdline(self, cmd, *args, **kwargs):
327 cmdline = [self.command, cmd] + list(args)
327 cmdline = [self.command, cmd] + list(args)
328 for k, v in kwargs.iteritems():
328 for k, v in kwargs.iteritems():
329 if len(k) == 1:
329 if len(k) == 1:
330 cmdline.append('-' + k)
330 cmdline.append('-' + k)
331 else:
331 else:
332 cmdline.append('--' + k.replace('_', '-'))
332 cmdline.append('--' + k.replace('_', '-'))
333 try:
333 try:
334 if len(k) == 1:
334 if len(k) == 1:
335 cmdline.append('' + v)
335 cmdline.append('' + v)
336 else:
336 else:
337 cmdline[-1] += '=' + v
337 cmdline[-1] += '=' + v
338 except TypeError:
338 except TypeError:
339 pass
339 pass
340 cmdline = [util.shellquote(arg) for arg in cmdline]
340 cmdline = [util.shellquote(arg) for arg in cmdline]
341 if not self.ui.debugflag:
341 if not self.ui.debugflag:
342 cmdline += ['2>', os.devnull]
342 cmdline += ['2>', os.devnull]
343 cmdline = ' '.join(cmdline)
343 cmdline = ' '.join(cmdline)
344 return cmdline
344 return cmdline
345
345
346 def _run(self, cmd, *args, **kwargs):
346 def _run(self, cmd, *args, **kwargs):
347 def popen(cmdline):
347 def popen(cmdline):
348 p = subprocess.Popen(cmdline, shell=True, bufsize=-1,
348 p = subprocess.Popen(cmdline, shell=True, bufsize=-1,
349 close_fds=util.closefds,
349 close_fds=util.closefds,
350 stdout=subprocess.PIPE)
350 stdout=subprocess.PIPE)
351 return p
351 return p
352 return self._dorun(popen, cmd, *args, **kwargs)
352 return self._dorun(popen, cmd, *args, **kwargs)
353
353
354 def _run2(self, cmd, *args, **kwargs):
354 def _run2(self, cmd, *args, **kwargs):
355 return self._dorun(util.popen2, cmd, *args, **kwargs)
355 return self._dorun(util.popen2, cmd, *args, **kwargs)
356
356
357 def _run3(self, cmd, *args, **kwargs):
357 def _run3(self, cmd, *args, **kwargs):
358 return self._dorun(util.popen3, cmd, *args, **kwargs)
358 return self._dorun(util.popen3, cmd, *args, **kwargs)
359
359
360 def _dorun(self, openfunc, cmd, *args, **kwargs):
360 def _dorun(self, openfunc, cmd, *args, **kwargs):
361 cmdline = self._cmdline(cmd, *args, **kwargs)
361 cmdline = self._cmdline(cmd, *args, **kwargs)
362 self.ui.debug('running: %s\n' % (cmdline,))
362 self.ui.debug('running: %s\n' % (cmdline,))
363 self.prerun()
363 self.prerun()
364 try:
364 try:
365 return openfunc(cmdline)
365 return openfunc(cmdline)
366 finally:
366 finally:
367 self.postrun()
367 self.postrun()
368
368
369 def run(self, cmd, *args, **kwargs):
369 def run(self, cmd, *args, **kwargs):
370 p = self._run(cmd, *args, **kwargs)
370 p = self._run(cmd, *args, **kwargs)
371 output = p.communicate()[0]
371 output = p.communicate()[0]
372 self.ui.debug(output)
372 self.ui.debug(output)
373 return output, p.returncode
373 return output, p.returncode
374
374
375 def runlines(self, cmd, *args, **kwargs):
375 def runlines(self, cmd, *args, **kwargs):
376 p = self._run(cmd, *args, **kwargs)
376 p = self._run(cmd, *args, **kwargs)
377 output = p.stdout.readlines()
377 output = p.stdout.readlines()
378 p.wait()
378 p.wait()
379 self.ui.debug(''.join(output))
379 self.ui.debug(''.join(output))
380 return output, p.returncode
380 return output, p.returncode
381
381
382 def checkexit(self, status, output=''):
382 def checkexit(self, status, output=''):
383 if status:
383 if status:
384 if output:
384 if output:
385 self.ui.warn(_('%s error:\n') % self.command)
385 self.ui.warn(_('%s error:\n') % self.command)
386 self.ui.warn(output)
386 self.ui.warn(output)
387 msg = util.explainexit(status)[0]
387 msg = util.explainexit(status)[0]
388 raise error.Abort('%s %s' % (self.command, msg))
388 raise error.Abort('%s %s' % (self.command, msg))
389
389
390 def run0(self, cmd, *args, **kwargs):
390 def run0(self, cmd, *args, **kwargs):
391 output, status = self.run(cmd, *args, **kwargs)
391 output, status = self.run(cmd, *args, **kwargs)
392 self.checkexit(status, output)
392 self.checkexit(status, output)
393 return output
393 return output
394
394
395 def runlines0(self, cmd, *args, **kwargs):
395 def runlines0(self, cmd, *args, **kwargs):
396 output, status = self.runlines(cmd, *args, **kwargs)
396 output, status = self.runlines(cmd, *args, **kwargs)
397 self.checkexit(status, ''.join(output))
397 self.checkexit(status, ''.join(output))
398 return output
398 return output
399
399
400 @propertycache
400 @propertycache
401 def argmax(self):
401 def argmax(self):
402 # POSIX requires at least 4096 bytes for ARG_MAX
402 # POSIX requires at least 4096 bytes for ARG_MAX
403 argmax = 4096
403 argmax = 4096
404 try:
404 try:
405 argmax = os.sysconf("SC_ARG_MAX")
405 argmax = os.sysconf("SC_ARG_MAX")
406 except (AttributeError, ValueError):
406 except (AttributeError, ValueError):
407 pass
407 pass
408
408
409 # Windows shells impose their own limits on command line length,
409 # Windows shells impose their own limits on command line length,
410 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
410 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
411 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
411 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
412 # details about cmd.exe limitations.
412 # details about cmd.exe limitations.
413
413
414 # Since ARG_MAX is for command line _and_ environment, lower our limit
414 # Since ARG_MAX is for command line _and_ environment, lower our limit
415 # (and make happy Windows shells while doing this).
415 # (and make happy Windows shells while doing this).
416 return argmax // 2 - 1
416 return argmax // 2 - 1
417
417
418 def _limit_arglist(self, arglist, cmd, *args, **kwargs):
418 def _limit_arglist(self, arglist, cmd, *args, **kwargs):
419 cmdlen = len(self._cmdline(cmd, *args, **kwargs))
419 cmdlen = len(self._cmdline(cmd, *args, **kwargs))
420 limit = self.argmax - cmdlen
420 limit = self.argmax - cmdlen
421 bytes = 0
421 bytes = 0
422 fl = []
422 fl = []
423 for fn in arglist:
423 for fn in arglist:
424 b = len(fn) + 3
424 b = len(fn) + 3
425 if bytes + b < limit or len(fl) == 0:
425 if bytes + b < limit or len(fl) == 0:
426 fl.append(fn)
426 fl.append(fn)
427 bytes += b
427 bytes += b
428 else:
428 else:
429 yield fl
429 yield fl
430 fl = [fn]
430 fl = [fn]
431 bytes = b
431 bytes = b
432 if fl:
432 if fl:
433 yield fl
433 yield fl
434
434
435 def xargs(self, arglist, cmd, *args, **kwargs):
435 def xargs(self, arglist, cmd, *args, **kwargs):
436 for l in self._limit_arglist(arglist, cmd, *args, **kwargs):
436 for l in self._limit_arglist(arglist, cmd, *args, **kwargs):
437 self.run0(cmd, *(list(args) + l), **kwargs)
437 self.run0(cmd, *(list(args) + l), **kwargs)
438
438
439 class mapfile(dict):
439 class mapfile(dict):
440 def __init__(self, ui, path):
440 def __init__(self, ui, path):
441 super(mapfile, self).__init__()
441 super(mapfile, self).__init__()
442 self.ui = ui
442 self.ui = ui
443 self.path = path
443 self.path = path
444 self.fp = None
444 self.fp = None
445 self.order = []
445 self.order = []
446 self._read()
446 self._read()
447
447
448 def _read(self):
448 def _read(self):
449 if not self.path:
449 if not self.path:
450 return
450 return
451 try:
451 try:
452 fp = open(self.path, 'r')
452 fp = open(self.path, 'r')
453 except IOError as err:
453 except IOError as err:
454 if err.errno != errno.ENOENT:
454 if err.errno != errno.ENOENT:
455 raise
455 raise
456 return
456 return
457 for i, line in enumerate(fp):
457 for i, line in enumerate(util.iterfile(fp)):
458 line = line.splitlines()[0].rstrip()
458 line = line.splitlines()[0].rstrip()
459 if not line:
459 if not line:
460 # Ignore blank lines
460 # Ignore blank lines
461 continue
461 continue
462 try:
462 try:
463 key, value = line.rsplit(' ', 1)
463 key, value = line.rsplit(' ', 1)
464 except ValueError:
464 except ValueError:
465 raise error.Abort(
465 raise error.Abort(
466 _('syntax error in %s(%d): key/value pair expected')
466 _('syntax error in %s(%d): key/value pair expected')
467 % (self.path, i + 1))
467 % (self.path, i + 1))
468 if key not in self:
468 if key not in self:
469 self.order.append(key)
469 self.order.append(key)
470 super(mapfile, self).__setitem__(key, value)
470 super(mapfile, self).__setitem__(key, value)
471 fp.close()
471 fp.close()
472
472
473 def __setitem__(self, key, value):
473 def __setitem__(self, key, value):
474 if self.fp is None:
474 if self.fp is None:
475 try:
475 try:
476 self.fp = open(self.path, 'a')
476 self.fp = open(self.path, 'a')
477 except IOError as err:
477 except IOError as err:
478 raise error.Abort(_('could not open map file %r: %s') %
478 raise error.Abort(_('could not open map file %r: %s') %
479 (self.path, err.strerror))
479 (self.path, err.strerror))
480 self.fp.write('%s %s\n' % (key, value))
480 self.fp.write('%s %s\n' % (key, value))
481 self.fp.flush()
481 self.fp.flush()
482 super(mapfile, self).__setitem__(key, value)
482 super(mapfile, self).__setitem__(key, value)
483
483
484 def close(self):
484 def close(self):
485 if self.fp:
485 if self.fp:
486 self.fp.close()
486 self.fp.close()
487 self.fp = None
487 self.fp = None
488
488
489 def makedatetimestamp(t):
489 def makedatetimestamp(t):
490 """Like util.makedate() but for time t instead of current time"""
490 """Like util.makedate() but for time t instead of current time"""
491 delta = (datetime.datetime.utcfromtimestamp(t) -
491 delta = (datetime.datetime.utcfromtimestamp(t) -
492 datetime.datetime.fromtimestamp(t))
492 datetime.datetime.fromtimestamp(t))
493 tz = delta.days * 86400 + delta.seconds
493 tz = delta.days * 86400 + delta.seconds
494 return t, tz
494 return t, tz
@@ -1,611 +1,611 b''
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 of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 from __future__ import absolute_import
7 from __future__ import absolute_import
8
8
9 import os
9 import os
10 import shlex
10 import shlex
11 import shutil
11 import shutil
12
12
13 from mercurial.i18n import _
13 from mercurial.i18n import _
14 from mercurial import (
14 from mercurial import (
15 encoding,
15 encoding,
16 error,
16 error,
17 hg,
17 hg,
18 util,
18 util,
19 )
19 )
20
20
21 from . import (
21 from . import (
22 bzr,
22 bzr,
23 common,
23 common,
24 cvs,
24 cvs,
25 darcs,
25 darcs,
26 filemap,
26 filemap,
27 git,
27 git,
28 gnuarch,
28 gnuarch,
29 hg as hgconvert,
29 hg as hgconvert,
30 monotone,
30 monotone,
31 p4,
31 p4,
32 subversion,
32 subversion,
33 )
33 )
34
34
35 mapfile = common.mapfile
35 mapfile = common.mapfile
36 MissingTool = common.MissingTool
36 MissingTool = common.MissingTool
37 NoRepo = common.NoRepo
37 NoRepo = common.NoRepo
38 SKIPREV = common.SKIPREV
38 SKIPREV = common.SKIPREV
39
39
40 bzr_source = bzr.bzr_source
40 bzr_source = bzr.bzr_source
41 convert_cvs = cvs.convert_cvs
41 convert_cvs = cvs.convert_cvs
42 convert_git = git.convert_git
42 convert_git = git.convert_git
43 darcs_source = darcs.darcs_source
43 darcs_source = darcs.darcs_source
44 gnuarch_source = gnuarch.gnuarch_source
44 gnuarch_source = gnuarch.gnuarch_source
45 mercurial_sink = hgconvert.mercurial_sink
45 mercurial_sink = hgconvert.mercurial_sink
46 mercurial_source = hgconvert.mercurial_source
46 mercurial_source = hgconvert.mercurial_source
47 monotone_source = monotone.monotone_source
47 monotone_source = monotone.monotone_source
48 p4_source = p4.p4_source
48 p4_source = p4.p4_source
49 svn_sink = subversion.svn_sink
49 svn_sink = subversion.svn_sink
50 svn_source = subversion.svn_source
50 svn_source = subversion.svn_source
51
51
52 orig_encoding = 'ascii'
52 orig_encoding = 'ascii'
53
53
54 def recode(s):
54 def recode(s):
55 if isinstance(s, unicode):
55 if isinstance(s, unicode):
56 return s.encode(orig_encoding, 'replace')
56 return s.encode(orig_encoding, 'replace')
57 else:
57 else:
58 return s.decode('utf-8').encode(orig_encoding, 'replace')
58 return s.decode('utf-8').encode(orig_encoding, 'replace')
59
59
60 def mapbranch(branch, branchmap):
60 def mapbranch(branch, branchmap):
61 '''
61 '''
62 >>> bmap = {'default': 'branch1'}
62 >>> bmap = {'default': 'branch1'}
63 >>> for i in ['', None]:
63 >>> for i in ['', None]:
64 ... mapbranch(i, bmap)
64 ... mapbranch(i, bmap)
65 'branch1'
65 'branch1'
66 'branch1'
66 'branch1'
67 >>> bmap = {'None': 'branch2'}
67 >>> bmap = {'None': 'branch2'}
68 >>> for i in ['', None]:
68 >>> for i in ['', None]:
69 ... mapbranch(i, bmap)
69 ... mapbranch(i, bmap)
70 'branch2'
70 'branch2'
71 'branch2'
71 'branch2'
72 >>> bmap = {'None': 'branch3', 'default': 'branch4'}
72 >>> bmap = {'None': 'branch3', 'default': 'branch4'}
73 >>> for i in ['None', '', None, 'default', 'branch5']:
73 >>> for i in ['None', '', None, 'default', 'branch5']:
74 ... mapbranch(i, bmap)
74 ... mapbranch(i, bmap)
75 'branch3'
75 'branch3'
76 'branch4'
76 'branch4'
77 'branch4'
77 'branch4'
78 'branch4'
78 'branch4'
79 'branch5'
79 'branch5'
80 '''
80 '''
81 # If branch is None or empty, this commit is coming from the source
81 # If branch is None or empty, this commit is coming from the source
82 # repository's default branch and destined for the default branch in the
82 # repository's default branch and destined for the default branch in the
83 # destination repository. For such commits, using a literal "default"
83 # destination repository. For such commits, using a literal "default"
84 # in branchmap below allows the user to map "default" to an alternate
84 # in branchmap below allows the user to map "default" to an alternate
85 # default branch in the destination repository.
85 # default branch in the destination repository.
86 branch = branchmap.get(branch or 'default', branch)
86 branch = branchmap.get(branch or 'default', branch)
87 # At some point we used "None" literal to denote the default branch,
87 # At some point we used "None" literal to denote the default branch,
88 # attempt to use that for backward compatibility.
88 # attempt to use that for backward compatibility.
89 if (not branch):
89 if (not branch):
90 branch = branchmap.get(str(None), branch)
90 branch = branchmap.get(str(None), branch)
91 return branch
91 return branch
92
92
93 source_converters = [
93 source_converters = [
94 ('cvs', convert_cvs, 'branchsort'),
94 ('cvs', convert_cvs, 'branchsort'),
95 ('git', convert_git, 'branchsort'),
95 ('git', convert_git, 'branchsort'),
96 ('svn', svn_source, 'branchsort'),
96 ('svn', svn_source, 'branchsort'),
97 ('hg', mercurial_source, 'sourcesort'),
97 ('hg', mercurial_source, 'sourcesort'),
98 ('darcs', darcs_source, 'branchsort'),
98 ('darcs', darcs_source, 'branchsort'),
99 ('mtn', monotone_source, 'branchsort'),
99 ('mtn', monotone_source, 'branchsort'),
100 ('gnuarch', gnuarch_source, 'branchsort'),
100 ('gnuarch', gnuarch_source, 'branchsort'),
101 ('bzr', bzr_source, 'branchsort'),
101 ('bzr', bzr_source, 'branchsort'),
102 ('p4', p4_source, 'branchsort'),
102 ('p4', p4_source, 'branchsort'),
103 ]
103 ]
104
104
105 sink_converters = [
105 sink_converters = [
106 ('hg', mercurial_sink),
106 ('hg', mercurial_sink),
107 ('svn', svn_sink),
107 ('svn', svn_sink),
108 ]
108 ]
109
109
110 def convertsource(ui, path, type, revs):
110 def convertsource(ui, path, type, revs):
111 exceptions = []
111 exceptions = []
112 if type and type not in [s[0] for s in source_converters]:
112 if type and type not in [s[0] for s in source_converters]:
113 raise error.Abort(_('%s: invalid source repository type') % type)
113 raise error.Abort(_('%s: invalid source repository type') % type)
114 for name, source, sortmode in source_converters:
114 for name, source, sortmode in source_converters:
115 try:
115 try:
116 if not type or name == type:
116 if not type or name == type:
117 return source(ui, path, revs), sortmode
117 return source(ui, path, revs), sortmode
118 except (NoRepo, MissingTool) as inst:
118 except (NoRepo, MissingTool) as inst:
119 exceptions.append(inst)
119 exceptions.append(inst)
120 if not ui.quiet:
120 if not ui.quiet:
121 for inst in exceptions:
121 for inst in exceptions:
122 ui.write("%s\n" % inst)
122 ui.write("%s\n" % inst)
123 raise error.Abort(_('%s: missing or unsupported repository') % path)
123 raise error.Abort(_('%s: missing or unsupported repository') % path)
124
124
125 def convertsink(ui, path, type):
125 def convertsink(ui, path, type):
126 if type and type not in [s[0] for s in sink_converters]:
126 if type and type not in [s[0] for s in sink_converters]:
127 raise error.Abort(_('%s: invalid destination repository type') % type)
127 raise error.Abort(_('%s: invalid destination repository type') % type)
128 for name, sink in sink_converters:
128 for name, sink in sink_converters:
129 try:
129 try:
130 if not type or name == type:
130 if not type or name == type:
131 return sink(ui, path)
131 return sink(ui, path)
132 except NoRepo as inst:
132 except NoRepo as inst:
133 ui.note(_("convert: %s\n") % inst)
133 ui.note(_("convert: %s\n") % inst)
134 except MissingTool as inst:
134 except MissingTool as inst:
135 raise error.Abort('%s\n' % inst)
135 raise error.Abort('%s\n' % inst)
136 raise error.Abort(_('%s: unknown repository type') % path)
136 raise error.Abort(_('%s: unknown repository type') % path)
137
137
138 class progresssource(object):
138 class progresssource(object):
139 def __init__(self, ui, source, filecount):
139 def __init__(self, ui, source, filecount):
140 self.ui = ui
140 self.ui = ui
141 self.source = source
141 self.source = source
142 self.filecount = filecount
142 self.filecount = filecount
143 self.retrieved = 0
143 self.retrieved = 0
144
144
145 def getfile(self, file, rev):
145 def getfile(self, file, rev):
146 self.retrieved += 1
146 self.retrieved += 1
147 self.ui.progress(_('getting files'), self.retrieved,
147 self.ui.progress(_('getting files'), self.retrieved,
148 item=file, total=self.filecount, unit=_('files'))
148 item=file, total=self.filecount, unit=_('files'))
149 return self.source.getfile(file, rev)
149 return self.source.getfile(file, rev)
150
150
151 def targetfilebelongstosource(self, targetfilename):
151 def targetfilebelongstosource(self, targetfilename):
152 return self.source.targetfilebelongstosource(targetfilename)
152 return self.source.targetfilebelongstosource(targetfilename)
153
153
154 def lookuprev(self, rev):
154 def lookuprev(self, rev):
155 return self.source.lookuprev(rev)
155 return self.source.lookuprev(rev)
156
156
157 def close(self):
157 def close(self):
158 self.ui.progress(_('getting files'), None)
158 self.ui.progress(_('getting files'), None)
159
159
160 class converter(object):
160 class converter(object):
161 def __init__(self, ui, source, dest, revmapfile, opts):
161 def __init__(self, ui, source, dest, revmapfile, opts):
162
162
163 self.source = source
163 self.source = source
164 self.dest = dest
164 self.dest = dest
165 self.ui = ui
165 self.ui = ui
166 self.opts = opts
166 self.opts = opts
167 self.commitcache = {}
167 self.commitcache = {}
168 self.authors = {}
168 self.authors = {}
169 self.authorfile = None
169 self.authorfile = None
170
170
171 # Record converted revisions persistently: maps source revision
171 # Record converted revisions persistently: maps source revision
172 # ID to target revision ID (both strings). (This is how
172 # ID to target revision ID (both strings). (This is how
173 # incremental conversions work.)
173 # incremental conversions work.)
174 self.map = mapfile(ui, revmapfile)
174 self.map = mapfile(ui, revmapfile)
175
175
176 # Read first the dst author map if any
176 # Read first the dst author map if any
177 authorfile = self.dest.authorfile()
177 authorfile = self.dest.authorfile()
178 if authorfile and os.path.exists(authorfile):
178 if authorfile and os.path.exists(authorfile):
179 self.readauthormap(authorfile)
179 self.readauthormap(authorfile)
180 # Extend/Override with new author map if necessary
180 # Extend/Override with new author map if necessary
181 if opts.get('authormap'):
181 if opts.get('authormap'):
182 self.readauthormap(opts.get('authormap'))
182 self.readauthormap(opts.get('authormap'))
183 self.authorfile = self.dest.authorfile()
183 self.authorfile = self.dest.authorfile()
184
184
185 self.splicemap = self.parsesplicemap(opts.get('splicemap'))
185 self.splicemap = self.parsesplicemap(opts.get('splicemap'))
186 self.branchmap = mapfile(ui, opts.get('branchmap'))
186 self.branchmap = mapfile(ui, opts.get('branchmap'))
187
187
188 def parsesplicemap(self, path):
188 def parsesplicemap(self, path):
189 """ check and validate the splicemap format and
189 """ check and validate the splicemap format and
190 return a child/parents dictionary.
190 return a child/parents dictionary.
191 Format checking has two parts.
191 Format checking has two parts.
192 1. generic format which is same across all source types
192 1. generic format which is same across all source types
193 2. specific format checking which may be different for
193 2. specific format checking which may be different for
194 different source type. This logic is implemented in
194 different source type. This logic is implemented in
195 checkrevformat function in source files like
195 checkrevformat function in source files like
196 hg.py, subversion.py etc.
196 hg.py, subversion.py etc.
197 """
197 """
198
198
199 if not path:
199 if not path:
200 return {}
200 return {}
201 m = {}
201 m = {}
202 try:
202 try:
203 fp = open(path, 'r')
203 fp = open(path, 'r')
204 for i, line in enumerate(fp):
204 for i, line in enumerate(util.iterfile(fp)):
205 line = line.splitlines()[0].rstrip()
205 line = line.splitlines()[0].rstrip()
206 if not line:
206 if not line:
207 # Ignore blank lines
207 # Ignore blank lines
208 continue
208 continue
209 # split line
209 # split line
210 lex = shlex.shlex(line, posix=True)
210 lex = shlex.shlex(line, posix=True)
211 lex.whitespace_split = True
211 lex.whitespace_split = True
212 lex.whitespace += ','
212 lex.whitespace += ','
213 line = list(lex)
213 line = list(lex)
214 # check number of parents
214 # check number of parents
215 if not (2 <= len(line) <= 3):
215 if not (2 <= len(line) <= 3):
216 raise error.Abort(_('syntax error in %s(%d): child parent1'
216 raise error.Abort(_('syntax error in %s(%d): child parent1'
217 '[,parent2] expected') % (path, i + 1))
217 '[,parent2] expected') % (path, i + 1))
218 for part in line:
218 for part in line:
219 self.source.checkrevformat(part)
219 self.source.checkrevformat(part)
220 child, p1, p2 = line[0], line[1:2], line[2:]
220 child, p1, p2 = line[0], line[1:2], line[2:]
221 if p1 == p2:
221 if p1 == p2:
222 m[child] = p1
222 m[child] = p1
223 else:
223 else:
224 m[child] = p1 + p2
224 m[child] = p1 + p2
225 # if file does not exist or error reading, exit
225 # if file does not exist or error reading, exit
226 except IOError:
226 except IOError:
227 raise error.Abort(_('splicemap file not found or error reading %s:')
227 raise error.Abort(_('splicemap file not found or error reading %s:')
228 % path)
228 % path)
229 return m
229 return m
230
230
231
231
232 def walktree(self, heads):
232 def walktree(self, heads):
233 '''Return a mapping that identifies the uncommitted parents of every
233 '''Return a mapping that identifies the uncommitted parents of every
234 uncommitted changeset.'''
234 uncommitted changeset.'''
235 visit = heads
235 visit = heads
236 known = set()
236 known = set()
237 parents = {}
237 parents = {}
238 numcommits = self.source.numcommits()
238 numcommits = self.source.numcommits()
239 while visit:
239 while visit:
240 n = visit.pop(0)
240 n = visit.pop(0)
241 if n in known:
241 if n in known:
242 continue
242 continue
243 if n in self.map:
243 if n in self.map:
244 m = self.map[n]
244 m = self.map[n]
245 if m == SKIPREV or self.dest.hascommitfrommap(m):
245 if m == SKIPREV or self.dest.hascommitfrommap(m):
246 continue
246 continue
247 known.add(n)
247 known.add(n)
248 self.ui.progress(_('scanning'), len(known), unit=_('revisions'),
248 self.ui.progress(_('scanning'), len(known), unit=_('revisions'),
249 total=numcommits)
249 total=numcommits)
250 commit = self.cachecommit(n)
250 commit = self.cachecommit(n)
251 parents[n] = []
251 parents[n] = []
252 for p in commit.parents:
252 for p in commit.parents:
253 parents[n].append(p)
253 parents[n].append(p)
254 visit.append(p)
254 visit.append(p)
255 self.ui.progress(_('scanning'), None)
255 self.ui.progress(_('scanning'), None)
256
256
257 return parents
257 return parents
258
258
259 def mergesplicemap(self, parents, splicemap):
259 def mergesplicemap(self, parents, splicemap):
260 """A splicemap redefines child/parent relationships. Check the
260 """A splicemap redefines child/parent relationships. Check the
261 map contains valid revision identifiers and merge the new
261 map contains valid revision identifiers and merge the new
262 links in the source graph.
262 links in the source graph.
263 """
263 """
264 for c in sorted(splicemap):
264 for c in sorted(splicemap):
265 if c not in parents:
265 if c not in parents:
266 if not self.dest.hascommitforsplicemap(self.map.get(c, c)):
266 if not self.dest.hascommitforsplicemap(self.map.get(c, c)):
267 # Could be in source but not converted during this run
267 # Could be in source but not converted during this run
268 self.ui.warn(_('splice map revision %s is not being '
268 self.ui.warn(_('splice map revision %s is not being '
269 'converted, ignoring\n') % c)
269 'converted, ignoring\n') % c)
270 continue
270 continue
271 pc = []
271 pc = []
272 for p in splicemap[c]:
272 for p in splicemap[c]:
273 # We do not have to wait for nodes already in dest.
273 # We do not have to wait for nodes already in dest.
274 if self.dest.hascommitforsplicemap(self.map.get(p, p)):
274 if self.dest.hascommitforsplicemap(self.map.get(p, p)):
275 continue
275 continue
276 # Parent is not in dest and not being converted, not good
276 # Parent is not in dest and not being converted, not good
277 if p not in parents:
277 if p not in parents:
278 raise error.Abort(_('unknown splice map parent: %s') % p)
278 raise error.Abort(_('unknown splice map parent: %s') % p)
279 pc.append(p)
279 pc.append(p)
280 parents[c] = pc
280 parents[c] = pc
281
281
282 def toposort(self, parents, sortmode):
282 def toposort(self, parents, sortmode):
283 '''Return an ordering such that every uncommitted changeset is
283 '''Return an ordering such that every uncommitted changeset is
284 preceded by all its uncommitted ancestors.'''
284 preceded by all its uncommitted ancestors.'''
285
285
286 def mapchildren(parents):
286 def mapchildren(parents):
287 """Return a (children, roots) tuple where 'children' maps parent
287 """Return a (children, roots) tuple where 'children' maps parent
288 revision identifiers to children ones, and 'roots' is the list of
288 revision identifiers to children ones, and 'roots' is the list of
289 revisions without parents. 'parents' must be a mapping of revision
289 revisions without parents. 'parents' must be a mapping of revision
290 identifier to its parents ones.
290 identifier to its parents ones.
291 """
291 """
292 visit = sorted(parents)
292 visit = sorted(parents)
293 seen = set()
293 seen = set()
294 children = {}
294 children = {}
295 roots = []
295 roots = []
296
296
297 while visit:
297 while visit:
298 n = visit.pop(0)
298 n = visit.pop(0)
299 if n in seen:
299 if n in seen:
300 continue
300 continue
301 seen.add(n)
301 seen.add(n)
302 # Ensure that nodes without parents are present in the
302 # Ensure that nodes without parents are present in the
303 # 'children' mapping.
303 # 'children' mapping.
304 children.setdefault(n, [])
304 children.setdefault(n, [])
305 hasparent = False
305 hasparent = False
306 for p in parents[n]:
306 for p in parents[n]:
307 if p not in self.map:
307 if p not in self.map:
308 visit.append(p)
308 visit.append(p)
309 hasparent = True
309 hasparent = True
310 children.setdefault(p, []).append(n)
310 children.setdefault(p, []).append(n)
311 if not hasparent:
311 if not hasparent:
312 roots.append(n)
312 roots.append(n)
313
313
314 return children, roots
314 return children, roots
315
315
316 # Sort functions are supposed to take a list of revisions which
316 # Sort functions are supposed to take a list of revisions which
317 # can be converted immediately and pick one
317 # can be converted immediately and pick one
318
318
319 def makebranchsorter():
319 def makebranchsorter():
320 """If the previously converted revision has a child in the
320 """If the previously converted revision has a child in the
321 eligible revisions list, pick it. Return the list head
321 eligible revisions list, pick it. Return the list head
322 otherwise. Branch sort attempts to minimize branch
322 otherwise. Branch sort attempts to minimize branch
323 switching, which is harmful for Mercurial backend
323 switching, which is harmful for Mercurial backend
324 compression.
324 compression.
325 """
325 """
326 prev = [None]
326 prev = [None]
327 def picknext(nodes):
327 def picknext(nodes):
328 next = nodes[0]
328 next = nodes[0]
329 for n in nodes:
329 for n in nodes:
330 if prev[0] in parents[n]:
330 if prev[0] in parents[n]:
331 next = n
331 next = n
332 break
332 break
333 prev[0] = next
333 prev[0] = next
334 return next
334 return next
335 return picknext
335 return picknext
336
336
337 def makesourcesorter():
337 def makesourcesorter():
338 """Source specific sort."""
338 """Source specific sort."""
339 keyfn = lambda n: self.commitcache[n].sortkey
339 keyfn = lambda n: self.commitcache[n].sortkey
340 def picknext(nodes):
340 def picknext(nodes):
341 return sorted(nodes, key=keyfn)[0]
341 return sorted(nodes, key=keyfn)[0]
342 return picknext
342 return picknext
343
343
344 def makeclosesorter():
344 def makeclosesorter():
345 """Close order sort."""
345 """Close order sort."""
346 keyfn = lambda n: ('close' not in self.commitcache[n].extra,
346 keyfn = lambda n: ('close' not in self.commitcache[n].extra,
347 self.commitcache[n].sortkey)
347 self.commitcache[n].sortkey)
348 def picknext(nodes):
348 def picknext(nodes):
349 return sorted(nodes, key=keyfn)[0]
349 return sorted(nodes, key=keyfn)[0]
350 return picknext
350 return picknext
351
351
352 def makedatesorter():
352 def makedatesorter():
353 """Sort revisions by date."""
353 """Sort revisions by date."""
354 dates = {}
354 dates = {}
355 def getdate(n):
355 def getdate(n):
356 if n not in dates:
356 if n not in dates:
357 dates[n] = util.parsedate(self.commitcache[n].date)
357 dates[n] = util.parsedate(self.commitcache[n].date)
358 return dates[n]
358 return dates[n]
359
359
360 def picknext(nodes):
360 def picknext(nodes):
361 return min([(getdate(n), n) for n in nodes])[1]
361 return min([(getdate(n), n) for n in nodes])[1]
362
362
363 return picknext
363 return picknext
364
364
365 if sortmode == 'branchsort':
365 if sortmode == 'branchsort':
366 picknext = makebranchsorter()
366 picknext = makebranchsorter()
367 elif sortmode == 'datesort':
367 elif sortmode == 'datesort':
368 picknext = makedatesorter()
368 picknext = makedatesorter()
369 elif sortmode == 'sourcesort':
369 elif sortmode == 'sourcesort':
370 picknext = makesourcesorter()
370 picknext = makesourcesorter()
371 elif sortmode == 'closesort':
371 elif sortmode == 'closesort':
372 picknext = makeclosesorter()
372 picknext = makeclosesorter()
373 else:
373 else:
374 raise error.Abort(_('unknown sort mode: %s') % sortmode)
374 raise error.Abort(_('unknown sort mode: %s') % sortmode)
375
375
376 children, actives = mapchildren(parents)
376 children, actives = mapchildren(parents)
377
377
378 s = []
378 s = []
379 pendings = {}
379 pendings = {}
380 while actives:
380 while actives:
381 n = picknext(actives)
381 n = picknext(actives)
382 actives.remove(n)
382 actives.remove(n)
383 s.append(n)
383 s.append(n)
384
384
385 # Update dependents list
385 # Update dependents list
386 for c in children.get(n, []):
386 for c in children.get(n, []):
387 if c not in pendings:
387 if c not in pendings:
388 pendings[c] = [p for p in parents[c] if p not in self.map]
388 pendings[c] = [p for p in parents[c] if p not in self.map]
389 try:
389 try:
390 pendings[c].remove(n)
390 pendings[c].remove(n)
391 except ValueError:
391 except ValueError:
392 raise error.Abort(_('cycle detected between %s and %s')
392 raise error.Abort(_('cycle detected between %s and %s')
393 % (recode(c), recode(n)))
393 % (recode(c), recode(n)))
394 if not pendings[c]:
394 if not pendings[c]:
395 # Parents are converted, node is eligible
395 # Parents are converted, node is eligible
396 actives.insert(0, c)
396 actives.insert(0, c)
397 pendings[c] = None
397 pendings[c] = None
398
398
399 if len(s) != len(parents):
399 if len(s) != len(parents):
400 raise error.Abort(_("not all revisions were sorted"))
400 raise error.Abort(_("not all revisions were sorted"))
401
401
402 return s
402 return s
403
403
404 def writeauthormap(self):
404 def writeauthormap(self):
405 authorfile = self.authorfile
405 authorfile = self.authorfile
406 if authorfile:
406 if authorfile:
407 self.ui.status(_('writing author map file %s\n') % authorfile)
407 self.ui.status(_('writing author map file %s\n') % authorfile)
408 ofile = open(authorfile, 'w+')
408 ofile = open(authorfile, 'w+')
409 for author in self.authors:
409 for author in self.authors:
410 ofile.write("%s=%s\n" % (author, self.authors[author]))
410 ofile.write("%s=%s\n" % (author, self.authors[author]))
411 ofile.close()
411 ofile.close()
412
412
413 def readauthormap(self, authorfile):
413 def readauthormap(self, authorfile):
414 afile = open(authorfile, 'r')
414 afile = open(authorfile, 'r')
415 for line in afile:
415 for line in afile:
416
416
417 line = line.strip()
417 line = line.strip()
418 if not line or line.startswith('#'):
418 if not line or line.startswith('#'):
419 continue
419 continue
420
420
421 try:
421 try:
422 srcauthor, dstauthor = line.split('=', 1)
422 srcauthor, dstauthor = line.split('=', 1)
423 except ValueError:
423 except ValueError:
424 msg = _('ignoring bad line in author map file %s: %s\n')
424 msg = _('ignoring bad line in author map file %s: %s\n')
425 self.ui.warn(msg % (authorfile, line.rstrip()))
425 self.ui.warn(msg % (authorfile, line.rstrip()))
426 continue
426 continue
427
427
428 srcauthor = srcauthor.strip()
428 srcauthor = srcauthor.strip()
429 dstauthor = dstauthor.strip()
429 dstauthor = dstauthor.strip()
430 if self.authors.get(srcauthor) in (None, dstauthor):
430 if self.authors.get(srcauthor) in (None, dstauthor):
431 msg = _('mapping author %s to %s\n')
431 msg = _('mapping author %s to %s\n')
432 self.ui.debug(msg % (srcauthor, dstauthor))
432 self.ui.debug(msg % (srcauthor, dstauthor))
433 self.authors[srcauthor] = dstauthor
433 self.authors[srcauthor] = dstauthor
434 continue
434 continue
435
435
436 m = _('overriding mapping for author %s, was %s, will be %s\n')
436 m = _('overriding mapping for author %s, was %s, will be %s\n')
437 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
437 self.ui.status(m % (srcauthor, self.authors[srcauthor], dstauthor))
438
438
439 afile.close()
439 afile.close()
440
440
441 def cachecommit(self, rev):
441 def cachecommit(self, rev):
442 commit = self.source.getcommit(rev)
442 commit = self.source.getcommit(rev)
443 commit.author = self.authors.get(commit.author, commit.author)
443 commit.author = self.authors.get(commit.author, commit.author)
444 commit.branch = mapbranch(commit.branch, self.branchmap)
444 commit.branch = mapbranch(commit.branch, self.branchmap)
445 self.commitcache[rev] = commit
445 self.commitcache[rev] = commit
446 return commit
446 return commit
447
447
448 def copy(self, rev):
448 def copy(self, rev):
449 commit = self.commitcache[rev]
449 commit = self.commitcache[rev]
450 full = self.opts.get('full')
450 full = self.opts.get('full')
451 changes = self.source.getchanges(rev, full)
451 changes = self.source.getchanges(rev, full)
452 if isinstance(changes, basestring):
452 if isinstance(changes, basestring):
453 if changes == SKIPREV:
453 if changes == SKIPREV:
454 dest = SKIPREV
454 dest = SKIPREV
455 else:
455 else:
456 dest = self.map[changes]
456 dest = self.map[changes]
457 self.map[rev] = dest
457 self.map[rev] = dest
458 return
458 return
459 files, copies, cleanp2 = changes
459 files, copies, cleanp2 = changes
460 pbranches = []
460 pbranches = []
461 if commit.parents:
461 if commit.parents:
462 for prev in commit.parents:
462 for prev in commit.parents:
463 if prev not in self.commitcache:
463 if prev not in self.commitcache:
464 self.cachecommit(prev)
464 self.cachecommit(prev)
465 pbranches.append((self.map[prev],
465 pbranches.append((self.map[prev],
466 self.commitcache[prev].branch))
466 self.commitcache[prev].branch))
467 self.dest.setbranch(commit.branch, pbranches)
467 self.dest.setbranch(commit.branch, pbranches)
468 try:
468 try:
469 parents = self.splicemap[rev]
469 parents = self.splicemap[rev]
470 self.ui.status(_('spliced in %s as parents of %s\n') %
470 self.ui.status(_('spliced in %s as parents of %s\n') %
471 (_(' and ').join(parents), rev))
471 (_(' and ').join(parents), rev))
472 parents = [self.map.get(p, p) for p in parents]
472 parents = [self.map.get(p, p) for p in parents]
473 except KeyError:
473 except KeyError:
474 parents = [b[0] for b in pbranches]
474 parents = [b[0] for b in pbranches]
475 parents.extend(self.map[x]
475 parents.extend(self.map[x]
476 for x in commit.optparents
476 for x in commit.optparents
477 if x in self.map)
477 if x in self.map)
478 if len(pbranches) != 2:
478 if len(pbranches) != 2:
479 cleanp2 = set()
479 cleanp2 = set()
480 if len(parents) < 3:
480 if len(parents) < 3:
481 source = progresssource(self.ui, self.source, len(files))
481 source = progresssource(self.ui, self.source, len(files))
482 else:
482 else:
483 # For an octopus merge, we end up traversing the list of
483 # For an octopus merge, we end up traversing the list of
484 # changed files N-1 times. This tweak to the number of
484 # changed files N-1 times. This tweak to the number of
485 # files makes it so the progress bar doesn't overflow
485 # files makes it so the progress bar doesn't overflow
486 # itself.
486 # itself.
487 source = progresssource(self.ui, self.source,
487 source = progresssource(self.ui, self.source,
488 len(files) * (len(parents) - 1))
488 len(files) * (len(parents) - 1))
489 newnode = self.dest.putcommit(files, copies, parents, commit,
489 newnode = self.dest.putcommit(files, copies, parents, commit,
490 source, self.map, full, cleanp2)
490 source, self.map, full, cleanp2)
491 source.close()
491 source.close()
492 self.source.converted(rev, newnode)
492 self.source.converted(rev, newnode)
493 self.map[rev] = newnode
493 self.map[rev] = newnode
494
494
495 def convert(self, sortmode):
495 def convert(self, sortmode):
496 try:
496 try:
497 self.source.before()
497 self.source.before()
498 self.dest.before()
498 self.dest.before()
499 self.source.setrevmap(self.map)
499 self.source.setrevmap(self.map)
500 self.ui.status(_("scanning source...\n"))
500 self.ui.status(_("scanning source...\n"))
501 heads = self.source.getheads()
501 heads = self.source.getheads()
502 parents = self.walktree(heads)
502 parents = self.walktree(heads)
503 self.mergesplicemap(parents, self.splicemap)
503 self.mergesplicemap(parents, self.splicemap)
504 self.ui.status(_("sorting...\n"))
504 self.ui.status(_("sorting...\n"))
505 t = self.toposort(parents, sortmode)
505 t = self.toposort(parents, sortmode)
506 num = len(t)
506 num = len(t)
507 c = None
507 c = None
508
508
509 self.ui.status(_("converting...\n"))
509 self.ui.status(_("converting...\n"))
510 for i, c in enumerate(t):
510 for i, c in enumerate(t):
511 num -= 1
511 num -= 1
512 desc = self.commitcache[c].desc
512 desc = self.commitcache[c].desc
513 if "\n" in desc:
513 if "\n" in desc:
514 desc = desc.splitlines()[0]
514 desc = desc.splitlines()[0]
515 # convert log message to local encoding without using
515 # convert log message to local encoding without using
516 # tolocal() because the encoding.encoding convert()
516 # tolocal() because the encoding.encoding convert()
517 # uses is 'utf-8'
517 # uses is 'utf-8'
518 self.ui.status("%d %s\n" % (num, recode(desc)))
518 self.ui.status("%d %s\n" % (num, recode(desc)))
519 self.ui.note(_("source: %s\n") % recode(c))
519 self.ui.note(_("source: %s\n") % recode(c))
520 self.ui.progress(_('converting'), i, unit=_('revisions'),
520 self.ui.progress(_('converting'), i, unit=_('revisions'),
521 total=len(t))
521 total=len(t))
522 self.copy(c)
522 self.copy(c)
523 self.ui.progress(_('converting'), None)
523 self.ui.progress(_('converting'), None)
524
524
525 if not self.ui.configbool('convert', 'skiptags'):
525 if not self.ui.configbool('convert', 'skiptags'):
526 tags = self.source.gettags()
526 tags = self.source.gettags()
527 ctags = {}
527 ctags = {}
528 for k in tags:
528 for k in tags:
529 v = tags[k]
529 v = tags[k]
530 if self.map.get(v, SKIPREV) != SKIPREV:
530 if self.map.get(v, SKIPREV) != SKIPREV:
531 ctags[k] = self.map[v]
531 ctags[k] = self.map[v]
532
532
533 if c and ctags:
533 if c and ctags:
534 nrev, tagsparent = self.dest.puttags(ctags)
534 nrev, tagsparent = self.dest.puttags(ctags)
535 if nrev and tagsparent:
535 if nrev and tagsparent:
536 # write another hash correspondence to override the
536 # write another hash correspondence to override the
537 # previous one so we don't end up with extra tag heads
537 # previous one so we don't end up with extra tag heads
538 tagsparents = [e for e in self.map.iteritems()
538 tagsparents = [e for e in self.map.iteritems()
539 if e[1] == tagsparent]
539 if e[1] == tagsparent]
540 if tagsparents:
540 if tagsparents:
541 self.map[tagsparents[0][0]] = nrev
541 self.map[tagsparents[0][0]] = nrev
542
542
543 bookmarks = self.source.getbookmarks()
543 bookmarks = self.source.getbookmarks()
544 cbookmarks = {}
544 cbookmarks = {}
545 for k in bookmarks:
545 for k in bookmarks:
546 v = bookmarks[k]
546 v = bookmarks[k]
547 if self.map.get(v, SKIPREV) != SKIPREV:
547 if self.map.get(v, SKIPREV) != SKIPREV:
548 cbookmarks[k] = self.map[v]
548 cbookmarks[k] = self.map[v]
549
549
550 if c and cbookmarks:
550 if c and cbookmarks:
551 self.dest.putbookmarks(cbookmarks)
551 self.dest.putbookmarks(cbookmarks)
552
552
553 self.writeauthormap()
553 self.writeauthormap()
554 finally:
554 finally:
555 self.cleanup()
555 self.cleanup()
556
556
557 def cleanup(self):
557 def cleanup(self):
558 try:
558 try:
559 self.dest.after()
559 self.dest.after()
560 finally:
560 finally:
561 self.source.after()
561 self.source.after()
562 self.map.close()
562 self.map.close()
563
563
564 def convert(ui, src, dest=None, revmapfile=None, **opts):
564 def convert(ui, src, dest=None, revmapfile=None, **opts):
565 global orig_encoding
565 global orig_encoding
566 orig_encoding = encoding.encoding
566 orig_encoding = encoding.encoding
567 encoding.encoding = 'UTF-8'
567 encoding.encoding = 'UTF-8'
568
568
569 # support --authors as an alias for --authormap
569 # support --authors as an alias for --authormap
570 if not opts.get('authormap'):
570 if not opts.get('authormap'):
571 opts['authormap'] = opts.get('authors')
571 opts['authormap'] = opts.get('authors')
572
572
573 if not dest:
573 if not dest:
574 dest = hg.defaultdest(src) + "-hg"
574 dest = hg.defaultdest(src) + "-hg"
575 ui.status(_("assuming destination %s\n") % dest)
575 ui.status(_("assuming destination %s\n") % dest)
576
576
577 destc = convertsink(ui, dest, opts.get('dest_type'))
577 destc = convertsink(ui, dest, opts.get('dest_type'))
578
578
579 try:
579 try:
580 srcc, defaultsort = convertsource(ui, src, opts.get('source_type'),
580 srcc, defaultsort = convertsource(ui, src, opts.get('source_type'),
581 opts.get('rev'))
581 opts.get('rev'))
582 except Exception:
582 except Exception:
583 for path in destc.created:
583 for path in destc.created:
584 shutil.rmtree(path, True)
584 shutil.rmtree(path, True)
585 raise
585 raise
586
586
587 sortmodes = ('branchsort', 'datesort', 'sourcesort', 'closesort')
587 sortmodes = ('branchsort', 'datesort', 'sourcesort', 'closesort')
588 sortmode = [m for m in sortmodes if opts.get(m)]
588 sortmode = [m for m in sortmodes if opts.get(m)]
589 if len(sortmode) > 1:
589 if len(sortmode) > 1:
590 raise error.Abort(_('more than one sort mode specified'))
590 raise error.Abort(_('more than one sort mode specified'))
591 if sortmode:
591 if sortmode:
592 sortmode = sortmode[0]
592 sortmode = sortmode[0]
593 else:
593 else:
594 sortmode = defaultsort
594 sortmode = defaultsort
595
595
596 if sortmode == 'sourcesort' and not srcc.hasnativeorder():
596 if sortmode == 'sourcesort' and not srcc.hasnativeorder():
597 raise error.Abort(_('--sourcesort is not supported by this data source')
597 raise error.Abort(_('--sourcesort is not supported by this data source')
598 )
598 )
599 if sortmode == 'closesort' and not srcc.hasnativeclose():
599 if sortmode == 'closesort' and not srcc.hasnativeclose():
600 raise error.Abort(_('--closesort is not supported by this data source'))
600 raise error.Abort(_('--closesort is not supported by this data source'))
601
601
602 fmap = opts.get('filemap')
602 fmap = opts.get('filemap')
603 if fmap:
603 if fmap:
604 srcc = filemap.filemap_source(ui, srcc, fmap)
604 srcc = filemap.filemap_source(ui, srcc, fmap)
605 destc.setfilemapmode(True)
605 destc.setfilemapmode(True)
606
606
607 if not revmapfile:
607 if not revmapfile:
608 revmapfile = destc.revmapfile()
608 revmapfile = destc.revmapfile()
609
609
610 c = converter(ui, srcc, destc, revmapfile, opts)
610 c = converter(ui, srcc, destc, revmapfile, opts)
611 c.convert(sortmode)
611 c.convert(sortmode)
General Comments 0
You need to be logged in to leave comments. Login now