Mercurial > hg
comparison hgext/convert/subversion.py @ 4797:09dae950919f
convert: svn: autodetect /branches, /tags, /trunk.
Various other branch handling improvement attempts too.
author | Brendan Cully <brendan@kublai.com> |
---|---|
date | Tue, 03 Jul 2007 19:26:41 -0700 |
parents | 26857a6f9dd0 |
children | 83c1bbb934ec |
comparison
equal
deleted
inserted
replaced
4796:26857a6f9dd0 | 4797:09dae950919f |
---|---|
24 import transport | 24 import transport |
25 except ImportError: | 25 except ImportError: |
26 pass | 26 pass |
27 | 27 |
28 class CompatibilityException(Exception): pass | 28 class CompatibilityException(Exception): pass |
29 | |
30 LOG_BATCH_SIZE = 50 | |
31 | 29 |
32 class svn_entry(object): | 30 class svn_entry(object): |
33 """Emulate a Subversion path change.""" | 31 """Emulate a Subversion path change.""" |
34 __slots__ = ['path', 'copyfrom_path', 'copyfrom_rev', 'action'] | 32 __slots__ = ['path', 'copyfrom_path', 'copyfrom_rev', 'action'] |
35 def __init__(self, entry): | 33 def __init__(self, entry): |
104 self.url = url | 102 self.url = url |
105 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8 | 103 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8 |
106 try: | 104 try: |
107 self.transport = transport.SvnRaTransport(url = url) | 105 self.transport = transport.SvnRaTransport(url = url) |
108 self.ra = self.transport.ra | 106 self.ra = self.transport.ra |
107 self.ctx = svn.client.create_context() | |
109 self.base = svn.ra.get_repos_root(self.ra) | 108 self.base = svn.ra.get_repos_root(self.ra) |
110 self.module = self.url[len(self.base):] | 109 self.module = self.url[len(self.base):] |
111 self.modulemap = {} # revision, module | 110 self.modulemap = {} # revision, module |
112 self.commits = {} | 111 self.commits = {} |
113 self.files = {} | 112 self.files = {} |
135 def revsplit(self, rev): | 134 def revsplit(self, rev): |
136 url, revnum = rev.encode(self.encoding).split('@', 1) | 135 url, revnum = rev.encode(self.encoding).split('@', 1) |
137 revnum = int(revnum) | 136 revnum = int(revnum) |
138 parts = url.split('/', 1) | 137 parts = url.split('/', 1) |
139 uuid = parts.pop(0)[4:] | 138 uuid = parts.pop(0)[4:] |
140 mod = '/' | 139 mod = '' |
141 if parts: | 140 if parts: |
142 mod += parts[0] | 141 mod = '/' + parts[0] |
143 return uuid, mod, revnum | 142 return uuid, mod, revnum |
144 | 143 |
145 def latest(self, path, stop=0): | 144 def latest(self, path, stop=0): |
146 'find the latest revision affecting path, up to stop' | 145 'find the latest revision affecting path, up to stop' |
147 if not stop: | 146 if not stop: |
180 def reparent(self, module): | 179 def reparent(self, module): |
181 svn_url = self.base + module | 180 svn_url = self.base + module |
182 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding)) | 181 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding)) |
183 svn.ra.reparent(self.ra, svn_url.encode(self.encoding)) | 182 svn.ra.reparent(self.ra, svn_url.encode(self.encoding)) |
184 | 183 |
185 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347, module=None): | 184 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347): |
186 def get_entry_from_path(path, module=self.module): | 185 def get_entry_from_path(path, module=self.module): |
187 # Given the repository url of this wc, say | 186 # Given the repository url of this wc, say |
188 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch" | 187 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch" |
189 # extract the "entry" portion (a relative path) from what | 188 # extract the "entry" portion (a relative path) from what |
190 # svn log --xml says, ie | 189 # svn log --xml says, ie |
209 | 208 |
210 if self.is_blacklisted(revnum): | 209 if self.is_blacklisted(revnum): |
211 self.ui.note('skipping blacklisted revision %d\n' % revnum) | 210 self.ui.note('skipping blacklisted revision %d\n' % revnum) |
212 return | 211 return |
213 | 212 |
214 self.ui.note("parsing revision %d\n" % revnum) | 213 self.ui.debug("parsing revision %d\n" % revnum) |
215 | 214 |
216 if orig_paths is None: | 215 if orig_paths is None: |
217 self.ui.debug('revision %d has no entries\n' % revnum) | 216 self.ui.debug('revision %d has no entries\n' % revnum) |
218 return | 217 return |
219 | 218 |
240 if path == self.module: # Follow branching back in history | 239 if path == self.module: # Follow branching back in history |
241 ent = orig_paths[path] | 240 ent = orig_paths[path] |
242 if ent: | 241 if ent: |
243 if ent.copyfrom_path: | 242 if ent.copyfrom_path: |
244 # ent.copyfrom_rev may not be the actual last revision | 243 # ent.copyfrom_rev may not be the actual last revision |
245 prev = self.latest(ent.copyfrom_path, revnum) | 244 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev) |
246 self.modulemap[prev] = ent.copyfrom_path | 245 self.modulemap[prev] = ent.copyfrom_path |
247 parents = [self.rev(prev, ent.copyfrom_path)] | 246 parents = [self.rev(prev, ent.copyfrom_path)] |
247 self.ui.note('found parent of branch %s at %d: %s\n' % \ | |
248 (self.module, prev, ent.copyfrom_path)) | |
248 else: | 249 else: |
249 self.ui.debug("No copyfrom path, don't know what to do.\n") | 250 self.ui.debug("No copyfrom path, don't know what to do.\n") |
250 # Maybe it was added and there is no more history. | 251 # Maybe it was added and there is no more history. |
251 entrypath = get_entry_from_path(path, module=self.module) | 252 entrypath = get_entry_from_path(path, module=self.module) |
252 # self.ui.write("entrypath %s\n" % entrypath) | 253 # self.ui.write("entrypath %s\n" % entrypath) |
271 copies[self.recode(entry)] = self.recode(copyfrom_path) | 272 copies[self.recode(entry)] = self.recode(copyfrom_path) |
272 entries.append(self.recode(entry)) | 273 entries.append(self.recode(entry)) |
273 elif kind == 0: # gone, but had better be a deleted *file* | 274 elif kind == 0: # gone, but had better be a deleted *file* |
274 self.ui.debug("gone from %s\n" % ent.copyfrom_rev) | 275 self.ui.debug("gone from %s\n" % ent.copyfrom_rev) |
275 | 276 |
276 fromrev = revnum - 1 | 277 # if a branch is created but entries are removed in the same |
277 # might always need to be revnum - 1 in these 3 lines? | 278 # changeset, get the right fromrev |
278 old_module = self.modulemap.get(fromrev, self.module) | 279 if parents: |
280 uuid, old_module, fromrev = self.revsplit(parents[0]) | |
281 else: | |
282 fromrev = revnum - 1 | |
283 # might always need to be revnum - 1 in these 3 lines? | |
284 old_module = self.modulemap.get(fromrev, self.module) | |
285 | |
279 basepath = old_module + "/" + get_entry_from_path(path, module=self.module) | 286 basepath = old_module + "/" + get_entry_from_path(path, module=self.module) |
280 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module) | 287 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module) |
281 | 288 |
282 def lookup_parts(p): | 289 def lookup_parts(p): |
283 rc = None | 290 rc = None |
284 parts = p.split("/") | 291 parts = p.split("/") |
285 for i in range(len(parts)): | 292 for i in range(len(parts)): |
286 part = "/".join(parts[:i]) | 293 part = "/".join(parts[:i]) |
287 info = part, copyfrom.get(part, None) | 294 info = part, copyfrom.get(part, None) |
288 if info[1] is not None: | 295 if info[1] is not None: |
289 self.ui.debug("Found parent directory %s\n" % info) | 296 self.ui.debug("Found parent directory %s\n" % info[1]) |
290 rc = info | 297 rc = info |
291 return rc | 298 return rc |
292 | 299 |
293 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath)) | 300 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath)) |
294 | 301 |
425 self.commits[rev] = cset | 432 self.commits[rev] = cset |
426 if self.child_cset and not self.child_cset.parents: | 433 if self.child_cset and not self.child_cset.parents: |
427 self.child_cset.parents = [rev] | 434 self.child_cset.parents = [rev] |
428 self.child_cset = cset | 435 self.child_cset = cset |
429 | 436 |
430 self.ui.note('fetching revision log from %d to %d\n' % \ | 437 self.ui.note('fetching revision log for "%s" from %d to %d\n' % \ |
431 (from_revnum, to_revnum)) | 438 (self.module, from_revnum, to_revnum)) |
432 | 439 |
433 if module is None: | |
434 module = self.module | |
435 try: | 440 try: |
436 discover_changed_paths = True | 441 discover_changed_paths = True |
437 strict_node_history = False | 442 strict_node_history = False |
438 svn.ra.get_log(self.ra, [module], from_revnum, to_revnum, 0, | 443 svn.ra.get_log(self.ra, [self.module], from_revnum, to_revnum, 0, |
439 discover_changed_paths, strict_node_history, | 444 discover_changed_paths, strict_node_history, |
440 parselogentry) | 445 parselogentry) |
441 self.last_revnum = to_revnum | 446 self.last_revnum = to_revnum |
442 except SubversionException, (_, num): | 447 except SubversionException, (_, num): |
443 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION: | 448 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION: |
444 raise NoSuchRevision(branch=self, | 449 raise NoSuchRevision(branch=self, |
445 revision="Revision number %d" % to_revnum) | 450 revision="Revision number %d" % to_revnum) |
446 raise | 451 raise |
447 | 452 |
448 def getheads(self): | 453 def getheads(self): |
449 # svn-url@rev | 454 # detect standard /branches, /tags, /trunk layout |
450 # Not safe if someone committed: | 455 optrev = svn.core.svn_opt_revision_t() |
451 self.heads = [self.head] | 456 optrev.kind = svn.core.svn_opt_revision_number |
452 # print self.commits.keys() | 457 optrev.value.number = self.last_changed |
458 rpath = self.url.strip('/') | |
459 paths = svn.client.ls(rpath, optrev, False, self.ctx) | |
460 if 'branches' in paths and 'trunk' in paths: | |
461 self.module += '/trunk' | |
462 lt = self.latest(self.module, self.last_changed) | |
463 self.head = self.rev(lt) | |
464 self.heads = [self.head] | |
465 branches = svn.client.ls(rpath + '/branches', optrev, False, self.ctx) | |
466 for branch in branches.keys(): | |
467 module = '/branches/' + branch | |
468 brevnum = self.latest(module, self.last_changed) | |
469 brev = self.rev(brevnum, module) | |
470 self.ui.note('found branch %s at %d\n' % (branch, brevnum)) | |
471 self.heads.append(brev) | |
472 else: | |
473 self.heads = [self.head] | |
453 return self.heads | 474 return self.heads |
454 | 475 |
455 def _getfile(self, file, rev): | 476 def _getfile(self, file, rev): |
456 io = StringIO() | 477 io = StringIO() |
457 # TODO: ra.get_file transmits the whole file instead of diffs. | 478 # TODO: ra.get_file transmits the whole file instead of diffs. |
495 return cl | 516 return cl |
496 | 517 |
497 def getcommit(self, rev): | 518 def getcommit(self, rev): |
498 if rev not in self.commits: | 519 if rev not in self.commits: |
499 uuid, module, revnum = self.revsplit(rev) | 520 uuid, module, revnum = self.revsplit(rev) |
500 minrev = revnum - LOG_BATCH_SIZE > 0 and revnum - LOG_BATCH_SIZE or 0 | 521 self.module = module |
501 self._fetch_revisions(from_revnum=revnum, to_revnum=0, | 522 self.reparent(module) |
502 module=module) | 523 self._fetch_revisions(from_revnum=revnum, to_revnum=0) |
503 return self.commits[rev] | 524 return self.commits[rev] |
504 | 525 |
505 def gettags(self): | 526 def gettags(self): |
506 tags = {} | 527 tags = {} |
507 def parselogentry(*arg, **args): | 528 def parselogentry(*arg, **args): |
526 def _find_children(self, path, revnum): | 547 def _find_children(self, path, revnum): |
527 path = path.strip("/") | 548 path = path.strip("/") |
528 | 549 |
529 def _find_children_fallback(path, revnum): | 550 def _find_children_fallback(path, revnum): |
530 # SWIG python bindings for getdir are broken up to at least 1.4.3 | 551 # SWIG python bindings for getdir are broken up to at least 1.4.3 |
531 if not hasattr(self, 'client_ctx'): | |
532 self.client_ctx = svn.client.create_context() | |
533 pool = Pool() | 552 pool = Pool() |
534 optrev = svn.core.svn_opt_revision_t() | 553 optrev = svn.core.svn_opt_revision_t() |
535 optrev.kind = svn.core.svn_opt_revision_number | 554 optrev.kind = svn.core.svn_opt_revision_number |
536 optrev.value.number = revnum | 555 optrev.value.number = revnum |
537 rpath = '/'.join([self.base, path]).strip('/') | 556 rpath = '/'.join([self.base, path]).strip('/') |
538 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev, True, self.client_ctx, pool).keys()] | 557 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev, True, self.ctx, pool).keys()] |
539 | 558 |
540 if hasattr(self, '_find_children_fallback'): | 559 if hasattr(self, '_find_children_fallback'): |
541 return _find_children_fallback(path, revnum) | 560 return _find_children_fallback(path, revnum) |
542 | 561 |
543 self.reparent("/" + path) | 562 self.reparent("/" + path) |