comparison hg_delta_editor.py @ 133:2242dd1163c6

hg_delta_editor: fix bad parent revision calculation in the case of a branch recycling a name. Also implemented marking branches as closed in both replay and stupid paths.
author Augie Fackler <durin42@gmail.com>
date Wed, 10 Dec 2008 14:29:05 -0600
parents 4d42dbbb5127
children cf6fe8457570
comparison
equal deleted inserted replaced
132:3a9d6cd18332 133:2242dd1163c6
105 105
106 self.clear_current_info() 106 self.clear_current_info()
107 self.author_host = author_host 107 self.author_host = author_host
108 108
109 def __setup_repo(self, repo_path): 109 def __setup_repo(self, repo_path):
110 '''Verify the repo is going to work out for us. 110 """Verify the repo is going to work out for us.
111 111
112 This method will fail an assertion if the repo exists but doesn't have 112 This method will fail an assertion if the repo exists but doesn't have
113 the Subversion metadata. 113 the Subversion metadata.
114 ''' 114 """
115 if os.path.isdir(repo_path) and len(os.listdir(repo_path)): 115 if os.path.isdir(repo_path) and len(os.listdir(repo_path)):
116 self.repo = hg.repository(self.ui, repo_path) 116 self.repo = hg.repository(self.ui, repo_path)
117 assert os.path.isfile(self.revmap_file) 117 assert os.path.isfile(self.revmap_file)
118 assert os.path.isfile(self.svn_url_file) 118 assert os.path.isfile(self.svn_url_file)
119 assert os.path.isfile(self.uuid_file) 119 assert os.path.isfile(self.uuid_file)
139 # Map fully qualified destination file paths to module source path 139 # Map fully qualified destination file paths to module source path
140 self.copies = {} 140 self.copies = {}
141 self.missing_plaintexts = set() 141 self.missing_plaintexts = set()
142 self.commit_branches_empty = {} 142 self.commit_branches_empty = {}
143 self.base_revision = None 143 self.base_revision = None
144 self.branches_to_delete = set()
144 145
145 def _save_metadata(self): 146 def _save_metadata(self):
146 '''Save the Subversion metadata. This should really be called after 147 '''Save the Subversion metadata. This should really be called after
147 every revision is created. 148 every revision is created.
148 ''' 149 '''
162 163
163 def _path_and_branch_for_path(self, path): 164 def _path_and_branch_for_path(self, path):
164 return self._split_branch_path(path)[:2] 165 return self._split_branch_path(path)[:2]
165 166
166 def _split_branch_path(self, path): 167 def _split_branch_path(self, path):
167 '''Figure out which branch inside our repo this path represents, and 168 """Figure out which branch inside our repo this path represents, and
168 also figure out which path inside that branch it is. 169 also figure out which path inside that branch it is.
169 170
170 Raises an exception if it can't perform its job. 171 Raises an exception if it can't perform its job.
171 ''' 172 """
172 path = self._normalize_path(path) 173 path = self._normalize_path(path)
173 if path.startswith('trunk'): 174 if path.startswith('trunk'):
174 p = path[len('trunk'):] 175 p = path[len('trunk'):]
175 if p and p[0] == '/': 176 if p and p[0] == '/':
176 p = p[1:] 177 p = p[1:]
184 p = p[1:] 185 p = p[1:]
185 return p, br, 'branches/' + br 186 return p, br, 'branches/' + br
186 return None, None, None 187 return None, None, None
187 188
188 def set_current_rev(self, rev): 189 def set_current_rev(self, rev):
189 '''Set the revision we're currently converting. 190 """Set the revision we're currently converting.
190 ''' 191 """
191 self.current_rev = rev 192 self.current_rev = rev
192 193
193 def set_file(self, path, data, isexec=False, islink=False): 194 def set_file(self, path, data, isexec=False, islink=False):
194 if islink: 195 if islink:
195 data = 'link ' + data 196 data = 'link ' + data
234 for num, br in self.revmap.iterkeys(): 235 for num, br in self.revmap.iterkeys():
235 if br != branch: 236 if br != branch:
236 continue 237 continue
237 if num <= number and num > real_num: 238 if num <= number and num > real_num:
238 real_num = num 239 real_num = num
239 if real_num == 0: 240 if branch in self.branches:
240 if branch in self.branches: 241 parent_branch = self.branches[branch][0]
241 parent_branch = self.branches[branch][0] 242 parent_branch_rev = self.branches[branch][1]
242 parent_branch_rev = self.branches[branch][1] 243 # check to see if this branch already existed and is the same
243 if parent_branch_rev <= 0: 244 if parent_branch_rev < real_num:
244 return None, None 245 return real_num, branch
245 branch_created_rev = self.branches[branch][2] 246 # if that wasn't true, then this is the a new branch with the
246 if parent_branch == 'trunk': 247 # same name as some old deleted branch
247 parent_branch = None 248 if parent_branch_rev <= 0 and real_num == 0:
248 if branch_created_rev <= number+1 and branch != parent_branch: 249 return None, None
249 return self.get_parent_svn_branch_and_rev( 250 branch_created_rev = self.branches[branch][2]
250 parent_branch_rev+1, 251 if parent_branch == 'trunk':
251 parent_branch) 252 parent_branch = None
253 if branch_created_rev <= number+1 and branch != parent_branch:
254 return self.get_parent_svn_branch_and_rev(
255 parent_branch_rev+1,
256 parent_branch)
252 if real_num != 0: 257 if real_num != 0:
253 return real_num, branch 258 return real_num, branch
254 return None, None 259 return None, None
255 260
256 def get_parent_revision(self, number, branch): 261 def get_parent_revision(self, number, branch):
263 268
264 def update_branch_tag_map_for_rev(self, revision): 269 def update_branch_tag_map_for_rev(self, revision):
265 paths = revision.paths 270 paths = revision.paths
266 added_branches = {} 271 added_branches = {}
267 added_tags = {} 272 added_tags = {}
273 self.branches_to_delete = set()
268 tags_to_delete = set() 274 tags_to_delete = set()
269 branches_to_delete = set()
270 for p in sorted(paths): 275 for p in sorted(paths):
271 fi, br = self._path_and_branch_for_path(p) 276 fi, br = self._path_and_branch_for_path(p)
272 if fi is not None: 277 if fi is not None:
273 if fi == '' and br not in self.branches: 278 if fi == '' and paths[p].action != 'D':
274 src_p = paths[p].copyfrom_path 279 src_p = paths[p].copyfrom_path
275 src_rev = paths[p].copyfrom_rev 280 src_rev = paths[p].copyfrom_rev
276 src_tag = self._is_path_tag(src_p) 281 src_tag = self._is_path_tag(src_p)
277 282
278 if not ((src_p and self._is_path_valid(src_p)) or 283 if not ((src_p and self._is_path_valid(src_p)) or
292 continue 297 continue
293 added_branches[br] = src_branch, src_rev, revision.revnum 298 added_branches[br] = src_branch, src_rev, revision.revnum
294 elif fi == '' and br in self.branches: 299 elif fi == '' and br in self.branches:
295 br2 = br or 'default' 300 br2 = br or 'default'
296 if br2 not in self.repo.branchtags() and paths[p].action == 'D': 301 if br2 not in self.repo.branchtags() and paths[p].action == 'D':
297 branches_to_delete.add(br) 302 self.branches_to_delete.add(br)
298 else: 303 else:
299 t_name = self._is_path_tag(p) 304 t_name = self._is_path_tag(p)
300 if t_name == False: 305 if t_name == False:
301 continue 306 continue
302 src_p, src_rev = paths[p].copyfrom_path, paths[p].copyfrom_rev 307 src_p, src_rev = paths[p].copyfrom_path, paths[p].copyfrom_rev
318 elif (paths[p].action == 'D' and p.endswith(t_name) 323 elif (paths[p].action == 'D' and p.endswith(t_name)
319 and t_name in self.tags): 324 and t_name in self.tags):
320 tags_to_delete.add(t_name) 325 tags_to_delete.add(t_name)
321 for t in tags_to_delete: 326 for t in tags_to_delete:
322 del self.tags[t] 327 del self.tags[t]
323 for br in branches_to_delete: 328 for br in self.branches_to_delete:
324 del self.branches[br] 329 del self.branches[br]
325 self.tags.update(added_tags) 330 self.tags.update(added_tags)
326 self.branches.update(added_branches) 331 self.branches.update(added_branches)
327 self._save_metadata() 332 self._save_metadata()
328 333
357 files = dict(files) 362 files = dict(files)
358 363
359 parents = (self.get_parent_revision(rev.revnum, branch), 364 parents = (self.get_parent_revision(rev.revnum, branch),
360 revlog.nullid) 365 revlog.nullid)
361 if branch is not None: 366 if branch is not None:
362 if branch not in self.branches and branch not in self.repo.branchtags(): 367 if (branch not in self.branches
368 and branch not in self.repo.branchtags()):
363 continue 369 continue
364 extra['branch'] = branch 370 extra['branch'] = branch
371 if (branch in self.branches_to_delete):
372 continue
365 parent_ctx = self.repo.changectx(parents[0]) 373 parent_ctx = self.repo.changectx(parents[0])
366 def filectxfn(repo, memctx, path): 374 def filectxfn(repo, memctx, path):
367 current_file = files[path] 375 current_file = files[path]
368 if current_file in self.deleted_files: 376 if current_file in self.deleted_files:
369 raise IOError() 377 raise IOError()
394 self.ui.status('committed as %s on branch %s\n' % 402 self.ui.status('committed as %s on branch %s\n' %
395 (node.hex(new_hash), (branch or 'default'))) 403 (node.hex(new_hash), (branch or 'default')))
396 if (rev.revnum, branch) not in self.revmap: 404 if (rev.revnum, branch) not in self.revmap:
397 self.add_to_revmap(rev.revnum, branch, new_hash) 405 self.add_to_revmap(rev.revnum, branch, new_hash)
398 # now we handle branches that need to be committed without any files 406 # now we handle branches that need to be committed without any files
407 for branch in self.branches_to_delete:
408 closed = revlog.nullid
409 if 'closed-branches' in self.repo.branchtags():
410 closed = self.repo['closed-branches'].node()
411 ha = self.get_parent_revision(rev.revnum, branch)
412 parentctx = self.repo.changectx(ha)
413 if parentctx.children():
414 continue
415 parents = (ha, closed)
416 def del_all_files(*args):
417 raise IOError
418 files = parentctx.manifest().keys()
419 current_ctx = context.memctx(self.repo,
420 parents,
421 rev.message or ' ',
422 files,
423 del_all_files,
424 '%s%s' % (rev.author,
425 self.author_host),
426 date,
427 {'branch': 'closed-branches'})
428 new_hash = self.repo.commitctx(current_ctx)
429 self.ui.status('Marked branch %s as closed.' % (branch or 'default'))
399 for branch in self.commit_branches_empty: 430 for branch in self.commit_branches_empty:
400 ha = self.get_parent_revision(rev.revnum, branch) 431 ha = self.get_parent_revision(rev.revnum, branch)
401 if ha == node.nullid: 432 if ha == node.nullid:
402 continue 433 continue
403 parent_ctx = self.repo.changectx(ha) 434 parent_ctx = self.repo.changectx(ha)
404 def del_all_files(*args): 435 def del_all_files(*args):
405 raise IOError 436 raise IOError
406 extra = {} 437 extra = {}
407 if branch: 438 if parent_ctx.children():
408 extra['branch'] = branch 439 # Target isn't an active head, no need to do things to it.
440 continue
441 if branch in self.branches_to_delete:
442 extra['branch'] = 'closed-branch'
409 # True here means nuke all files 443 # True here means nuke all files
410 files = [] 444 files = []
411 if self.commit_branches_empty[branch]: 445 if self.commit_branches_empty[branch]:
412 files = parent_ctx.manifest().keys() 446 files = parent_ctx.manifest().keys()
413 current_ctx = context.memctx(self.repo, 447 current_ctx = context.memctx(self.repo,
466 return open(self.svn_url_file).read() 500 return open(self.svn_url_file).read()
467 501
468 @stash_exception_on_self 502 @stash_exception_on_self
469 def delete_entry(self, path, revision_bogus, parent_baton, pool=None): 503 def delete_entry(self, path, revision_bogus, parent_baton, pool=None):
470 br_path, branch = self._path_and_branch_for_path(path) 504 br_path, branch = self._path_and_branch_for_path(path)
505 if br_path == '':
506 self.branches_to_delete.add(branch)
471 if br_path is not None: 507 if br_path is not None:
472 ha = self.get_parent_revision(self.current_rev.revnum, branch) 508 ha = self.get_parent_revision(self.current_rev.revnum, branch)
473 if ha == revlog.nullid: 509 if ha == revlog.nullid:
474 return 510 return
475 ctx = self.repo.changectx(ha) 511 ctx = self.repo.changectx(ha)