##// END OF EJS Templates
convert: deal with empty splicemap path (issue3311)
Matt Mackall -
r16265:7887b976 stable
parent child Browse files
Show More
@@ -1,443 +1,445
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
7
8 import base64, errno
8 import base64, errno
9 import os
9 import os
10 import cPickle as pickle
10 import cPickle as pickle
11 from mercurial import util
11 from mercurial import util
12 from mercurial.i18n import _
12 from mercurial.i18n import _
13
13
14 propertycache = util.propertycache
14 propertycache = util.propertycache
15
15
16 def encodeargs(args):
16 def encodeargs(args):
17 def encodearg(s):
17 def encodearg(s):
18 lines = base64.encodestring(s)
18 lines = base64.encodestring(s)
19 lines = [l.splitlines()[0] for l in lines]
19 lines = [l.splitlines()[0] for l in lines]
20 return ''.join(lines)
20 return ''.join(lines)
21
21
22 s = pickle.dumps(args)
22 s = pickle.dumps(args)
23 return encodearg(s)
23 return encodearg(s)
24
24
25 def decodeargs(s):
25 def decodeargs(s):
26 s = base64.decodestring(s)
26 s = base64.decodestring(s)
27 return pickle.loads(s)
27 return pickle.loads(s)
28
28
29 class MissingTool(Exception):
29 class MissingTool(Exception):
30 pass
30 pass
31
31
32 def checktool(exe, name=None, abort=True):
32 def checktool(exe, name=None, abort=True):
33 name = name or exe
33 name = name or exe
34 if not util.findexe(exe):
34 if not util.findexe(exe):
35 exc = abort and util.Abort or MissingTool
35 exc = abort and util.Abort or MissingTool
36 raise exc(_('cannot find required "%s" tool') % name)
36 raise exc(_('cannot find required "%s" tool') % name)
37
37
38 class NoRepo(Exception):
38 class NoRepo(Exception):
39 pass
39 pass
40
40
41 SKIPREV = 'SKIP'
41 SKIPREV = 'SKIP'
42
42
43 class commit(object):
43 class commit(object):
44 def __init__(self, author, date, desc, parents, branch=None, rev=None,
44 def __init__(self, author, date, desc, parents, branch=None, rev=None,
45 extra={}, sortkey=None):
45 extra={}, sortkey=None):
46 self.author = author or 'unknown'
46 self.author = author or 'unknown'
47 self.date = date or '0 0'
47 self.date = date or '0 0'
48 self.desc = desc
48 self.desc = desc
49 self.parents = parents
49 self.parents = parents
50 self.branch = branch
50 self.branch = branch
51 self.rev = rev
51 self.rev = rev
52 self.extra = extra
52 self.extra = extra
53 self.sortkey = sortkey
53 self.sortkey = sortkey
54
54
55 class converter_source(object):
55 class converter_source(object):
56 """Conversion source interface"""
56 """Conversion source interface"""
57
57
58 def __init__(self, ui, path=None, rev=None):
58 def __init__(self, ui, path=None, rev=None):
59 """Initialize conversion source (or raise NoRepo("message")
59 """Initialize conversion source (or raise NoRepo("message")
60 exception if path is not a valid repository)"""
60 exception if path is not a valid repository)"""
61 self.ui = ui
61 self.ui = ui
62 self.path = path
62 self.path = path
63 self.rev = rev
63 self.rev = rev
64
64
65 self.encoding = 'utf-8'
65 self.encoding = 'utf-8'
66
66
67 def before(self):
67 def before(self):
68 pass
68 pass
69
69
70 def after(self):
70 def after(self):
71 pass
71 pass
72
72
73 def setrevmap(self, revmap):
73 def setrevmap(self, revmap):
74 """set the map of already-converted revisions"""
74 """set the map of already-converted revisions"""
75 pass
75 pass
76
76
77 def getheads(self):
77 def getheads(self):
78 """Return a list of this repository's heads"""
78 """Return a list of this repository's heads"""
79 raise NotImplementedError()
79 raise NotImplementedError()
80
80
81 def getfile(self, name, rev):
81 def getfile(self, name, rev):
82 """Return a pair (data, mode) where data is the file content
82 """Return a pair (data, mode) where data is the file content
83 as a string and mode one of '', 'x' or 'l'. rev is the
83 as a string and mode one of '', 'x' or 'l'. rev is the
84 identifier returned by a previous call to getchanges(). Raise
84 identifier returned by a previous call to getchanges(). Raise
85 IOError to indicate that name was deleted in rev.
85 IOError to indicate that name was deleted in rev.
86 """
86 """
87 raise NotImplementedError()
87 raise NotImplementedError()
88
88
89 def getchanges(self, version):
89 def getchanges(self, version):
90 """Returns a tuple of (files, copies).
90 """Returns a tuple of (files, copies).
91
91
92 files is a sorted list of (filename, id) tuples for all files
92 files is a sorted list of (filename, id) tuples for all files
93 changed between version and its first parent returned by
93 changed between version and its first parent returned by
94 getcommit(). id is the source revision id of the file.
94 getcommit(). id is the source revision id of the file.
95
95
96 copies is a dictionary of dest: source
96 copies is a dictionary of dest: source
97 """
97 """
98 raise NotImplementedError()
98 raise NotImplementedError()
99
99
100 def getcommit(self, version):
100 def getcommit(self, version):
101 """Return the commit object for version"""
101 """Return the commit object for version"""
102 raise NotImplementedError()
102 raise NotImplementedError()
103
103
104 def gettags(self):
104 def gettags(self):
105 """Return the tags as a dictionary of name: revision
105 """Return the tags as a dictionary of name: revision
106
106
107 Tag names must be UTF-8 strings.
107 Tag names must be UTF-8 strings.
108 """
108 """
109 raise NotImplementedError()
109 raise NotImplementedError()
110
110
111 def recode(self, s, encoding=None):
111 def recode(self, s, encoding=None):
112 if not encoding:
112 if not encoding:
113 encoding = self.encoding or 'utf-8'
113 encoding = self.encoding or 'utf-8'
114
114
115 if isinstance(s, unicode):
115 if isinstance(s, unicode):
116 return s.encode("utf-8")
116 return s.encode("utf-8")
117 try:
117 try:
118 return s.decode(encoding).encode("utf-8")
118 return s.decode(encoding).encode("utf-8")
119 except:
119 except:
120 try:
120 try:
121 return s.decode("latin-1").encode("utf-8")
121 return s.decode("latin-1").encode("utf-8")
122 except:
122 except:
123 return s.decode(encoding, "replace").encode("utf-8")
123 return s.decode(encoding, "replace").encode("utf-8")
124
124
125 def getchangedfiles(self, rev, i):
125 def getchangedfiles(self, rev, i):
126 """Return the files changed by rev compared to parent[i].
126 """Return the files changed by rev compared to parent[i].
127
127
128 i is an index selecting one of the parents of rev. The return
128 i is an index selecting one of the parents of rev. The return
129 value should be the list of files that are different in rev and
129 value should be the list of files that are different in rev and
130 this parent.
130 this parent.
131
131
132 If rev has no parents, i is None.
132 If rev has no parents, i is None.
133
133
134 This function is only needed to support --filemap
134 This function is only needed to support --filemap
135 """
135 """
136 raise NotImplementedError()
136 raise NotImplementedError()
137
137
138 def converted(self, rev, sinkrev):
138 def converted(self, rev, sinkrev):
139 '''Notify the source that a revision has been converted.'''
139 '''Notify the source that a revision has been converted.'''
140 pass
140 pass
141
141
142 def hasnativeorder(self):
142 def hasnativeorder(self):
143 """Return true if this source has a meaningful, native revision
143 """Return true if this source has a meaningful, native revision
144 order. For instance, Mercurial revisions are store sequentially
144 order. For instance, Mercurial revisions are store sequentially
145 while there is no such global ordering with Darcs.
145 while there is no such global ordering with Darcs.
146 """
146 """
147 return False
147 return False
148
148
149 def lookuprev(self, rev):
149 def lookuprev(self, rev):
150 """If rev is a meaningful revision reference in source, return
150 """If rev is a meaningful revision reference in source, return
151 the referenced identifier in the same format used by getcommit().
151 the referenced identifier in the same format used by getcommit().
152 return None otherwise.
152 return None otherwise.
153 """
153 """
154 return None
154 return None
155
155
156 def getbookmarks(self):
156 def getbookmarks(self):
157 """Return the bookmarks as a dictionary of name: revision
157 """Return the bookmarks as a dictionary of name: revision
158
158
159 Bookmark names are to be UTF-8 strings.
159 Bookmark names are to be UTF-8 strings.
160 """
160 """
161 return {}
161 return {}
162
162
163 class converter_sink(object):
163 class converter_sink(object):
164 """Conversion sink (target) interface"""
164 """Conversion sink (target) interface"""
165
165
166 def __init__(self, ui, path):
166 def __init__(self, ui, path):
167 """Initialize conversion sink (or raise NoRepo("message")
167 """Initialize conversion sink (or raise NoRepo("message")
168 exception if path is not a valid repository)
168 exception if path is not a valid repository)
169
169
170 created is a list of paths to remove if a fatal error occurs
170 created is a list of paths to remove if a fatal error occurs
171 later"""
171 later"""
172 self.ui = ui
172 self.ui = ui
173 self.path = path
173 self.path = path
174 self.created = []
174 self.created = []
175
175
176 def getheads(self):
176 def getheads(self):
177 """Return a list of this repository's heads"""
177 """Return a list of this repository's heads"""
178 raise NotImplementedError()
178 raise NotImplementedError()
179
179
180 def revmapfile(self):
180 def revmapfile(self):
181 """Path to a file that will contain lines
181 """Path to a file that will contain lines
182 source_rev_id sink_rev_id
182 source_rev_id sink_rev_id
183 mapping equivalent revision identifiers for each system."""
183 mapping equivalent revision identifiers for each system."""
184 raise NotImplementedError()
184 raise NotImplementedError()
185
185
186 def authorfile(self):
186 def authorfile(self):
187 """Path to a file that will contain lines
187 """Path to a file that will contain lines
188 srcauthor=dstauthor
188 srcauthor=dstauthor
189 mapping equivalent authors identifiers for each system."""
189 mapping equivalent authors identifiers for each system."""
190 return None
190 return None
191
191
192 def putcommit(self, files, copies, parents, commit, source, revmap):
192 def putcommit(self, files, copies, parents, commit, source, revmap):
193 """Create a revision with all changed files listed in 'files'
193 """Create a revision with all changed files listed in 'files'
194 and having listed parents. 'commit' is a commit object
194 and having listed parents. 'commit' is a commit object
195 containing at a minimum the author, date, and message for this
195 containing at a minimum the author, date, and message for this
196 changeset. 'files' is a list of (path, version) tuples,
196 changeset. 'files' is a list of (path, version) tuples,
197 'copies' is a dictionary mapping destinations to sources,
197 'copies' is a dictionary mapping destinations to sources,
198 'source' is the source repository, and 'revmap' is a mapfile
198 'source' is the source repository, and 'revmap' is a mapfile
199 of source revisions to converted revisions. Only getfile() and
199 of source revisions to converted revisions. Only getfile() and
200 lookuprev() should be called on 'source'.
200 lookuprev() should be called on 'source'.
201
201
202 Note that the sink repository is not told to update itself to
202 Note that the sink repository is not told to update itself to
203 a particular revision (or even what that revision would be)
203 a particular revision (or even what that revision would be)
204 before it receives the file data.
204 before it receives the file data.
205 """
205 """
206 raise NotImplementedError()
206 raise NotImplementedError()
207
207
208 def puttags(self, tags):
208 def puttags(self, tags):
209 """Put tags into sink.
209 """Put tags into sink.
210
210
211 tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
211 tags: {tagname: sink_rev_id, ...} where tagname is an UTF-8 string.
212 Return a pair (tag_revision, tag_parent_revision), or (None, None)
212 Return a pair (tag_revision, tag_parent_revision), or (None, None)
213 if nothing was changed.
213 if nothing was changed.
214 """
214 """
215 raise NotImplementedError()
215 raise NotImplementedError()
216
216
217 def setbranch(self, branch, pbranches):
217 def setbranch(self, branch, pbranches):
218 """Set the current branch name. Called before the first putcommit
218 """Set the current branch name. Called before the first putcommit
219 on the branch.
219 on the branch.
220 branch: branch name for subsequent commits
220 branch: branch name for subsequent commits
221 pbranches: (converted parent revision, parent branch) tuples"""
221 pbranches: (converted parent revision, parent branch) tuples"""
222 pass
222 pass
223
223
224 def setfilemapmode(self, active):
224 def setfilemapmode(self, active):
225 """Tell the destination that we're using a filemap
225 """Tell the destination that we're using a filemap
226
226
227 Some converter_sources (svn in particular) can claim that a file
227 Some converter_sources (svn in particular) can claim that a file
228 was changed in a revision, even if there was no change. This method
228 was changed in a revision, even if there was no change. This method
229 tells the destination that we're using a filemap and that it should
229 tells the destination that we're using a filemap and that it should
230 filter empty revisions.
230 filter empty revisions.
231 """
231 """
232 pass
232 pass
233
233
234 def before(self):
234 def before(self):
235 pass
235 pass
236
236
237 def after(self):
237 def after(self):
238 pass
238 pass
239
239
240 def putbookmarks(self, bookmarks):
240 def putbookmarks(self, bookmarks):
241 """Put bookmarks into sink.
241 """Put bookmarks into sink.
242
242
243 bookmarks: {bookmarkname: sink_rev_id, ...}
243 bookmarks: {bookmarkname: sink_rev_id, ...}
244 where bookmarkname is an UTF-8 string.
244 where bookmarkname is an UTF-8 string.
245 """
245 """
246 pass
246 pass
247
247
248 def hascommit(self, rev):
248 def hascommit(self, rev):
249 """Return True if the sink contains rev"""
249 """Return True if the sink contains rev"""
250 raise NotImplementedError()
250 raise NotImplementedError()
251
251
252 class commandline(object):
252 class commandline(object):
253 def __init__(self, ui, command):
253 def __init__(self, ui, command):
254 self.ui = ui
254 self.ui = ui
255 self.command = command
255 self.command = command
256
256
257 def prerun(self):
257 def prerun(self):
258 pass
258 pass
259
259
260 def postrun(self):
260 def postrun(self):
261 pass
261 pass
262
262
263 def _cmdline(self, cmd, closestdin, *args, **kwargs):
263 def _cmdline(self, cmd, closestdin, *args, **kwargs):
264 cmdline = [self.command, cmd] + list(args)
264 cmdline = [self.command, cmd] + list(args)
265 for k, v in kwargs.iteritems():
265 for k, v in kwargs.iteritems():
266 if len(k) == 1:
266 if len(k) == 1:
267 cmdline.append('-' + k)
267 cmdline.append('-' + k)
268 else:
268 else:
269 cmdline.append('--' + k.replace('_', '-'))
269 cmdline.append('--' + k.replace('_', '-'))
270 try:
270 try:
271 if len(k) == 1:
271 if len(k) == 1:
272 cmdline.append('' + v)
272 cmdline.append('' + v)
273 else:
273 else:
274 cmdline[-1] += '=' + v
274 cmdline[-1] += '=' + v
275 except TypeError:
275 except TypeError:
276 pass
276 pass
277 cmdline = [util.shellquote(arg) for arg in cmdline]
277 cmdline = [util.shellquote(arg) for arg in cmdline]
278 if not self.ui.debugflag:
278 if not self.ui.debugflag:
279 cmdline += ['2>', util.nulldev]
279 cmdline += ['2>', util.nulldev]
280 if closestdin:
280 if closestdin:
281 cmdline += ['<', util.nulldev]
281 cmdline += ['<', util.nulldev]
282 cmdline = ' '.join(cmdline)
282 cmdline = ' '.join(cmdline)
283 return cmdline
283 return cmdline
284
284
285 def _run(self, cmd, *args, **kwargs):
285 def _run(self, cmd, *args, **kwargs):
286 return self._dorun(util.popen, cmd, True, *args, **kwargs)
286 return self._dorun(util.popen, cmd, True, *args, **kwargs)
287
287
288 def _run2(self, cmd, *args, **kwargs):
288 def _run2(self, cmd, *args, **kwargs):
289 return self._dorun(util.popen2, cmd, False, *args, **kwargs)
289 return self._dorun(util.popen2, cmd, False, *args, **kwargs)
290
290
291 def _dorun(self, openfunc, cmd, closestdin, *args, **kwargs):
291 def _dorun(self, openfunc, cmd, closestdin, *args, **kwargs):
292 cmdline = self._cmdline(cmd, closestdin, *args, **kwargs)
292 cmdline = self._cmdline(cmd, closestdin, *args, **kwargs)
293 self.ui.debug('running: %s\n' % (cmdline,))
293 self.ui.debug('running: %s\n' % (cmdline,))
294 self.prerun()
294 self.prerun()
295 try:
295 try:
296 return openfunc(cmdline)
296 return openfunc(cmdline)
297 finally:
297 finally:
298 self.postrun()
298 self.postrun()
299
299
300 def run(self, cmd, *args, **kwargs):
300 def run(self, cmd, *args, **kwargs):
301 fp = self._run(cmd, *args, **kwargs)
301 fp = self._run(cmd, *args, **kwargs)
302 output = fp.read()
302 output = fp.read()
303 self.ui.debug(output)
303 self.ui.debug(output)
304 return output, fp.close()
304 return output, fp.close()
305
305
306 def runlines(self, cmd, *args, **kwargs):
306 def runlines(self, cmd, *args, **kwargs):
307 fp = self._run(cmd, *args, **kwargs)
307 fp = self._run(cmd, *args, **kwargs)
308 output = fp.readlines()
308 output = fp.readlines()
309 self.ui.debug(''.join(output))
309 self.ui.debug(''.join(output))
310 return output, fp.close()
310 return output, fp.close()
311
311
312 def checkexit(self, status, output=''):
312 def checkexit(self, status, output=''):
313 if status:
313 if status:
314 if output:
314 if output:
315 self.ui.warn(_('%s error:\n') % self.command)
315 self.ui.warn(_('%s error:\n') % self.command)
316 self.ui.warn(output)
316 self.ui.warn(output)
317 msg = util.explainexit(status)[0]
317 msg = util.explainexit(status)[0]
318 raise util.Abort('%s %s' % (self.command, msg))
318 raise util.Abort('%s %s' % (self.command, msg))
319
319
320 def run0(self, cmd, *args, **kwargs):
320 def run0(self, cmd, *args, **kwargs):
321 output, status = self.run(cmd, *args, **kwargs)
321 output, status = self.run(cmd, *args, **kwargs)
322 self.checkexit(status, output)
322 self.checkexit(status, output)
323 return output
323 return output
324
324
325 def runlines0(self, cmd, *args, **kwargs):
325 def runlines0(self, cmd, *args, **kwargs):
326 output, status = self.runlines(cmd, *args, **kwargs)
326 output, status = self.runlines(cmd, *args, **kwargs)
327 self.checkexit(status, ''.join(output))
327 self.checkexit(status, ''.join(output))
328 return output
328 return output
329
329
330 @propertycache
330 @propertycache
331 def argmax(self):
331 def argmax(self):
332 # POSIX requires at least 4096 bytes for ARG_MAX
332 # POSIX requires at least 4096 bytes for ARG_MAX
333 argmax = 4096
333 argmax = 4096
334 try:
334 try:
335 argmax = os.sysconf("SC_ARG_MAX")
335 argmax = os.sysconf("SC_ARG_MAX")
336 except:
336 except:
337 pass
337 pass
338
338
339 # Windows shells impose their own limits on command line length,
339 # Windows shells impose their own limits on command line length,
340 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
340 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
341 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
341 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
342 # details about cmd.exe limitations.
342 # details about cmd.exe limitations.
343
343
344 # Since ARG_MAX is for command line _and_ environment, lower our limit
344 # Since ARG_MAX is for command line _and_ environment, lower our limit
345 # (and make happy Windows shells while doing this).
345 # (and make happy Windows shells while doing this).
346 return argmax // 2 - 1
346 return argmax // 2 - 1
347
347
348 def limit_arglist(self, arglist, cmd, closestdin, *args, **kwargs):
348 def limit_arglist(self, arglist, cmd, closestdin, *args, **kwargs):
349 cmdlen = len(self._cmdline(cmd, closestdin, *args, **kwargs))
349 cmdlen = len(self._cmdline(cmd, closestdin, *args, **kwargs))
350 limit = self.argmax - cmdlen
350 limit = self.argmax - cmdlen
351 bytes = 0
351 bytes = 0
352 fl = []
352 fl = []
353 for fn in arglist:
353 for fn in arglist:
354 b = len(fn) + 3
354 b = len(fn) + 3
355 if bytes + b < limit or len(fl) == 0:
355 if bytes + b < limit or len(fl) == 0:
356 fl.append(fn)
356 fl.append(fn)
357 bytes += b
357 bytes += b
358 else:
358 else:
359 yield fl
359 yield fl
360 fl = [fn]
360 fl = [fn]
361 bytes = b
361 bytes = b
362 if fl:
362 if fl:
363 yield fl
363 yield fl
364
364
365 def xargs(self, arglist, cmd, *args, **kwargs):
365 def xargs(self, arglist, cmd, *args, **kwargs):
366 for l in self.limit_arglist(arglist, cmd, True, *args, **kwargs):
366 for l in self.limit_arglist(arglist, cmd, True, *args, **kwargs):
367 self.run0(cmd, *(list(args) + l), **kwargs)
367 self.run0(cmd, *(list(args) + l), **kwargs)
368
368
369 class mapfile(dict):
369 class mapfile(dict):
370 def __init__(self, ui, path):
370 def __init__(self, ui, path):
371 super(mapfile, self).__init__()
371 super(mapfile, self).__init__()
372 self.ui = ui
372 self.ui = ui
373 self.path = path
373 self.path = path
374 self.fp = None
374 self.fp = None
375 self.order = []
375 self.order = []
376 self._read()
376 self._read()
377
377
378 def _read(self):
378 def _read(self):
379 if not self.path:
379 if not self.path:
380 return
380 return
381 try:
381 try:
382 fp = open(self.path, 'r')
382 fp = open(self.path, 'r')
383 except IOError, err:
383 except IOError, err:
384 if err.errno != errno.ENOENT:
384 if err.errno != errno.ENOENT:
385 raise
385 raise
386 return
386 return
387 for i, line in enumerate(fp):
387 for i, line in enumerate(fp):
388 line = line.splitlines()[0].rstrip()
388 line = line.splitlines()[0].rstrip()
389 if not line:
389 if not line:
390 # Ignore blank lines
390 # Ignore blank lines
391 continue
391 continue
392 try:
392 try:
393 key, value = line.rsplit(' ', 1)
393 key, value = line.rsplit(' ', 1)
394 except ValueError:
394 except ValueError:
395 raise util.Abort(
395 raise util.Abort(
396 _('syntax error in %s(%d): key/value pair expected')
396 _('syntax error in %s(%d): key/value pair expected')
397 % (self.path, i + 1))
397 % (self.path, i + 1))
398 if key not in self:
398 if key not in self:
399 self.order.append(key)
399 self.order.append(key)
400 super(mapfile, self).__setitem__(key, value)
400 super(mapfile, self).__setitem__(key, value)
401 fp.close()
401 fp.close()
402
402
403 def __setitem__(self, key, value):
403 def __setitem__(self, key, value):
404 if self.fp is None:
404 if self.fp is None:
405 try:
405 try:
406 self.fp = open(self.path, 'a')
406 self.fp = open(self.path, 'a')
407 except IOError, err:
407 except IOError, err:
408 raise util.Abort(_('could not open map file %r: %s') %
408 raise util.Abort(_('could not open map file %r: %s') %
409 (self.path, err.strerror))
409 (self.path, err.strerror))
410 self.fp.write('%s %s\n' % (key, value))
410 self.fp.write('%s %s\n' % (key, value))
411 self.fp.flush()
411 self.fp.flush()
412 super(mapfile, self).__setitem__(key, value)
412 super(mapfile, self).__setitem__(key, value)
413
413
414 def close(self):
414 def close(self):
415 if self.fp:
415 if self.fp:
416 self.fp.close()
416 self.fp.close()
417 self.fp = None
417 self.fp = None
418
418
419 def parsesplicemap(path):
419 def parsesplicemap(path):
420 """Parse a splicemap, return a child/parents dictionary."""
420 """Parse a splicemap, return a child/parents dictionary."""
421 if not path:
422 return {}
421 m = {}
423 m = {}
422 try:
424 try:
423 fp = open(path, 'r')
425 fp = open(path, 'r')
424 for i, line in enumerate(fp):
426 for i, line in enumerate(fp):
425 line = line.splitlines()[0].rstrip()
427 line = line.splitlines()[0].rstrip()
426 if not line:
428 if not line:
427 # Ignore blank lines
429 # Ignore blank lines
428 continue
430 continue
429 try:
431 try:
430 child, parents = line.split(' ', 1)
432 child, parents = line.split(' ', 1)
431 parents = parents.replace(',', ' ').split()
433 parents = parents.replace(',', ' ').split()
432 except ValueError:
434 except ValueError:
433 raise util.Abort(_('syntax error in %s(%d): child parent1'
435 raise util.Abort(_('syntax error in %s(%d): child parent1'
434 '[,parent2] expected') % (path, i + 1))
436 '[,parent2] expected') % (path, i + 1))
435 pp = []
437 pp = []
436 for p in parents:
438 for p in parents:
437 if p not in pp:
439 if p not in pp:
438 pp.append(p)
440 pp.append(p)
439 m[child] = pp
441 m[child] = pp
440 except IOError, e:
442 except IOError, e:
441 if e.errno != errno.ENOENT:
443 if e.errno != errno.ENOENT:
442 raise
444 raise
443 return m
445 return m
General Comments 0
You need to be logged in to leave comments. Login now