Mercurial > hgsubversion
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): |