def delbranch(self, branch, node, rev): pctx = self.repo[node] files = pctx.manifest().keys() extra = self.genextra(rev.revnum, branch) self.mapbranch(extra, True) ctx = context.memctx(self.repo, (node, revlog.nullid), rev.message or util.default_commit_msg(self.ui), [], lambda x, y, z: None, self.authors[rev.author], self.fixdate(rev.date), extra) new = self.repo.commitctx(ctx) self.ui.status('Marked branch %s as closed.\n' % (branch or 'default'))
def delbranch(self, branch, node, rev): pctx = self.repo[node] files = pctx.manifest().keys() extra = self.genextra(rev.revnum, branch) self.mapbranch(extra, True) ctx = context.memctx(self.repo, (node, revlog.nullid), rev.message or util.default_commit_msg(self.ui), [], lambda x, y, z: None, self.authors[rev.author], self.fixdate(rev.date), extra) new = self.repo.svn_commitctx(ctx) self.ui.status('Marked branch %s as closed.\n' % (branch or 'default'))
def movetag(self, tag, hash, rev, date): if tag in self.tags and self.tags[tag] == hash: return # determine branch from earliest unclosed ancestor branchparent = self.repo[hash] while branchparent.extra().get('close'): branchparent = branchparent.parents()[0] branch = self.get_source_rev(ctx=branchparent)[1] parentctx = self.repo[self.get_parent_revision(rev.revnum + 1, branch)] if '.hgtags' in parentctx: tagdata = parentctx.filectx('.hgtags').data() else: tagdata = '' tagdata += '%s %s\n' % (node.hex(hash), self.tagmap.get(tag, tag)) def hgtagsfn(repo, memctx, path): assert path == '.hgtags' return context.memfilectx(path=path, data=tagdata, islink=False, isexec=False, copied=False) revnum, branch = self.get_source_rev(ctx=parentctx)[:2] newparent = None for child in parentctx.children(): if (self.get_source_rev(ctx=child)[1] == branch and child.extra().get('close', False)): newparent = child if newparent: parentctx = newparent revnum, branch = self.get_source_rev(ctx=parentctx)[:2] ctx = context.memctx(self.repo, (parentctx.node(), node.nullid), rev.message or util.default_commit_msg(self.ui), ['.hgtags', ], hgtagsfn, self.authors[rev.author], date, parentctx.extra()) new_hash = self.repo.svn_commitctx(ctx) if not newparent: assert self.revmap[revnum, branch] == parentctx.node() self.revmap[revnum, branch] = new_hash self.tags[tag] = hash, rev.revnum util.describe_commit(self.ui, new_hash, branch)
def movetag(self, tag, hash, rev, date): if tag in self.tags and self.tags[tag] == hash: return # determine branch from earliest unclosed ancestor branchparent = self.repo[hash] while branchparent.extra().get('close'): branchparent = branchparent.parents()[0] branch = self.get_source_rev(ctx=branchparent)[1] parentctx = self.repo[self.get_parent_revision(rev.revnum + 1, branch)] if '.hgtags' in parentctx: tagdata = parentctx.filectx('.hgtags').data() else: tagdata = '' tagdata += '%s %s\n' % (node.hex(hash), self.tagmap.get(tag, tag)) def hgtagsfn(repo, memctx, path): assert path == '.hgtags' return context.memfilectx(path=path, data=tagdata, islink=False, isexec=False, copied=False) revnum, branch = self.get_source_rev(ctx=parentctx)[:2] newparent = None for child in parentctx.children(): if (self.get_source_rev(ctx=child)[1] == branch and child.extra().get('close', False)): newparent = child if newparent: parentctx = newparent revnum, branch = self.get_source_rev(ctx=parentctx)[:2] ctx = context.memctx(self.repo, (parentctx.node(), node.nullid), rev.message or util.default_commit_msg(self.ui), [ '.hgtags', ], hgtagsfn, self.authors[rev.author], date, parentctx.extra()) new_hash = self.repo.commitctx(ctx) if not newparent: assert self.revmap[revnum, branch] == parentctx.node() self.revmap[revnum, branch] = new_hash self.tags[tag] = hash, rev.revnum util.describe_commit(self.ui, new_hash, branch)
def pull(repo, source, heads=[], force=False): """pull new revisions from Subversion""" assert source.capable('subversion') svn_url = source.svnurl # Split off #rev svn_url, heads, checkout = util.parseurl(svn_url, heads) old_encoding = util.swap_out_encoding() try: stopat_rev = int(checkout or 0) except ValueError: raise hgutil.Abort('unrecognised Subversion revision %s: ' 'only numbers work.' % checkout) have_replay = not repo.ui.configbool('hgsubversion', 'stupid') if not have_replay: repo.ui.note('fetching stupidly...\n') svn = source.svn meta = repo.svnmeta(svn.uuid, svn.subdir) layout = repo.ui.config('hgsubversion', 'layout', 'auto') if layout == 'auto': rootlist = svn.list_dir('', revision=(stopat_rev or None)) if sum(map(lambda x: x in rootlist, ('branches', 'tags', 'trunk'))): layout = 'standard' else: layout = 'single' repo.ui.setconfig('hgsubversion', 'layout', layout) repo.ui.note('using %s layout\n' % layout) branch = repo.ui.config('hgsubversion', 'branch') if branch: if layout != 'single': msg = ('branch cannot be specified for Subversion clones using ' 'standard directory layout') raise hgutil.Abort(msg) meta.branchmap['default'] = branch ui = repo.ui start = meta.revmap.youngest origrevcount = len(meta.revmap) if start <= 0: # we are initializing a new repository start = repo.ui.config('hgsubversion', 'startrev', 0) if isinstance(start, str) and start.upper() == 'HEAD': start = svn.last_changed_rev else: start = int(start) if start > 0: if layout == 'standard': raise hgutil.Abort('non-zero start revisions are only ' 'supported for single-directory clones.') ui.note('starting at revision %d; any prior will be ignored\n' % start) # fetch all revisions *including* the one specified... start -= 1 # anything less than zero makes no sense if start < 0: start = 0 oldrevisions = len(meta.revmap) if stopat_rev: total = stopat_rev - start else: total = svn.HEAD - start try: try: # start converting revisions firstrun = True for r in svn.revisions(start=start, stop=stopat_rev): if (r.author is None and r.message == 'This is an empty revision for padding.'): continue tbdelta = meta.update_branch_tag_map_for_rev(r) # got a 502? Try more than once! tries = 0 converted = False while not converted: try: msg = '' if r.message: msg = r.message.strip() if not msg: msg = util.default_commit_msg(ui) else: msg = [s.strip() for s in msg.splitlines() if s][0] if getattr(ui, 'termwidth', False): w = ui.termwidth() else: w = hgutil.termwidth() bits = (r.revnum, r.author, msg) ui.status(('[r%d] %s: %s' % bits)[:w] + '\n') util.progress(ui, 'pull', r.revnum - start, total=total) meta.save_tbdelta(tbdelta) close = pullfuns[have_replay](ui, meta, svn, r, tbdelta, firstrun) meta.committags(r, close) for branch, parent in close.iteritems(): if parent in (None, node.nullid): continue meta.delbranch(branch, parent, r) meta.save() converted = True firstrun = False except svnwrap.SubversionRepoCanNotReplay, e: #pragma: no cover ui.status('%s\n' % e.message) stupidmod.print_your_svn_is_old_message(ui) have_replay = False except svnwrap.SubversionException, e: #pragma: no cover if (e.args[1] == svnwrap.ERR_RA_DAV_REQUEST_FAILED and '502' in str(e) and tries < 3): tries += 1 ui.status('Got a 502, retrying (%s)\n' % tries) else: ui.traceback() raise hgutil.Abort(*e.args)
def convert_rev(ui, meta, svn, r, tbdelta, firstrun): # this server fails at replay branches = branches_in_paths(meta, tbdelta, r.paths, r.revnum, svn.checkpath, svn.list_files) brpaths = branches.values() bad_branch_paths = {} for br, bp in branches.iteritems(): bad_branch_paths[br] = [] # This next block might be needed, but for now I'm omitting it until it # can be proven necessary. # for bad in brpaths: # if bad.startswith(bp) and len(bad) > len(bp): # bad_branch_paths[br].append(bad[len(bp)+1:]) # We've go a branch that contains other branches. We have to be careful # to get results similar to real replay in this case. for existingbr in meta.branches: bad = meta.remotename(existingbr) if bad.startswith(bp) and len(bad) > len(bp): bad_branch_paths[br].append(bad[len(bp)+1:]) deleted_branches = {} for p in r.paths: tag = meta.get_path_tag(p) if tag and tag not in meta.tags: continue branch = meta.localname(p) if not (r.paths[p].action == 'R' and branch in meta.branches): continue closed = checkbranch(meta, r, branch) if closed is not None: deleted_branches[branch] = closed date = meta.fixdate(r.date) check_deleted_branches = set(tbdelta['branches'][1]) for b in branches: parentctx = meta.repo[meta.get_parent_revision(r.revnum, b)] tag = meta.get_path_tag(meta.remotename(b)) kind = svn.checkpath(branches[b], r.revnum) if kind != 'd': if not tag: # Branch does not exist at this revision. Get parent # revision and remove everything. deleted_branches[b] = parentctx.node() continue incremental = (meta.revmap.oldest > 0) if incremental: try: files_touched, filectxfn2 = diff_branchrev( ui, svn, meta, b, branches[b], r, parentctx) except BadPatchApply, e: # Either this revision or the previous one does not exist. ui.note("Fetching entire revision: %s.\n" % e.args[0]) incremental = False if not incremental: files_touched, filectxfn2 = fetch_branchrev( svn, meta, b, branches[b], r, parentctx) externals = {} if meta.layout != 'single': externals = fetch_externals(ui, svn, branches[b], r, parentctx) externals = svnexternals.getchanges(ui, meta.repo, parentctx, externals) files_touched.extend(externals) def filectxfn(repo, memctx, path): if path in externals: if externals[path] is None: raise IOError(errno.ENOENT, 'no externals') return context.memfilectx(path=path, data=externals[path], islink=False, isexec=False, copied=None) for bad in bad_branch_paths[b]: if path.startswith(bad): raise IOError(errno.ENOENT, 'Path %s is bad' % path) return filectxfn2(repo, memctx, path) if '' in files_touched: files_touched.remove('') excluded = [f for f in files_touched if f not in meta.filemap] for f in excluded: files_touched.remove(f) if b: # Regular tag without modifications, it will be committed by # svnmeta.committag(), we can skip the whole branch for now if (tag and tag not in meta.tags and b not in meta.branches and b not in meta.repo.branchtags() and not files_touched): continue if parentctx.node() == node.nullid and not files_touched: meta.repo.ui.debug('skipping commit since parent is null and no files touched.\n') continue for f in files_touched: if f: # this is a case that really shouldn't ever happen, it means # something is very wrong assert f[0] != '/' extra = meta.genextra(r.revnum, b) if tag: if parentctx.node() == node.nullid: continue extra.update({'branch': parentctx.extra().get('branch', None), 'close': 1}) origbranch = extra.get('branch', None) meta.mapbranch(extra) current_ctx = context.memctx(meta.repo, [parentctx.node(), revlog.nullid], r.message or util.default_commit_msg(ui), files_touched, filectxfn, meta.authors[r.author], date, extra) ha = meta.repo.commitctx(current_ctx) if not tag: if (not origbranch in meta.branches and not meta.get_path_tag(meta.remotename(origbranch))): meta.branches[origbranch] = None, 0, r.revnum meta.revmap[r.revnum, b] = ha else: meta.movetag(tag, ha, r, date) meta.addedtags.pop(tag, None) util.describe_commit(ui, ha, b)
def convert_rev(ui, meta, svn, r, tbdelta, firstrun): editor = meta.editor editor.current.clear() editor.current.rev = r if firstrun and meta.revmap.oldest <= 0: # We know nothing about this project, so fetch everything before # trying to apply deltas. ui.debug('replay: fetching full revision\n') svn.get_revision(r.revnum, editor) else: svn.get_replay(r.revnum, editor, meta.revmap.oldest) current = editor.current current.findmissing(svn) updateexternals(ui, meta, current) if current.exception is not None: #pragma: no cover traceback.print_exception(*current.exception) raise ReplayException() if current.missing: raise MissingPlainTextError() # paranoidly generate the list of files to commit files_to_commit = set(current.files.keys()) files_to_commit.update(current.symlinks.keys()) files_to_commit.update(current.execfiles.keys()) files_to_commit.update(current.deleted.keys()) # back to a list and sort so we get sane behavior files_to_commit = list(files_to_commit) files_to_commit.sort() branch_batches = {} rev = current.rev date = meta.fixdate(rev.date) # build up the branches that have files on them for f in files_to_commit: if not meta.is_path_valid(f): continue p, b = meta.split_branch_path(f)[:2] if b not in branch_batches: branch_batches[b] = [] branch_batches[b].append((p, f)) closebranches = {} for branch in tbdelta['branches'][1]: branchedits = meta.revmap.branchedits(branch, rev) if len(branchedits) < 1: # can't close a branch that never existed continue ha = branchedits[0][1] closebranches[branch] = ha extraempty = (set(tbdelta['branches'][0]) - (set(current.emptybranches) | set(branch_batches.keys()))) current.emptybranches.update([(x, False) for x in extraempty]) # 1. handle normal commits closedrevs = closebranches.values() for branch, files in branch_batches.iteritems(): if branch in current.emptybranches and files: del current.emptybranches[branch] files = dict(files) parents = meta.get_parent_revision(rev.revnum, branch), revlog.nullid if parents[0] in closedrevs and branch in meta.closebranches: continue extra = meta.genextra(rev.revnum, branch) tag = None if branch is not None: # New regular tag without modifications, it will be committed by # svnmeta.committag(), we can skip the whole branch for now tag = meta.get_path_tag(meta.remotename(branch)) if (tag and tag not in meta.tags and branch not in meta.branches and branch not in meta.repo.branchtags() and not files): continue parentctx = meta.repo.changectx(parents[0]) if tag: if parentctx.node() == node.nullid: continue extra.update({ 'branch': parentctx.extra().get('branch', None), 'close': 1 }) def filectxfn(repo, memctx, path): current_file = files[path] if current_file in current.deleted: raise IOError(errno.ENOENT, '%s is deleted' % path) copied = current.copies.get(current_file) flags = parentctx.flags(path) is_exec = current.execfiles.get(current_file, 'x' in flags) is_link = current.symlinks.get(current_file, 'l' in flags) if current_file in current.files: data = current.files[current_file] if is_link and data.startswith('link '): data = data[len('link '):] elif is_link: ui.debug('file marked as link, but may contain data: ' '%s (%r)\n' % (current_file, flags)) else: data = parentctx.filectx(path).data() return context.memfilectx(path=path, data=data, islink=is_link, isexec=is_exec, copied=copied) meta.mapbranch(extra) current_ctx = context.memctx( meta.repo, parents, rev.message or util.default_commit_msg(ui), files.keys(), filectxfn, meta.authors[rev.author], date, extra) new_hash = meta.repo.commitctx(current_ctx) util.describe_commit(ui, new_hash, branch) if (rev.revnum, branch) not in meta.revmap and not tag: meta.revmap[rev.revnum, branch] = new_hash if tag: meta.movetag(tag, new_hash, rev, date) meta.addedtags.pop(tag, None) # 2. handle branches that need to be committed without any files for branch in current.emptybranches: ha = meta.get_parent_revision(rev.revnum, branch) if ha == node.nullid: continue parent_ctx = meta.repo.changectx(ha) def del_all_files(*args): raise IOError(errno.ENOENT, 'deleting all files') # True here meant nuke all files, shouldn't happen with branch closing if current.emptybranches[branch]: #pragma: no cover raise hgutil.Abort('Empty commit to an open branch attempted. ' 'Please report this issue.') extra = meta.genextra(rev.revnum, branch) meta.mapbranch(extra) current_ctx = context.memctx(meta.repo, (ha, node.nullid), rev.message or ' ', [], del_all_files, meta.authors[rev.author], date, extra) new_hash = meta.repo.commitctx(current_ctx) util.describe_commit(ui, new_hash, branch) if (rev.revnum, branch) not in meta.revmap: meta.revmap[rev.revnum, branch] = new_hash return closebranches
def convert_rev(ui, meta, svn, r, tbdelta, firstrun): editor = meta.editor editor.current.clear() editor.current.rev = r if firstrun and meta.revmap.oldest <= 0: # We know nothing about this project, so fetch everything before # trying to apply deltas. ui.debug('replay: fetching full revision\n') svn.get_revision(r.revnum, editor) else: svn.get_replay(r.revnum, editor, meta.revmap.oldest) current = editor.current current.findmissing(svn) updateexternals(ui, meta, current) if current.exception is not None: #pragma: no cover traceback.print_exception(*current.exception) raise ReplayException() if current.missing: raise MissingPlainTextError() # paranoidly generate the list of files to commit files_to_commit = set(current.files.keys()) files_to_commit.update(current.symlinks.keys()) files_to_commit.update(current.execfiles.keys()) files_to_commit.update(current.deleted.keys()) # back to a list and sort so we get sane behavior files_to_commit = list(files_to_commit) files_to_commit.sort() branch_batches = {} rev = current.rev date = meta.fixdate(rev.date) # build up the branches that have files on them for f in files_to_commit: if not meta.is_path_valid(f): continue p, b = meta.split_branch_path(f)[:2] if b not in branch_batches: branch_batches[b] = [] branch_batches[b].append((p, f)) closebranches = {} for branch in tbdelta['branches'][1]: branchedits = meta.revmap.branchedits(branch, rev) if len(branchedits) < 1: # can't close a branch that never existed continue ha = branchedits[0][1] closebranches[branch] = ha extraempty = (set(tbdelta['branches'][0]) - (set(current.emptybranches) | set(branch_batches.keys()))) current.emptybranches.update([(x, False) for x in extraempty]) # 1. handle normal commits closedrevs = closebranches.values() for branch, files in branch_batches.iteritems(): if branch in current.emptybranches and files: del current.emptybranches[branch] files = dict(files) parents = meta.get_parent_revision(rev.revnum, branch), revlog.nullid if parents[0] in closedrevs and branch in meta.closebranches: continue extra = meta.genextra(rev.revnum, branch) tag = None if branch is not None: # New regular tag without modifications, it will be committed by # svnmeta.committag(), we can skip the whole branch for now tag = meta.get_path_tag(meta.remotename(branch)) if (tag and tag not in meta.tags and branch not in meta.branches and branch not in meta.repo.branchtags() and not files): continue parentctx = meta.repo.changectx(parents[0]) if tag: if parentctx.node() == node.nullid: continue extra.update({'branch': parentctx.extra().get('branch', None), 'close': 1}) def filectxfn(repo, memctx, path): current_file = files[path] if current_file in current.deleted: raise IOError(errno.ENOENT, '%s is deleted' % path) copied = current.copies.get(current_file) flags = parentctx.flags(path) is_exec = current.execfiles.get(current_file, 'x' in flags) is_link = current.symlinks.get(current_file, 'l' in flags) if current_file in current.files: data = current.files[current_file] if is_link and data.startswith('link '): data = data[len('link '):] elif is_link: ui.debug('file marked as link, but may contain data: ' '%s (%r)\n' % (current_file, flags)) else: data = parentctx.filectx(path).data() return context.memfilectx(path=path, data=data, islink=is_link, isexec=is_exec, copied=copied) meta.mapbranch(extra) current_ctx = context.memctx(meta.repo, parents, rev.message or util.default_commit_msg(ui), files.keys(), filectxfn, meta.authors[rev.author], date, extra) new_hash = meta.repo.commitctx(current_ctx) util.describe_commit(ui, new_hash, branch) if (rev.revnum, branch) not in meta.revmap and not tag: meta.revmap[rev.revnum, branch] = new_hash if tag: meta.movetag(tag, new_hash, rev, date) meta.addedtags.pop(tag, None) # 2. handle branches that need to be committed without any files for branch in current.emptybranches: ha = meta.get_parent_revision(rev.revnum, branch) if ha == node.nullid: continue parent_ctx = meta.repo.changectx(ha) def del_all_files(*args): raise IOError(errno.ENOENT, 'deleting all files') # True here meant nuke all files, shouldn't happen with branch closing if current.emptybranches[branch]: #pragma: no cover raise hgutil.Abort('Empty commit to an open branch attempted. ' 'Please report this issue.') extra = meta.genextra(rev.revnum, branch) meta.mapbranch(extra) current_ctx = context.memctx(meta.repo, (ha, node.nullid), rev.message or ' ', [], del_all_files, meta.authors[rev.author], date, extra) new_hash = meta.repo.commitctx(current_ctx) util.describe_commit(ui, new_hash, branch) if (rev.revnum, branch) not in meta.revmap: meta.revmap[rev.revnum, branch] = new_hash return closebranches