def verifydata(svndata): svnworker = svnrepo.svnremoterepo(ui, url).svn i = 0 res = True for fn, type in svndata: i += 1 if type != 'f': continue fp = fn if branchpath: fp = branchpath + '/' + fn data, mode = svnworker.get_file(posixpath.normpath(fp), srev) try: fctx = ctx[fn] except error.LookupError: yield i, "%s\0%r" % (fn, res) continue if not fctx.data() == data: ui.write('difference in: %s\n' % fn) diff_file(fn, data) res = False if not fctx.flags() == mode: ui.write('wrong flags for: %s\n' % fn) res = False yield i, "%s\0%r" % (fn, res)
def genignore(ui, repo, force=False, **opts): """generate .hgignore from svn:ignore properties. """ if repo is None: raise error.RepoError("There is no Mercurial repository" " here (.hg not found)") ignpath = repo.wjoin('.hgignore') if not force and os.path.exists(ignpath): raise hgutil.Abort('not overwriting existing .hgignore, try --force?') svn = svnrepo.svnremoterepo(repo.ui).svn meta = repo.svnmeta() hashes = meta.revmap.hashes() parent = util.parentrev(ui, repo, meta, hashes) r, br = hashes[parent.node()] branchpath = meta.layoutobj.remotename(br) if branchpath: branchpath += '/' ignorelines = ['.hgignore', 'syntax:glob'] dirs = [''] + [d[0] for d in svn.list_files(branchpath, r) if d[1] == 'd'] for dir in dirs: path = '%s%s' % (branchpath, dir) props = svn.list_props(path, r) if 'svn:ignore' not in props: continue lines = props['svn:ignore'].strip().split('\n') ignorelines += [dir and (dir + '/' + prop) or prop for prop in lines if prop.strip()] repo.wopener('.hgignore', 'w').write('\n'.join(ignorelines) + '\n')
def genignore(ui, repo, force=False, **opts): """generate .hgignore from svn:ignore properties. """ if repo is None: raise error.RepoError("There is no Mercurial repository" " here (.hg not found)") ignpath = repo.wjoin(".hgignore") if not force and os.path.exists(ignpath): raise hgutil.Abort("not overwriting existing .hgignore, try --force?") svn = svnrepo.svnremoterepo(repo.ui).svn meta = repo.svnmeta() hashes = meta.revmap.hashes() parent = util.parentrev(ui, repo, meta, hashes) r, br = hashes[parent.node()] if meta.layout == "single": branchpath = "" else: branchpath = br and ("branches/%s/" % br) or "trunk/" ignorelines = [".hgignore", "syntax:glob"] dirs = [""] + [d[0] for d in svn.list_files(branchpath, r) if d[1] == "d"] for dir in dirs: path = "%s%s" % (branchpath, dir) props = svn.list_props(path, r) if "svn:ignore" not in props: continue lines = props["svn:ignore"].strip().split("\n") ignorelines += [dir and (dir + "/" + prop) or prop for prop in lines] repo.wopener(".hgignore", "w").write("\n".join(ignorelines) + "\n")
def verify(ui, repo, *args, **opts): '''verify current revision against Subversion repository ''' if repo is None: raise error.RepoError("There is no Mercurial repository" " here (.hg not found)") ctx = repo[opts.get('rev', '.')] if 'close' in ctx.extra(): ui.write('cannot verify closed branch') return 0 srev = ctx.extra().get('convert_revision') if srev is None: raise hgutil.Abort('revision %s not from SVN' % ctx) srev = int(srev.split('@')[1]) ui.write('verifying %s against r%i\n' % (ctx, srev)) url = repo.ui.expandpath('default') if args: url = args[0] svn = svnrepo.svnremoterepo(ui, url).svn meta = repo.svnmeta(svn.uuid, svn.subdir) btypes = {'default': 'trunk'} if meta.layout == 'standard': branchpath = btypes.get(ctx.branch(), 'branches/%s' % ctx.branch()) else: branchpath = '' svnfiles = set() result = 0 for fn, type in svn.list_files(posixpath.normpath(branchpath), srev): if type != 'f': continue svnfiles.add(fn) fp = fn if branchpath: fp = branchpath + '/' + fn data, mode = svn.get_file(posixpath.normpath(fp), srev) fctx = ctx[fn] dmatch = fctx.data() == data mmatch = fctx.flags() == mode if not (dmatch and mmatch): ui.write('difference in file %s' % fn) result = 1 hgfiles = set(ctx) hgfiles.discard('.hgtags') hgfiles.discard('.hgsvnexternals') if hgfiles != svnfiles: missing = set(hgfiles).symmetric_difference(svnfiles) ui.write('missing files: %s' % (', '.join(missing))) result = 1 return result
def _buildmeta(ui, repo, args, partial=False, skipuuid=False): if repo is None: raise error.RepoError("There is no Mercurial repository" " here (.hg not found)") dest = None validateuuid = False if len(args) == 1: dest = args[0] validateuuid = True elif len(args) > 1: raise error.Abort('rebuildmeta takes 1 or no arguments') url = repo.ui.expandpath(dest or repo.ui.config('paths', 'default-push') or repo.ui.config('paths', 'default') or '') meta = svnmeta.SVNMeta(repo, skiperrorcheck=True) svn = None if meta.subdir is None: svn = svnrepo.svnremoterepo(ui, url).svn meta.subdir = svn.subdir youngest = 0 startrev = 0 branchinfo = {} if not partial: hgutil.unlinkpath(meta.revmap_file, ignoremissing=True) revmap = meta.revmap if partial: try: # we can't use meta.lastpulled here because we are bootstraping the # lastpulled and want to keep the cached value on disk during a # partial rebuild foundpartialinfo = False youngestpath = os.path.join(meta.metapath, 'lastpulled') if os.path.exists(youngestpath): youngest = util.load(youngestpath) lasthash = revmap.lasthash if len(revmap) > 0 and lasthash: startrev = repo[lasthash].rev() + 1 branchinfo = util.load(meta.branch_info_file) foundpartialinfo = True if not foundpartialinfo: ui.status('missing some metadata -- doing a full rebuild\n') partial = False except IOError, err: if err.errno != errno.ENOENT: raise ui.status('missing some metadata -- doing a full rebuild\n') except AttributeError: ui.status('no metadata available -- doing a full rebuild\n')
def verify(ui, repo, args=None, **opts): '''verify current revision against Subversion repository ''' if repo is None: raise error.RepoError("There is no Mercurial repository" " here (.hg not found)") ctx = repo[opts.get('rev', '.')] if 'close' in ctx.extra(): ui.write('cannot verify closed branch') return 0 convert_revision = ctx.extra().get('convert_revision') if convert_revision is None or not convert_revision.startswith('svn:'): raise hgutil.Abort('revision %s not from SVN' % ctx) if args: url = repo.ui.expandpath(args[0]) else: url = repo.ui.expandpath('default') svn = svnrepo.svnremoterepo(ui, url).svn meta = repo.svnmeta(svn.uuid, svn.subdir) srev, branch, branchpath = meta.get_source_rev(ctx=ctx) branchpath = branchpath[len(svn.subdir.lstrip('/')):] ui.write('verifying %s against r%i\n' % (ctx, srev)) svnfiles = set() result = 0 for fn, type in svn.list_files(branchpath, srev): if type != 'f': continue svnfiles.add(fn) fp = fn if branchpath: fp = branchpath + '/' + fn data, mode = svn.get_file(posixpath.normpath(fp), srev) fctx = ctx[fn] dmatch = fctx.data() == data mmatch = fctx.flags() == mode if not (dmatch and mmatch): ui.write('difference in file %s\n' % fn) result = 1 hgfiles = set(ctx) - util.ignoredfiles if hgfiles != svnfiles: missing = set(hgfiles).symmetric_difference(svnfiles) ui.write('missing files: %s\n' % (', '.join(missing))) result = 1 return result
def info(ui, repo, **opts): """show Subversion details similar to `svn info' """ if repo is None: raise error.RepoError("There is no Mercurial repository" " here (.hg not found)") meta = repo.svnmeta() hashes = meta.revmap.hashes() if opts.get("rev"): parent = repo[opts["rev"]] else: parent = util.parentrev(ui, repo, meta, hashes) pn = parent.node() if pn not in hashes: ui.status("Not a child of an svn revision.\n") return 0 r, br = hashes[pn] subdir = parent.extra()["convert_revision"][40:].split("@")[0] if meta.layout == "single": branchpath = "" elif br == None: branchpath = "/trunk" elif br.startswith("../"): branchpath = "/%s" % br[3:] subdir = subdir.replace("branches/../", "") else: branchpath = "/branches/%s" % br remoterepo = svnrepo.svnremoterepo(repo.ui) url = "%s%s" % (remoterepo.svnurl, branchpath) author = meta.authors.reverselookup(parent.user()) # cleverly figure out repo root w/o actually contacting the server reporoot = url[: len(url) - len(subdir)] ui.write( """URL: %(url)s Repository Root: %(reporoot)s Repository UUID: %(uuid)s Revision: %(revision)s Node Kind: directory Last Changed Author: %(author)s Last Changed Rev: %(revision)s Last Changed Date: %(date)s\n""" % { "reporoot": reporoot, "uuid": meta.uuid, "url": url, "author": author, "revision": r, # TODO I'd like to format this to the user's local TZ if possible "date": hgutil.datestr(parent.date(), "%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)"), } )
def info(ui, repo, **opts): """show Subversion details similar to `svn info' """ if repo is None: raise error.RepoError("There is no Mercurial repository" " here (.hg not found)") meta = repo.svnmeta() hashes = meta.revmap.hashes() if opts.get('rev'): parent = repo[opts['rev']] else: parent = util.parentrev(ui, repo, meta, hashes) pn = parent.node() if pn not in hashes: ui.status('Not a child of an svn revision.\n') return 0 r, br = hashes[pn] subdir = parent.extra()['convert_revision'][40:].split('@')[0] if meta.layout == 'single': branchpath = '' elif br == None: branchpath = '/trunk' elif br.startswith('../'): branchpath = '/%s' % br[3:] subdir = subdir.replace('branches/../', '') else: branchpath = '/branches/%s' % br remoterepo = svnrepo.svnremoterepo(repo.ui) url = '%s%s' % (remoterepo.svnurl, branchpath) author = meta.authors.reverselookup(parent.user()) # cleverly figure out repo root w/o actually contacting the server reporoot = url[:len(url)-len(subdir)] ui.write('''URL: %(url)s Repository Root: %(reporoot)s Repository UUID: %(uuid)s Revision: %(revision)s Node Kind: directory Last Changed Author: %(author)s Last Changed Rev: %(revision)s Last Changed Date: %(date)s\n''' % {'reporoot': reporoot, 'uuid': meta.uuid, 'url': url, 'author': author, 'revision': r, # TODO I'd like to format this to the user's local TZ if possible 'date': hgutil.datestr(parent.date(), '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)') })
def info(ui, repo, **opts): """show Subversion details similar to `svn info' """ if repo is None: raise error.RepoError("There is no Mercurial repository" " here (.hg not found)") meta = repo.svnmeta() hashes = meta.revmap.hashes() if opts.get('rev'): parent = repo[opts['rev']] else: parent = util.parentrev(ui, repo, meta, hashes) pn = parent.node() if pn not in hashes: ui.status('Not a child of an svn revision.\n') return 0 r, br = hashes[pn] subdir = util.getsvnrev(parent)[40:].split('@')[0] remoterepo = svnrepo.svnremoterepo(repo.ui) url = meta.layoutobj.remotepath(br, remoterepo.svnurl) author = meta.authors.reverselookup(parent.user()) # cleverly figure out repo root w/o actually contacting the server reporoot = url[:len(url) - len(subdir)] ui.write( '''URL: %(url)s Repository Root: %(reporoot)s Repository UUID: %(uuid)s Revision: %(revision)s Node Kind: directory Last Changed Author: %(author)s Last Changed Rev: %(revision)s Last Changed Date: %(date)s\n''' % { 'reporoot': reporoot, 'uuid': meta.uuid, 'url': url, 'author': author, 'revision': r, # TODO I'd like to format this to the user's local TZ if possible 'date': compathacks.datestr(parent.date(), '%Y-%m-%d %H:%M:%S %1%2 (%a, %d %b %Y)') })
def _buildmeta(ui, repo, args, partial=False, skipuuid=False): if repo is None: raise error.RepoError("There is no Mercurial repository" " here (.hg not found)") dest = None validateuuid = False if len(args) == 1: dest = args[0] validateuuid = True elif len(args) > 1: raise hgutil.Abort('rebuildmeta takes 1 or no arguments') url = repo.ui.expandpath(dest or repo.ui.config('paths', 'default-push') or repo.ui.config('paths', 'default') or '') meta = svnmeta.SVNMeta(repo, skiperrorcheck=True) svn = None if meta.subdir is None: svn = svnrepo.svnremoterepo(ui, url).svn meta.subdir = svn.subdir youngest = 0 startrev = 0 sofar = [] branchinfo = {} if partial: try: # we can't use meta.lastpulled here because we are bootstraping the # lastpulled and want to keep the cached value on disk during a # partial rebuild foundpartialinfo = False youngestpath = os.path.join(meta.metapath, 'lastpulled') if os.path.exists(youngestpath): youngest = util.load(youngestpath) sofar = list(maps.RevMap.readmapfile(meta.revmap_file)) if sofar and len(sofar[-1].split(' ', 2)) > 1: lasthash = sofar[-1].split(' ', 2)[1] startrev = repo[lasthash].rev() + 1 branchinfo = util.load(meta.branch_info_file) foundpartialinfo = True if not foundpartialinfo: ui.status('missing some metadata -- doing a full rebuild\n') partial = False except IOError, err: if err.errno != errno.ENOENT: raise ui.status('missing some metadata -- doing a full rebuild\n') except AttributeError: ui.status('no metadata available -- doing a full rebuild\n')
def listauthors(ui, args, authors=None, **opts): """list all authors in a Subversion repository """ if not len(args): ui.status('No repository specified.\n') return svn = svnrepo.svnremoterepo(ui, args[0]).svn author_set = set() for rev in svn.revisions(): author_set.add(str(rev.author)) # So None becomes 'None' if authors: authorfile = open(authors, 'w') authorfile.write('%s=\n' % '=\n'.join(sorted(author_set))) authorfile.close() else: ui.write('%s\n' % '\n'.join(sorted(author_set)))
def listauthors(ui, args, authors=None, **opts): """list all authors in a Subversion repository """ if not len(args): ui.status("No repository specified.\n") return svn = svnrepo.svnremoterepo(ui, args[0]).svn author_set = set() for rev in svn.revisions(): if rev.author is None: author_set.add("(no author)") else: author_set.add(rev.author) if authors: authorfile = open(authors, "w") authorfile.write("%s=\n" % "=\n".join(sorted(author_set))) authorfile.close() else: ui.write("%s\n" % "\n".join(sorted(author_set)))
def listauthors(ui, args, authors=None, **opts): """list all authors in a Subversion repository """ if not len(args): ui.status('No repository specified.\n') return svn = svnrepo.svnremoterepo(ui, args[0]).svn author_set = set() for rev in svn.revisions(): if rev.author is None: author_set.add('(no author)') else: author_set.add(rev.author) if authors: authorfile = open(authors, 'w') authorfile.write('%s=\n' % '=\n'.join(sorted(author_set))) authorfile.close() else: ui.write('%s\n' % '\n'.join(sorted(author_set)))
def verify(ui, repo, args=None, **opts): '''verify current revision against Subversion repository ''' if repo is None: raise error.RepoError("There is no Mercurial repository" " here (.hg not found)") ctx = repo[opts.get('rev', '.')] if 'close' in ctx.extra(): ui.write('cannot verify closed branch') return 0 convert_revision = ctx.extra().get('convert_revision') if convert_revision is None or not convert_revision.startswith('svn:'): raise hgutil.Abort('revision %s not from SVN' % ctx) if args: url = repo.ui.expandpath(args[0]) else: url = repo.ui.expandpath('default') svn = svnrepo.svnremoterepo(ui, url).svn meta = repo.svnmeta(svn.uuid, svn.subdir) srev, branch, branchpath = meta.get_source_rev(ctx=ctx) branchpath = branchpath[len(svn.subdir.lstrip('/')):] branchurl = ('%s/%s' % (url, branchpath)).strip('/') ui.write('verifying %s against %s@%i\n' % (ctx, branchurl, srev)) def diff_file(path, svndata): fctx = ctx[path] if ui.verbose and not fctx.isbinary(): svndesc = '%s/%s/%s@%d' % (svn.svn_url, branchpath, path, srev) hgdesc = '%s@%s' % (path, ctx) for c in difflib.unified_diff(svndata.splitlines(True), fctx.data().splitlines(True), svndesc, hgdesc): ui.note(c) if opts.get('stupid', ui.configbool('hgsubversion', 'stupid')): svnfiles = set() result = 0 hgfiles = set(ctx) - util.ignoredfiles def verifydata(svndata): svnworker = svnrepo.svnremoterepo(ui, url).svn i = 0 res = True for fn, type in svndata: i += 1 if type != 'f': continue fp = fn if branchpath: fp = branchpath + '/' + fn data, mode = svnworker.get_file(posixpath.normpath(fp), srev) try: fctx = ctx[fn] except error.LookupError: yield i, "%s\0%r" % (fn, res) continue if not fctx.data() == data: ui.write('difference in: %s\n' % fn) diff_file(fn, data) res = False if not fctx.flags() == mode: ui.write('wrong flags for: %s\n' % fn) res = False yield i, "%s\0%r" % (fn, res) if url.startswith('file://'): perarg = 0.00001 else: perarg = 0.000001 svndata = svn.list_files(branchpath, srev) w = worker.worker(repo.ui, perarg, verifydata, (), tuple(svndata)) i = 0 for _, t in w: ui.progress('verify', i, total=len(hgfiles)) i += 1 fn, ok = t.split('\0', 2) if not bool(ok): result = 1 svnfiles.add(fn) if hgfiles != svnfiles: unexpected = hgfiles - svnfiles for f in sorted(unexpected): ui.write('unexpected file: %s\n' % f) missing = svnfiles - hgfiles for f in sorted(missing): ui.write('missing file: %s\n' % f) result = 1 ui.progress('verify', None, total=len(hgfiles)) else: class VerifyEditor(svnwrap.Editor): """editor that verifies a repository against the given context.""" def __init__(self, ui, ctx): self.ui = ui self.ctx = ctx self.unexpected = set(ctx) - util.ignoredfiles self.missing = set() self.failed = False self.total = len(self.unexpected) self.seen = 0 def open_root(self, base_revnum, pool=None): pass def add_directory(self, path, parent_baton, copyfrom_path, copyfrom_revision, pool=None): self.file = None self.props = None def open_directory(self, path, parent_baton, base_revision, pool=None): self.file = None self.props = None def add_file(self, path, parent_baton=None, copyfrom_path=None, copyfrom_revision=None, file_pool=None): if path in self.unexpected: self.unexpected.remove(path) self.file = path self.props = {} else: self.total += 1 self.missing.add(path) self.failed = True self.file = None self.props = None self.seen += 1 self.ui.progress('verify', self.seen, total=self.total) def open_file(self, path, base_revnum): raise NotImplementedError() def apply_textdelta(self, file_baton, base_checksum, pool=None): stream = svnwrap.SimpleStringIO(closing=False) handler = svnwrap.apply_txdelta('', stream) if not callable(handler): raise hgutil.Abort('Error in Subversion bindings: ' 'cannot call handler!') def txdelt_window(window): handler(window) # window being None means we're done if window: return fctx = self.ctx[self.file] hgdata = fctx.data() svndata = stream.getvalue() if 'svn:executable' in self.props: if fctx.flags() != 'x': self.ui.warn('wrong flags for: %s\n' % self.file) self.failed = True elif 'svn:special' in self.props: hgdata = 'link ' + hgdata if fctx.flags() != 'l': self.ui.warn('wrong flags for: %s\n' % self.file) self.failed = True elif fctx.flags(): self.ui.warn('wrong flags for: %s\n' % self.file) self.failed = True if hgdata != svndata: self.ui.warn('difference in: %s\n' % self.file) diff_file(self.file, svndata) self.failed = True if self.file is not None: return txdelt_window def change_dir_prop(self, dir_baton, name, value, pool=None): pass def change_file_prop(self, file_baton, name, value, pool=None): if self.props is not None: self.props[name] = value def close_file(self, file_baton, checksum, pool=None): pass def close_directory(self, dir_baton, pool=None): pass def delete_entry(self, path, revnum, pool=None): raise NotImplementedError() def check(self): self.ui.progress('verify', None, total=self.total) for f in self.unexpected: self.ui.warn('unexpected file: %s\n' % f) self.failed = True for f in self.missing: self.ui.warn('missing file: %s\n' % f) self.failed = True return not self.failed v = VerifyEditor(ui, ctx) svnrepo.svnremoterepo(ui, branchurl).svn.get_revision(srev, v) if v.check(): result = 0 else: result = 1 return result
# the layout based on the commits (where subdir is compared to the # revpath extracted from the commit) if meta.layout == 'auto': meta.layout = meta.layout_from_commit(subdir, revpath, ctx.branch()) elif meta.layout == 'single': assert (subdir or '/') == revpath, ('Possible layout detection' ' defect in replay') # write repository uuid if required if meta.uuid is None or validateuuid: validateuuid = False uuid = convinfo[4:40] if not skipuuid: if svn is None: svn = svnrepo.svnremoterepo(ui, url).svn if uuid != svn.uuid: raise hgutil.Abort('remote svn repository identifier ' 'does not match') meta.uuid = uuid # don't reflect closed branches if (ctx.extra().get('close') and not ctx.files() or ctx.parents()[0].node() in skipped): skipped.add(ctx.node()) continue # find commitpath, write to revmap commitpath = revpath[len(subdir)+1:] tag_locations = meta.layoutobj.taglocations
def rebuildmeta(ui, repo, args, **opts): """rebuild hgsubversion metadata using values stored in revisions """ if repo is None: raise error.RepoError("There is no Mercurial repository" " here (.hg not found)") dest = None if len(args) == 1: dest = args[0] elif len(args) > 1: raise hgutil.Abort("rebuildmeta takes 1 or no arguments") uuid = None url = repo.ui.expandpath( dest or repo.ui.config("paths", "default-push") or repo.ui.config("paths", "default") or "" ) svn = svnrepo.svnremoterepo(ui, url).svn subdir = svn.subdir svnmetadir = os.path.join(repo.path, "svn") if not os.path.exists(svnmetadir): os.makedirs(svnmetadir) lastpulled = open(os.path.join(svnmetadir, "lastpulled"), "wb") revmap = open(os.path.join(svnmetadir, "rev_map"), "w") revmap.write("1\n") last_rev = -1 branchinfo = {} noderevnums = {} tagfile = os.path.join(svnmetadir, "tagmap") if os.path.exists(maps.Tags.filepath(repo)): os.unlink(maps.Tags.filepath(repo)) tags = maps.Tags(repo) layout = None skipped = set() closed = set() numrevs = len(repo) subdirfile = open(os.path.join(svnmetadir, "subdir"), "w") subdirfile.write(subdir.strip("/")) subdirfile.close() # ctx.children() visits all revisions in the repository after ctx. Calling # it would make us use O(revisions^2) time, so we perform an extra traversal # of the repository instead. During this traversal, we find all converted # changesets that close a branch, and store their first parent youngest = 0 for rev in repo: util.progress(ui, "prepare", rev, total=numrevs) ctx = repo[rev] extra = ctx.extra() convinfo = extra.get("convert_revision", None) if not convinfo: continue svnrevnum = int(convinfo.rsplit("@", 1)[1]) youngest = max(youngest, svnrevnum) if extra.get("close", None) is None: continue droprev = lambda x: x.rsplit("@", 1)[0] parentctx = ctx.parents()[0] parentinfo = parentctx.extra().get("convert_revision", "@") if droprev(parentinfo) == droprev(convinfo): closed.add(parentctx.rev()) lastpulled.write(str(youngest) + "\n") util.progress(ui, "prepare", None, total=numrevs) for rev in repo: util.progress(ui, "rebuild", rev, total=numrevs) ctx = repo[rev] convinfo = ctx.extra().get("convert_revision", None) if not convinfo: continue if ".hgtags" in ctx.files(): parent = ctx.parents()[0] parentdata = "" if ".hgtags" in parent: parentdata = parent.filectx(".hgtags").data() newdata = ctx.filectx(".hgtags").data() for newtag in newdata[len(parentdata) : -1].split("\n"): ha, tag = newtag.split(" ", 1) tagged = repo[ha].extra().get("convert_revision", None) if tagged is None: tagged = -1 else: tagged = int(tagged[40:].split("@")[1]) # This is max(tagged rev, tagging rev) because if it is a normal # tag, the tagging revision has the right rev number. However, if it # was an edited tag, then the tagged revision has the correct revision # number. tagging = int(convinfo[40:].split("@")[1]) tagrev = max(tagged, tagging) tags[tag] = node.bin(ha), tagrev # check that the conversion metadata matches expectations assert convinfo.startswith("svn:") revpath, revision = convinfo[40:].split("@") if subdir and subdir[0] != "/": subdir = "/" + subdir if subdir and subdir[-1] == "/": subdir = subdir[:-1] assert revpath.startswith(subdir), "That does not look like the " "right location in the repo." if layout is None: if (subdir or "/") == revpath: layout = "single" else: layout = "standard" f = open(os.path.join(svnmetadir, "layout"), "w") f.write(layout) f.close() elif layout == "single": assert (subdir or "/") == revpath, "Possible layout detection" " defect in replay" # write repository uuid if required if uuid is None: uuid = convinfo[4:40] assert uuid == svn.uuid, "UUIDs did not match!" uuidfile = open(os.path.join(svnmetadir, "uuid"), "w") uuidfile.write(uuid) uuidfile.close() # don't reflect closed branches if ctx.extra().get("close") and not ctx.files() or ctx.parents()[0].node() in skipped: skipped.add(ctx.node()) continue # find commitpath, write to revmap commitpath = revpath[len(subdir) + 1 :] if layout == "standard": if commitpath.startswith("branches/"): commitpath = commitpath[len("branches/") :] elif commitpath == "trunk": commitpath = "" else: if commitpath.startswith("tags/") and ctx.extra().get("close"): continue commitpath = "../" + commitpath else: commitpath = "" revmap.write("%s %s %s\n" % (revision, ctx.hex(), commitpath)) revision = int(revision) noderevnums[ctx.node()] = revision if revision > last_rev: last_rev = revision # deal with branches if not commitpath: branch = None elif not commitpath.startswith("../"): branch = commitpath elif ctx.parents()[0].node() != node.nullid: parent = ctx while parent.node() != node.nullid: parentextra = parent.extra() parentinfo = parentextra.get("convert_revision") assert parentinfo parent = parent.parents()[0] parentpath = parentinfo[40:].split("@")[0][len(subdir) + 1 :] if parentpath.startswith("tags/") and parentextra.get("close"): continue elif parentpath.startswith("branches/"): branch = parentpath[len("branches/") :] elif parentpath == "trunk": branch = None else: branch = "../" + parentpath break else: branch = commitpath if rev in closed: # a direct child of this changeset closes the branch; drop it branchinfo.pop(branch, None) elif ctx.extra().get("close"): pass elif branch not in branchinfo: parent = ctx.parents()[0] if parent.node() in noderevnums and parent.branch() != ctx.branch(): parentbranch = parent.branch() if parentbranch == "default": parentbranch = None else: parentbranch = None branchinfo[branch] = (parentbranch, noderevnums.get(parent.node(), 0), revision) util.progress(ui, "rebuild", None, total=numrevs) # save off branch info branchinfofile = open(os.path.join(svnmetadir, "branch_info"), "w") pickle.dump(branchinfo, branchinfofile) branchinfofile.close()
def verify(ui, repo, args=None, **opts): '''verify current revision against Subversion repository ''' if repo is None: raise error.RepoError("There is no Mercurial repository" " here (.hg not found)") ctx = repo[opts.get('rev', '.')] if 'close' in ctx.extra(): ui.write('cannot verify closed branch') return 0 convert_revision = ctx.extra().get('convert_revision') if convert_revision is None or not convert_revision.startswith('svn:'): raise error.Abort('revision %s not from SVN' % ctx) if args: url = repo.ui.expandpath(args[0]) else: url = repo.ui.expandpath('default') svn = svnrepo.svnremoterepo(ui, url).svn meta = repo.svnmeta(svn.uuid, svn.subdir) srev, branch, branchpath = meta.get_source_rev(ctx=ctx) branchpath = branchpath[len(svn.subdir.lstrip('/')):] branchurl = ('%s/%s' % (url, branchpath)).strip('/') ui.write('verifying %s against %s@%i\n' % (ctx, branchurl, srev)) def diff_file(path, svndata): fctx = ctx[path] if ui.verbose and not fctx.isbinary(): svndesc = '%s/%s/%s@%d' % (svn.svn_url, branchpath, path, srev) hgdesc = '%s@%s' % (path, ctx) for c in difflib.unified_diff(svndata.splitlines(True), fctx.data().splitlines(True), svndesc, hgdesc): ui.note(c) if opts.get('stupid', ui.configbool('hgsubversion', 'stupid')): svnfiles = set() result = 0 hgfiles = set(ctx) - util.ignoredfiles def verifydata(svndata): svnworker = svnrepo.svnremoterepo(ui, url).svn i = 0 res = True for fn, type in svndata: i += 1 if type != 'f': continue fp = fn if branchpath: fp = branchpath + '/' + fn data, mode = svnworker.get_file(posixpath.normpath(fp), srev) try: fctx = ctx[fn] except error.LookupError: yield i, "%s\0%r" % (fn, res) continue if not fctx.data() == data: ui.write('difference in: %s\n' % fn) diff_file(fn, data) res = False if not fctx.flags() == mode: ui.write('wrong flags for: %s\n' % fn) res = False yield i, "%s\0%r" % (fn, res) if url.startswith('file://'): perarg = 0.00001 else: perarg = 0.000001 svndata = svn.list_files(branchpath, srev) w = worker.worker(repo.ui, perarg, verifydata, (), tuple(svndata)) i = 0 for _, t in w: compathacks.progress(ui, 'verify', i, total=len(hgfiles)) i += 1 fn, ok = t.split('\0', 2) if not bool(ok): result = 1 svnfiles.add(fn) if hgfiles != svnfiles: unexpected = hgfiles - svnfiles for f in sorted(unexpected): ui.write('unexpected file: %s\n' % f) missing = svnfiles - hgfiles for f in sorted(missing): ui.write('missing file: %s\n' % f) result = 1 compathacks.progress(ui, 'verify', None, total=len(hgfiles)) else: class VerifyEditor(svnwrap.Editor): """editor that verifies a repository against the given context.""" def __init__(self, ui, ctx): self.ui = ui self.ctx = ctx self.unexpected = set(ctx) - util.ignoredfiles self.missing = set() self.failed = False self.total = len(self.unexpected) self.seen = 0 def open_root(self, base_revnum, pool=None): pass def add_directory(self, path, parent_baton, copyfrom_path, copyfrom_revision, pool=None): self.file = None self.props = None def open_directory(self, path, parent_baton, base_revision, pool=None): self.file = None self.props = None def add_file(self, path, parent_baton=None, copyfrom_path=None, copyfrom_revision=None, file_pool=None): if path in self.unexpected: self.unexpected.remove(path) self.file = path self.props = {} else: self.total += 1 self.missing.add(path) self.failed = True self.file = None self.props = None self.seen += 1 compathacks.progress(self.ui, 'verify', self.seen, total=self.total) def open_file(self, path, base_revnum): raise NotImplementedError() def apply_textdelta(self, file_baton, base_checksum, pool=None): stream = svnwrap.SimpleStringIO(closing=False) handler = svnwrap.apply_txdelta('', stream) if not callable(handler): raise error.Abort('Error in Subversion bindings: ' 'cannot call handler!') def txdelt_window(window): handler(window) # window being None means we're done if window: return fctx = self.ctx[self.file] hgdata = fctx.data() svndata = stream.getvalue() if 'svn:executable' in self.props: if fctx.flags() != 'x': self.ui.write('wrong flags for: %s\n' % self.file) self.failed = True elif 'svn:special' in self.props: hgdata = 'link ' + hgdata if fctx.flags() != 'l': self.ui.write('wrong flags for: %s\n' % self.file) self.failed = True elif fctx.flags(): self.ui.write('wrong flags for: %s\n' % self.file) self.failed = True if hgdata != svndata: self.ui.write('difference in: %s\n' % self.file) diff_file(self.file, svndata) self.failed = True if self.file is not None: return txdelt_window def change_dir_prop(self, dir_baton, name, value, pool=None): pass def change_file_prop(self, file_baton, name, value, pool=None): if self.props is not None: self.props[name] = value def close_file(self, file_baton, checksum, pool=None): pass def close_directory(self, dir_baton, pool=None): pass def delete_entry(self, path, revnum, pool=None): raise NotImplementedError() def check(self): compathacks.progress(self.ui, 'verify', None, total=self.total) for f in self.unexpected: self.ui.write('unexpected file: %s\n' % f) self.failed = True for f in self.missing: self.ui.write('missing file: %s\n' % f) self.failed = True return not self.failed v = VerifyEditor(ui, ctx) svnrepo.svnremoterepo(ui, branchurl).svn.get_revision(srev, v) if v.check(): result = 0 else: result = 1 return result
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() # TODO implement skipto support skipto_rev = 0 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 have_replay and not callable( delta.svn_txdelta_apply(None, None, None)[0]): #pragma: no cover repo.ui.status('You are using old Subversion SWIG bindings. Replay ' 'will not work until you upgrade to 1.5.0 or newer. ' 'Falling back to a slower method that may be buggier. ' 'Please upgrade, or contribute a patch to use the ' 'ctypes bindings instead of SWIG.\n') have_replay = False elif not have_replay: repo.ui.note('fetching stupidly...\n') # TODO: do credentials specified in the URL still work? svn = svnrepo.svnremoterepo(repo.ui, svn_url).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) start = max(meta.revmap.seen, skipto_rev) initializing_repo = meta.revmap.seen <= 0 ui = repo.ui if initializing_repo and start > 0: raise hgutil.Abort('Revision skipping at repository initialization ' 'remains unimplemented.') oldrevisions = len(meta.revmap) cnt = 0 if stopat_rev: total = stopat_rev - start else: total = svn.HEAD - start try: try: # start converting revisions 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 else: msg = [s.strip() for s in msg.splitlines() if s][0] w = hgutil.termwidth() bits = (r.revnum, r.author, msg) cnt += 1 ui.status(('[r%d] %s: %s\n' % bits)[:w]) util.progress(ui, 'pull', cnt, total=total) meta.save_tbdelta(tbdelta) close = pullfuns[have_replay](ui, meta, svn, r, tbdelta) 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 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 core.SubversionException, e: #pragma: no cover if (e.apr_err == core.SVN_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: raise hgutil.Abort(*e.args)
def push(repo, dest, force, revs): """push revisions starting at a specified head back to Subversion. """ assert not revs, 'designated revisions for push remains unimplemented.' cmdutil.bail_if_changed(repo) ui = repo.ui old_encoding = util.swap_out_encoding() # TODO: implement --rev/#rev support # TODO: do credentials specified in the URL still work? svnurl = repo.ui.expandpath(dest.svnurl) svn = svnrepo.svnremoterepo(repo.ui, svnurl).svn meta = repo.svnmeta(svn.uuid) # Strategy: # 1. Find all outgoing commits from this head if len(repo.parents()) != 1: ui.status('Cowardly refusing to push branch merge\n') return 1 workingrev = repo.parents()[0] ui.status('searching for changes\n') hashes = meta.revmap.hashes() outgoing = util.outgoing_revisions(repo, hashes, workingrev.node()) if not (outgoing and len(outgoing)): ui.status('no changes found\n') return 0 while outgoing: oldest = outgoing.pop(-1) old_ctx = repo[oldest] if len(old_ctx.parents()) != 1: ui.status('Found a branch merge, this needs discussion and ' 'implementation.\n') return 1 base_n = old_ctx.parents()[0].node() old_children = repo[base_n].children() svnbranch = repo[base_n].branch() oldtip = base_n samebranchchildren = [c for c in repo[oldtip].children() if c.branch() == svnbranch and c.node() in hashes] while samebranchchildren: oldtip = samebranchchildren[0].node() samebranchchildren = [c for c in repo[oldtip].children() if c.branch() == svnbranch and c.node() in hashes] # 2. Commit oldest revision that needs to be pushed base_revision = hashes[base_n][0] try: pushmod.commit(ui, repo, old_ctx, meta, base_revision, svn) except pushmod.NoFilesException: ui.warn("Could not push revision %s because it had no changes in svn.\n" % old_ctx) return 1 # 3. Fetch revisions from svn # TODO: this probably should pass in the source explicitly - rev too? r = repo.pull(dest, force=force) assert not r or r == 0 # 4. Find the new head of the target branch oldtipctx = repo[oldtip] replacement = [c for c in oldtipctx.children() if c not in old_children and c.branch() == oldtipctx.branch()] assert len(replacement) == 1, 'Replacement node came back as: %r' % replacement replacement = replacement[0] # 5. Rebase all children of the currently-pushing rev to the new branch heads = repo.heads(old_ctx.node()) for needs_transplant in heads: def extrafn(ctx, extra): if ctx.node() == oldest: return extra['branch'] = ctx.branch() # TODO: can we avoid calling our own rebase wrapper here? rebase(hgrebase.rebase, ui, repo, svn=True, svnextrafn=extrafn, svnsourcerev=needs_transplant) repo = hg.repository(ui, meta.path) for child in repo[replacement.node()].children(): rebasesrc = node.bin(child.extra().get('rebase_source', node.hex(node.nullid))) if rebasesrc in outgoing: while rebasesrc in outgoing: rebsrcindex = outgoing.index(rebasesrc) outgoing = (outgoing[0:rebsrcindex] + [child.node(), ] + outgoing[rebsrcindex+1:]) children = [c for c in child.children() if c.branch() == child.branch()] if children: child = children[0] rebasesrc = node.bin(child.extra().get('rebase_source', node.hex(node.nullid))) # TODO: stop constantly creating the SVNMeta instances. meta = repo.svnmeta(svn.uuid) hashes = meta.revmap.hashes() util.swap_out_encoding(old_encoding) return 0
def rebuildmeta(ui, repo, args, **opts): """rebuild hgsubversion metadata using values stored in revisions """ if repo is None: raise error.RepoError("There is no Mercurial repository" " here (.hg not found)") dest = None if len(args) == 1: dest = args[0] elif len(args) > 1: raise hgutil.Abort('rebuildmeta takes 1 or no arguments') uuid = None url = repo.ui.expandpath(dest or repo.ui.config('paths', 'default-push') or repo.ui.config('paths', 'default') or '') svn = svnrepo.svnremoterepo(ui, url).svn subdir = svn.subdir svnmetadir = os.path.join(repo.path, 'svn') if not os.path.exists(svnmetadir): os.makedirs(svnmetadir) revmap = open(os.path.join(svnmetadir, 'rev_map'), 'w') revmap.write('1\n') last_rev = -1 branchinfo = {} noderevnums = {} tagfile = os.path.join(svnmetadir, 'tagmap') if os.path.exists(maps.Tags.filepath(repo)): os.unlink(maps.Tags.filepath(repo)) tags = maps.Tags(repo) layout = None skipped = set() closed = set() numrevs = len(repo) subdirfile = open(os.path.join(svnmetadir, 'subdir'), 'w') subdirfile.write(subdir.strip('/')) subdirfile.close() # ctx.children() visits all revisions in the repository after ctx. Calling # it would make us use O(revisions^2) time, so we perform an extra traversal # of the repository instead. During this traversal, we find all converted # changesets that close a branch, and store their first parent for rev in repo: util.progress(ui, 'prepare', rev, total=numrevs) ctx = repo[rev] extra = ctx.extra() convinfo = extra.get('convert_revision', None) if not convinfo or not extra.get('close', None): continue droprev = lambda x: x.rsplit('@', 1)[0] parentctx = ctx.parents()[0] parentinfo = parentctx.extra().get('convert_revision', '@') if droprev(parentinfo) == droprev(convinfo): closed.add(parentctx.rev()) util.progress(ui, 'prepare', None, total=numrevs) for rev in repo: util.progress(ui, 'rebuild', rev, total=numrevs) ctx = repo[rev] convinfo = ctx.extra().get('convert_revision', None) if not convinfo: continue if '.hgtags' in ctx.files(): parent = ctx.parents()[0] parentdata = '' if '.hgtags' in parent: parentdata = parent.filectx('.hgtags').data() newdata = ctx.filectx('.hgtags').data() for newtag in newdata[len(parentdata):-1].split('\n'): ha, tag = newtag.split(' ', 1) tagged = repo[ha].extra().get('convert_revision', None) if tagged is None: tagged = -1 else: tagged = int(tagged[40:].split('@')[1]) # This is max(tagged rev, tagging rev) because if it is a normal # tag, the tagging revision has the right rev number. However, if it # was an edited tag, then the tagged revision has the correct revision # number. tagging = int(convinfo[40:].split('@')[1]) tagrev = max(tagged, tagging) tags[tag] = node.bin(ha), tagrev # check that the conversion metadata matches expectations assert convinfo.startswith('svn:') revpath, revision = convinfo[40:].split('@') if subdir and subdir[0] != '/': subdir = '/' + subdir if subdir and subdir[-1] == '/': subdir = subdir[:-1] assert revpath.startswith(subdir), ('That does not look like the ' 'right location in the repo.') if layout is None: if (subdir or '/') == revpath: layout = 'single' else: layout = 'standard' f = open(os.path.join(svnmetadir, 'layout'), 'w') f.write(layout) f.close() elif layout == 'single': assert (subdir or '/') == revpath, ('Possible layout detection' ' defect in replay') # write repository uuid if required if uuid is None: uuid = convinfo[4:40] assert uuid == svn.uuid, 'UUIDs did not match!' uuidfile = open(os.path.join(svnmetadir, 'uuid'), 'w') uuidfile.write(uuid) uuidfile.close() # don't reflect closed branches if (ctx.extra().get('close') and not ctx.files() or ctx.parents()[0].node() in skipped): skipped.add(ctx.node()) continue # find commitpath, write to revmap commitpath = revpath[len(subdir)+1:] if layout == 'standard': if commitpath.startswith('branches/'): commitpath = commitpath[len('branches/'):] elif commitpath == 'trunk': commitpath = '' else: if commitpath.startswith('tags/') and ctx.extra().get('close'): continue commitpath = '../' + commitpath else: commitpath = '' revmap.write('%s %s %s\n' % (revision, ctx.hex(), commitpath)) revision = int(revision) noderevnums[ctx.node()] = revision if revision > last_rev: last_rev = revision # deal with branches if not commitpath: branch = None elif not commitpath.startswith('../'): branch = commitpath elif ctx.parents()[0].node() != node.nullid: parent = ctx while parent.node() != node.nullid: parentextra = parent.extra() parentinfo = parentextra.get('convert_revision') assert parentinfo parent = parent.parents()[0] parentpath = parentinfo[40:].split('@')[0][len(subdir) + 1:] if parentpath.startswith('tags/') and parentextra.get('close'): continue elif parentpath.startswith('branches/'): branch = parentpath[len('branches/'):] elif parentpath == 'trunk': branch = None else: branch = '../' + parentpath break else: branch = commitpath if rev in closed: # a direct child of this changeset closes the branch; drop it branchinfo.pop(branch, None) elif ctx.extra().get('close'): pass elif branch not in branchinfo: parent = ctx.parents()[0] if (parent.node() in noderevnums and parent.branch() != ctx.branch()): parentbranch = parent.branch() if parentbranch == 'default': parentbranch = None else: parentbranch = None branchinfo[branch] = (parentbranch, noderevnums.get(parent.node(), 0), revision) util.progress(ui, 'rebuild', None, total=numrevs) # save off branch info branchinfofile = open(os.path.join(svnmetadir, 'branch_info'), 'w') pickle.dump(branchinfo, branchinfofile) branchinfofile.close()