Mercurial > hgsubversion
comparison svnwrap/svn_swig_wrapper.py @ 300:4aba7542f6a9
Various cleanups, cosmetics and removal of superfluous assertions.
author | Dan Villiom Podlaski Christiansen <danchr@gmail.com> |
---|---|
date | Fri, 27 Mar 2009 03:16:21 +0100 |
parents | 32d3f1716e66 |
children | 79440ed81011 |
comparison
equal
deleted
inserted
replaced
299:3e27514d575c | 300:4aba7542f6a9 |
---|---|
3 import os | 3 import os |
4 import shutil | 4 import shutil |
5 import sys | 5 import sys |
6 import tempfile | 6 import tempfile |
7 import hashlib | 7 import hashlib |
8 import collections | |
9 import gc | |
8 | 10 |
9 from svn import client | 11 from svn import client |
10 from svn import core | 12 from svn import core |
11 from svn import delta | 13 from svn import delta |
12 from svn import ra | 14 from svn import ra |
15 | |
16 from mercurial import util as hgutil | |
13 | 17 |
14 def version(): | 18 def version(): |
15 return '%d.%d.%d' % (core.SVN_VER_MAJOR, core.SVN_VER_MINOR, core.SVN_VER_MICRO) | 19 return '%d.%d.%d' % (core.SVN_VER_MAJOR, core.SVN_VER_MINOR, core.SVN_VER_MICRO) |
16 | 20 |
17 if (core.SVN_VER_MAJOR, core.SVN_VER_MINOR, core.SVN_VER_MICRO) < (1, 5, 0): #pragma: no cover | 21 if (core.SVN_VER_MAJOR, core.SVN_VER_MINOR, core.SVN_VER_MICRO) < (1, 5, 0): #pragma: no cover |
41 | 45 |
42 @staticmethod | 46 @staticmethod |
43 def get_client_string(pool): | 47 def get_client_string(pool): |
44 return 'hgsubversion' | 48 return 'hgsubversion' |
45 | 49 |
46 | |
47 def user_pass_prompt(realm, default_username, ms, pool): #pragma: no cover | 50 def user_pass_prompt(realm, default_username, ms, pool): #pragma: no cover |
51 # FIXME: should use getpass() and username() from mercurial.ui | |
48 creds = core.svn_auth_cred_simple_t() | 52 creds = core.svn_auth_cred_simple_t() |
49 creds.may_save = ms | 53 creds.may_save = ms |
50 if default_username: | 54 if default_username: |
51 sys.stderr.write('Auth realm: %s\n' % (realm,)) | 55 sys.stderr.write('Auth realm: %s\n' % (realm,)) |
52 creds.username = default_username | 56 creds.username = default_username |
144 def __init__(self, url='', username=''): | 148 def __init__(self, url='', username=''): |
145 self.svn_url = url | 149 self.svn_url = url |
146 self.uname = username | 150 self.uname = username |
147 self.auth_baton_pool = core.Pool() | 151 self.auth_baton_pool = core.Pool() |
148 self.auth_baton = _create_auth_baton(self.auth_baton_pool) | 152 self.auth_baton = _create_auth_baton(self.auth_baton_pool) |
153 # self.init_ra_and_client() assumes that a pool already exists | |
154 self.pool = core.Pool() | |
149 | 155 |
150 self.init_ra_and_client() | 156 self.init_ra_and_client() |
151 self.uuid = ra.get_uuid(self.ra, self.pool) | 157 self.uuid = ra.get_uuid(self.ra, self.pool) |
152 repo_root = ra.get_repos_root(self.ra, self.pool) | 158 repo_root = ra.get_repos_root(self.ra, self.pool) |
153 # *will* have a leading '/', would not if we used get_repos_root2 | 159 # *will* have a leading '/', would not if we used get_repos_root2 |
158 | 164 |
159 def init_ra_and_client(self): | 165 def init_ra_and_client(self): |
160 """Initializes the RA and client layers, because sometimes getting | 166 """Initializes the RA and client layers, because sometimes getting |
161 unified diffs runs the remote server out of open files. | 167 unified diffs runs the remote server out of open files. |
162 """ | 168 """ |
163 # while we're in here we'll recreate our pool | 169 # Debugging code; retained for possible later use. |
170 if False: | |
171 gc.collect() | |
172 import pympler.muppy.tracker | |
173 try: | |
174 self.memory_tracker | |
175 try: | |
176 self.memory_tracker.print_diff(self.memory_base) | |
177 except: | |
178 print 'HOP' | |
179 self.memory_base = self.memory_tracker.create_summary() | |
180 except: | |
181 print 'HEP' | |
182 self.memory_tracker = pympler.muppy.tracker.SummaryTracker() | |
183 | |
184 # while we're in here we'll recreate our pool, but first, we clear it | |
185 # and destroy it to make possible leaks cause fatal errors. | |
186 self.pool.clear() | |
187 self.pool.destroy() | |
164 self.pool = core.Pool() | 188 self.pool = core.Pool() |
165 self.client_context = client.create_context() | 189 self.client_context = client.create_context() |
166 | 190 |
167 self.client_context.auth_baton = self.auth_baton | 191 self.client_context.auth_baton = self.auth_baton |
168 self.client_context.config = svn_config | 192 self.client_context.config = svn_config |
183 return 0 | 207 return 0 |
184 START = property(START) | 208 START = property(START) |
185 | 209 |
186 def branches(self): | 210 def branches(self): |
187 """Get the branches defined in this repo assuming a standard layout. | 211 """Get the branches defined in this repo assuming a standard layout. |
212 | |
213 This method should be eliminated; this class does not have | |
214 sufficient knowledge to yield all known tags. | |
188 """ | 215 """ |
189 branches = self.list_dir('branches').keys() | 216 branches = self.list_dir('branches').keys() |
190 branch_info = {} | 217 branch_info = {} |
191 head=self.HEAD | 218 head=self.HEAD |
192 for b in branches: | 219 for b in branches: |
205 | 232 |
206 def tags(self): | 233 def tags(self): |
207 """Get the current tags in this repo assuming a standard layout. | 234 """Get the current tags in this repo assuming a standard layout. |
208 | 235 |
209 This returns a dictionary of tag: (source path, source rev) | 236 This returns a dictionary of tag: (source path, source rev) |
237 | |
238 This method should be eliminated; this class does not have | |
239 sufficient knowledge to yield all known tags. | |
210 """ | 240 """ |
211 return self.tags_at_rev(self.HEAD) | 241 return self.tags_at_rev(self.HEAD) |
212 tags = property(tags) | 242 tags = property(tags) |
213 | 243 |
214 def tags_at_rev(self, revision): | 244 def tags_at_rev(self, revision): |
245 """Get the tags in this repo at the given revision, assuming a | |
246 standard layout. | |
247 | |
248 This returns a dictionary of tag: (source path, source rev) | |
249 | |
250 This method should be eliminated; this class does not have | |
251 sufficient knowledge to yield all known tags. | |
252 """ | |
215 try: | 253 try: |
216 tags = self.list_dir('tags', revision=revision).keys() | 254 tags = self.list_dir('tags', revision=revision).keys() |
217 except core.SubversionException, e: | 255 except core.SubversionException, e: |
218 if e.apr_err == core.SVN_ERR_FS_NOT_FOUND: | 256 if e.apr_err == core.SVN_ERR_FS_NOT_FOUND: |
219 return {} | 257 return {} |
275 """ | 313 """ |
276 # NB: you'd think this would work, but you'd be wrong. I'm pretty | 314 # NB: you'd think this would work, but you'd be wrong. I'm pretty |
277 # convinced there must be some kind of svn bug here. | 315 # convinced there must be some kind of svn bug here. |
278 #return self.fetch_history_at_paths(['tags', 'trunk', 'branches'], | 316 #return self.fetch_history_at_paths(['tags', 'trunk', 'branches'], |
279 # start=start) | 317 # start=start) |
280 # this does the same thing, but at the repo root + filtering. It's | 318 # However, we no longer need such filtering, as we gracefully handle |
281 # kind of tough cookies, sadly. | 319 # branches located at arbitrary locations. |
282 for r in self.fetch_history_at_paths([''], start=start, | 320 return self.fetch_history_at_paths([''], start=start, stop=stop, |
283 chunk_size=chunk_size): | 321 chunk_size=chunk_size) |
284 should_yield = False | |
285 i = 0 | |
286 paths = list(r.paths.keys()) | |
287 while i < len(paths) and not should_yield: | |
288 p = paths[i] | |
289 if (p.startswith('trunk') or p.startswith('tags') | |
290 or p.startswith('branches')): | |
291 should_yield = True | |
292 i += 1 | |
293 if should_yield: | |
294 yield r | |
295 | |
296 | 322 |
297 def fetch_history_at_paths(self, paths, start=None, stop=None, | 323 def fetch_history_at_paths(self, paths, start=None, stop=None, |
298 chunk_size=1000): | 324 chunk_size=_chunk_size): |
299 revisions = [] | 325 '''TODO: This method should be merged with self.revisions() as |
300 def callback(paths, revnum, author, date, message, pool): | 326 they are now functionally equivalent.''' |
301 r = Revision(revnum, author, message, date, paths, | |
302 strip_path=self.subdir) | |
303 revisions.append(r) | |
304 if not start: | 327 if not start: |
305 start = self.START | 328 start = self.START |
306 if not stop: | 329 if not stop: |
307 stop = self.HEAD | 330 stop = self.HEAD |
308 while stop > start: | 331 while stop > start: |
309 ra.get_log(self.ra, | 332 def callback(paths, revnum, author, date, message, pool): |
310 paths, | 333 r = Revision(revnum, author, message, date, paths, |
311 start+1, | 334 strip_path=self.subdir) |
312 stop, | 335 revisions.append(r) |
313 chunk_size, #limit of how many log messages to load | 336 # use a queue; we only access revisions in a FIFO manner |
314 True, # don't need to know changed paths | 337 revisions = collections.deque() |
315 True, # stop on copies | 338 |
316 callback, | 339 try: |
317 self.pool) | 340 # TODO: using min(start + chunk_size, stop) may be preferable; |
318 if len(revisions) < chunk_size: | 341 # ra.get_log(), even with chunk_size set, takes a while |
319 # this means there was no history for the path, so force the | 342 # when converting the 65k+ rev. in LLVM. |
320 # loop to exit | 343 ra.get_log(self.ra, |
321 start = stop | 344 paths, |
345 start+1, | |
346 stop, | |
347 chunk_size, #limit of how many log messages to load | |
348 True, # don't need to know changed paths | |
349 True, # stop on copies | |
350 callback, | |
351 self.pool) | |
352 except core.SubversionException, e: | |
353 if e.apr_err not in [core.SVN_ERR_FS_NOT_FOUND]: | |
354 raise | |
355 else: | |
356 raise hgutil.Abort('%s not found at revision %d!' | |
357 % (self.subdir.rstrip('/'), stop)) | |
358 | |
359 while len(revisions) > 1: | |
360 yield revisions.popleft() | |
361 # Now is a good time to do a quick garbage collection. | |
362 gc.collect(0) | |
363 | |
364 if len(revisions) == 0: | |
365 # exit the loop; there is no history for the path. | |
366 break | |
322 else: | 367 else: |
323 start = revisions[-1].revnum | 368 r = revisions.popleft() |
324 while len(revisions) > 0: | 369 start = r.revnum |
325 yield revisions[0] | 370 yield r |
326 revisions.pop(0) | 371 self.init_ra_and_client() |
372 # Now is a good time to do a thorough garbage colection. | |
373 gc.collect() | |
327 | 374 |
328 def commit(self, paths, message, file_data, base_revision, addeddirs, | 375 def commit(self, paths, message, file_data, base_revision, addeddirs, |
329 deleteddirs, properties, copies): | 376 deleteddirs, properties, copies): |
330 """Commits the appropriate targets from revision in editor's store. | 377 """Commits the appropriate targets from revision in editor's store. |
331 """ | 378 """ |
340 False, | 387 False, |
341 self.pool) | 388 self.pool) |
342 checksum = [] | 389 checksum = [] |
343 # internal dir batons can fall out of scope and get GCed before svn is | 390 # internal dir batons can fall out of scope and get GCed before svn is |
344 # done with them. This prevents that (credit to gvn for the idea). | 391 # done with them. This prevents that (credit to gvn for the idea). |
392 # TODO: verify that these are not the cause of our leaks | |
345 batons = [edit_baton, ] | 393 batons = [edit_baton, ] |
346 def driver_cb(parent, path, pool): | 394 def driver_cb(parent, path, pool): |
347 if not parent: | 395 if not parent: |
348 bat = editor.open_root(edit_baton, base_revision, self.pool) | 396 bat = editor.open_root(edit_baton, base_revision, self.pool) |
349 batons.append(bat) | 397 batons.append(bat) |
372 frompath, fromrev = copies.get(path, (None, -1)) | 420 frompath, fromrev = copies.get(path, (None, -1)) |
373 if frompath: | 421 if frompath: |
374 frompath = self.svn_url + '/' + frompath | 422 frompath = self.svn_url + '/' + frompath |
375 baton = editor.add_file(path, parent, frompath, fromrev, pool) | 423 baton = editor.add_file(path, parent, frompath, fromrev, pool) |
376 except (core.SubversionException, TypeError), e: #pragma: no cover | 424 except (core.SubversionException, TypeError), e: #pragma: no cover |
377 print e.message | 425 print e |
378 raise | 426 raise |
379 elif action == 'delete': | 427 elif action == 'delete': |
380 baton = editor.delete_entry(path, base_revision, parent, pool) | 428 baton = editor.delete_entry(path, base_revision, parent, pool) |
381 compute_delta = False | 429 compute_delta = False |
382 | 430 |
402 delta.path_driver(editor, edit_baton, base_revision, paths, driver_cb, | 450 delta.path_driver(editor, edit_baton, base_revision, paths, driver_cb, |
403 self.pool) | 451 self.pool) |
404 editor.close_edit(edit_baton, self.pool) | 452 editor.close_edit(edit_baton, self.pool) |
405 | 453 |
406 def get_replay(self, revision, editor, oldest_rev_i_have=0): | 454 def get_replay(self, revision, editor, oldest_rev_i_have=0): |
407 # this method has a tendency to chew through RAM if you don't re-init | |
408 self.init_ra_and_client() | |
409 e_ptr, e_baton = delta.make_editor(editor) | 455 e_ptr, e_baton = delta.make_editor(editor) |
410 try: | 456 try: |
411 ra.replay(self.ra, revision, oldest_rev_i_have, True, e_ptr, | 457 ra.replay(self.ra, revision, oldest_rev_i_have, True, e_ptr, |
412 e_baton, self.pool) | 458 e_baton, self.pool) |
413 except core.SubversionException, e: #pragma: no cover | 459 except core.SubversionException, e: #pragma: no cover |
414 # can I depend on this number being constant? | |
415 if (e.apr_err == core.SVN_ERR_RA_NOT_IMPLEMENTED or | 460 if (e.apr_err == core.SVN_ERR_RA_NOT_IMPLEMENTED or |
416 e.apr_err == core.SVN_ERR_UNSUPPORTED_FEATURE): | 461 e.apr_err == core.SVN_ERR_UNSUPPORTED_FEATURE): |
417 raise SubversionRepoCanNotReplay, ('This Subversion server ' | 462 raise SubversionRepoCanNotReplay, ('This Subversion server ' |
418 'is older than 1.4.0, and cannot satisfy replay requests.') | 463 'is older than 1.4.0, and cannot satisfy replay requests.') |
419 else: | 464 else: |
423 deleted=True, ignore_type=False): | 468 deleted=True, ignore_type=False): |
424 """Gets a unidiff of path at revision against revision-1. | 469 """Gets a unidiff of path at revision against revision-1. |
425 """ | 470 """ |
426 if not self.hasdiff3: | 471 if not self.hasdiff3: |
427 raise SubversionRepoCanNotDiff() | 472 raise SubversionRepoCanNotDiff() |
428 # works around an svn server keeping too many open files (observed | |
429 # in an svnserve from the 1.2 era) | |
430 self.init_ra_and_client() | |
431 | 473 |
432 assert path[0] != '/' | 474 assert path[0] != '/' |
433 url = self.svn_url + '/' + path | 475 url = self.svn_url + '/' + path |
434 url2 = url | 476 url2 = url |
435 if other_path is not None: | 477 if other_path is not None: |
489 mode = ("svn:special" in info) and 'l' or mode | 531 mode = ("svn:special" in info) and 'l' or mode |
490 except core.SubversionException, e: | 532 except core.SubversionException, e: |
491 notfound = (core.SVN_ERR_FS_NOT_FOUND, | 533 notfound = (core.SVN_ERR_FS_NOT_FOUND, |
492 core.SVN_ERR_RA_DAV_PATH_NOT_FOUND) | 534 core.SVN_ERR_RA_DAV_PATH_NOT_FOUND) |
493 if e.apr_err in notfound: # File not found | 535 if e.apr_err in notfound: # File not found |
494 raise IOError() | 536 raise IOError, e.args[0] |
495 raise | 537 raise |
496 if mode == 'l': | 538 if mode == 'l': |
497 linkprefix = "link " | 539 linkprefix = "link " |
498 if data.startswith(linkprefix): | 540 if data.startswith(linkprefix): |
499 data = data[len(linkprefix):] | 541 data = data[len(linkprefix):] |
531 'dirpath' and 'kind' is 'f' if the entry is a file, 'd' if it is a | 573 'dirpath' and 'kind' is 'f' if the entry is a file, 'd' if it is a |
532 directory. Raise IOError if the directory cannot be found at given | 574 directory. Raise IOError if the directory cannot be found at given |
533 revision. | 575 revision. |
534 """ | 576 """ |
535 dirpath = dirpath.strip('/') | 577 dirpath = dirpath.strip('/') |
536 pool = core.Pool() | |
537 rpath = '/'.join([self.svn_url, dirpath]).strip('/') | 578 rpath = '/'.join([self.svn_url, dirpath]).strip('/') |
538 rev = optrev(revision) | 579 rev = optrev(revision) |
539 try: | 580 try: |
540 entries = client.ls(rpath, rev, True, self.client_context, pool) | 581 entries = client.ls(rpath, rev, True, self.client_context, |
582 self.pool) | |
541 except core.SubversionException, e: | 583 except core.SubversionException, e: |
542 if e.apr_err == core.SVN_ERR_FS_NOT_FOUND: | 584 if e.apr_err == core.SVN_ERR_FS_NOT_FOUND: |
543 raise IOError('%s cannot be found at r%d' % (dirpath, revision)) | 585 raise IOError('%s cannot be found at r%d' % (dirpath, revision)) |
544 raise | 586 raise |
545 for path, e in entries.iteritems(): | 587 for path, e in entries.iteritems(): |