##// END OF EJS Templates
Don't decode unicode strings....
Thomas Arendsen Hein -
r5287:c6f932d3 default
parent child Browse files
Show More
@@ -1,143 +1,145
1 1 # common code for the convert extension
2 2 import base64
3 3 import cPickle as pickle
4 4
5 5 def encodeargs(args):
6 6 def encodearg(s):
7 7 lines = base64.encodestring(s)
8 8 lines = [l.splitlines()[0] for l in lines]
9 9 return ''.join(lines)
10 10
11 11 s = pickle.dumps(args)
12 12 return encodearg(s)
13 13
14 14 def decodeargs(s):
15 15 s = base64.decodestring(s)
16 16 return pickle.loads(s)
17 17
18 18 class NoRepo(Exception): pass
19 19
20 20 class commit(object):
21 21 def __init__(self, author, date, desc, parents, branch=None, rev=None):
22 22 self.author = author
23 23 self.date = date
24 24 self.desc = desc
25 25 self.parents = parents
26 26 self.branch = branch
27 27 self.rev = rev
28 28
29 29 class converter_source(object):
30 30 """Conversion source interface"""
31 31
32 32 def __init__(self, ui, path, rev=None):
33 33 """Initialize conversion source (or raise NoRepo("message")
34 34 exception if path is not a valid repository)"""
35 35 self.ui = ui
36 36 self.path = path
37 37 self.rev = rev
38 38
39 39 self.encoding = 'utf-8'
40 40
41 41 def setrevmap(self, revmap):
42 42 """set the map of already-converted revisions"""
43 43 pass
44 44
45 45 def getheads(self):
46 46 """Return a list of this repository's heads"""
47 47 raise NotImplementedError()
48 48
49 49 def getfile(self, name, rev):
50 50 """Return file contents as a string"""
51 51 raise NotImplementedError()
52 52
53 53 def getmode(self, name, rev):
54 54 """Return file mode, eg. '', 'x', or 'l'"""
55 55 raise NotImplementedError()
56 56
57 57 def getchanges(self, version):
58 58 """Returns a tuple of (files, copies)
59 59 Files is a sorted list of (filename, id) tuples for all files changed
60 60 in version, where id is the source revision id of the file.
61 61
62 62 copies is a dictionary of dest: source
63 63 """
64 64 raise NotImplementedError()
65 65
66 66 def getcommit(self, version):
67 67 """Return the commit object for version"""
68 68 raise NotImplementedError()
69 69
70 70 def gettags(self):
71 71 """Return the tags as a dictionary of name: revision"""
72 72 raise NotImplementedError()
73 73
74 74 def recode(self, s, encoding=None):
75 75 if not encoding:
76 76 encoding = self.encoding or 'utf-8'
77 77
78 if isinstance(s, unicode):
79 return s.encode("utf-8")
78 80 try:
79 81 return s.decode(encoding).encode("utf-8")
80 82 except:
81 83 try:
82 84 return s.decode("latin-1").encode("utf-8")
83 85 except:
84 86 return s.decode(encoding, "replace").encode("utf-8")
85 87
86 88 class converter_sink(object):
87 89 """Conversion sink (target) interface"""
88 90
89 91 def __init__(self, ui, path):
90 92 """Initialize conversion sink (or raise NoRepo("message")
91 93 exception if path is not a valid repository)"""
92 94 raise NotImplementedError()
93 95
94 96 def getheads(self):
95 97 """Return a list of this repository's heads"""
96 98 raise NotImplementedError()
97 99
98 100 def revmapfile(self):
99 101 """Path to a file that will contain lines
100 102 source_rev_id sink_rev_id
101 103 mapping equivalent revision identifiers for each system."""
102 104 raise NotImplementedError()
103 105
104 106 def authorfile(self):
105 107 """Path to a file that will contain lines
106 108 srcauthor=dstauthor
107 109 mapping equivalent authors identifiers for each system."""
108 110 return None
109 111
110 112 def putfile(self, f, e, data):
111 113 """Put file for next putcommit().
112 114 f: path to file
113 115 e: '', 'x', or 'l' (regular file, executable, or symlink)
114 116 data: file contents"""
115 117 raise NotImplementedError()
116 118
117 119 def delfile(self, f):
118 120 """Delete file for next putcommit().
119 121 f: path to file"""
120 122 raise NotImplementedError()
121 123
122 124 def putcommit(self, files, parents, commit):
123 125 """Create a revision with all changed files listed in 'files'
124 126 and having listed parents. 'commit' is a commit object containing
125 127 at a minimum the author, date, and message for this changeset.
126 128 Called after putfile() and delfile() calls. Note that the sink
127 129 repository is not told to update itself to a particular revision
128 130 (or even what that revision would be) before it receives the
129 131 file data."""
130 132 raise NotImplementedError()
131 133
132 134 def puttags(self, tags):
133 135 """Put tags into sink.
134 136 tags: {tagname: sink_rev_id, ...}"""
135 137 raise NotImplementedError()
136 138
137 139 def setbranch(self, branch, pbranch, parents):
138 140 """Set the current branch name. Called before the first putfile
139 141 on the branch.
140 142 branch: branch name for subsequent commits
141 143 pbranch: branch name of parent commit
142 144 parents: destination revisions of parent"""
143 145 pass
@@ -1,645 +1,646
1 1 # Subversion 1.4/1.5 Python API backend
2 2 #
3 3 # Copyright(C) 2007 Daniel Holth et al
4 4 #
5 5 # Configuration options:
6 6 #
7 7 # convert.svn.trunk
8 8 # Relative path to the trunk (default: "trunk")
9 9 # convert.svn.branches
10 10 # Relative path to tree of branches (default: "branches")
11 11 #
12 12 # Set these in a hgrc, or on the command line as follows:
13 13 #
14 14 # hg convert --config convert.svn.trunk=wackoname [...]
15 15
16 16 import locale
17 17 import os
18 18 import sys
19 19 import cPickle as pickle
20 20 from mercurial import util
21 21
22 22 # Subversion stuff. Works best with very recent Python SVN bindings
23 23 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
24 24 # these bindings.
25 25
26 26 from cStringIO import StringIO
27 27
28 28 from common import NoRepo, commit, converter_source, encodeargs, decodeargs
29 29
30 30 try:
31 31 from svn.core import SubversionException, Pool
32 32 import svn
33 33 import svn.client
34 34 import svn.core
35 35 import svn.ra
36 36 import svn.delta
37 37 import transport
38 38 except ImportError:
39 39 pass
40 40
41 41 def geturl(path):
42 42 try:
43 43 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
44 44 except SubversionException:
45 45 pass
46 46 if os.path.isdir(path):
47 47 return 'file://%s' % os.path.normpath(os.path.abspath(path))
48 48 return path
49 49
50 50 def optrev(number):
51 51 optrev = svn.core.svn_opt_revision_t()
52 52 optrev.kind = svn.core.svn_opt_revision_number
53 53 optrev.value.number = number
54 54 return optrev
55 55
56 56 class changedpath(object):
57 57 def __init__(self, p):
58 58 self.copyfrom_path = p.copyfrom_path
59 59 self.copyfrom_rev = p.copyfrom_rev
60 60 self.action = p.action
61 61
62 62 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
63 63 strict_node_history=False):
64 64 protocol = -1
65 65 def receiver(orig_paths, revnum, author, date, message, pool):
66 66 if orig_paths is not None:
67 67 for k, v in orig_paths.iteritems():
68 68 orig_paths[k] = changedpath(v)
69 69 pickle.dump((orig_paths, revnum, author, date, message),
70 70 fp, protocol)
71 71
72 72 try:
73 73 # Use an ra of our own so that our parent can consume
74 74 # our results without confusing the server.
75 75 t = transport.SvnRaTransport(url=url)
76 76 svn.ra.get_log(t.ra, paths, start, end, limit,
77 77 discover_changed_paths,
78 78 strict_node_history,
79 79 receiver)
80 80 except SubversionException, (inst, num):
81 81 pickle.dump(num, fp, protocol)
82 82 else:
83 83 pickle.dump(None, fp, protocol)
84 84 fp.close()
85 85
86 86 def debugsvnlog(ui, **opts):
87 87 """Fetch SVN log in a subprocess and channel them back to parent to
88 88 avoid memory collection issues.
89 89 """
90 90 util.set_binary(sys.stdin)
91 91 util.set_binary(sys.stdout)
92 92 args = decodeargs(sys.stdin.read())
93 93 get_log_child(sys.stdout, *args)
94 94
95 95 # SVN conversion code stolen from bzr-svn and tailor
96 96 class convert_svn(converter_source):
97 97 def __init__(self, ui, url, rev=None):
98 98 super(convert_svn, self).__init__(ui, url, rev=rev)
99 99
100 100 try:
101 101 SubversionException
102 102 except NameError:
103 103 msg = 'subversion python bindings could not be loaded\n'
104 104 ui.warn(msg)
105 105 raise NoRepo(msg)
106 106
107 107 self.encoding = locale.getpreferredencoding()
108 108 self.lastrevs = {}
109 109
110 110 latest = None
111 111 try:
112 112 # Support file://path@rev syntax. Useful e.g. to convert
113 113 # deleted branches.
114 114 at = url.rfind('@')
115 115 if at >= 0:
116 116 latest = int(url[at+1:])
117 117 url = url[:at]
118 118 except ValueError, e:
119 119 pass
120 120 self.url = geturl(url)
121 121 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
122 122 try:
123 123 self.transport = transport.SvnRaTransport(url=self.url)
124 124 self.ra = self.transport.ra
125 125 self.ctx = self.transport.client
126 126 self.base = svn.ra.get_repos_root(self.ra)
127 127 self.module = self.url[len(self.base):]
128 128 self.modulemap = {} # revision, module
129 129 self.commits = {}
130 130 self.paths = {}
131 131 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
132 132 except SubversionException, e:
133 133 raise NoRepo("couldn't open SVN repo %s" % self.url)
134 134
135 135 if rev:
136 136 try:
137 137 latest = int(rev)
138 138 except ValueError:
139 139 raise util.Abort('svn: revision %s is not an integer' % rev)
140 140
141 141 try:
142 142 self.get_blacklist()
143 143 except IOError, e:
144 144 pass
145 145
146 146 self.last_changed = self.latest(self.module, latest)
147 147
148 148 self.head = self.revid(self.last_changed)
149 149
150 150 def setrevmap(self, revmap):
151 151 lastrevs = {}
152 152 for revid in revmap.keys():
153 153 uuid, module, revnum = self.revsplit(revid)
154 154 lastrevnum = lastrevs.setdefault(module, revnum)
155 155 if revnum > lastrevnum:
156 156 lastrevs[module] = revnum
157 157 self.lastrevs = lastrevs
158 158
159 159 def exists(self, path, optrev):
160 160 try:
161 161 return svn.client.ls(self.url.rstrip('/') + '/' + path,
162 162 optrev, False, self.ctx)
163 163 except SubversionException, err:
164 164 return []
165 165
166 166 def getheads(self):
167 167 # detect standard /branches, /tags, /trunk layout
168 168 rev = optrev(self.last_changed)
169 169 rpath = self.url.strip('/')
170 170 cfgtrunk = self.ui.config('convert', 'svn.trunk')
171 171 cfgbranches = self.ui.config('convert', 'svn.branches')
172 172 trunk = (cfgtrunk or 'trunk').strip('/')
173 173 branches = (cfgbranches or 'branches').strip('/')
174 174 if self.exists(trunk, rev) and self.exists(branches, rev):
175 175 self.ui.note('found trunk at %r and branches at %r\n' %
176 176 (trunk, branches))
177 177 oldmodule = self.module
178 178 self.module += '/' + trunk
179 179 lt = self.latest(self.module, self.last_changed)
180 180 self.head = self.revid(lt)
181 181 self.heads = [self.head]
182 182 branchnames = svn.client.ls(rpath + '/' + branches, rev, False,
183 183 self.ctx)
184 184 for branch in branchnames.keys():
185 185 if oldmodule:
186 186 module = '/' + oldmodule + '/' + branches + '/' + branch
187 187 else:
188 188 module = '/' + branches + '/' + branch
189 189 brevnum = self.latest(module, self.last_changed)
190 190 brev = self.revid(brevnum, module)
191 191 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
192 192 self.heads.append(brev)
193 193 elif cfgtrunk or cfgbranches:
194 194 raise util.Abort('trunk/branch layout expected, but not found')
195 195 else:
196 196 self.ui.note('working with one branch\n')
197 197 self.heads = [self.head]
198 198 return self.heads
199 199
200 200 def getfile(self, file, rev):
201 201 data, mode = self._getfile(file, rev)
202 202 self.modecache[(file, rev)] = mode
203 203 return data
204 204
205 205 def getmode(self, file, rev):
206 206 return self.modecache[(file, rev)]
207 207
208 208 def getchanges(self, rev):
209 209 self.modecache = {}
210 210 (paths, parents) = self.paths[rev]
211 211 files, copies = self.expandpaths(rev, paths, parents)
212 212 files.sort()
213 213 files = zip(files, [rev] * len(files))
214 214
215 215 # caller caches the result, so free it here to release memory
216 216 del self.paths[rev]
217 217 return (files, copies)
218 218
219 219 def getcommit(self, rev):
220 220 if rev not in self.commits:
221 221 uuid, module, revnum = self.revsplit(rev)
222 222 self.module = module
223 223 self.reparent(module)
224 224 stop = self.lastrevs.get(module, 0)
225 225 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
226 226 commit = self.commits[rev]
227 227 # caller caches the result, so free it here to release memory
228 228 del self.commits[rev]
229 229 return commit
230 230
231 231 def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
232 232 strict_node_history=False):
233 233
234 234 def parent(fp):
235 235 while True:
236 236 entry = pickle.load(fp)
237 237 try:
238 238 orig_paths, revnum, author, date, message = entry
239 239 except:
240 240 if entry is None:
241 241 break
242 242 raise SubversionException("child raised exception", entry)
243 243 yield entry
244 244
245 245 args = [self.url, paths, start, end, limit, discover_changed_paths,
246 246 strict_node_history]
247 247 arg = encodeargs(args)
248 248 hgexe = util.hgexecutable()
249 249 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
250 250 stdin, stdout = os.popen2(cmd, 'b')
251 251
252 252 stdin.write(arg)
253 253 stdin.close()
254 254
255 255 for p in parent(stdout):
256 256 yield p
257 257
258 258 def gettags(self):
259 259 tags = {}
260 260 start = self.revnum(self.head)
261 261 try:
262 262 for entry in self.get_log(['/tags'], 0, start):
263 263 orig_paths, revnum, author, date, message = entry
264 264 for path in orig_paths:
265 265 if not path.startswith('/tags/'):
266 266 continue
267 267 ent = orig_paths[path]
268 268 source = ent.copyfrom_path
269 269 rev = ent.copyfrom_rev
270 270 tag = path.split('/', 2)[2]
271 271 tags[tag] = self.revid(rev, module=source)
272 272 except SubversionException, (inst, num):
273 273 self.ui.note('no tags found at revision %d\n' % start)
274 274 return tags
275 275
276 276 # -- helper functions --
277 277
278 278 def revid(self, revnum, module=None):
279 279 if not module:
280 280 module = self.module
281 return (u"svn:%s%s@%s" % (self.uuid, module, revnum)).decode(self.encoding)
281 return u"svn:%s%s@%s" % (self.uuid, module.decode(self.encoding),
282 revnum)
282 283
283 284 def revnum(self, rev):
284 285 return int(rev.split('@')[-1])
285 286
286 287 def revsplit(self, rev):
287 288 url, revnum = rev.encode(self.encoding).split('@', 1)
288 289 revnum = int(revnum)
289 290 parts = url.split('/', 1)
290 291 uuid = parts.pop(0)[4:]
291 292 mod = ''
292 293 if parts:
293 294 mod = '/' + parts[0]
294 295 return uuid, mod, revnum
295 296
296 297 def latest(self, path, stop=0):
297 298 'find the latest revision affecting path, up to stop'
298 299 if not stop:
299 300 stop = svn.ra.get_latest_revnum(self.ra)
300 301 try:
301 302 self.reparent('')
302 303 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
303 304 self.reparent(self.module)
304 305 except SubversionException:
305 306 dirent = None
306 307 if not dirent:
307 308 raise util.Abort('%s not found up to revision %d' % (path, stop))
308 309
309 310 return dirent.created_rev
310 311
311 312 def get_blacklist(self):
312 313 """Avoid certain revision numbers.
313 314 It is not uncommon for two nearby revisions to cancel each other
314 315 out, e.g. 'I copied trunk into a subdirectory of itself instead
315 316 of making a branch'. The converted repository is significantly
316 317 smaller if we ignore such revisions."""
317 318 self.blacklist = util.set()
318 319 blacklist = self.blacklist
319 320 for line in file("blacklist.txt", "r"):
320 321 if not line.startswith("#"):
321 322 try:
322 323 svn_rev = int(line.strip())
323 324 blacklist.add(svn_rev)
324 325 except ValueError, e:
325 326 pass # not an integer or a comment
326 327
327 328 def is_blacklisted(self, svn_rev):
328 329 return svn_rev in self.blacklist
329 330
330 331 def reparent(self, module):
331 332 svn_url = self.base + module
332 333 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
333 334 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
334 335
335 336 def expandpaths(self, rev, paths, parents):
336 337 def get_entry_from_path(path, module=self.module):
337 338 # Given the repository url of this wc, say
338 339 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
339 340 # extract the "entry" portion (a relative path) from what
340 341 # svn log --xml says, ie
341 342 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
342 343 # that is to say "tests/PloneTestCase.py"
343 344 if path.startswith(module):
344 345 relative = path[len(module):]
345 346 if relative.startswith('/'):
346 347 return relative[1:]
347 348 else:
348 349 return relative
349 350
350 351 # The path is outside our tracked tree...
351 352 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
352 353 return None
353 354
354 355 entries = []
355 356 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
356 357 copies = {}
357 358 revnum = self.revnum(rev)
358 359
359 360 if revnum in self.modulemap:
360 361 new_module = self.modulemap[revnum]
361 362 if new_module != self.module:
362 363 self.module = new_module
363 364 self.reparent(self.module)
364 365
365 366 for path, ent in paths:
366 367 entrypath = get_entry_from_path(path, module=self.module)
367 368 entry = entrypath.decode(self.encoding)
368 369
369 370 kind = svn.ra.check_path(self.ra, entrypath, revnum)
370 371 if kind == svn.core.svn_node_file:
371 372 if ent.copyfrom_path:
372 373 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
373 374 if copyfrom_path:
374 375 self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
375 376 # It's probably important for hg that the source
376 377 # exists in the revision's parent, not just the
377 378 # ent.copyfrom_rev
378 379 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
379 380 if fromkind != 0:
380 381 copies[self.recode(entry)] = self.recode(copyfrom_path)
381 382 entries.append(self.recode(entry))
382 383 elif kind == 0: # gone, but had better be a deleted *file*
383 384 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
384 385
385 386 # if a branch is created but entries are removed in the same
386 387 # changeset, get the right fromrev
387 388 if parents:
388 389 uuid, old_module, fromrev = self.revsplit(parents[0])
389 390 else:
390 391 fromrev = revnum - 1
391 392 # might always need to be revnum - 1 in these 3 lines?
392 393 old_module = self.modulemap.get(fromrev, self.module)
393 394
394 395 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
395 396 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
396 397
397 398 def lookup_parts(p):
398 399 rc = None
399 400 parts = p.split("/")
400 401 for i in range(len(parts)):
401 402 part = "/".join(parts[:i])
402 403 info = part, copyfrom.get(part, None)
403 404 if info[1] is not None:
404 405 self.ui.debug("Found parent directory %s\n" % info[1])
405 406 rc = info
406 407 return rc
407 408
408 409 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
409 410
410 411 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
411 412
412 413 # need to remove fragment from lookup_parts and replace with copyfrom_path
413 414 if frompath is not None:
414 415 self.ui.debug("munge-o-matic\n")
415 416 self.ui.debug(entrypath + '\n')
416 417 self.ui.debug(entrypath[len(frompath):] + '\n')
417 418 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
418 419 fromrev = froment.copyfrom_rev
419 420 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
420 421
421 422 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
422 423 if fromkind == svn.core.svn_node_file: # a deleted file
423 424 entries.append(self.recode(entry))
424 425 elif fromkind == svn.core.svn_node_dir:
425 426 # print "Deleted/moved non-file:", revnum, path, ent
426 427 # children = self._find_children(path, revnum - 1)
427 428 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
428 429 # Sometimes this is tricky. For example: in
429 430 # The Subversion Repository revision 6940 a dir
430 431 # was copied and one of its files was deleted
431 432 # from the new location in the same commit. This
432 433 # code can't deal with that yet.
433 434 if ent.action == 'C':
434 435 children = self._find_children(path, fromrev)
435 436 else:
436 437 oroot = entrypath.strip('/')
437 438 nroot = path.strip('/')
438 439 children = self._find_children(oroot, fromrev)
439 440 children = [s.replace(oroot,nroot) for s in children]
440 441 # Mark all [files, not directories] as deleted.
441 442 for child in children:
442 443 # Can we move a child directory and its
443 444 # parent in the same commit? (probably can). Could
444 445 # cause problems if instead of revnum -1,
445 446 # we have to look in (copyfrom_path, revnum - 1)
446 447 entrypath = get_entry_from_path("/" + child, module=old_module)
447 448 if entrypath:
448 449 entry = self.recode(entrypath.decode(self.encoding))
449 450 if entry in copies:
450 451 # deleted file within a copy
451 452 del copies[entry]
452 453 else:
453 454 entries.append(entry)
454 455 else:
455 456 self.ui.debug('unknown path in revision %d: %s\n' % \
456 457 (revnum, path))
457 458 elif kind == svn.core.svn_node_dir:
458 459 # Should probably synthesize normal file entries
459 460 # and handle as above to clean up copy/rename handling.
460 461
461 462 # If the directory just had a prop change,
462 463 # then we shouldn't need to look for its children.
463 464 # Also this could create duplicate entries. Not sure
464 465 # whether this will matter. Maybe should make entries a set.
465 466 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
466 467 # This will fail if a directory was copied
467 468 # from another branch and then some of its files
468 469 # were deleted in the same transaction.
469 470 children = self._find_children(path, revnum)
470 471 children.sort()
471 472 for child in children:
472 473 # Can we move a child directory and its
473 474 # parent in the same commit? (probably can). Could
474 475 # cause problems if instead of revnum -1,
475 476 # we have to look in (copyfrom_path, revnum - 1)
476 477 entrypath = get_entry_from_path("/" + child, module=self.module)
477 478 # print child, self.module, entrypath
478 479 if entrypath:
479 480 # Need to filter out directories here...
480 481 kind = svn.ra.check_path(self.ra, entrypath, revnum)
481 482 if kind != svn.core.svn_node_dir:
482 483 entries.append(self.recode(entrypath))
483 484
484 485 # Copies here (must copy all from source)
485 486 # Probably not a real problem for us if
486 487 # source does not exist
487 488
488 489 # Can do this with the copy command "hg copy"
489 490 # if ent.copyfrom_path:
490 491 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
491 492 # module=self.module)
492 493 # copyto_entry = entrypath
493 494 #
494 495 # print "copy directory", copyfrom_entry, 'to', copyto_entry
495 496 #
496 497 # copies.append((copyfrom_entry, copyto_entry))
497 498
498 499 if ent.copyfrom_path:
499 500 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
500 501 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
501 502 if copyfrom_entry:
502 503 copyfrom[path] = ent
503 504 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
504 505
505 506 # Good, /probably/ a regular copy. Really should check
506 507 # to see whether the parent revision actually contains
507 508 # the directory in question.
508 509 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
509 510 children.sort()
510 511 for child in children:
511 512 entrypath = get_entry_from_path("/" + child, module=self.module)
512 513 if entrypath:
513 514 entry = entrypath.decode(self.encoding)
514 515 # print "COPY COPY From", copyfrom_entry, entry
515 516 copyto_path = path + entry[len(copyfrom_entry):]
516 517 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
517 518 # print "COPY", entry, "COPY To", copyto_entry
518 519 copies[self.recode(copyto_entry)] = self.recode(entry)
519 520 # copy from quux splort/quuxfile
520 521
521 522 return (entries, copies)
522 523
523 524 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
524 525 self.child_cset = None
525 526 def parselogentry(orig_paths, revnum, author, date, message):
526 527 self.ui.debug("parsing revision %d (%d changes)\n" %
527 528 (revnum, len(orig_paths)))
528 529
529 530 if revnum in self.modulemap:
530 531 new_module = self.modulemap[revnum]
531 532 if new_module != self.module:
532 533 self.module = new_module
533 534 self.reparent(self.module)
534 535
535 536 rev = self.revid(revnum)
536 537 # branch log might return entries for a parent we already have
537 538 if (rev in self.commits or
538 539 (revnum < self.lastrevs.get(self.module, 0))):
539 540 return
540 541
541 542 parents = []
542 543 # check whether this revision is the start of a branch
543 544 if self.module in orig_paths:
544 545 ent = orig_paths[self.module]
545 546 if ent.copyfrom_path:
546 547 # ent.copyfrom_rev may not be the actual last revision
547 548 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
548 549 self.modulemap[prev] = ent.copyfrom_path
549 550 parents = [self.revid(prev, ent.copyfrom_path)]
550 551 self.ui.note('found parent of branch %s at %d: %s\n' % \
551 552 (self.module, prev, ent.copyfrom_path))
552 553 else:
553 554 self.ui.debug("No copyfrom path, don't know what to do.\n")
554 555
555 556 self.modulemap[revnum] = self.module # track backwards in time
556 557
557 558 orig_paths = orig_paths.items()
558 559 orig_paths.sort()
559 560 paths = []
560 561 # filter out unrelated paths
561 562 for path, ent in orig_paths:
562 563 if not path.startswith(self.module):
563 564 self.ui.debug("boring@%s: %s\n" % (revnum, path))
564 565 continue
565 566 paths.append((path, ent))
566 567
567 568 self.paths[rev] = (paths, parents)
568 569
569 570 # Example SVN datetime. Includes microseconds.
570 571 # ISO-8601 conformant
571 572 # '2007-01-04T17:35:00.902377Z'
572 573 date = util.parsedate(date[:18] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
573 574
574 575 log = message and self.recode(message)
575 576 author = author and self.recode(author) or ''
576 577 try:
577 578 branch = self.module.split("/")[-1]
578 579 if branch == 'trunk':
579 580 branch = ''
580 581 except IndexError:
581 582 branch = None
582 583
583 584 cset = commit(author=author,
584 585 date=util.datestr(date),
585 586 desc=log,
586 587 parents=parents,
587 588 branch=branch,
588 589 rev=rev.encode('utf-8'))
589 590
590 591 self.commits[rev] = cset
591 592 if self.child_cset and not self.child_cset.parents:
592 593 self.child_cset.parents = [rev]
593 594 self.child_cset = cset
594 595
595 596 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
596 597 (self.module, from_revnum, to_revnum))
597 598
598 599 try:
599 600 for entry in self.get_log([self.module], from_revnum, to_revnum):
600 601 orig_paths, revnum, author, date, message = entry
601 602 if self.is_blacklisted(revnum):
602 603 self.ui.note('skipping blacklisted revision %d\n' % revnum)
603 604 continue
604 605 if orig_paths is None:
605 606 self.ui.debug('revision %d has no entries\n' % revnum)
606 607 continue
607 608 parselogentry(orig_paths, revnum, author, date, message)
608 609 except SubversionException, (inst, num):
609 610 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
610 611 raise NoSuchRevision(branch=self,
611 612 revision="Revision number %d" % to_revnum)
612 613 raise
613 614
614 615 def _getfile(self, file, rev):
615 616 io = StringIO()
616 617 # TODO: ra.get_file transmits the whole file instead of diffs.
617 618 mode = ''
618 619 try:
619 620 revnum = self.revnum(rev)
620 621 if self.module != self.modulemap[revnum]:
621 622 self.module = self.modulemap[revnum]
622 623 self.reparent(self.module)
623 624 info = svn.ra.get_file(self.ra, file, revnum, io)
624 625 if isinstance(info, list):
625 626 info = info[-1]
626 627 mode = ("svn:executable" in info) and 'x' or ''
627 628 mode = ("svn:special" in info) and 'l' or mode
628 629 except SubversionException, e:
629 630 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
630 631 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
631 632 if e.apr_err in notfound: # File not found
632 633 raise IOError()
633 634 raise
634 635 data = io.getvalue()
635 636 if mode == 'l':
636 637 link_prefix = "link "
637 638 if data.startswith(link_prefix):
638 639 data = data[len(link_prefix):]
639 640 return data, mode
640 641
641 642 def _find_children(self, path, revnum):
642 643 path = path.strip('/')
643 644 pool = Pool()
644 645 rpath = '/'.join([self.base, path]).strip('/')
645 646 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
General Comments 0
You need to be logged in to leave comments. Login now