##// END OF EJS Templates
convert: tolerate embedded spaces in filemap source revisions
Patrick Mezard -
r7073:af1117f3 default
parent child Browse files
Show More
@@ -1,357 +1,357 b''
1 1 # common code for the convert extension
2 2 import base64, errno
3 3 import os
4 4 import cPickle as pickle
5 from mercurial import util
5 from mercurial import util, strutil
6 6 from mercurial.i18n import _
7 7
8 8 def encodeargs(args):
9 9 def encodearg(s):
10 10 lines = base64.encodestring(s)
11 11 lines = [l.splitlines()[0] for l in lines]
12 12 return ''.join(lines)
13 13
14 14 s = pickle.dumps(args)
15 15 return encodearg(s)
16 16
17 17 def decodeargs(s):
18 18 s = base64.decodestring(s)
19 19 return pickle.loads(s)
20 20
21 21 class MissingTool(Exception): pass
22 22
23 23 def checktool(exe, name=None, abort=True):
24 24 name = name or exe
25 25 if not util.find_exe(exe):
26 26 exc = abort and util.Abort or MissingTool
27 27 raise exc(_('cannot find required "%s" tool') % name)
28 28
29 29 class NoRepo(Exception): pass
30 30
31 31 SKIPREV = 'SKIP'
32 32
33 33 class commit(object):
34 34 def __init__(self, author, date, desc, parents, branch=None, rev=None,
35 35 extra={}):
36 36 self.author = author or 'unknown'
37 37 self.date = date or '0 0'
38 38 self.desc = desc
39 39 self.parents = parents
40 40 self.branch = branch
41 41 self.rev = rev
42 42 self.extra = extra
43 43
44 44 class converter_source(object):
45 45 """Conversion source interface"""
46 46
47 47 def __init__(self, ui, path=None, rev=None):
48 48 """Initialize conversion source (or raise NoRepo("message")
49 49 exception if path is not a valid repository)"""
50 50 self.ui = ui
51 51 self.path = path
52 52 self.rev = rev
53 53
54 54 self.encoding = 'utf-8'
55 55
56 56 def before(self):
57 57 pass
58 58
59 59 def after(self):
60 60 pass
61 61
62 62 def setrevmap(self, revmap):
63 63 """set the map of already-converted revisions"""
64 64 pass
65 65
66 66 def getheads(self):
67 67 """Return a list of this repository's heads"""
68 68 raise NotImplementedError()
69 69
70 70 def getfile(self, name, rev):
71 71 """Return file contents as a string"""
72 72 raise NotImplementedError()
73 73
74 74 def getmode(self, name, rev):
75 75 """Return file mode, eg. '', 'x', or 'l'"""
76 76 raise NotImplementedError()
77 77
78 78 def getchanges(self, version):
79 79 """Returns a tuple of (files, copies)
80 80 Files is a sorted list of (filename, id) tuples for all files changed
81 81 in version, where id is the source revision id of the file.
82 82
83 83 copies is a dictionary of dest: source
84 84 """
85 85 raise NotImplementedError()
86 86
87 87 def getcommit(self, version):
88 88 """Return the commit object for version"""
89 89 raise NotImplementedError()
90 90
91 91 def gettags(self):
92 92 """Return the tags as a dictionary of name: revision"""
93 93 raise NotImplementedError()
94 94
95 95 def recode(self, s, encoding=None):
96 96 if not encoding:
97 97 encoding = self.encoding or 'utf-8'
98 98
99 99 if isinstance(s, unicode):
100 100 return s.encode("utf-8")
101 101 try:
102 102 return s.decode(encoding).encode("utf-8")
103 103 except:
104 104 try:
105 105 return s.decode("latin-1").encode("utf-8")
106 106 except:
107 107 return s.decode(encoding, "replace").encode("utf-8")
108 108
109 109 def getchangedfiles(self, rev, i):
110 110 """Return the files changed by rev compared to parent[i].
111 111
112 112 i is an index selecting one of the parents of rev. The return
113 113 value should be the list of files that are different in rev and
114 114 this parent.
115 115
116 116 If rev has no parents, i is None.
117 117
118 118 This function is only needed to support --filemap
119 119 """
120 120 raise NotImplementedError()
121 121
122 122 def converted(self, rev, sinkrev):
123 123 '''Notify the source that a revision has been converted.'''
124 124 pass
125 125
126 126
127 127 class converter_sink(object):
128 128 """Conversion sink (target) interface"""
129 129
130 130 def __init__(self, ui, path):
131 131 """Initialize conversion sink (or raise NoRepo("message")
132 132 exception if path is not a valid repository)
133 133
134 134 created is a list of paths to remove if a fatal error occurs
135 135 later"""
136 136 self.ui = ui
137 137 self.path = path
138 138 self.created = []
139 139
140 140 def getheads(self):
141 141 """Return a list of this repository's heads"""
142 142 raise NotImplementedError()
143 143
144 144 def revmapfile(self):
145 145 """Path to a file that will contain lines
146 146 source_rev_id sink_rev_id
147 147 mapping equivalent revision identifiers for each system."""
148 148 raise NotImplementedError()
149 149
150 150 def authorfile(self):
151 151 """Path to a file that will contain lines
152 152 srcauthor=dstauthor
153 153 mapping equivalent authors identifiers for each system."""
154 154 return None
155 155
156 156 def putfile(self, f, e, data):
157 157 """Put file for next putcommit().
158 158 f: path to file
159 159 e: '', 'x', or 'l' (regular file, executable, or symlink)
160 160 data: file contents"""
161 161 raise NotImplementedError()
162 162
163 163 def delfile(self, f):
164 164 """Delete file for next putcommit().
165 165 f: path to file"""
166 166 raise NotImplementedError()
167 167
168 168 def putcommit(self, files, parents, commit):
169 169 """Create a revision with all changed files listed in 'files'
170 170 and having listed parents. 'commit' is a commit object containing
171 171 at a minimum the author, date, and message for this changeset.
172 172 Called after putfile() and delfile() calls. Note that the sink
173 173 repository is not told to update itself to a particular revision
174 174 (or even what that revision would be) before it receives the
175 175 file data."""
176 176 raise NotImplementedError()
177 177
178 178 def puttags(self, tags):
179 179 """Put tags into sink.
180 180 tags: {tagname: sink_rev_id, ...}"""
181 181 raise NotImplementedError()
182 182
183 183 def setbranch(self, branch, pbranches):
184 184 """Set the current branch name. Called before the first putfile
185 185 on the branch.
186 186 branch: branch name for subsequent commits
187 187 pbranches: (converted parent revision, parent branch) tuples"""
188 188 pass
189 189
190 190 def setfilemapmode(self, active):
191 191 """Tell the destination that we're using a filemap
192 192
193 193 Some converter_sources (svn in particular) can claim that a file
194 194 was changed in a revision, even if there was no change. This method
195 195 tells the destination that we're using a filemap and that it should
196 196 filter empty revisions.
197 197 """
198 198 pass
199 199
200 200 def before(self):
201 201 pass
202 202
203 203 def after(self):
204 204 pass
205 205
206 206
207 207 class commandline(object):
208 208 def __init__(self, ui, command):
209 209 self.ui = ui
210 210 self.command = command
211 211
212 212 def prerun(self):
213 213 pass
214 214
215 215 def postrun(self):
216 216 pass
217 217
218 218 def _cmdline(self, cmd, *args, **kwargs):
219 219 cmdline = [self.command, cmd] + list(args)
220 220 for k, v in kwargs.iteritems():
221 221 if len(k) == 1:
222 222 cmdline.append('-' + k)
223 223 else:
224 224 cmdline.append('--' + k.replace('_', '-'))
225 225 try:
226 226 if len(k) == 1:
227 227 cmdline.append('' + v)
228 228 else:
229 229 cmdline[-1] += '=' + v
230 230 except TypeError:
231 231 pass
232 232 cmdline = [util.shellquote(arg) for arg in cmdline]
233 233 cmdline += ['2>', util.nulldev, '<', util.nulldev]
234 234 cmdline = ' '.join(cmdline)
235 235 return cmdline
236 236
237 237 def _run(self, cmd, *args, **kwargs):
238 238 cmdline = self._cmdline(cmd, *args, **kwargs)
239 239 self.ui.debug('running: %s\n' % (cmdline,))
240 240 self.prerun()
241 241 try:
242 242 return util.popen(cmdline)
243 243 finally:
244 244 self.postrun()
245 245
246 246 def run(self, cmd, *args, **kwargs):
247 247 fp = self._run(cmd, *args, **kwargs)
248 248 output = fp.read()
249 249 self.ui.debug(output)
250 250 return output, fp.close()
251 251
252 252 def runlines(self, cmd, *args, **kwargs):
253 253 fp = self._run(cmd, *args, **kwargs)
254 254 output = fp.readlines()
255 255 self.ui.debug(''.join(output))
256 256 return output, fp.close()
257 257
258 258 def checkexit(self, status, output=''):
259 259 if status:
260 260 if output:
261 261 self.ui.warn(_('%s error:\n') % self.command)
262 262 self.ui.warn(output)
263 263 msg = util.explain_exit(status)[0]
264 264 raise util.Abort(_('%s %s') % (self.command, msg))
265 265
266 266 def run0(self, cmd, *args, **kwargs):
267 267 output, status = self.run(cmd, *args, **kwargs)
268 268 self.checkexit(status, output)
269 269 return output
270 270
271 271 def runlines0(self, cmd, *args, **kwargs):
272 272 output, status = self.runlines(cmd, *args, **kwargs)
273 273 self.checkexit(status, ''.join(output))
274 274 return output
275 275
276 276 def getargmax(self):
277 277 if '_argmax' in self.__dict__:
278 278 return self._argmax
279 279
280 280 # POSIX requires at least 4096 bytes for ARG_MAX
281 281 self._argmax = 4096
282 282 try:
283 283 self._argmax = os.sysconf("SC_ARG_MAX")
284 284 except:
285 285 pass
286 286
287 287 # Windows shells impose their own limits on command line length,
288 288 # down to 2047 bytes for cmd.exe under Windows NT/2k and 2500 bytes
289 289 # for older 4nt.exe. See http://support.microsoft.com/kb/830473 for
290 290 # details about cmd.exe limitations.
291 291
292 292 # Since ARG_MAX is for command line _and_ environment, lower our limit
293 293 # (and make happy Windows shells while doing this).
294 294
295 295 self._argmax = self._argmax/2 - 1
296 296 return self._argmax
297 297
298 298 def limit_arglist(self, arglist, cmd, *args, **kwargs):
299 299 limit = self.getargmax() - len(self._cmdline(cmd, *args, **kwargs))
300 300 bytes = 0
301 301 fl = []
302 302 for fn in arglist:
303 303 b = len(fn) + 3
304 304 if bytes + b < limit or len(fl) == 0:
305 305 fl.append(fn)
306 306 bytes += b
307 307 else:
308 308 yield fl
309 309 fl = [fn]
310 310 bytes = b
311 311 if fl:
312 312 yield fl
313 313
314 314 def xargs(self, arglist, cmd, *args, **kwargs):
315 315 for l in self.limit_arglist(arglist, cmd, *args, **kwargs):
316 316 self.run0(cmd, *(list(args) + l), **kwargs)
317 317
318 318 class mapfile(dict):
319 319 def __init__(self, ui, path):
320 320 super(mapfile, self).__init__()
321 321 self.ui = ui
322 322 self.path = path
323 323 self.fp = None
324 324 self.order = []
325 325 self._read()
326 326
327 327 def _read(self):
328 328 if self.path is None:
329 329 return
330 330 try:
331 331 fp = open(self.path, 'r')
332 332 except IOError, err:
333 333 if err.errno != errno.ENOENT:
334 334 raise
335 335 return
336 336 for line in fp:
337 key, value = line[:-1].split(' ', 1)
337 key, value = strutil.rsplit(line[:-1], ' ', 1)
338 338 if key not in self:
339 339 self.order.append(key)
340 340 super(mapfile, self).__setitem__(key, value)
341 341 fp.close()
342 342
343 343 def __setitem__(self, key, value):
344 344 if self.fp is None:
345 345 try:
346 346 self.fp = open(self.path, 'a')
347 347 except IOError, err:
348 348 raise util.Abort(_('could not open map file %r: %s') %
349 349 (self.path, err.strerror))
350 350 self.fp.write('%s %s\n' % (key, value))
351 351 self.fp.flush()
352 352 super(mapfile, self).__setitem__(key, value)
353 353
354 354 def close(self):
355 355 if self.fp:
356 356 self.fp.close()
357 357 self.fp = None
@@ -1,34 +1,41 b''
1 1 # strutil.py - string utilities for Mercurial
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 def findall(haystack, needle, start=0, end=None):
9 9 if end is None:
10 10 end = len(haystack)
11 11 if end < 0:
12 12 end += len(haystack)
13 13 if start < 0:
14 14 start += len(haystack)
15 15 while start < end:
16 16 c = haystack.find(needle, start, end)
17 17 if c == -1:
18 18 break
19 19 yield c
20 20 start = c + 1
21 21
22 22 def rfindall(haystack, needle, start=0, end=None):
23 23 if end is None:
24 24 end = len(haystack)
25 25 if end < 0:
26 26 end += len(haystack)
27 27 if start < 0:
28 28 start += len(haystack)
29 29 while end >= 0:
30 30 c = haystack.rfind(needle, start, end)
31 31 if c == -1:
32 32 break
33 33 yield c
34 34 end = c - 1
35
36 def rsplit(s, sep=None, maxsplit=-1):
37 try:
38 return s.rsplit(sep, maxsplit)
39 except AttributeError:
40 return [chunk[::-1] for chunk in
41 s[::-1].split(sep, maxsplit)[::-1]]
General Comments 0
You need to be logged in to leave comments. Login now