comparison hgsubversion/hg_delta_editor.py @ 413:ac0cc3c9ea63

sort HgChangeReceiver methods and properties Keep editor-only bits close to editor bits, move the rest up, in order of ascending complexity (including use of other methods).
author Dirkjan Ochtman <dirkjan@ochtman.nl>
date Thu, 11 Jun 2009 08:40:20 +0200
parents d71972428fce
children 343da842dbe6
comparison
equal deleted inserted replaced
412:5cba60948f92 413:ac0cc3c9ea63
92 self.closebranches = set() 92 self.closebranches = set()
93 self.externals = {} 93 self.externals = {}
94 94
95 95
96 class HgChangeReceiver(delta.Editor): 96 class HgChangeReceiver(delta.Editor):
97
98 def last_known_revision(self):
99 """Obtain the highest numbered -- i.e. latest -- revision known.
100
101 Currently, this function just iterates over the entire revision map
102 using the max() builtin. This may be slow for extremely large
103 repositories, but for now, it's fast enough.
104 """
105 try:
106 return max(k[0] for k in self.revmap.iterkeys())
107 except ValueError:
108 return 0
109 97
110 def __init__(self, repo, uuid=None, subdir=''): 98 def __init__(self, repo, uuid=None, subdir=''):
111 """path is the path to the target hg repo. 99 """path is the path to the target hg repo.
112 100
113 subdir is the subdirectory of the edits *on the svn server*. 101 subdir is the subdirectory of the edits *on the svn server*.
157 if authors: self.authors.load(authors) 145 if authors: self.authors.load(authors)
158 146
159 self.lastdate = '1970-01-01 00:00:00 -0000' 147 self.lastdate = '1970-01-01 00:00:00 -0000'
160 self.filemap = maps.FileMap(repo) 148 self.filemap = maps.FileMap(repo)
161 149
150 def _get_uuid(self):
151 return open(os.path.join(self.meta_data_dir, 'uuid')).read()
152
153 def _set_uuid(self, uuid):
154 if not uuid:
155 return
156 elif os.path.isfile(os.path.join(self.meta_data_dir, 'uuid')):
157 stored_uuid = self._get_uuid()
158 assert stored_uuid
159 if uuid != stored_uuid:
160 raise hgutil.Abort('unable to operate on unrelated repository')
161 else:
162 if uuid:
163 f = open(os.path.join(self.meta_data_dir, 'uuid'), 'w')
164 f.write(uuid)
165 f.flush()
166 f.close()
167 else:
168 raise hgutil.Abort('unable to operate on unrelated repository')
169
170 uuid = property(_get_uuid, _set_uuid, None,
171 'Error-checked UUID of source Subversion repository.')
172
173 @property
174 def meta_data_dir(self):
175 return os.path.join(self.path, '.hg', 'svn')
176
177 @property
178 def branch_info_file(self):
179 return os.path.join(self.meta_data_dir, 'branch_info')
180
181 @property
182 def tag_locations_file(self):
183 return os.path.join(self.meta_data_dir, 'tag_locations')
184
185 @property
186 def authors_file(self):
187 return os.path.join(self.meta_data_dir, 'authors')
188
162 def hashes(self): 189 def hashes(self):
163 return dict((v, k) for (k, v) in self.revmap.iteritems()) 190 return dict((v, k) for (k, v) in self.revmap.iteritems())
191
192 def branchedits(self, branch, rev):
193 check = lambda x: x[0][1] == branch and x[0][0] < rev.revnum
194 return sorted(filter(check, self.revmap.iteritems()), reverse=True)
195
196 def last_known_revision(self):
197 """Obtain the highest numbered -- i.e. latest -- revision known.
198
199 Currently, this function just iterates over the entire revision map
200 using the max() builtin. This may be slow for extremely large
201 repositories, but for now, it's fast enough.
202 """
203 try:
204 return max(k[0] for k in self.revmap.iterkeys())
205 except ValueError:
206 return 0
164 207
165 def fixdate(self, date): 208 def fixdate(self, date):
166 if date is not None: 209 if date is not None:
167 date = date.replace('T', ' ').replace('Z', '').split('.')[0] 210 date = date.replace('T', ' ').replace('Z', '').split('.')[0]
168 date += ' -0000' 211 date += ' -0000'
174 def _save_metadata(self): 217 def _save_metadata(self):
175 '''Save the Subversion metadata. This should really be called after 218 '''Save the Subversion metadata. This should really be called after
176 every revision is created. 219 every revision is created.
177 ''' 220 '''
178 pickle_atomic(self.branches, self.branch_info_file, self.meta_data_dir) 221 pickle_atomic(self.branches, self.branch_info_file, self.meta_data_dir)
179
180 def _path_and_branch_for_path(self, path, existing=True):
181 return self._split_branch_path(path, existing=existing)[:2]
182
183 def _branch_for_path(self, path, existing=True):
184 return self._path_and_branch_for_path(path, existing=existing)[1]
185 222
186 def _localname(self, path): 223 def _localname(self, path):
187 """Compute the local name for a branch located at path. 224 """Compute the local name for a branch located at path.
188 """ 225 """
189 assert not path.startswith('tags/') 226 assert not path.startswith('tags/')
197 if branch == 'default' or branch is None: 234 if branch == 'default' or branch is None:
198 return 'trunk' 235 return 'trunk'
199 elif branch.startswith('../'): 236 elif branch.startswith('../'):
200 return branch[3:] 237 return branch[3:]
201 return 'branches/%s' % branch 238 return 'branches/%s' % branch
239
240 def _normalize_path(self, path):
241 '''Normalize a path to strip of leading slashes and our subdir if we
242 have one.
243 '''
244 if path and path[0] == '/':
245 path = path[1:]
246 if path and path.startswith(self.subdir):
247 path = path[len(self.subdir):]
248 if path and path[0] == '/':
249 path = path[1:]
250 return path
251
252 def _is_path_tag(self, path):
253 """If path could represent the path to a tag, returns the potential tag
254 name. Otherwise, returns False.
255
256 Note that it's only a tag if it was copied from the path '' in a branch
257 (or tag) we have, for our purposes.
258 """
259 path = self._normalize_path(path)
260 for tagspath in self.tag_locations:
261 onpath = path.startswith(tagspath)
262 longer = len(path) > len('%s/' % tagspath)
263 if path and onpath and longer:
264 tag, subpath = path[len(tagspath) + 1:], ''
265 return tag
266 return False
202 267
203 def _split_branch_path(self, path, existing=True): 268 def _split_branch_path(self, path, existing=True):
204 """Figure out which branch inside our repo this path represents, and 269 """Figure out which branch inside our repo this path represents, and
205 also figure out which path inside that branch it is. 270 also figure out which path inside that branch it is.
206 271
237 ln = self._localname(test) 302 ln = self._localname(test)
238 if ln and ln.startswith('../'): 303 if ln and ln.startswith('../'):
239 return None, None, None 304 return None, None, None
240 return path, ln, test 305 return path, ln, test
241 306
242 def set_file(self, path, data, isexec=False, islink=False):
243 if islink:
244 data = 'link ' + data
245 self.current.files[path] = data
246 self.current.execfiles[path] = isexec
247 self.current.symlinks[path] = islink
248 if path in self.current.deleted:
249 del self.current.deleted[path]
250 if path in self.current.missing:
251 self.current.missing.remove(path)
252
253 def delete_file(self, path):
254 self.current.deleted[path] = True
255 if path in self.current.files:
256 del self.current.files[path]
257 self.current.execfiles[path] = False
258 self.current.symlinks[path] = False
259 self.ui.note('D %s\n' % path)
260
261 def _normalize_path(self, path):
262 '''Normalize a path to strip of leading slashes and our subdir if we
263 have one.
264 '''
265 if path and path[0] == '/':
266 path = path[1:]
267 if path and path.startswith(self.subdir):
268 path = path[len(self.subdir):]
269 if path and path[0] == '/':
270 path = path[1:]
271 return path
272
273 def _is_path_valid(self, path): 307 def _is_path_valid(self, path):
274 if path is None: 308 if path is None:
275 return False 309 return False
276 subpath = self._split_branch_path(path)[0] 310 subpath = self._split_branch_path(path)[0]
277 if subpath is None: 311 if subpath is None:
278 return False 312 return False
279 return subpath in self.filemap 313 return subpath in self.filemap
280
281 def _is_path_tag(self, path):
282 """If path could represent the path to a tag, returns the potential tag
283 name. Otherwise, returns False.
284
285 Note that it's only a tag if it was copied from the path '' in a branch
286 (or tag) we have, for our purposes.
287 """
288 path = self._normalize_path(path)
289 for tagspath in self.tag_locations:
290 onpath = path.startswith(tagspath)
291 longer = len(path) > len('%s/' % tagspath)
292 if path and onpath and longer:
293 tag, subpath = path[len(tagspath) + 1:], ''
294 return tag
295 return False
296 314
297 def get_parent_svn_branch_and_rev(self, number, branch): 315 def get_parent_svn_branch_and_rev(self, number, branch):
298 number -= 1 316 number -= 1
299 if (number, branch) in self.revmap: 317 if (number, branch) in self.revmap:
300 return number, branch 318 return number, branch
330 ''' 348 '''
331 r, br = self.get_parent_svn_branch_and_rev(number, branch) 349 r, br = self.get_parent_svn_branch_and_rev(number, branch)
332 if r is not None: 350 if r is not None:
333 return self.revmap[r, br] 351 return self.revmap[r, br]
334 return revlog.nullid 352 return revlog.nullid
335
336 def _svnpath(self, branch):
337 """Return the relative path in svn of branch.
338 """
339 if branch == None or branch == 'default':
340 return 'trunk'
341 elif branch.startswith('../'):
342 return branch[3:]
343 return 'branches/%s' % branch
344
345 def _determine_parent_branch(self, p, src_path, src_rev, revnum):
346 if src_path is not None:
347 src_file, src_branch = self._path_and_branch_for_path(src_path)
348 src_tag = self._is_path_tag(src_path)
349 if src_tag != False:
350 # also case 2
351 src_branch, src_rev = self.tags[src_tag]
352 return {self._localname(p): (src_branch, src_rev, revnum )}
353 if src_file == '':
354 # case 2
355 return {self._localname(p): (src_branch, src_rev, revnum )}
356 return {}
357 353
358 def update_branch_tag_map_for_rev(self, revision): 354 def update_branch_tag_map_for_rev(self, revision):
359 paths = revision.paths 355 paths = revision.paths
360 added_branches = {} 356 added_branches = {}
361 added_tags = {} 357 added_tags = {}
447 self.ui.status('Tagged %s@%s as %s\n' % 443 self.ui.status('Tagged %s@%s as %s\n' %
448 (info[0] or 'trunk', info[1], t)) 444 (info[0] or 'trunk', info[1], t))
449 self.tags.update(tbdelta['tags'][0]) 445 self.tags.update(tbdelta['tags'][0])
450 self.branches.update(tbdelta['branches'][0]) 446 self.branches.update(tbdelta['branches'][0])
451 447
452 def _updateexternals(self):
453 if not self.current.externals:
454 return
455 # Accumulate externals records for all branches
456 revnum = self.current.rev.revnum
457 branches = {}
458 for path, entry in self.current.externals.iteritems():
459 if not self._is_path_valid(path):
460 self.ui.warn('WARNING: Invalid path %s in externals\n' % path)
461 continue
462 p, b, bp = self._split_branch_path(path)
463 if bp not in branches:
464 external = svnexternals.externalsfile()
465 parent = self.get_parent_revision(revnum, b)
466 pctx = self.repo[parent]
467 if '.hgsvnexternals' in pctx:
468 external.read(pctx['.hgsvnexternals'].data())
469 branches[bp] = external
470 else:
471 external = branches[bp]
472 external[p] = entry
473
474 # Register the file changes
475 for bp, external in branches.iteritems():
476 path = bp + '/.hgsvnexternals'
477 if external:
478 self.set_file(path, external.write(), False, False)
479 else:
480 self.delete_file(path)
481
482 def branchedits(self, branch, rev):
483 check = lambda x: x[0][1] == branch and x[0][0] < rev.revnum
484 return sorted(filter(check, self.revmap.iteritems()), reverse=True)
485
486 def committags(self, delta, rev, endbranches): 448 def committags(self, delta, rev, endbranches):
487 449
488 date = self.fixdate(rev.date) 450 date = self.fixdate(rev.date)
489 # determine additions/deletions per branch 451 # determine additions/deletions per branch
490 branches = {} 452 branches = {}
533 self.revmap[rev.revnum, b] = new 495 self.revmap[rev.revnum, b] = new
534 if b in endbranches: 496 if b in endbranches:
535 endbranches.pop(b) 497 endbranches.pop(b)
536 bname = b or 'default' 498 bname = b or 'default'
537 self.ui.status('Marked branch %s as closed.\n' % bname) 499 self.ui.status('Marked branch %s as closed.\n' % bname)
500
501 def delbranch(self, branch, node, rev):
502 pctx = self.repo[node]
503 files = pctx.manifest().keys()
504 extra = {'close': 1}
505 if self.usebranchnames:
506 extra['branch'] = branch or 'default'
507 ctx = context.memctx(self.repo,
508 (node, revlog.nullid),
509 rev.message or util.default_commit_msg,
510 [],
511 lambda x, y, z: None,
512 self.authors[rev.author],
513 self.fixdate(rev.date),
514 extra)
515 new = self.repo.commitctx(ctx)
516 self.ui.status('Marked branch %s as closed.\n' % (branch or 'default'))
517
518 def set_file(self, path, data, isexec=False, islink=False):
519 if islink:
520 data = 'link ' + data
521 self.current.files[path] = data
522 self.current.execfiles[path] = isexec
523 self.current.symlinks[path] = islink
524 if path in self.current.deleted:
525 del self.current.deleted[path]
526 if path in self.current.missing:
527 self.current.missing.remove(path)
528
529 def delete_file(self, path):
530 self.current.deleted[path] = True
531 if path in self.current.files:
532 del self.current.files[path]
533 self.current.execfiles[path] = False
534 self.current.symlinks[path] = False
535 self.ui.note('D %s\n' % path)
536
537 def _svnpath(self, branch):
538 """Return the relative path in svn of branch.
539 """
540 if branch == None or branch == 'default':
541 return 'trunk'
542 elif branch.startswith('../'):
543 return branch[3:]
544 return 'branches/%s' % branch
545
546 def _path_and_branch_for_path(self, path, existing=True):
547 return self._split_branch_path(path, existing=existing)[:2]
548
549 def _branch_for_path(self, path, existing=True):
550 return self._path_and_branch_for_path(path, existing=existing)[1]
551
552 def _determine_parent_branch(self, p, src_path, src_rev, revnum):
553 if src_path is not None:
554 src_file, src_branch = self._path_and_branch_for_path(src_path)
555 src_tag = self._is_path_tag(src_path)
556 if src_tag != False:
557 # also case 2
558 src_branch, src_rev = self.tags[src_tag]
559 return {self._localname(p): (src_branch, src_rev, revnum )}
560 if src_file == '':
561 # case 2
562 return {self._localname(p): (src_branch, src_rev, revnum )}
563 return {}
564
565 def _updateexternals(self):
566 if not self.current.externals:
567 return
568 # Accumulate externals records for all branches
569 revnum = self.current.rev.revnum
570 branches = {}
571 for path, entry in self.current.externals.iteritems():
572 if not self._is_path_valid(path):
573 self.ui.warn('WARNING: Invalid path %s in externals\n' % path)
574 continue
575 p, b, bp = self._split_branch_path(path)
576 if bp not in branches:
577 external = svnexternals.externalsfile()
578 parent = self.get_parent_revision(revnum, b)
579 pctx = self.repo[parent]
580 if '.hgsvnexternals' in pctx:
581 external.read(pctx['.hgsvnexternals'].data())
582 branches[bp] = external
583 else:
584 external = branches[bp]
585 external[p] = entry
586
587 # Register the file changes
588 for bp, external in branches.iteritems():
589 path = bp + '/.hgsvnexternals'
590 if external:
591 self.set_file(path, external.write(), False, False)
592 else:
593 self.delete_file(path)
538 594
539 def commit_current_delta(self, tbdelta): 595 def commit_current_delta(self, tbdelta):
540 if hasattr(self, '_exception_info'): #pragma: no cover 596 if hasattr(self, '_exception_info'): #pragma: no cover
541 traceback.print_exception(*self._exception_info) 597 traceback.print_exception(*self._exception_info)
542 raise ReplayException() 598 raise ReplayException()
670 continue 726 continue
671 self.delbranch(branch, parent, rev) 727 self.delbranch(branch, parent, rev)
672 728
673 self._save_metadata() 729 self._save_metadata()
674 self.current.clear() 730 self.current.clear()
675
676 def delbranch(self, branch, node, rev):
677 pctx = self.repo[node]
678 files = pctx.manifest().keys()
679 extra = {'close': 1}
680 if self.usebranchnames:
681 extra['branch'] = branch or 'default'
682 ctx = context.memctx(self.repo,
683 (node, revlog.nullid),
684 rev.message or util.default_commit_msg,
685 [],
686 lambda x, y, z: None,
687 self.authors[rev.author],
688 self.fixdate(rev.date),
689 extra)
690 new = self.repo.commitctx(ctx)
691 self.ui.status('Marked branch %s as closed.\n' % (branch or 'default'))
692
693 def _get_uuid(self):
694 return open(os.path.join(self.meta_data_dir, 'uuid')).read()
695
696 def _set_uuid(self, uuid):
697 if not uuid:
698 return
699 elif os.path.isfile(os.path.join(self.meta_data_dir, 'uuid')):
700 stored_uuid = self._get_uuid()
701 assert stored_uuid
702 if uuid != stored_uuid:
703 raise hgutil.Abort('unable to operate on unrelated repository')
704 else:
705 if uuid:
706 f = open(os.path.join(self.meta_data_dir, 'uuid'), 'w')
707 f.write(uuid)
708 f.flush()
709 f.close()
710 else:
711 raise hgutil.Abort('unable to operate on unrelated repository')
712
713 uuid = property(_get_uuid, _set_uuid, None,
714 'Error-checked UUID of source Subversion repository.')
715
716 @property
717 def meta_data_dir(self):
718 return os.path.join(self.path, '.hg', 'svn')
719
720 @property
721 def branch_info_file(self):
722 return os.path.join(self.meta_data_dir, 'branch_info')
723
724 @property
725 def tag_locations_file(self):
726 return os.path.join(self.meta_data_dir, 'tag_locations')
727
728 @property
729 def authors_file(self):
730 return os.path.join(self.meta_data_dir, 'authors')
731 731
732 # Here come all the actual editor methods 732 # Here come all the actual editor methods
733 733
734 @ieditor 734 @ieditor
735 def delete_entry(self, path, revision_bogus, parent_baton, pool=None): 735 def delete_entry(self, path, revision_bogus, parent_baton, pool=None):