diff options
author | Qijiang Fan <fqj1994@gmail.com> | 2011-10-25 12:37:40 +0800 |
---|---|---|
committer | Qijiang Fan <fqj1994@gmail.com> | 2011-10-25 12:37:40 +0800 |
commit | 4d96cae645ca33d56b4a4961f2b59bef82773b58 (patch) | |
tree | 3a7085b11447f986526447bceca48170e3602af4 | |
parent | 6e8f1b6597c85527b4721f376e0360bb25543cc8 (diff) |
Upstream version 1.3
54 files changed, 1302 insertions, 357 deletions
diff --git a/.hg_archival.txt b/.hg_archival.txt new file mode 100644 index 0000000..916b999 --- /dev/null +++ b/.hg_archival.txt @@ -0,0 +1,4 @@ +repo: f2636cfed11500fdc47d1e3822d8e4a2bd636bf7 +node: 0cbf9fd89672e73165e1bb4db1ec8f7f65b95c94 +branch: default +tag: 1.3 @@ -12,3 +12,8 @@ MANIFEST dist *.egg-info hgsubversion/__version__.py +nbproject +.project +.pydevproject +.settings +*.orig @@ -4,3 +4,4 @@ 8e621dbb82d4363a85317638ad237e2817c56347 1.1.1 093ae2915b452539b44390ee4ea14987484e1eee 1.1.2 708234ad6c97fb52417e0b46a86c8373e25123a5 1.2 +4bbc6bf947f56a92e95a04a27b94a9f72d5482d7 1.2.1 diff --git a/hgsubversion/__init__.py b/hgsubversion/__init__.py index 10fb63f..cc336c8 100644 --- a/hgsubversion/__init__.py +++ b/hgsubversion/__init__.py @@ -38,14 +38,14 @@ try: # force demandimport to load templatekw templatekw.keywords except ImportError: - templatekw = None + templatekw = None try: from mercurial import revset # force demandimport to load revset revset.methods except ImportError: - revset = None + revset = None try: from mercurial import subrepo @@ -99,14 +99,21 @@ wrapcmds = { # cmd: generic, target, fixdoc, ppopts, opts ]), } - -# only need the discovery variant of this code when we drop hg < 1.6 try: from mercurial import discovery + def findcommonoutgoing(orig, *args, **opts): + capable = getattr(args[1], 'capable', lambda x: False) + if capable('subversion'): + return wrappers.findcommonoutgoing(*args, **opts) + else: + return orig(*args, **opts) + extensions.wrapfunction(discovery, 'findcommonoutgoing', findcommonoutgoing) +except AttributeError: + # only need the discovery variant of this code when we drop hg < 1.6 def findoutgoing(orig, *args, **opts): capable = getattr(args[1], 'capable', lambda x: False) if capable('subversion'): - return wrappers.outgoing(*args, **opts) + return wrappers.findoutgoing(*args, **opts) else: return orig(*args, **opts) extensions.wrapfunction(discovery, 'findoutgoing', findoutgoing) @@ -169,7 +176,7 @@ def extsetup(): def reposetup(ui, repo): if repo.local(): - svnrepo.generate_repo_class(ui, repo) + svnrepo.generate_repo_class(ui, repo) _old_local = hg.schemes['file'] def _lookup(url): diff --git a/hgsubversion/editor.py b/hgsubversion/editor.py index 44c39b4..54c04ac 100644 --- a/hgsubversion/editor.py +++ b/hgsubversion/editor.py @@ -8,6 +8,7 @@ from mercurial import node import svnwrap import util +import svnexternals class RevisionData(object): @@ -118,7 +119,7 @@ class HgEditor(svnwrap.Editor): # assuming it is a directory self.current.externals[path] = None map(self.current.delete, [pat for pat in self.current.files.iterkeys() - if pat.startswith(path+'/')]) + if pat.startswith(path + '/')]) for f in ctx.walk(util.PrefixMatch(br_path2)): f_p = '%s/%s' % (path, f[len(br_path2):]) if f_p not in self.current.files: @@ -231,11 +232,11 @@ class HgEditor(svnwrap.Editor): if tag: changeid = self.meta.tags[tag] source_rev, source_branch = self.meta.get_source_rev(changeid)[:2] - cp_f = '' + frompath = '' else: source_rev = copyfrom_revision - cp_f, source_branch = self.meta.split_branch_path(copyfrom_path)[:2] - if cp_f == '' and br_path == '': + frompath, source_branch = self.meta.split_branch_path(copyfrom_path)[:2] + if frompath == '' and br_path == '': assert br_path is not None tmp = source_branch, source_rev, self.current.rev.revnum self.meta.branches[branch] = tmp @@ -243,23 +244,22 @@ class HgEditor(svnwrap.Editor): if new_hash == node.nullid: self.current.missing.add('%s/' % path) return path - cp_f_ctx = self.repo.changectx(new_hash) - if cp_f != '/' and cp_f != '': - cp_f = '%s/' % cp_f + fromctx = self.repo.changectx(new_hash) + if frompath != '/' and frompath != '': + frompath = '%s/' % frompath else: - cp_f = '' + frompath = '' copies = {} - for f in cp_f_ctx: - if not f.startswith(cp_f): + for f in fromctx: + if not f.startswith(frompath): continue - f2 = f[len(cp_f):] - fctx = cp_f_ctx.filectx(f) - fp_c = path + '/' + f2 - self.current.set(fp_c, fctx.data(), 'x' in fctx.flags(), 'l' in fctx.flags()) - if fp_c in self.current.deleted: - del self.current.deleted[fp_c] + fctx = fromctx.filectx(f) + dest = path + '/' + f[len(frompath):] + self.current.set(dest, fctx.data(), 'x' in fctx.flags(), 'l' in fctx.flags()) + if dest in self.current.deleted: + del self.current.deleted[dest] if branch == source_branch: - copies[fp_c] = f + copies[dest] = f if copies: # Preserve the directory copy records if no file was changed between # the source and destination revisions, or discard it completely. @@ -267,8 +267,15 @@ class HgEditor(svnwrap.Editor): if parentid != revlog.nullid: parentctx = self.repo.changectx(parentid) for k, v in copies.iteritems(): - if util.issamefile(parentctx, cp_f_ctx, v): + if util.issamefile(parentctx, fromctx, v): self.current.copies[k] = v + # Copy the externals definitions of copied directories + fromext = svnexternals.parse(self.ui, fromctx) + for p, v in fromext.iteritems(): + pp = p and (p + '/') or '' + if pp.startswith(frompath): + dest = (path + '/' + pp[len(frompath):]).rstrip('/') + self.current.externals[dest] = v return path @svnwrap.ieditor diff --git a/hgsubversion/maps.py b/hgsubversion/maps.py index 7fbe2cf..93a3cbb 100644 --- a/hgsubversion/maps.py +++ b/hgsubversion/maps.py @@ -5,6 +5,7 @@ from mercurial import util as hgutil from mercurial import node import svncommands +import util class AuthorMap(dict): '''A mapping from Subversion-style authors to Mercurial-style @@ -34,6 +35,8 @@ class AuthorMap(dict): def load(self, path): ''' Load mappings from a file at the specified path. ''' + + path = os.path.expandvars(path) if not os.path.exists(path): return @@ -43,12 +46,9 @@ class AuthorMap(dict): self.ui.note('reading authormap from %s\n' % path) f = open(path, 'r') - for number, line in enumerate(f): - - if writing: - writing.write(line) + for number, line_org in enumerate(f): - line = line.split('#')[0] + line = line_org.split('#')[0] if not line.strip(): continue @@ -61,10 +61,15 @@ class AuthorMap(dict): src = src.strip() dst = dst.strip() - self.ui.debug('adding author %s to author map\n' % src) - if src in self and dst != self[src]: - msg = 'overriding author: "%s" to "%s" (%s)\n' - self.ui.status(msg % (self[src], dst, src)) + + if writing: + if not src in self: + self.ui.debug('adding author %s to author map\n' % src) + elif dst != self[src]: + msg = 'overriding author: "%s" to "%s" (%s)\n' + self.ui.status(msg % (self[src], dst, src)) + writing.write(line_org) + self[src] = dst f.close() @@ -132,14 +137,14 @@ class Tags(dict): print 'tagmap too new -- please upgrade' raise NotImplementedError for l in f: - hash, revision, tag = l.split(' ', 2) + ha, revision, tag = l.split(' ', 2) revision = int(revision) tag = tag[:-1] if self.endrev is not None and revision > self.endrev: break if not tag: continue - dict.__setitem__(self, tag, node.bin(hash)) + dict.__setitem__(self, tag, node.bin(ha)) f.close() def _write(self): @@ -164,11 +169,11 @@ class Tags(dict): def __setitem__(self, tag, info): if not tag: raise hgutil.Abort('tag cannot be empty') - hash, revision = info + ha, revision = info f = open(self.path, 'a') - f.write('%s %s %s\n' % (node.hex(hash), revision, tag)) + f.write('%s %s %s\n' % (node.hex(ha), revision, tag)) f.close() - dict.__setitem__(self, tag, hash) + dict.__setitem__(self, tag, ha) class RevMap(dict): @@ -178,13 +183,28 @@ class RevMap(dict): def __init__(self, repo): dict.__init__(self) self.path = os.path.join(repo.path, 'svn', 'rev_map') - self.youngest = 0 + self.ypath = os.path.join(repo.path, 'svn', 'lastpulled') + # TODO(durin42): Consider moving management of the youngest + # file to svnmeta itself rather than leaving it here. + # must load youngest file first, or else self._load() can + # clobber the info + _yonngest_str = util.load_string(self.ypath, '0') + self._youngest = int(_yonngest_str.strip()) self.oldest = 0 if os.path.isfile(self.path): self._load() else: self._write() + def _set_youngest(self, rev): + self._youngest = max(self._youngest, rev) + util.save_string(self.ypath, str(self._youngest) + '\n') + + def _get_youngest(self): + return self._youngest + + youngest = property(_get_youngest, _set_youngest) + def hashes(self): return dict((v, k) for (k, v) in self.iteritems()) @@ -199,7 +219,7 @@ class RevMap(dict): print 'revmap too new -- please upgrade' raise NotImplementedError for l in f: - revnum, hash, branch = l.split(' ', 2) + revnum, ha, branch = l.split(' ', 2) if branch == '\n': branch = None else: @@ -209,7 +229,7 @@ class RevMap(dict): self.youngest = revnum if revnum < self.oldest or not self.oldest: self.oldest = revnum - dict.__setitem__(self, (revnum, branch), node.bin(hash)) + dict.__setitem__(self, (revnum, branch), node.bin(ha)) f.close() def _write(self): @@ -217,17 +237,17 @@ class RevMap(dict): f.write('%s\n' % self.VERSION) f.close() - def __setitem__(self, key, hash): + def __setitem__(self, key, ha): revnum, branch = key f = open(self.path, 'a') b = branch or '' - f.write(str(revnum) + ' ' + node.hex(hash) + ' ' + b + '\n') + f.write(str(revnum) + ' ' + node.hex(ha) + ' ' + b + '\n') f.close() if revnum > self.youngest or not self.youngest: self.youngest = revnum if revnum < self.oldest or not self.oldest: self.oldest = revnum - dict.__setitem__(self, (revnum, branch), hash) + dict.__setitem__(self, (revnum, branch), ha) class FileMap(object): @@ -247,12 +267,12 @@ class FileMap(object): yield name[:e], name[e+1:] e = name.rfind('/', 0, e) - def check(self, map, path): - map = getattr(self, map) - for pre, suf in self._rpairs(path): - if pre not in map: + def check(self, m, path): + m = getattr(self, m) + for pre, _suf in self._rpairs(path): + if pre not in m: continue - return map[pre] + return m[pre] return None def __contains__(self, path): @@ -268,13 +288,17 @@ class FileMap(object): return False return True - def add(self, fn, map, path): - mapping = getattr(self, map) + # Needed so empty filemaps are false + def __len__(self): + return len(self.include) + len(self.exclude) + + def add(self, fn, m, path): + mapping = getattr(self, m) if path in mapping: msg = 'duplicate %s entry in %s: "%s"\n' - self.ui.status(msg % (map, fn, path)) + self.ui.status(msg % (m, fn, path)) return - bits = map.strip('e'), path + bits = m.strip('e'), path self.ui.debug('%sing %s\n' % bits) mapping[path] = path @@ -365,8 +389,8 @@ class TagMap(dict): oldname = newname other = - The oldname tag from SVN will be represented as newname in the hg tags; - the other tag will not be reflected in the hg repository. + The oldname tag from SVN will be represented as newname in the hg tags; + the other tag will not be reflected in the hg repository. ''' def __init__(self, ui, path): diff --git a/hgsubversion/pushmod.py b/hgsubversion/pushmod.py index e1f5231..7c0a104 100644 --- a/hgsubversion/pushmod.py +++ b/hgsubversion/pushmod.py @@ -122,6 +122,9 @@ def commit(ui, repo, rev_ctx, meta, base_revision, svn): props.setdefault(file, {})['svn:executable'] = '*' if 'l' in fctx.flags(): props.setdefault(file, {})['svn:special'] = '*' + isbinary = hgutil.binary(new_data) + if isbinary: + props.setdefault(file, {})['svn:mime-type'] = 'application/octet-stream' if file not in parent: renamed = fctx.renamed() @@ -141,6 +144,8 @@ def commit(ui, repo, rev_ctx, meta, base_revision, svn): if ('l' in parent.filectx(file).flags() and 'l' not in rev_ctx.filectx(file).flags()): props.setdefault(file, {})['svn:special'] = None + if hgutil.binary(base_data) and not isbinary: + props.setdefault(file, {})['svn:mime-type'] = None action = 'modify' else: pos = file.rfind('/') @@ -178,11 +183,7 @@ def commit(ui, repo, rev_ctx, meta, base_revision, svn): if tf in file_data and tf != ntf: file_data[ntf] = file_data[tf] if tf in props: - props[ntf] = props[tf] - del props[tf] - if hgutil.binary(file_data[ntf][1]): - props.setdefault(ntf, {}).update(props.get(ntf, {})) - props.setdefault(ntf, {})['svn:mime-type'] = 'application/octet-stream' + props[ntf] = props.pop(tf) del file_data[tf] addeddirs = [svnpath(d) for d in addeddirs] diff --git a/hgsubversion/replay.py b/hgsubversion/replay.py index 92db01e..10d3b48 100644 --- a/hgsubversion/replay.py +++ b/hgsubversion/replay.py @@ -174,7 +174,7 @@ def convert_rev(ui, meta, svn, r, tbdelta, firstrun): date, extra) - new_hash = meta.repo.commitctx(current_ctx) + new_hash = meta.repo.svn_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 @@ -209,7 +209,7 @@ def convert_rev(ui, meta, svn, r, tbdelta, firstrun): meta.authors[rev.author], date, extra) - new_hash = meta.repo.commitctx(current_ctx) + new_hash = meta.repo.svn_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 diff --git a/hgsubversion/stupid.py b/hgsubversion/stupid.py index 50e13d6..159396e 100644 --- a/hgsubversion/stupid.py +++ b/hgsubversion/stupid.py @@ -2,10 +2,11 @@ import cStringIO import errno import re -from mercurial import patch -from mercurial import node from mercurial import context +from mercurial import node +from mercurial import patch from mercurial import revlog +from mercurial import util as hgutil import svnwrap import svnexternals @@ -49,7 +50,6 @@ def print_your_svn_is_old_message(ui): #pragma: no cover ui.status("In light of that, I'll fall back and do diffs, but it won't do " "as good a job. You should really upgrade your server.\n") - def mempatchproxy(parentctx, files): # Avoid circular references patch.patchfile -> mempatch patchfile = patch.patchfile @@ -79,17 +79,32 @@ def mempatchproxy(parentctx, files): def filteriterhunks(meta): iterhunks = patch.iterhunks - def filterhunks(ui, fp, sourcefile=None, textmode=False): + def filterhunks(*args, **kwargs): + # ui, fp, sourcefile=None, textmode=False applycurrent = False # Passing False instead of textmode because we should never # be ignoring EOL type. - if not iterhunks.func_defaults: - # Since 1.7 (cfedc529e4a1) - gen = iterhunks(ui, fp) - elif len(iterhunks.func_defaults) == 1: - gen = iterhunks(ui, fp, sourcefile) + if iterhunks.func_code.co_argcount == 1: + # Since 1.9 (28762bb767dc) + fp = args[0] + gen = iterhunks(fp) else: - gen = iterhunks(ui, fp, sourcefile, textmode) + ui, fp = args[:2] + if len(args) > 2: + sourcefile = args[2] + else: + sourcefile = kwargs.get('sourcefile', None) + if len(args) > 3: + textmode = args[3] + else: + textmode = kwargs.get('textmode', False) + if not iterhunks.func_defaults: + # Since 1.7 (cfedc529e4a1) + gen = iterhunks(ui, fp) + elif len(iterhunks.func_defaults) == 1: + gen = iterhunks(ui, fp, sourcefile) + else: + gen = iterhunks(ui, fp, sourcefile, textmode) for data in gen: if data[0] == 'file': if data[1][1] in meta.filemap: @@ -101,6 +116,71 @@ def filteriterhunks(meta): yield data return filterhunks +def patchrepoold(ui, meta, parentctx, patchfp): + files = {} + try: + oldpatchfile = patch.patchfile + olditerhunks = patch.iterhunks + patch.patchfile = mempatchproxy(parentctx, files) + patch.iterhunks = filteriterhunks(meta) + try: + # We can safely ignore the changed list since we are + # handling non-git patches. Touched files are known + # by our memory patcher. + patch_st = patch.applydiff(ui, patchfp, {}, strip=0) + finally: + patch.patchfile = oldpatchfile + patch.iterhunks = olditerhunks + except patch.PatchError: + # TODO: this happens if the svn server has the wrong mime + # type stored and doesn't know a file is binary. It would + # be better to do one file at a time and only do a + # full fetch on files that had problems. + raise BadPatchApply('patching failed') + # if this patch didn't apply right, fall back to exporting the + # entire rev. + if patch_st == -1: + assert False, ('This should only happen on case-insensitive' + ' volumes.') + elif patch_st == 1: + # When converting Django, I saw fuzz on .po files that was + # causing revisions to end up failing verification. If that + # can be fixed, maybe this won't ever be reached. + raise BadPatchApply('patching succeeded with fuzz') + return files + +try: + class svnbackend(patch.repobackend): + def getfile(self, fname): + data, (islink, isexec) = super(svnbackend, self).getfile(fname) + if islink: + data = 'link ' + data + return data, (islink, isexec) +except AttributeError: + svnbackend = None + +def patchrepo(ui, meta, parentctx, patchfp): + if not svnbackend: + return patchrepoold(ui, meta, parentctx, patchfp) + store = patch.filestore() + try: + touched = set() + backend = svnbackend(ui, meta.repo, parentctx, store) + ret = patch.patchbackend(ui, backend, patchfp, 0, touched) + if ret < 0: + raise BadPatchApply('patching failed') + if ret > 0: + raise BadPatchApply('patching succeeded with fuzz') + files = {} + for f in touched: + try: + data, mode, copied = store.getfile(f) + files[f] = data + except IOError: + files[f] = None + return files + finally: + store.close() def diff_branchrev(ui, svn, meta, branch, branchpath, r, parentctx): """Extract all 'branch' content at a given revision. @@ -146,38 +226,9 @@ def diff_branchrev(ui, svn, meta, branch, branchpath, r, parentctx): # are marked as touched. Content is loaded on demand. touched_files.update(any_file_re.findall(d)) if d2.strip() and len(re.findall('\n[-+]', d2.strip())) > 0: - try: - oldpatchfile = patch.patchfile - olditerhunks = patch.iterhunks - patch.patchfile = mempatchproxy(parentctx, files_data) - patch.iterhunks = filteriterhunks(meta) - try: - # We can safely ignore the changed list since we are - # handling non-git patches. Touched files are known - # by our memory patcher. - patch_st = patch.applydiff(ui, cStringIO.StringIO(d2), - {}, strip=0) - finally: - patch.patchfile = oldpatchfile - patch.iterhunks = olditerhunks - except patch.PatchError: - # TODO: this happens if the svn server has the wrong mime - # type stored and doesn't know a file is binary. It would - # be better to do one file at a time and only do a - # full fetch on files that had problems. - raise BadPatchApply('patching failed') + files_data = patchrepo(ui, meta, parentctx, cStringIO.StringIO(d2)) for x in files_data.iterkeys(): ui.note('M %s\n' % x) - # if this patch didn't apply right, fall back to exporting the - # entire rev. - if patch_st == -1: - assert False, ('This should only happen on case-insensitive' - ' volumes.') - elif patch_st == 1: - # When converting Django, I saw fuzz on .po files that was - # causing revisions to end up failing verification. If that - # can be fixed, maybe this won't ever be reached. - raise BadPatchApply('patching succeeded with fuzz') else: ui.status('Not using patch for %s, diff had no hunks.\n' % r.revnum) @@ -357,7 +408,7 @@ def fetch_externals(ui, svn, branchpath, r, parentctx): # revision in the common case. dirs = set(externals) if parentctx.node() == revlog.nullid: - dirs.update([p for p,k in svn.list_files(branchpath, r.revnum) if k == 'd']) + dirs.update([p for p, k in svn.list_files(branchpath, r.revnum) if k == 'd']) dirs.add('') else: branchprefix = (branchpath and branchpath + '/') or branchpath @@ -509,7 +560,7 @@ def branches_in_paths(meta, tbdelta, paths, revnum, checkpath, listdir): # we need to detect those branches. It's a little thorny and slow, but # seems to be the best option. elif paths[p].copyfrom_path and not p.startswith('tags/'): - paths_need_discovery.extend(['%s/%s' % (p,x[0]) + paths_need_discovery.extend(['%s/%s' % (p, x[0]) for x in listdir(p, revnum) if x[1] == 'f']) @@ -539,6 +590,9 @@ def branches_in_paths(meta, tbdelta, paths, revnum, checkpath, listdir): def convert_rev(ui, meta, svn, r, tbdelta, firstrun): # this server fails at replay + if meta.filemap: + raise hgutil.Abort('filemaps currently unsupported with stupid replay.') + branches = branches_in_paths(meta, tbdelta, r.paths, r.revnum, svn.checkpath, svn.list_files) brpaths = branches.values() @@ -659,7 +713,7 @@ def convert_rev(ui, meta, svn, r, tbdelta, firstrun): meta.authors[r.author], date, extra) - ha = meta.repo.commitctx(current_ctx) + ha = meta.repo.svn_commitctx(current_ctx) if not tag: if (not origbranch in meta.branches diff --git a/hgsubversion/svncommands.py b/hgsubversion/svncommands.py index 529810c..0ae1262 100644 --- a/hgsubversion/svncommands.py +++ b/hgsubversion/svncommands.py @@ -55,7 +55,7 @@ def verify(ui, repo, args=None, **opts): svnfiles.add(fn) fp = fn if branchpath: - fp = branchpath + '/' + fn + fp = branchpath + '/' + fn data, mode = svn.get_file(posixpath.normpath(fp), srev) fctx = ctx[fn] dmatch = fctx.data() == data @@ -95,6 +95,7 @@ def rebuildmeta(ui, repo, args, **opts): 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 @@ -120,13 +121,18 @@ def rebuildmeta(ui, repo, args, **opts): # 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 not convinfo or not extra.get('close', None): + if extra.get('close', None) is None: continue droprev = lambda x: x.rsplit('@', 1)[0] @@ -136,6 +142,7 @@ def rebuildmeta(ui, repo, args, **opts): 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: @@ -238,7 +245,8 @@ def rebuildmeta(ui, repo, args, **opts): if parentpath.startswith('tags/') and parentextra.get('close'): continue - elif parentpath.startswith('branches/'): branch = parentpath[len('branches/'):] + elif parentpath.startswith('branches/'): + branch = parentpath[len('branches/'):] elif parentpath == 'trunk': branch = None else: diff --git a/hgsubversion/svnexternals.py b/hgsubversion/svnexternals.py index 51a5c9a..38cc40b 100644 --- a/hgsubversion/svnexternals.py +++ b/hgsubversion/svnexternals.py @@ -12,6 +12,14 @@ try: except (ImportError, AttributeError), e: subrepo = None +passpegrev = True # see svnsubrepo below +try: + canonpath = hgutil.canonpath +except (ImportError, AttributeError): + from mercurial import scmutil + canonpath = scmutil.canonpath + passpegrev = False + import util class externalsfile(dict): @@ -51,7 +59,6 @@ class externalsfile(dict): def read(self, data): self.clear() fp = cStringIO.StringIO(data) - dirs = {} target = None for line in fp.readlines(): if not line.strip(): @@ -112,7 +119,7 @@ def parsedefinition(line): revgroup = 2 path, rev, source = m.group(1, 2, 3) try: - nrev = int(rev) + int(rev) # ensure revision is int()able, so we bail otherwise norevline = line[:m.start(revgroup)] + '{REV}' + line[m.end(revgroup):] except (TypeError, ValueError): norevline = line @@ -139,6 +146,9 @@ def parsedefinitions(ui, repo, svnroot, exts): defs = [] for base in sorted(exts): for line in exts[base]: + if not line.strip() or line.lstrip().startswith('#'): + # Ignore comments and blank lines + continue try: path, rev, source, pegrev, norevline = parsedefinition(line) except BadDefinition: @@ -148,7 +158,7 @@ def parsedefinitions(ui, repo, svnroot, exts): if source is None: continue wpath = hgutil.pconvert(os.path.join(base, path)) - wpath = hgutil.canonpath(repo.root, '', wpath) + wpath = canonpath(repo.root, '', wpath) defs.append((wpath, rev, source, pegrev, norevline, base)) # Check target dirs are not nested defs.sort() @@ -221,7 +231,7 @@ class externalsupdater: if rev: revspec = ['-r', rev] if os.path.isdir(path): - exturl, extroot, extrev = getsvninfo(path) + exturl, _extroot, extrev = getsvninfo(path) # Comparing the source paths is not enough, but I don't # know how to compare path+pegrev. The following update # might fail if the path was replaced by another unrelated @@ -336,7 +346,7 @@ def getchanges(ui, repo, parentctx, exts): if exts: defs = parsedefinitions(ui, repo, '', exts) hgsub, hgsubstate = [], [] - for path, rev, source, pegrev, norevline, base in sorted(defs): + for path, rev, _source, _pegrev, norevline, base in sorted(defs): hgsub.append('%s = [hgsubversion] %s:%s\n' % (path, base, norevline)) if rev is None: @@ -402,7 +412,12 @@ if subrepo: svnurl = self._ctx._repo.ui.expandpath('default') svnroot = getsvninfo(util.normalize_url(svnurl))[1] source = resolvesource(self._ui, svnroot, source) - if pegrev is not None: + # hg < 1.9 svnsubrepo calls "svn checkout" with --rev + # only, so peg revisions are correctly used. 1.9 and + # higher, append the rev as a peg revision to the source + # URL, so we cannot add our own. We assume that "-r10 + # url@2" will be similar to "url@10" most of the time. + if pegrev is not None and passpegrev: source = source + '@' + pegrev state = (source, state[1]) # hg-1.7.4-c19b9282d3a7 introduced the overwrite argument diff --git a/hgsubversion/svnmeta.py b/hgsubversion/svnmeta.py index 3c91b0e..68ab4a7 100644 --- a/hgsubversion/svnmeta.py +++ b/hgsubversion/svnmeta.py @@ -257,7 +257,7 @@ class SVNMeta(object): branchpath = branch[3:] else: branchpath = 'branches/%s' % branch - path = '%s/%s' % (subdir , branchpath) + path = '%s/%s' % (subdir, branchpath) extra['convert_revision'] = 'svn:%(uuid)s%(path)s@%(rev)s' % { 'uuid': self.uuid, @@ -368,7 +368,7 @@ class SVNMeta(object): else: path = test.split('/')[-1] test = '/'.join(test.split('/')[:-1]) - ln = self.localname(test) + ln = self.localname(test) if ln and ln.startswith('../'): return None, None, None return path, ln, test @@ -424,7 +424,7 @@ class SVNMeta(object): branch_created_rev = self.branches[branch][2] if parent_branch == 'trunk': parent_branch = None - if branch_created_rev <= number+1 and branch != parent_branch: + if branch_created_rev <= number + 1 and branch != parent_branch: # did the branch exist in previous run if exact and branch in self.prevbranches: if self.prevbranches[branch][1] < real_num: @@ -450,7 +450,7 @@ class SVNMeta(object): return node.hex(self.revmap[tagged]) tag = fromtag # Reference an existing tag - limitedtags = maps.Tags(self.repo, endrev=number-1) + limitedtags = maps.Tags(self.repo, endrev=number - 1) if tag in limitedtags: return limitedtags[tag] r, br = self.get_parent_svn_branch_and_rev(number - 1, branch, exact) @@ -609,7 +609,7 @@ class SVNMeta(object): branchparent = branchparent.parents()[0] branch = self.get_source_rev(ctx=branchparent)[1] - parentctx = self.repo[self.get_parent_revision(rev.revnum+1, branch)] + parentctx = self.repo[self.get_parent_revision(rev.revnum + 1, branch)] if '.hgtags' in parentctx: tagdata = parentctx.filectx('.hgtags').data() else: @@ -639,7 +639,7 @@ class SVNMeta(object): self.authors[rev.author], date, parentctx.extra()) - new_hash = self.repo.commitctx(ctx) + new_hash = self.repo.svn_commitctx(ctx) if not newparent: assert self.revmap[revnum, branch] == parentctx.node() self.revmap[revnum, branch] = new_hash @@ -704,7 +704,7 @@ class SVNMeta(object): self.authors[rev.author], date, extra) - new = self.repo.commitctx(ctx) + new = self.repo.svn_commitctx(ctx) if not fromtag and (rev.revnum, b) not in self.revmap: self.revmap[rev.revnum, b] = new @@ -726,5 +726,5 @@ class SVNMeta(object): self.authors[rev.author], self.fixdate(rev.date), extra) - new = self.repo.commitctx(ctx) + new = self.repo.svn_commitctx(ctx) self.ui.status('Marked branch %s as closed.\n' % (branch or 'default')) diff --git a/hgsubversion/svnrepo.py b/hgsubversion/svnrepo.py index 58ce252..6bb3cd9 100644 --- a/hgsubversion/svnrepo.py +++ b/hgsubversion/svnrepo.py @@ -14,6 +14,8 @@ subclass: pull() is called on the instance pull *to*, but not the one pulled are used to distinguish and filter these operations from others. """ +import errno + from mercurial import error from mercurial import util as hgutil from mercurial import httprepo @@ -26,6 +28,25 @@ import svnmeta propertycache = hgutil.propertycache +class ctxctx(object): + """Proxies a ctx object and ensures files is never empty.""" + def __init__(self, ctx): + self._ctx = ctx + + def files(self): + return self._ctx.files() or ['.svn'] + + def filectx(self, path, filelog=None): + if path == '.svn': + raise IOError(errno.ENOENT, '.svn is a fake file') + return self._ctx.filectx(path, filelog=filelog) + + def __getattr__(self, name): + return getattr(self._ctx, name) + + def __getitem__(self, key): + return self._ctx[key] + def generate_repo_class(ui, repo): """ This function generates the local repository wrapper. """ @@ -53,6 +74,10 @@ def generate_repo_class(ui, repo): return wrapper class svnlocalrepo(superclass): + def svn_commitctx(self, ctx): + """Commits a ctx, but defeats manifest recycling introduced in hg 1.9.""" + return self.commitctx(ctxctx(ctx)) + # TODO use newbranch to allow branch creation in Subversion? @remotesvn def push(self, remote, force=False, revs=None, newbranch=None): @@ -64,7 +89,7 @@ def generate_repo_class(ui, repo): @remotesvn def findoutgoing(self, remote, base=None, heads=None, force=False): - return wrappers.outgoing(repo, remote, heads, force) + return wrappers.findoutgoing(repo, remote, heads, force) def svnmeta(self, uuid=None, subdir=None): return svnmeta.SVNMeta(self, uuid, subdir) diff --git a/hgsubversion/svnwrap/common.py b/hgsubversion/svnwrap/common.py index 9b42b6a..211ff6d 100644 --- a/hgsubversion/svnwrap/common.py +++ b/hgsubversion/svnwrap/common.py @@ -38,7 +38,7 @@ def parse_url(url, user=None, passwd=None): user, passwd = userpass, '' user, passwd = urllib.unquote(user), urllib.unquote(passwd) if user and scheme == 'svn+ssh': - netloc = '@'.join((user, netloc, )) + netloc = '@'.join((user, netloc,)) url = urlparse.urlunparse((scheme, netloc, path, params, query, fragment)) return (user or None, passwd or None, url) diff --git a/hgsubversion/svnwrap/subvertpy_wrapper.py b/hgsubversion/svnwrap/subvertpy_wrapper.py index ab8cff8..0023d5a 100644 --- a/hgsubversion/svnwrap/subvertpy_wrapper.py +++ b/hgsubversion/svnwrap/subvertpy_wrapper.py @@ -87,7 +87,7 @@ class PathAdapter(object): self.copyfrom_path = intern(self.copyfrom_path) class AbstractEditor(object): - __slots__ = ('editor', ) + __slots__ = ('editor',) def __init__(self, editor): self.editor = editor @@ -131,7 +131,7 @@ class AbstractEditor(object): self.editor.delete_entry(path, revnum, None) class FileEditor(AbstractEditor): - __slots__ = ('path', ) + __slots__ = ('path',) def __init__(self, editor, path): super(FileEditor, self).__init__(editor) @@ -145,7 +145,7 @@ class FileEditor(AbstractEditor): del self.path class DirectoryEditor(AbstractEditor): - __slots__ = ('path', ) + __slots__ = ('path',) def __init__(self, editor, path): super(DirectoryEditor, self).__init__(editor) @@ -306,7 +306,7 @@ class SubversionRepo(object): # ra.get_log(), even with chunk_size set, takes a while # when converting the 65k+ rev. in LLVM. self.remote.get_log(paths=paths, revprops=revprops, - start=start+1, end=stop, limit=chunk_size, + start=start + 1, end=stop, limit=chunk_size, discover_changed_paths=True, callback=callback) except SubversionException, e: @@ -417,9 +417,10 @@ class SubversionRepo(object): try: self.remote.replay(revision, oldestrev, AbstractEditor(editor)) - except SubversionException, e: #pragma: no cover + except (SubversionException, NotImplementedError), e: #pragma: no cover # can I depend on this number being constant? - if (e.args[1] == subvertpy.ERR_RA_NOT_IMPLEMENTED or + if (isinstance(e, NotImplementedError) or + e.args[1] == subvertpy.ERR_RA_NOT_IMPLEMENTED or e.args[1] == subvertpy.ERR_UNSUPPORTED_FEATURE): msg = ('This Subversion server is older than 1.4.0, and ' 'cannot satisfy replay requests.') @@ -476,7 +477,7 @@ class SubversionRepo(object): # File not found raise IOError(errno.ENOENT, e.args[0]) raise - if mode == 'l': + if mode == 'l': linkprefix = "link " if data.startswith(linkprefix): data = data[len(linkprefix):] @@ -530,4 +531,4 @@ class SubversionRepo(object): if not path or path == '.': return self.svn_url assert path[0] != '/', path - return '/'.join((self.svn_url, urllib.quote(path).rstrip('/'), )) + return '/'.join((self.svn_url, urllib.quote(path).rstrip('/'),)) diff --git a/hgsubversion/svnwrap/svn_swig_wrapper.py b/hgsubversion/svnwrap/svn_swig_wrapper.py index a6bd906..50ec898 100644 --- a/hgsubversion/svnwrap/svn_swig_wrapper.py +++ b/hgsubversion/svnwrap/svn_swig_wrapper.py @@ -293,7 +293,7 @@ class SubversionRepo(object): # when converting the 65k+ rev. in LLVM. ra.get_log(self.ra, paths, - start+1, + start + 1, stop, chunk_size, #limit of how many log messages to load True, # don't need to know changed paths @@ -497,7 +497,7 @@ class SubversionRepo(object): if e.args[1] in notfound: # File not found raise IOError(errno.ENOENT, e.args[0]) raise - if mode == 'l': + if mode == 'l': linkprefix = "link " if data.startswith(linkprefix): data = data[len(linkprefix):] diff --git a/hgsubversion/util.py b/hgsubversion/util.py index c0dfd1b..f55893e 100644 --- a/hgsubversion/util.py +++ b/hgsubversion/util.py @@ -35,7 +35,7 @@ def filterdiff(diff, oldrev, newrev): diff) oldrev = formatrev(oldrev) newrev = formatrev(newrev) - diff = a_re.sub(r'--- \1'+ oldrev, diff) + diff = a_re.sub(r'--- \1' + oldrev, diff) diff = b_re.sub(r'+++ \1' + newrev, diff) diff = devnull_re.sub(r'\1 /dev/null\t(working copy)', diff) diff = header_re.sub(r'Index: \1' + '\n' + ('=' * 67), diff) @@ -53,16 +53,21 @@ def parentrev(ui, repo, meta, hashes): def islocalrepo(url): - if not url.startswith('file:///'): + path = str(url) # convert once up front + if path.startswith('file:///'): + prefixlen = len('file://') + elif path.startswith('file:/'): + prefixlen = len('file:') + else: return False - if '#' in url.split('/')[-1]: # strip off #anchor - url = url[:url.rfind('#')] - path = url[len('file://'):] + if '#' in path.split('/')[-1]: # strip off #anchor + path = path[:path.rfind('#')] + path = url[prefixlen:] path = urllib.url2pathname(path).replace(os.sep, '/') while '/' in path: - if reduce(lambda x,y: x and y, + if reduce(lambda x, y: x and y, map(lambda p: os.path.exists(os.path.join(path, p)), - ('hooks', 'format', 'db', ))): + ('hooks', 'format', 'db',))): return True path = path.rsplit('/', 1)[0] return False @@ -94,6 +99,29 @@ def normalize_url(url): url = '%s#%s' % (url, checkout) return url + +def load_string(file_path, default=None, limit=1024): + if not os.path.exists(file_path): + return default + try: + f = open(file_path, 'r') + ret = f.read(limit) + f.close() + except: + return default + if ret == '': + return default + return ret + + +def save_string(file_path, string): + if string is None: + string = "" + f = open(file_path, 'wb') + f.write(str(string)) + f.close() + + # TODO remove when we drop 1.3 support def progress(ui, *args, **kwargs): if getattr(ui, 'progress', False): @@ -149,6 +177,24 @@ def outgoing_revisions(repo, reverse_map, sourcerev): if sourcerev.node() != node.nullid: return outgoing_rev_hashes +def outgoing_common_and_heads(repo, reverse_map, sourcerev): + """Given a repo and an hg_editor, determines outgoing revisions for the + current working copy state. Returns a tuple (common, heads) like + discovery.findcommonoutgoing does. + """ + if sourcerev in reverse_map: + return ([sourcerev], [sourcerev]) # nothing outgoing + sourcecx = repo[sourcerev] + while (not sourcecx.node() in reverse_map + and sourcecx.node() != node.nullid): + ps = sourcecx.parents() + if len(ps) != 1: + raise hgutil.Abort("Sorry, can't find svn parent of a merge revision.") + sourcecx = ps[0] + if sourcecx.node() != node.nullid: + return ([sourcecx.node()], [sourcerev]) + return ([sourcerev], [sourcerev]) # nothing outgoing + def default_commit_msg(ui): return ui.config('hgsubversion', 'defaultmessage', '') @@ -203,10 +249,22 @@ def _templatehelper(ctx, kw): else: raise hgutil.Abort('unrecognized hgsubversion keyword %s' % kw) +def svnrevkw(**args): + """:svnrev: String. Converted subversion revision number.""" + return _templatehelper(args['ctx'], 'svnrev') + +def svnpathkw(**args): + """:svnpath: String. Converted subversion revision project path.""" + return _templatehelper(args['ctx'], 'svnpath') + +def svnuuidkw(**args): + """:svnuuid: String. Converted subversion revision repository identifier.""" + return _templatehelper(args['ctx'], 'svnuuid') + templatekeywords = { - 'svnrev': (lambda repo, ctx, templ, **a: _templatehelper(ctx, 'svnrev')), - 'svnpath': (lambda repo, ctx, templ, **a: _templatehelper(ctx, 'svnpath')), - 'svnuuid': (lambda repo, ctx, templ, **a: _templatehelper(ctx, 'svnuuid')), + 'svnrev': svnrevkw, + 'svnpath': svnpathkw, + 'svnuuid': svnuuidkw, } def revset_fromsvn(repo, subset, x): diff --git a/hgsubversion/wrappers.py b/hgsubversion/wrappers.py index c698447..3c16b31 100644 --- a/hgsubversion/wrappers.py +++ b/hgsubversion/wrappers.py @@ -15,6 +15,12 @@ import svnwrap import svnrepo import util +try: + from mercurial import scmutil + revpair = scmutil.revpair +except ImportError: + revpair = cmdutil.revpair + pullfuns = { True: replay.convert_rev, False: stupidmod.convert_rev, @@ -77,13 +83,22 @@ def incoming(orig, ui, repo, origsource='default', **opts): ui.status('%s%s\n' % (l1.ljust(13), val)) -def outgoing(repo, dest=None, heads=None, force=False): +def findcommonoutgoing(repo, other, onlyheads=None, force=False, commoninc=None): + assert other.capable('subversion') + # split off #rev; TODO implement --revision/#rev support + svn = other.svn + meta = repo.svnmeta(svn.uuid, svn.subdir) + parent = repo.parents()[0].node() + hashes = meta.revmap.hashes() + return util.outgoing_common_and_heads(repo, hashes, parent) + + +def findoutgoing(repo, dest=None, heads=None, force=False): """show changesets not found in the Subversion repository """ assert dest.capable('subversion') - # split off #rev; TODO implement --revision/#rev support - svnurl, revs, checkout = util.parseurl(dest.svnurl, heads) + #svnurl, revs, checkout = util.parseurl(dest.svnurl, heads) svn = dest.svn meta = repo.svnmeta(svn.uuid, svn.subdir) parent = repo.parents()[0].node() @@ -104,7 +119,7 @@ def diff(orig, ui, repo, *args, **opts): if o_r: parent = repo[o_r[-1]].parents()[0] opts['rev'] = ['%s:.' % node.hex(parent.node()), ] - node1, node2 = cmdutil.revpair(repo, opts['rev']) + node1, node2 = revpair(repo, opts['rev']) baserev, _junk = hashes.get(node1, (-1, 'junk')) newrev, _junk = hashes.get(node2, (-1, 'junk')) it = patch.diff(repo, node1, node2, @@ -122,7 +137,11 @@ 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) + if hasattr(cmdutil, 'bail_if_changed'): + cmdutil.bail_if_changed(repo) + else: + # Since 1.9 (d68ddccf276b) + cmdutil.bailifchanged(repo) checkpush = getattr(repo, 'checkpush', None) if checkpush: checkpush(force, revs) @@ -291,11 +310,13 @@ def pull(repo, source, heads=[], force=False): total = stopat_rev - start else: total = svn.HEAD - start + lastpulled = None try: try: # start converting revisions firstrun = True for r in svn.revisions(start=start, stop=stopat_rev): + lastpulled = r.revnum if (r.author is None and r.message == 'This is an empty revision for padding.'): continue @@ -352,6 +373,8 @@ def pull(repo, source, heads=[], force=False): util.progress(ui, 'pull', None, total=total) util.swap_out_encoding(old_encoding) + if lastpulled is not None: + meta.revmap.youngest = lastpulled revisions = len(meta.revmap) - oldrevisions if revisions == 0: @@ -431,7 +454,13 @@ def clone(orig, ui, source, dest=None, **opts): """ data = {} - def hgclonewrapper(orig, ui, origsource, dest, **opts): + def hgclonewrapper(orig, ui, *args, **opts): + if getattr(hg, 'peer', None): + # Since 1.9 (d976542986d2) + origsource = args[1] + else: + origsource = args[0] + if isinstance(origsource, str): source, branch, checkout = util.parseurl(ui.expandpath(origsource), opts.get('branch')) @@ -445,7 +474,7 @@ def clone(orig, ui, source, dest=None, **opts): data['branches'] = branches ui.setconfig('hgsubversion', 'branch', branches[-1]) - data['srcrepo'], data['dstrepo'] = orig(ui, origsource, dest, **opts) + data['srcrepo'], data['dstrepo'] = orig(ui, *args, **opts) for opt, (section, name) in optionmap.iteritems(): if opt in opts and opts[opt]: @@ -18,7 +18,8 @@ except ImportError: from distutils.core import setup def runcmd(cmd, env): - p = subprocess.Popen(cmd, stdout=subprocess.PIPE, + shell = os.name == 'nt' + p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=shell, stderr=subprocess.PIPE, env=env) out, err = p.communicate() # If root is executing setup.py, but the repository is owned by @@ -71,7 +72,7 @@ elif os.path.exists('.hg_archival.txt'): kw = dict([t.strip() for t in l.split(':', 1)] for l in open('.hg_archival.txt')) if 'tag' in kw: - version = kw['tag'] + version = kw['tag'] elif 'latesttag' in kw: version = '%(latesttag)s+%(latesttagdistance)s-%(node).12s' % kw else: @@ -106,22 +107,22 @@ except ImportError: requires.append('subvertpy>=0.7.4') setup( - name = 'hgsubversion', - version = version, - url = 'http://bitbucket.org/durin42/hgsubversion', - license = 'GNU GPL', - author = 'Augie Fackler, others', - author_email = 'durin42@gmail.com', - description = ('hgsubversion is a Mercurial extension for working with ' + name='hgsubversion', + version=version, + url='http://bitbucket.org/durin42/hgsubversion', + license='GNU GPL', + author='Augie Fackler, others', + author_email='durin42@gmail.com', + description=('hgsubversion is a Mercurial extension for working with ' 'Subversion repositories.'), - long_description = open(os.path.join(os.path.dirname(__file__), + long_description=open(os.path.join(os.path.dirname(__file__), 'README')).read(), - keywords = 'mercurial', - packages = ('hgsubversion', 'hgsubversion.svnwrap'), - package_data = { 'hgsubversion': ['help/subversion.rst'] }, - platforms = 'any', + keywords='mercurial', + packages=('hgsubversion', 'hgsubversion.svnwrap'), + package_data={ 'hgsubversion': ['help/subversion.rst'] }, + platforms='any', install_requires=requires, - classifiers = [ + classifiers=[ 'License :: OSI Approved :: GNU General Public License (GPL)', 'Intended Audience :: Developers', 'Topic :: Software Development :: Version Control', @@ -129,5 +130,5 @@ setup( 'Programming Language :: Python', 'Operating System :: OS Independent', ], - cmdclass = {'build_py': build_py}, + cmdclass={'build_py': build_py}, ) diff --git a/tests/comprehensive/test_stupid_pull.py b/tests/comprehensive/test_stupid_pull.py index 0381b4f..466d460 100644 --- a/tests/comprehensive/test_stupid_pull.py +++ b/tests/comprehensive/test_stupid_pull.py @@ -28,7 +28,7 @@ def _do_case(self, name, layout): checkout_path += '/' + subdir u.setconfig('hgsubversion', 'stupid', '1') u.setconfig('hgsubversion', 'layout', layout) - hg.clone(u, test_util.fileurl(checkout_path), wc2_path, update=False) + test_util.hgclone(u, test_util.fileurl(checkout_path), wc2_path, update=False) if layout == 'single': self.assertEqual(len(self.repo.heads()), 1) self.repo2 = hg.repository(ui.ui(), wc2_path) @@ -45,14 +45,17 @@ attrs = {'_do_case': _do_case, } for case in (f for f in os.listdir(test_util.FIXTURES) if f.endswith('.svndump')): name = 'test_' + case[:-len('.svndump')] - attrs[name] = buildmethod(case, name, 'auto') + # Automatic layout branchtag collision exposes a minor defect + # here, but since it isn't a regression we suppress the test case. + if case != 'branchtagcollision.svndump': + attrs[name] = buildmethod(case, name, 'auto') name += '_single' attrs[name] = buildmethod(case, name, 'single') -StupidPullTests = type('StupidPullTests', (test_util.TestBase, ), attrs) +StupidPullTests = type('StupidPullTests', (test_util.TestBase,), attrs) def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(StupidPullTests), + all_tests = [unittest.TestLoader().loadTestsFromTestCase(StupidPullTests), ] - return unittest.TestSuite(all) + return unittest.TestSuite(all_tests) diff --git a/tests/comprehensive/test_verify.py b/tests/comprehensive/test_verify.py index 42414ab..65bdfa9 100644 --- a/tests/comprehensive/test_verify.py +++ b/tests/comprehensive/test_verify.py @@ -51,5 +51,5 @@ for case in fixtures: VerifyTests = type('VerifyTests', (test_util.TestBase,), attrs) def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(VerifyTests)] - return unittest.TestSuite(all) + all_tests = [unittest.TestLoader().loadTestsFromTestCase(VerifyTests)] + return unittest.TestSuite(all_tests) diff --git a/tests/fixtures/branchtagcollision.sh b/tests/fixtures/branchtagcollision.sh new file mode 100755 index 0000000..2660fe3 --- /dev/null +++ b/tests/fixtures/branchtagcollision.sh @@ -0,0 +1,37 @@ +#!/bin/bash +# +# Generate branchtagcollision.svndump +# +# Generates an svn repository with a branch and a tag that have the same name. + + +mkdir temp +cd temp + +svnadmin create testrepo +svn checkout file://`pwd`/testrepo client + +cd client +mkdir trunk +mkdir branches +mkdir tags + +svn add trunk branches tags +svn commit -m "Initial commit" + +echo "fileA" >> trunk/fileA +svn add trunk/fileA +svn commit -m "Added fileA" + +svn cp trunk branches/A +svn commit -m "added branch" + +echo "fileB" >> trunk/fileB +svn add trunk/fileB +svn commit -m "Added fileB" + +svn cp trunk tags/A +svn commit -m "added bad tag" + +cd .. +svnadmin dump testrepo > ../branchtagcollision.svndump diff --git a/tests/fixtures/branchtagcollision.svndump b/tests/fixtures/branchtagcollision.svndump new file mode 100644 index 0000000..f0d2e6f --- /dev/null +++ b/tests/fixtures/branchtagcollision.svndump @@ -0,0 +1,198 @@ +SVN-fs-dump-format-version: 2 + +UUID: 764a21f0-1c44-4bc9-b81b-0321cc58934d + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2011-05-24T15:46:13.951233Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 114 +Content-length: 114 + +K 7 +svn:log +V 14 +Initial commit +K 10 +svn:author +V 5 +augie +K 8 +svn:date +V 27 +2011-05-24T15:46:14.518711Z +PROPS-END + +Node-path: branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: tags +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Revision-number: 2 +Prop-content-length: 111 +Content-length: 111 + +K 7 +svn:log +V 11 +Added fileA +K 10 +svn:author +V 5 +augie +K 8 +svn:date +V 27 +2011-05-24T15:46:14.922304Z +PROPS-END + +Node-path: trunk/fileA +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 6 +Text-content-md5: 017715e60b9a9450d604e0d489ebc83a +Text-content-sha1: d0bcb0015aaadb5317419294648c8da6714af81f +Content-length: 16 + +PROPS-END +fileA + + +Revision-number: 3 +Prop-content-length: 112 +Content-length: 112 + +K 7 +svn:log +V 12 +added branch +K 10 +svn:author +V 5 +augie +K 8 +svn:date +V 27 +2011-05-24T15:46:15.328642Z +PROPS-END + +Node-path: branches/A +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: trunk + + +Node-path: branches/A/fileA +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk/fileA +Text-copy-source-md5: 017715e60b9a9450d604e0d489ebc83a +Text-copy-source-sha1: d0bcb0015aaadb5317419294648c8da6714af81f + + +Revision-number: 4 +Prop-content-length: 111 +Content-length: 111 + +K 7 +svn:log +V 11 +Added fileB +K 10 +svn:author +V 5 +augie +K 8 +svn:date +V 27 +2011-05-24T15:46:15.616098Z +PROPS-END + +Node-path: trunk/fileB +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 6 +Text-content-md5: 4eb63bbdec5dfa425e3735dc1d4c5ee8 +Text-content-sha1: 03939175ceac92b9c6464d037a0243e22563c423 +Content-length: 16 + +PROPS-END +fileB + + +Revision-number: 5 +Prop-content-length: 113 +Content-length: 113 + +K 7 +svn:log +V 13 +added bad tag +K 10 +svn:author +V 5 +augie +K 8 +svn:date +V 27 +2011-05-24T15:46:16.057440Z +PROPS-END + +Node-path: tags/A +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 1 +Node-copyfrom-path: trunk + + +Node-path: tags/A/fileA +Node-kind: file +Node-action: add +Node-copyfrom-rev: 2 +Node-copyfrom-path: trunk/fileA +Text-copy-source-md5: 017715e60b9a9450d604e0d489ebc83a +Text-copy-source-sha1: d0bcb0015aaadb5317419294648c8da6714af81f + + +Node-path: tags/A/fileB +Node-kind: file +Node-action: add +Node-copyfrom-rev: 4 +Node-copyfrom-path: trunk/fileB +Text-copy-source-md5: 4eb63bbdec5dfa425e3735dc1d4c5ee8 +Text-copy-source-sha1: 03939175ceac92b9c6464d037a0243e22563c423 + + diff --git a/tests/fixtures/externals.sh b/tests/fixtures/externals.sh index 45bc1f9..26722e6 100755 --- a/tests/fixtures/externals.sh +++ b/tests/fixtures/externals.sh @@ -37,7 +37,10 @@ svn propset -F externals svn:externals . svn ci -m "set externals on ." # Add another one cat > externals <<EOF +# A comment, then an empty line, then a blank line + ^/externals/project1 deps/project1 + -r2 ^/externals/project2@2 deps/project2 EOF svn propset -F externals svn:externals . diff --git a/tests/fixtures/externals.svndump b/tests/fixtures/externals.svndump index cebf4b5..824e4b9 100644 --- a/tests/fixtures/externals.svndump +++ b/tests/fixtures/externals.svndump @@ -1,6 +1,6 @@ SVN-fs-dump-format-version: 2 -UUID: ac40e40a-7fbf-47e1-90a7-c1de12a5b013 +UUID: 2fcff1c7-6cef-40bf-9072-468ceec83032 Revision-number: 0 Prop-content-length: 56 @@ -9,7 +9,7 @@ Content-length: 56 K 8 svn:date V 27 -2009-05-03T14:07:14.149596Z +2011-02-25T14:33:01.299441Z PROPS-END Revision-number: 1 @@ -27,7 +27,7 @@ pmezard K 8 svn:date V 27 -2009-05-03T14:07:14.234026Z +2011-02-25T14:33:01.321997Z PROPS-END Node-path: branches @@ -72,7 +72,7 @@ pmezard K 8 svn:date V 27 -2009-05-03T14:07:15.135488Z +2011-02-25T14:33:02.082464Z PROPS-END Node-path: externals/project1 @@ -134,7 +134,7 @@ pmezard K 8 svn:date V 27 -2009-05-03T14:07:16.180110Z +2011-02-25T14:33:03.076026Z PROPS-END Node-path: trunk @@ -179,19 +179,22 @@ pmezard K 8 svn:date V 27 -2009-05-03T14:07:17.092210Z +2011-02-25T14:33:04.058124Z PROPS-END Node-path: trunk Node-kind: dir Node-action: change -Prop-content-length: 111 -Content-length: 111 +Prop-content-length: 169 +Content-length: 169 K 13 svn:externals -V 76 +V 133 +# A comment, then an empty line, then a blank line + ^/externals/project1 deps/project1 + -r2 ^/externals/project2@2 deps/project2 PROPS-END @@ -212,7 +215,7 @@ pmezard K 8 svn:date V 27 -2009-05-03T14:07:18.165337Z +2011-02-25T14:33:05.096545Z PROPS-END Node-path: trunk @@ -272,7 +275,7 @@ pmezard K 8 svn:date V 27 -2009-05-03T14:07:21.092911Z +2011-02-25T14:33:08.059874Z PROPS-END Node-path: branches/branch1 @@ -306,7 +309,7 @@ pmezard K 8 svn:date V 27 -2009-05-03T14:07:23.097507Z +2011-02-25T14:33:10.059295Z PROPS-END Node-path: branches/branch2 @@ -344,7 +347,7 @@ pmezard K 8 svn:date V 27 -2009-05-03T14:07:24.086967Z +2011-02-25T14:33:11.058317Z PROPS-END Node-path: trunk/subdir @@ -366,7 +369,7 @@ pmezard K 8 svn:date V 27 -2009-05-03T14:07:25.093557Z +2011-02-25T14:33:12.058270Z PROPS-END Node-path: trunk/subdir2 @@ -393,7 +396,7 @@ pmezard K 8 svn:date V 27 -2009-05-03T14:07:27.088306Z +2011-02-25T14:33:14.058780Z PROPS-END Node-path: externals/project2 @@ -415,7 +418,7 @@ pmezard K 8 svn:date V 27 -2009-05-03T14:07:28.065953Z +2011-02-25T14:33:15.045928Z PROPS-END Node-path: trunk/a diff --git a/tests/fixtures/mergeexternals.sh b/tests/fixtures/mergeexternals.sh new file mode 100644 index 0000000..af413ab --- /dev/null +++ b/tests/fixtures/mergeexternals.sh @@ -0,0 +1,50 @@ +#!/bin/sh +# +# Generate mergeexternals.svndump +# + +mkdir temp +cd temp + +mkdir project-orig +cd project-orig +mkdir trunk +mkdir branches +cd .. + +svnadmin create testrepo +svnurl=file://`pwd`/testrepo +svn import project-orig $svnurl -m "init project" + +svn co $svnurl project +cd project/trunk +mkdir d1 +echo a > d1/a +mkdir d2 +echo b > d2/b +mkdir -p common/ext +echo c > common/ext/c +svn add d1 d2 common +svn ci -m addfiles +svn up +svn propset svn:externals '^/trunk/common/ext ext' d1 +svn propset svn:externals '^/trunk/common/ext ext' d2 +svn ci -m addexternals +cd .. +svn up +svn cp trunk branches/branch +cd branches +svn ci -m addbranch +cd branch +mkdir d3 +echo d > d3/d +svn add d3 +svn propset svn:externals '^/trunk/common/ext ext3' d3 +svn ci -m touchbranch +cd ../../trunk +svn merge '^/branches/branch' +svn up +svn ci -m 'merge' +cd ../.. + +svnadmin dump testrepo > ../mergeexternals.svndump diff --git a/tests/fixtures/mergeexternals.svndump b/tests/fixtures/mergeexternals.svndump new file mode 100644 index 0000000..2aee90a --- /dev/null +++ b/tests/fixtures/mergeexternals.svndump @@ -0,0 +1,306 @@ +SVN-fs-dump-format-version: 2 + +UUID: b402ceb9-6185-4dce-93a1-92de515c5c8b + +Revision-number: 0 +Prop-content-length: 56 +Content-length: 56 + +K 8 +svn:date +V 27 +2011-02-25T13:54:38.654361Z +PROPS-END + +Revision-number: 1 +Prop-content-length: 114 +Content-length: 114 + +K 7 +svn:log +V 12 +init project +K 10 +svn:author +V 7 +pmezard +K 8 +svn:date +V 27 +2011-02-25T13:54:38.675100Z +PROPS-END + +Node-path: branches +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: trunk +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Revision-number: 2 +Prop-content-length: 109 +Content-length: 109 + +K 7 +svn:log +V 8 +addfiles +K 10 +svn:author +V 7 +pmezard +K 8 +svn:date +V 27 +2011-02-25T13:54:39.078800Z +PROPS-END + +Node-path: trunk/common +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: trunk/common/ext +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: trunk/common/ext/c +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 2 +Text-content-md5: 2cd6ee2c70b0bde53fbe6cac3c8b8bb1 +Text-content-sha1: 2b66fd261ee5c6cfc8de7fa466bab600bcfe4f69 +Content-length: 12 + +PROPS-END +c + + +Node-path: trunk/d1 +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: trunk/d1/a +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 2 +Text-content-md5: 60b725f10c9c85c70d97880dfe8191b3 +Text-content-sha1: 3f786850e387550fdab836ed7e6dc881de23001b +Content-length: 12 + +PROPS-END +a + + +Node-path: trunk/d2 +Node-kind: dir +Node-action: add +Prop-content-length: 10 +Content-length: 10 + +PROPS-END + + +Node-path: trunk/d2/b +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 2 +Text-content-md5: 3b5d5c3712955042212316173ccf37be +Text-content-sha1: 89e6c98d92887913cadf06b2adb97f26cde4849b +Content-length: 12 + +PROPS-END +b + + +Revision-number: 3 +Prop-content-length: 114 +Content-length: 114 + +K 7 +svn:log +V 12 +addexternals +K 10 +svn:author +V 7 +pmezard +K 8 +svn:date +V 27 +2011-02-25T13:54:41.071346Z +PROPS-END + +Node-path: trunk/d1 +Node-kind: dir +Node-action: change +Prop-content-length: 58 +Content-length: 58 + +K 13 +svn:externals +V 23 +^/trunk/common/ext ext + +PROPS-END + + +Node-path: trunk/d2 +Node-kind: dir +Node-action: change +Prop-content-length: 58 +Content-length: 58 + +K 13 +svn:externals +V 23 +^/trunk/common/ext ext + +PROPS-END + + +Revision-number: 4 +Prop-content-length: 110 +Content-length: 110 + +K 7 +svn:log +V 9 +addbranch +K 10 +svn:author +V 7 +pmezard +K 8 +svn:date +V 27 +2011-02-25T13:54:44.043149Z +PROPS-END + +Node-path: branches/branch +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 3 +Node-copyfrom-path: trunk + + +Revision-number: 5 +Prop-content-length: 113 +Content-length: 113 + +K 7 +svn:log +V 11 +touchbranch +K 10 +svn:author +V 7 +pmezard +K 8 +svn:date +V 27 +2011-02-25T13:54:45.080319Z +PROPS-END + +Node-path: branches/branch/d3 +Node-kind: dir +Node-action: add +Prop-content-length: 59 +Content-length: 59 + +K 13 +svn:externals +V 24 +^/trunk/common/ext ext3 + +PROPS-END + + +Node-path: branches/branch/d3/d +Node-kind: file +Node-action: add +Prop-content-length: 10 +Text-content-length: 2 +Text-content-md5: e29311f6f1bf1af907f9ef9f44b8328b +Text-content-sha1: e983f374794de9c64e3d1c1de1d490c0756eeeff +Content-length: 12 + +PROPS-END +d + + +Revision-number: 6 +Prop-content-length: 106 +Content-length: 106 + +K 7 +svn:log +V 5 +merge +K 10 +svn:author +V 7 +pmezard +K 8 +svn:date +V 27 +2011-02-25T13:54:48.049151Z +PROPS-END + +Node-path: trunk +Node-kind: dir +Node-action: change +Prop-content-length: 55 +Content-length: 55 + +K 13 +svn:mergeinfo +V 20 +/branches/branch:4-5 +PROPS-END + + +Node-path: trunk/d3 +Node-kind: dir +Node-action: add +Node-copyfrom-rev: 5 +Node-copyfrom-path: branches/branch/d3 +Prop-content-length: 59 +Content-length: 59 + +K 13 +svn:externals +V 24 +^/trunk/common/ext ext3 + +PROPS-END + + diff --git a/tests/fixtures/rsvn.py b/tests/fixtures/rsvn.py index d92c1e8..b48a5d8 100755 --- a/tests/fixtures/rsvn.py +++ b/tests/fixtures/rsvn.py @@ -169,7 +169,7 @@ def rsvn(pool): sys.stderr.write(str(e) + '\n\n') usage() sys.exit(1) - + for opt, value in opts: if opt == '--version': print '%s version %s' % (os.path.basename(sys.argv[0]), VERSION) @@ -181,75 +181,75 @@ def rsvn(pool): username = value elif opt == '--message': log_msg = value - + if log_msg == None: usage('Missing --message argument') sys.exit(1) - + if len(args) != 1: usage('Missing repository path argument') sys.exit(1) - + repos_path = args[0] print 'Accessing repository at [%s]' % repos_path repository = Repository(repos_path, pool) sub = repository.subpool() - + try: txn = repository.begin(username, log_msg) - + # Read commands from STDIN lineno = 0 for line in sys.stdin: lineno += 1 - + core.svn_pool_clear(sub) try: if COMMENT_RE.search(line): continue - + match = RCOPY_RE.search(line) if match: src = match.group(1) dest = match.group(2) txn.copy(src, dest, sub) continue - + match = RMOVE_RE.search(line) if match: src = match.group(1) dest = match.group(2) txn.move(src, dest, sub) continue - + match = RMKDIR_RE.search(line) if match: entry = match.group(1) txn.mkdir(entry, sub) continue - + match = RDELETE_RE.search(line) if match: entry = match.group(1) txn.delete(entry, sub) continue - + raise NameError, ('Unknown command [%s] on line %d' % (line, lineno)) - + except: - sys.stderr.write(('Exception occured while processing line %d:\n' % + sys.stderr.write(('Exception occured while processing line %d:\n' % lineno)) etype, value, tb = sys.exc_info() traceback.print_exception(etype, value, tb, None, sys.stderr) sys.stderr.write('\n') txn.rollback() sys.exit(1) - + new_rev = txn.commit() print '\nCommitted revision %d.' % new_rev - + finally: print '\nRepository closed.' diff --git a/tests/run.py b/tests/run.py index ce4ad34..98cf0a6 100644 --- a/tests/run.py +++ b/tests/run.py @@ -93,23 +93,23 @@ if __name__ == '__main__': import tempfile sys.stdout = tempfile.TemporaryFile() - all = tests() + all_tests = tests() args = [i.split('.py')[0].replace('-', '_') for i in args] if not args: check = lambda x: options.comprehensive or not comprehensive(x) - mods = [m for (n, m) in sorted(all.iteritems()) if check(m)] + mods = [m for (n, m) in sorted(all_tests.iteritems()) if check(m)] suite = [m.suite() for m in mods] else: suite = [] for arg in args: if arg == 'test_util': continue - elif arg not in all: + elif arg not in all_tests: print >> sys.stderr, 'test module %s not available' % arg else: - suite.append(all[arg].suite()) + suite.append(all_tests[arg].suite()) runner = unittest.TextTestRunner(**testargs) result = runner.run(unittest.TestSuite(suite)) diff --git a/tests/test_binaryfiles.py b/tests/test_binaryfiles.py index dcdaa6d..7be3747 100644 --- a/tests/test_binaryfiles.py +++ b/tests/test_binaryfiles.py @@ -11,6 +11,6 @@ class TestFetchBinaryFiles(test_util.TestBase): self.test_binaryfiles(True) def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchBinaryFiles), + all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestFetchBinaryFiles), ] - return unittest.TestSuite(all) + return unittest.TestSuite(all_tests) diff --git a/tests/test_diff.py b/tests/test_diff.py index 43d40ed..464ae76 100644 --- a/tests/test_diff.py +++ b/tests/test_diff.py @@ -33,11 +33,11 @@ class DiffTests(test_util.TestBase): ]) u = ui.ui() u.pushbuffer() - wrappers.diff(lambda x,y,z: None, u, self.repo, svn=True) + wrappers.diff(lambda x, y, z: None, u, self.repo, svn=True) self.assertEqual(u.popbuffer(), expected_diff_output) def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(DiffTests), + all_tests = [unittest.TestLoader().loadTestsFromTestCase(DiffTests), ] - return unittest.TestSuite(all) + return unittest.TestSuite(all_tests) diff --git a/tests/test_externals.py b/tests/test_externals.py index 9fcab76..2927b11 100644 --- a/tests/test_externals.py +++ b/tests/test_externals.py @@ -10,7 +10,7 @@ try: subrepo.svnsubrepo hgutil.checknlink except (ImportError, AttributeError), e: - print >>sys.stderr, 'test_externals: skipping .hgsub tests' + print >> sys.stderr, 'test_externals: skipping .hgsub tests' subrepo = None from hgsubversion import svnexternals @@ -80,12 +80,16 @@ class TestFetchExternals(test_util.TestBase): ref0 = """[.] ^/externals/project1 deps/project1 """ - self.assertEqual(ref0, repo[0]['.hgsvnexternals'].data()) - ref1 = """[.] + self.assertMultiLineEqual(ref0, repo[0]['.hgsvnexternals'].data()) + ref1 = """\ +[.] + # A comment, then an empty line, then a blank line + ^/externals/project1 deps/project1 + -r2 ^/externals/project2@2 deps/project2 """ - self.assertEqual(ref1, repo[1]['.hgsvnexternals'].data()) + self.assertMultiLineEqual(ref1, repo[1]['.hgsvnexternals'].data()) ref2 = """[.] -r2 ^/externals/project2@2 deps/project2 @@ -149,6 +153,8 @@ class TestFetchExternals(test_util.TestBase): checkdeps(['subdir/deps/project1'], ['deps/project2'], repo, 4) def test_hgsub(self, stupid=False): + if subrepo is None: + return repo = self._load_fixture_and_fetch('externals.svndump', externals='subrepos', stupid=stupid) @@ -179,7 +185,7 @@ HEAD subdir/deps/project1 HEAD subdir2/deps/project1 """, repo[2]['.hgsubstate'].data()) - self.assertEqual("""\ + self.assertMultiLineEqual("""\ deps/project2 = [hgsubversion] :-r{REV} ^/externals/project2@2 deps/project2 subdir/deps/project1 = [hgsubversion] subdir:^/externals/project1 deps/project1 """, repo[3]['.hgsub'].data()) @@ -254,6 +260,22 @@ deps/project2 = [hgsubversion] :-r{REV} ^/externals/project2@2 deps/project2 repo.wwrite('subdir/deps/project1/a', 'foobar', '') commands.update(ui, repo, node='4', clean=True) + def test_mergeexternals(self, stupid=False): + if subrepo is None: + return + repo = self._load_fixture_and_fetch('mergeexternals.svndump', + externals='subrepos', + stupid=stupid) + # Check merged directories externals are fine + self.assertEqual("""\ +d1/ext = [hgsubversion] d1:^/trunk/common/ext ext +d2/ext = [hgsubversion] d2:^/trunk/common/ext ext +d3/ext3 = [hgsubversion] d3:^/trunk/common/ext ext3 +""", repo['tip']['.hgsub'].data()) + + def test_mergeexternals_stupid(self): + self.test_mergeexternals(True) + class TestPushExternals(test_util.TestBase): def test_push_externals(self, stupid=False): test_util.load_fixture_and_fetch('pushexternals.svndump', @@ -336,7 +358,7 @@ HEAD subdir2/deps/project2 self.assertchanges(changes, self.repo['tip']) # Check .hgsub and .hgsubstate were not pushed - self.assertEqual(['dir', 'subdir1', 'subdir1/a','subdir2', + self.assertEqual(['dir', 'subdir1', 'subdir1/a', 'subdir2', 'subdir2/a'], self.svnls('trunk')) # Remove all references from one directory, add a new one @@ -386,7 +408,7 @@ HEAD subdir1/deps/project1 self.assertchanges(changes, self.repo['tip']) def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchExternals), + all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestFetchExternals), unittest.TestLoader().loadTestsFromTestCase(TestPushExternals), ] - return unittest.TestSuite(all) + return unittest.TestSuite(all_tests) diff --git a/tests/test_fetch_branches.py b/tests/test_fetch_branches.py index 5c00354..a9645f0 100644 --- a/tests/test_fetch_branches.py +++ b/tests/test_fetch_branches.py @@ -16,14 +16,19 @@ class TestFetchBranches(test_util.TestBase): def _load_fixture_and_fetch_with_anchor(self, fixture_name, anchor): test_util.load_svndump_fixture(self.repo_path, fixture_name) source = '%s#%s' % (test_util.fileurl(self.repo_path), anchor) - repo = hg.clone(self.ui(), source=source, dest=self.wc_path) + test_util.hgclone(self.ui(), source, self.wc_path) return hg.repository(self.ui(), self.wc_path) - def openbranches(self, repo): + def branches(self, repo): hctxs = [repo[hn] for hn in repo.heads()] - branches = set(ctx.branch() for ctx in hctxs if - ctx.extra().get('close', None) != '1') - return sorted(branches) + openbranches = set(ctx.branch() for ctx in hctxs if + ctx.extra().get('close', None) != '1') + closedbranches = set(ctx.branch() for ctx in hctxs if + ctx.extra().get('close', None) == '1') + return sorted(openbranches), sorted(closedbranches) + + def openbranches(self, repo): + return self.branches(repo)[0] def test_rename_branch_parent(self, stupid=False): repo = self._load_fixture_and_fetch('rename_branch_parent_dir.svndump', stupid) @@ -58,10 +63,10 @@ class TestFetchBranches(test_util.TestBase): def test_renamed_branch_to_trunk(self, stupid=False): repo = self._load_fixture_and_fetch('branch_rename_to_trunk.svndump', stupid) - self.assertEqual(node.hex(repo['default'].node()), - '14d252aef315857df241dd3fa4bc7833b09bd2f5') self.assertEqual(repo['default'].parents()[0].branch(), 'dev_branch') + self.assert_('iota' in repo['default']) self.assertEqual(repo['old_trunk'].parents()[0].branch(), 'default') + self.assert_('iota' not in repo['old_trunk']) expected = ['default', 'old_trunk'] self.assertEqual(self.openbranches(repo), expected) @@ -127,8 +132,10 @@ class TestFetchBranches(test_util.TestBase): def test_branch_delete_parent_dir(self, stupid=False): repo = self._load_fixture_and_fetch('branch_delete_parent_dir.svndump', stupid) - self.assertEqual(node.hex(repo['tip'].node()), - '4108a81a82c7925d5551091165dc54c41b06a8a8') + openb, closedb = self.branches(repo) + self.assertEqual(openb, []) + self.assertEqual(closedb, ['dev_branch']) + self.assertEqual(list(repo['dev_branch']), ['foo']) def test_replace_branch_with_branch(self, stupid=False): repo = self._load_fixture_and_fetch('replace_branch_with_branch.svndump', @@ -156,6 +163,6 @@ class TestFetchBranches(test_util.TestBase): self.test_replace_branch_with_branch(True) def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchBranches), + all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestFetchBranches), ] - return unittest.TestSuite(all) + return unittest.TestSuite(all_tests) diff --git a/tests/test_fetch_command.py b/tests/test_fetch_command.py index 02db7e1..f86416c 100644 --- a/tests/test_fetch_command.py +++ b/tests/test_fetch_command.py @@ -165,7 +165,7 @@ class TestBasicRepoLayout(test_util.TestBase): commands.clone(ui, repo_url + subdir, wc_path) commands.clone(ui, repo_url + quoted_subdir, wc2_path) - repo = hg.repository(ui, wc_path) + repo = hg.repository(ui, wc_path) repo2 = hg.repository(ui, wc2_path) self.assertEqual(repo['tip'].extra()['convert_revision'], @@ -224,7 +224,7 @@ class TestStupidPull(test_util.TestBase): '1a6c3f30911d57abb67c257ec0df3e7bc44786f7') def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(TestBasicRepoLayout), + all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestBasicRepoLayout), unittest.TestLoader().loadTestsFromTestCase(TestStupidPull), ] - return unittest.TestSuite(all) + return unittest.TestSuite(all_tests) diff --git a/tests/test_fetch_exec.py b/tests/test_fetch_exec.py index 2b2bec6..4742d27 100644 --- a/tests/test_fetch_exec.py +++ b/tests/test_fetch_exec.py @@ -33,6 +33,6 @@ class TestFetchExec(test_util.TestBase): self.test_empty_prop_val_executable(True) def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchExec), + all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestFetchExec), ] - return unittest.TestSuite(all) + return unittest.TestSuite(all_tests) diff --git a/tests/test_fetch_mappings.py b/tests/test_fetch_mappings.py index 2eba177..83149ef 100644 --- a/tests/test_fetch_mappings.py +++ b/tests/test_fetch_mappings.py @@ -26,7 +26,7 @@ class MapTests(test_util.TestBase): @property def branchmap(self): return os.path.join(self.tmpdir, 'branchmap') - + @property def tagmap(self): return os.path.join(self.tmpdir, 'tagmap') @@ -96,8 +96,8 @@ class MapTests(test_util.TestBase): test = maps.AuthorMap(self.ui(), self.authors) fromself = set(test) test.load(orig) - all = set(test) - self.assertEqual(fromself.symmetric_difference(all), set()) + all_tests = set(test) + self.assertEqual(fromself.symmetric_difference(all_tests), set()) def test_file_map(self, stupid=False): test_util.load_svndump_fixture(self.repo_path, 'replace_trunk_with_branch.svndump') @@ -112,7 +112,8 @@ class MapTests(test_util.TestBase): self.assertEqual(node.hex(self.repo['default'].node()), 'e524296152246b3837fe9503c83b727075835155') def test_file_map_stupid(self): - self.test_file_map(True) + # TODO: re-enable test if we ever reinstate this feature + self.assertRaises(hgutil.Abort, self.test_file_map, True) def test_file_map_exclude(self, stupid=False): test_util.load_svndump_fixture(self.repo_path, 'replace_trunk_with_branch.svndump') @@ -127,7 +128,8 @@ class MapTests(test_util.TestBase): self.assertEqual(node.hex(self.repo['default'].node()), 'b37a3c0297b71f989064d9b545b5a478bbed7cc1') def test_file_map_exclude_stupid(self): - self.test_file_map_exclude(True) + # TODO: re-enable test if we ever reinstate this feature + self.assertRaises(hgutil.Abort, self.test_file_map_exclude, True) def test_branchmap(self, stupid=False): test_util.load_svndump_fixture(self.repo_path, 'branchmap.svndump') @@ -220,8 +222,8 @@ class MapTests(test_util.TestBase): # clone & rebuild ui = self.ui(stupid) - src, dest = hg.clone(ui, self.wc_path, self.wc_path + '_clone', - update=False) + src, dest = test_util.hgclone(ui, self.wc_path, self.wc_path + '_clone', + update=False) svncommands.rebuildmeta(ui, dest, args=[test_util.fileurl(self.repo_path)]) diff --git a/tests/test_fetch_renames.py b/tests/test_fetch_renames.py index c15ad92..64b69d3 100644 --- a/tests/test_fetch_renames.py +++ b/tests/test_fetch_renames.py @@ -75,6 +75,6 @@ class TestFetchRenames(test_util.TestBase): self._test_case(True) def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchRenames), + all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestFetchRenames), ] - return unittest.TestSuite(all) + return unittest.TestSuite(all_tests) diff --git a/tests/test_fetch_symlinks.py b/tests/test_fetch_symlinks.py index 8f6504d..ecb2522 100644 --- a/tests/test_fetch_symlinks.py +++ b/tests/test_fetch_symlinks.py @@ -39,7 +39,7 @@ class TestFetchSymlinks(test_util.TestBase): 'linka4': 'link to this', }, } - + for rev in repo: ctx = repo[rev] for f in ctx.manifest(): @@ -53,6 +53,6 @@ class TestFetchSymlinks(test_util.TestBase): self.test_symlinks(True) def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchSymlinks), + all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestFetchSymlinks), ] - return unittest.TestSuite(all) + return unittest.TestSuite(all_tests) diff --git a/tests/test_fetch_truncated.py b/tests/test_fetch_truncated.py index 0f7d21d..9cb7300 100644 --- a/tests/test_fetch_truncated.py +++ b/tests/test_fetch_truncated.py @@ -31,6 +31,6 @@ class TestFetchTruncatedHistory(test_util.TestBase): self.test_truncated_history(True) def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(TestFetchTruncatedHistory), + all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestFetchTruncatedHistory), ] - return unittest.TestSuite(all) + return unittest.TestSuite(all_tests) diff --git a/tests/test_pull.py b/tests/test_pull.py index fb3a4f5..b0f61b2 100644 --- a/tests/test_pull.py +++ b/tests/test_pull.py @@ -2,6 +2,7 @@ import test_util import os.path import subprocess +from mercurial import node from mercurial import ui from mercurial import util as hgutil from mercurial import commands @@ -28,7 +29,7 @@ class TestPull(test_util.TestBase): commands.pull(self.repo.ui, repo) self.assertEqual(state, repo.parents()) self.assertTrue('tip' not in repo[None].tags()) - + def test_onerevision_doupdate(self): repo = self._load_fixture_and_fetch('single_rev.svndump') state = repo.parents() @@ -42,12 +43,21 @@ class TestPull(test_util.TestBase): self.commitchanges((('alpha', 'alpha', 'Changed another way'),)) state = repo.parents() self._add_svn_rev({'trunk/alpha': 'Changed one way'}) - self.assertRaises(hgutil.Abort, commands.pull, - self.repo.ui, repo, update=True) + try: + commands.pull(self.repo.ui, repo, update=True) + except hgutil.Abort: + # hg < 1.9 raised when crossing branches + pass self.assertEqual(state, repo.parents()) self.assertTrue('tip' not in repo[None].tags()) self.assertEqual(len(repo.heads()), 2) + def test_tag_repull_doesnt_happen(self): + repo = self._load_fixture_and_fetch('branchtagcollision.svndump') + oldheads = map(node.hex, repo.heads()) + commands.pull(repo.ui, repo) + self.assertEqual(oldheads, map(node.hex, repo.heads())) + def suite(): import unittest, sys return unittest.findTestCases(sys.modules[__name__]) diff --git a/tests/test_push_command.py b/tests/test_push_command.py index 60209c0..0f078b4 100644 --- a/tests/test_push_command.py +++ b/tests/test_push_command.py @@ -43,7 +43,7 @@ class PushTests(test_util.TestBase): file_callback, 'an_author', '2008-10-07 20:59:48 -0500', - {'branch': 'default',}) + {'branch': 'default', }) new_hash = repo.commitctx(ctx) hg.update(repo, repo['tip'].node()) old_tip = repo['tip'].node() @@ -64,7 +64,7 @@ class PushTests(test_util.TestBase): file_callback, 'an_author', '2008-10-07 20:59:48 -0500', - {'branch': 'default',}) + {'branch': 'default', }) new_hash = repo.commitctx(ctx) hg.update(repo, repo['tip'].node()) # Touch an existing file @@ -116,7 +116,7 @@ class PushTests(test_util.TestBase): filectxfn=file_callback, user='an_author', date='2008-10-07 20:59:48 -0500', - extra={'branch': 'default',}) + extra={'branch': 'default', }) new_hash = repo.commitctx(ctx) if not commit: return # some tests use this test as an extended setup. @@ -161,7 +161,7 @@ class PushTests(test_util.TestBase): file_callback, 'an_author', '2008-10-07 20:59:48 -0500', - {'branch': 'default',}) + {'branch': 'default', }) new_hash = repo.commitctx(ctx) if not commit: return # some tests use this test as an extended setup. @@ -183,7 +183,7 @@ class PushTests(test_util.TestBase): copied=False) oldtiphash = self.repo['default'].node() ctx = context.memctx(self.repo, - (self.repo[0].node(), revlog.nullid, ), + (self.repo[0].node(), revlog.nullid,), 'automated test', ['gamma', ], filectxfn, @@ -229,7 +229,7 @@ class PushTests(test_util.TestBase): file_callback, 'an_author', '2008-10-07 20:59:48 -0500', - {'branch': 'default',}) + {'branch': 'default', }) new_hash = repo.commitctx(ctx) hg.update(repo, repo['tip'].node()) self.pushrevisions() @@ -264,7 +264,7 @@ class PushTests(test_util.TestBase): file_callback, 'an_author', '2008-10-07 20:59:48 -0500', - {'branch': 'the_branch',}) + {'branch': 'the_branch', }) new_hash = repo.commitctx(ctx) hg.update(repo, repo['tip'].node()) if push: @@ -278,20 +278,20 @@ class PushTests(test_util.TestBase): self.test_push_to_branch(push=False) wc2path = self.wc_path + '_clone' u = self.repo.ui - hg.clone(self.repo.ui, self.wc_path, wc2path, update=False) + test_util.hgclone(self.repo.ui, self.wc_path, wc2path, update=False) res = self.pushrevisions() self.assertEqual(0, res) oldf = open(os.path.join(self.wc_path, '.hg', 'hgrc')) hgrc = oldf.read() oldf.close() shutil.rmtree(self.wc_path) - hg.clone(u, wc2path, self.wc_path, update=False) + test_util.hgclone(u, wc2path, self.wc_path, update=False) oldf = open(os.path.join(self.wc_path, '.hg', 'hgrc'), 'w') oldf.write(hgrc) oldf.close() # do a commit here - self.commitchanges([('foobaz', 'foobaz', 'This file is added on default.', ), + self.commitchanges([('foobaz', 'foobaz', 'This file is added on default.',), ], parent='default', message='commit to default') @@ -469,13 +469,13 @@ class PushTests(test_util.TestBase): def test_push_outdated_base_text(self): self.test_push_two_revs() - changes = [('adding_file', 'adding_file', 'different_content', ), + changes = [('adding_file', 'adding_file', 'different_content',), ] par = self.repo['tip'].rev() self.commitchanges(changes, parent=par) self.pushrevisions() changes = [('adding_file', 'adding_file', - 'even_more different_content', ), + 'even_more different_content',), ] self.commitchanges(changes, parent=par) try: @@ -490,12 +490,12 @@ class PushTests(test_util.TestBase): def suite(): test_classes = [PushTests, ] - tests = [] + all_tests = [] # This is the quickest hack I could come up with to load all the tests from # both classes. Would love a patch that simplifies this without adding # dependencies. for tc in test_classes: for attr in dir(tc): if attr.startswith('test_'): - tests.append(tc(attr)) - return unittest.TestSuite(tests) + all_tests.append(tc(attr)) + return unittest.TestSuite(all_tests) diff --git a/tests/test_push_dirs.py b/tests/test_push_dirs.py index 7d828fd..3b43130 100644 --- a/tests/test_push_dirs.py +++ b/tests/test_push_dirs.py @@ -48,7 +48,7 @@ class TestPushDirectories(test_util.TestBase): def test_push_new_dir_project_root_not_repo_root(self): self._load_fixture_and_fetch('fetch_missing_files_subdir.svndump', subdir='foo') - changes = [('magic_new/a', 'magic_new/a', 'ohai', ), + changes = [('magic_new/a', 'magic_new/a', 'ohai',), ] self.commitchanges(changes) self.pushrevisions() @@ -64,20 +64,21 @@ class TestPushDirectories(test_util.TestBase): def test_push_new_file_existing_dir_root_not_repo_root(self): self._load_fixture_and_fetch('empty_dir_in_trunk_not_repo_root.svndump', subdir='project') - changes = [('narf/a', 'narf/a', 'ohai', ), + changes = [('narf/a', 'narf/a', 'ohai',), ] self.commitchanges(changes) self.assertEqual(self.svnls('project/trunk'), ['a', - 'narf',]) + 'narf', + ]) self.pushrevisions() self.assertEqual(self.svnls('project/trunk'), ['a', 'narf', 'narf/a']) - changes = [('narf/a', None, None, ), + changes = [('narf/a', None, None,), ] self.commitchanges(changes) self.pushrevisions() - self.assertEqual(self.svnls('project/trunk'), ['a' ,]) + self.assertEqual(self.svnls('project/trunk'), ['a']) def test_push_single_dir_change_in_subdir(self): # Tests simple pushing from default branch to a single dir repo @@ -104,6 +105,6 @@ class TestPushDirectories(test_util.TestBase): 'tag_r3/new']) def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(TestPushDirectories), + all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestPushDirectories), ] - return unittest.TestSuite(all) + return unittest.TestSuite(all_tests) diff --git a/tests/test_push_eol.py b/tests/test_push_eol.py index 9ef754a..fe89529 100644 --- a/tests/test_push_eol.py +++ b/tests/test_push_eol.py @@ -37,6 +37,6 @@ class TestPushEol(test_util.TestBase): self._test_push_dirs(True) def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(TestPushEol), + all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestPushEol), ] - return unittest.TestSuite(all) + return unittest.TestSuite(all_tests) diff --git a/tests/test_push_renames.py b/tests/test_push_renames.py index b38df37..ea7acff 100644 --- a/tests/test_push_renames.py +++ b/tests/test_push_renames.py @@ -79,9 +79,9 @@ class TestPushRenames(test_util.TestBase): ('geek/delta', 'geek/delta', 'content',), ('geek/gamma', 'geek/gamma', 'content',), ('geek/later/pi', 'geek/later/pi', 'content geek/later/pi',), - ('geek/later/rho', 'geek/later/rho', 'content geek/later/rho', ), - ('geek/other/blah', 'geek/other/blah', 'content geek/other/blah', ), - ('geek/other/another/layer', 'geek/other/another/layer', 'content deep file', ), + ('geek/later/rho', 'geek/later/rho', 'content geek/later/rho',), + ('geek/other/blah', 'geek/other/blah', 'content geek/other/blah',), + ('geek/other/another/layer', 'geek/other/another/layer', 'content deep file',), ] self.commitchanges(changes) @@ -90,35 +90,35 @@ class TestPushRenames(test_util.TestBase): changes = [ # rename (copy + remove) all of geek to greek - ('geek/alpha', 'greek/alpha', None, ), - ('geek/beta', 'greek/beta', None, ), - ('geek/delta', 'greek/delta', None, ), - ('geek/gamma', 'greek/gamma', None, ), - ('geek/later/pi', 'greek/later/pi', None, ), - ('geek/later/rho', 'greek/later/rho', None, ), - ('geek/other/blah', 'greek/other/blah', None, ), - ('geek/other/another/layer', 'greek/other/another/layer', None, ), + ('geek/alpha', 'greek/alpha', None,), + ('geek/beta', 'greek/beta', None,), + ('geek/delta', 'greek/delta', None,), + ('geek/gamma', 'greek/gamma', None,), + ('geek/later/pi', 'greek/later/pi', None,), + ('geek/later/rho', 'greek/later/rho', None,), + ('geek/other/blah', 'greek/other/blah', None,), + ('geek/other/another/layer', 'greek/other/another/layer', None,), - ('geek/alpha', None, None, ), - ('geek/beta', None, None, ), - ('geek/delta', None, None, ), - ('geek/gamma', None, None, ), - ('geek/later/pi', None, None, ), - ('geek/later/rho', None, None, ), - ('geek/other/blah', None, None, ), - ('geek/other/another/layer', None, None, ), + ('geek/alpha', None, None,), + ('geek/beta', None, None,), + ('geek/delta', None, None,), + ('geek/gamma', None, None,), + ('geek/later/pi', None, None,), + ('geek/later/rho', None, None,), + ('geek/other/blah', None, None,), + ('geek/other/another/layer', None, None,), ] self.commitchanges(changes) self.pushrevisions() # print '\n'.join(sorted(self.svnls('trunk'))) assert reduce(lambda x, y: x and y, - ('geek' not in f for f in self.svnls('trunk'))),( + ('geek' not in f for f in self.svnls('trunk'))), ( 'This failure means rename of an entire tree is broken.' ' There is a print on the preceding line commented out ' 'that should help you.') def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(TestPushRenames), + all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestPushRenames), ] - return unittest.TestSuite(all) + return unittest.TestSuite(all_tests) diff --git a/tests/test_rebuildmeta.py b/tests/test_rebuildmeta.py index 5a38aad..7182dbb 100644 --- a/tests/test_rebuildmeta.py +++ b/tests/test_rebuildmeta.py @@ -12,6 +12,17 @@ from mercurial import ui from hgsubversion import svncommands from hgsubversion import svnmeta +# These test repositories have harmless skew in rebuildmeta for the +# last-pulled-rev because the last rev in svn causes absolutely no +# changes in hg. +expect_youngest_skew = [('file_mixed_with_branches.svndump', False, False), + ('file_mixed_with_branches.svndump', True, False), + ('unrelatedbranch.svndump', False, False), + ('unrelatedbranch.svndump', True, False), + ] + + + def _do_case(self, name, stupid, single): subdir = test_util.subdir.get(name, '') layout = 'auto' @@ -21,7 +32,7 @@ def _do_case(self, name, stupid, single): assert len(self.repo) > 0 wc2_path = self.wc_path + '_clone' u = ui.ui() - src, dest = hg.clone(u, self.wc_path, wc2_path, update=False) + src, dest = test_util.hgclone(u, self.wc_path, wc2_path, update=False) # insert a wrapper that prevents calling changectx.children() def failfn(orig, ctx): @@ -44,12 +55,18 @@ def _do_case(self, name, stupid, single): self.assertTrue(os.path.isdir(os.path.join(src.path, 'svn')), 'no .hg/svn directory in the destination!') dest = hg.repository(u, os.path.dirname(dest.path)) - for tf in ('rev_map', 'uuid', 'tagmap', 'layout', 'subdir', ): + for tf in ('lastpulled', 'rev_map', 'uuid', 'tagmap', 'layout', 'subdir',): + stf = os.path.join(src.path, 'svn', tf) self.assertTrue(os.path.isfile(stf), '%r is missing!' % stf) dtf = os.path.join(dest.path, 'svn', tf) self.assertTrue(os.path.isfile(dtf), '%r is missing!' % tf) old, new = open(stf).read(), open(dtf).read() + if tf == 'lastpulled' and (name, + stupid, single) in expect_youngest_skew: + self.assertNotEqual(old, new, + 'rebuildmeta unexpected match on youngest rev!') + continue self.assertMultiLineEqual(old, new) self.assertEqual(src.branchtags(), dest.branchtags()) srcbi = pickle.load(open(os.path.join(src.path, 'svn', 'branch_info'))) @@ -96,10 +113,10 @@ for case in [f for f in os.listdir(test_util.FIXTURES) if f.endswith('.svndump') name = bname + '_single' attrs[name] = buildmethod(case, name, False, True) -RebuildMetaTests = type('RebuildMetaTests', (test_util.TestBase, ), attrs) +RebuildMetaTests = type('RebuildMetaTests', (test_util.TestBase,), attrs) def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(RebuildMetaTests), + all_tests = [unittest.TestLoader().loadTestsFromTestCase(RebuildMetaTests), ] - return unittest.TestSuite(all) + return unittest.TestSuite(all_tests) diff --git a/tests/test_single_dir_clone.py b/tests/test_single_dir_clone.py index 2def325..2e035a5 100644 --- a/tests/test_single_dir_clone.py +++ b/tests/test_single_dir_clone.py @@ -4,7 +4,6 @@ import errno import shutil import unittest -from mercurial import dispatch from mercurial import commands from mercurial import context from mercurial import hg @@ -92,19 +91,32 @@ class TestSingleDir(test_util.TestBase): islink=False, isexec=False, copied=False) + elif path == 'adding_binary': + return context.memfilectx(path=path, + data='\0binary', + islink=False, + isexec=False, + copied=False) raise IOError(errno.EINVAL, 'Invalid operation: ' + path) ctx = context.memctx(repo, (repo['tip'].node(), node.nullid), 'automated test', - ['adding_file'], + ['adding_file', 'adding_binary'], file_callback, 'an_author', '2009-10-19 18:49:30 -0500', - {'branch': 'default',}) + {'branch': 'default', }) repo.commitctx(ctx) hg.update(repo, repo['tip'].node()) self.pushrevisions() self.assertTrue('adding_file' in self.svnls('')) + self.assertEqual('application/octet-stream', + self.svnpropget('adding_binary', 'svn:mime-type')) + # Now add another commit and test mime-type being reset + changes = [('adding_binary', 'adding_binary', 'no longer binary')] + self.commitchanges(changes) + self.pushrevisions() + self.assertEqual('', self.svnpropget('adding_binary', 'svn:mime-type')) def test_push_single_dir_at_subdir(self): repo = self._load_fixture_and_fetch('branch_from_tag.svndump', @@ -124,7 +136,7 @@ class TestSingleDir(test_util.TestBase): filectxfn, 'an_author', '2009-10-19 18:49:30 -0500', - {'branch': 'localhacking',}) + {'branch': 'localhacking', }) n = repo.commitctx(ctx) self.assertEqual(self.repo['tip']['bogus'].data(), 'contents of bogus') @@ -159,7 +171,7 @@ class TestSingleDir(test_util.TestBase): file_callback, 'an_author', '2009-10-19 18:49:30 -0500', - {'branch': 'default',}) + {'branch': 'default', }) repo.commitctx(ctx) hg.update(repo, repo['tip'].node()) self.pushrevisions(expected_extra_back=1) @@ -194,7 +206,7 @@ class TestSingleDir(test_util.TestBase): file_callback(name), 'an_author', '2009-10-19 18:49:30 -0500', - {'branch': name,})) + {'branch': name, })) parent = repo['tip'].node() commit_to_branch('default', parent) @@ -223,7 +235,7 @@ class TestSingleDir(test_util.TestBase): if stupid: cmd.append('--stupid') cmd += [test_util.fileurl(self.repo_path), self.wc_path] - dispatch.dispatch(cmd) + test_util.dispatch(cmd) def file_callback(repo, memctx, path): if path == 'adding_file': @@ -240,7 +252,7 @@ class TestSingleDir(test_util.TestBase): file_callback, 'an_author', '2009-10-19 18:49:30 -0500', - {'branch': 'default',}) + {'branch': 'default', }) self.repo.commitctx(ctx) hg.update(self.repo, self.repo['tip'].node()) self.pushrevisions() @@ -254,5 +266,5 @@ class TestSingleDir(test_util.TestBase): self.test_push_single_dir_renamed_branch(True) def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(TestSingleDir)] - return unittest.TestSuite(all) + all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestSingleDir)] + return unittest.TestSuite(all_tests) diff --git a/tests/test_startrev.py b/tests/test_startrev.py index 7e51fb9..026e5be 100644 --- a/tests/test_startrev.py +++ b/tests/test_startrev.py @@ -61,10 +61,10 @@ for case in [f for f in os.listdir(test_util.FIXTURES) if f.endswith('.svndump') name = bname + '_stupid' attrs[name] = buildmethod(case, name, subdir, True) -StartRevTests = type('StartRevTests', (test_util.TestBase, ), attrs) +StartRevTests = type('StartRevTests', (test_util.TestBase,), attrs) def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(StartRevTests), + all_tests = [unittest.TestLoader().loadTestsFromTestCase(StartRevTests), ] - return unittest.TestSuite(all) + return unittest.TestSuite(all_tests) diff --git a/tests/test_svnwrap.py b/tests/test_svnwrap.py index 5fe6aab..c5a5d2e 100644 --- a/tests/test_svnwrap.py +++ b/tests/test_svnwrap.py @@ -22,10 +22,10 @@ class TestBasicRepoLayout(unittest.TestCase): def setUp(self): self.tmpdir = tempfile.mkdtemp('svnwrap_test') self.repo_path = '%s/testrepo' % self.tmpdir - subprocess.call(['svnadmin', 'create', self.repo_path,]) + subprocess.call(['svnadmin', 'create', self.repo_path, ]) inp = open(os.path.join(os.path.dirname(__file__), 'fixtures', 'project_root_at_repo_root.svndump')) - proc = subprocess.call(['svnadmin', 'load', self.repo_path,], + proc = subprocess.call(['svnadmin', 'load', self.repo_path, ], stdin=inp, close_fds=test_util.canCloseFds, stdout=subprocess.PIPE, @@ -57,10 +57,10 @@ class TestRootAsSubdirOfRepo(TestBasicRepoLayout): def setUp(self): self.tmpdir = tempfile.mkdtemp('svnwrap_test') self.repo_path = '%s/testrepo' % self.tmpdir - subprocess.call(['svnadmin', 'create', self.repo_path,]) + subprocess.call(['svnadmin', 'create', self.repo_path, ]) inp = open(os.path.join(os.path.dirname(__file__), 'fixtures', 'project_root_not_repo_root.svndump')) - ret = subprocess.call(['svnadmin', 'load', self.repo_path,], + ret = subprocess.call(['svnadmin', 'load', self.repo_path, ], stdin=inp, close_fds=test_util.canCloseFds, stdout=subprocess.PIPE, @@ -71,6 +71,6 @@ class TestRootAsSubdirOfRepo(TestBasicRepoLayout): )) def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(TestBasicRepoLayout), + all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestBasicRepoLayout), unittest.TestLoader().loadTestsFromTestCase(TestRootAsSubdirOfRepo)] - return unittest.TestSuite(all) + return unittest.TestSuite(all_tests) diff --git a/tests/test_tags.py b/tests/test_tags.py index 297591e..55cbb0c 100644 --- a/tests/test_tags.py +++ b/tests/test_tags.py @@ -115,7 +115,7 @@ rename a tag "Note: this test failing may be because of a rebuildmeta failure.\n" "You should check that before assuming issues with this test.\n") wc2_path = self.wc_path + '2' - src, dest = hg.clone(repo.ui, self.wc_path, wc2_path, update=False) + src, dest = test_util.hgclone(repo.ui, self.wc_path, wc2_path, update=False) svncommands.rebuildmeta(repo.ui, dest, args=[test_util.fileurl(self.repo_path), ]) @@ -138,9 +138,9 @@ rename a tag openheads = [h for h in heads if not repo[h].extra().get('close', False)] closedheads = set(heads) - set(openheads) self.assertEqual(len(openheads), 1) - self.assertEqual(len(closedheads), headcount-1) + self.assertEqual(len(closedheads), headcount - 1) closedheads = sorted(list(closedheads), - cmp=lambda x,y: cmp(repo[x].rev(), repo[y].rev())) + cmp=lambda x, y: cmp(repo[x].rev(), repo[y].rev())) # closeme has no open heads for h in openheads: diff --git a/tests/test_template_keywords.py b/tests/test_template_keywords.py index 87449ba..be1b4a7 100644 --- a/tests/test_template_keywords.py +++ b/tests/test_template_keywords.py @@ -15,7 +15,7 @@ try: from mercurial import revset revset.methods except ImportError: - revset = None + revset = None class CapturingUI(ui.ui): @@ -82,5 +82,5 @@ class TestLogKeywords(test_util.TestBase): template='{rev}:{svnrev} ', **defaults) def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(TestLogKeywords),] - return unittest.TestSuite(all) + all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestLogKeywords), ] + return unittest.TestSuite(all_tests) diff --git a/tests/test_unaffected_core.py b/tests/test_unaffected_core.py index 4fe1dea..f680ada 100644 --- a/tests/test_unaffected_core.py +++ b/tests/test_unaffected_core.py @@ -10,6 +10,13 @@ from mercurial import hg from mercurial import node from mercurial import ui +def _dispatch(ui, cmd): + try: + req = dispatch.request(cmd, ui=ui) + dispatch._dispatch(req) + except AttributeError: + dispatch._dispatch(ui, cmd) + class TestMercurialCore(test_util.TestBase): ''' Test that the core Mercurial operations aren't broken by hgsubversion. @@ -19,7 +26,7 @@ class TestMercurialCore(test_util.TestBase): def test_update(self): ''' Test 'clone --updaterev' ''' ui = self.ui() - dispatch._dispatch(ui, ['init', self.wc_path]) + _dispatch(ui, ['init', self.wc_path]) repo = self.repo repo.ui.setconfig('ui', 'username', 'anonymous') @@ -39,7 +46,7 @@ class TestMercurialCore(test_util.TestBase): self.assertEqual(len(repo), 3) updaterev = 1 - dispatch._dispatch(ui, ['clone', self.wc_path, self.wc_path + '2', + _dispatch(ui, ['clone', self.wc_path, self.wc_path + '2', '--updaterev=%s' % updaterev]) repo2 = hg.repository(ui, self.wc_path + '2') @@ -50,7 +57,7 @@ class TestMercurialCore(test_util.TestBase): def test_branch(self): ''' Test 'clone --branch' ''' ui = self.ui() - dispatch._dispatch(ui, ['init', self.wc_path]) + _dispatch(ui, ['init', self.wc_path]) repo = self.repo repo.ui.setconfig('ui', 'username', 'anonymous') @@ -73,7 +80,7 @@ class TestMercurialCore(test_util.TestBase): self.assertEqual(len(repo), 3) branch = 'B1' - dispatch._dispatch(ui, ['clone', self.wc_path, self.wc_path + '2', + _dispatch(ui, ['clone', self.wc_path, self.wc_path + '2', '--branch', branch]) repo2 = hg.repository(ui, self.wc_path + '2') @@ -81,5 +88,5 @@ class TestMercurialCore(test_util.TestBase): self.assertEqual(repo[branch].hex(), repo2['.'].hex()) def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(TestMercurialCore)] - return unittest.TestSuite(all) + all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestMercurialCore)] + return unittest.TestSuite(all_tests) diff --git a/tests/test_urls.py b/tests/test_urls.py index cdde420..d18aeb5 100644 --- a/tests/test_urls.py +++ b/tests/test_urls.py @@ -29,16 +29,16 @@ class TestSubversionUrls(test_util.TestBase): def test_svnssh_preserve_user(self): self.assertEqual( - ('user', 't3stpw', 'svn+ssh://user@svn.testurl.com/repo', ), + ('user', 't3stpw', 'svn+ssh://user@svn.testurl.com/repo',), parse_url('svn+ssh://user:t3stpw@svn.testurl.com/repo')) self.assertEqual( - ('bob', '123abc', 'svn+ssh://bob@svn.testurl.com/repo', ), + ('bob', '123abc', 'svn+ssh://bob@svn.testurl.com/repo',), parse_url('svn+ssh://user:t3stpw@svn.testurl.com/repo', 'bob', '123abc')) self.assertEqual( - ('user2', None, 'svn+ssh://user2@svn.testurl.com/repo', ), + ('user2', None, 'svn+ssh://user2@svn.testurl.com/repo',), parse_url('svn+ssh://user2@svn.testurl.com/repo')) self.assertEqual( - ('bob', None, 'svn+ssh://bob@svn.testurl.com/repo', ), + ('bob', None, 'svn+ssh://bob@svn.testurl.com/repo',), parse_url('svn+ssh://user2@svn.testurl.com/repo', 'bob')) def test_user_password_url(self): @@ -75,5 +75,5 @@ class TestSubversionUrls(test_util.TestBase): self.assertEqual(repo1.svnurl, repo2.svnurl) def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(TestSubversionUrls)] - return unittest.TestSuite(all) + all_tests = [unittest.TestLoader().loadTestsFromTestCase(TestSubversionUrls)] + return unittest.TestSuite(all_tests) diff --git a/tests/test_util.py b/tests/test_util.py index 0239618..63b02f8 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -18,7 +18,7 @@ sys.path.insert(0, _rootdir) from mercurial import cmdutil from mercurial import commands from mercurial import context -from mercurial import dispatch +from mercurial import dispatch as dispatchmod from mercurial import hg from mercurial import i18n from mercurial import node @@ -43,7 +43,7 @@ from hgsubversion import util # "Note that on Windows, you cannot set close_fds to true and # also redirect the standard handles by setting stdin, stdout or # stderr." -canCloseFds='win32' not in sys.platform +canCloseFds = 'win32' not in sys.platform if not 'win32' in sys.platform: def kill_process(popen_obj): @@ -75,7 +75,7 @@ else: DWORD, 'dwProcessId', ) - CloseHandle = WINAPI(BOOL, ctypes.windll.kernel32.CloseHandle, + CloseHandle = WINAPI(BOOL, ctypes.windll.kernel32.CloseHandle, HANDLE, 'hObject' ) @@ -163,13 +163,20 @@ def load_svndump_fixture(path, fixture_name): already exist. ''' if os.path.exists(path): rmtree(path) - subprocess.call(['svnadmin', 'create', path,], + subprocess.call(['svnadmin', 'create', path, ], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) inp = open(os.path.join(FIXTURES, fixture_name)) - proc = subprocess.Popen(['svnadmin', 'load', path,], stdin=inp, + proc = subprocess.Popen(['svnadmin', 'load', path, ], stdin=inp, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) proc.communicate() +def dispatch(cmd): + try: + req = dispatchmod.request(cmd) + dispatchmod.dispatch(req) + except AttributeError, e: + dispatchmod.dispatch(cmd) + def load_fixture_and_fetch(fixture_name, repo_path, wc_path, stupid=False, subdir='', noupdate=True, layout='auto', startrev=0, externals=None): @@ -191,7 +198,7 @@ def load_fixture_and_fetch(fixture_name, repo_path, wc_path, stupid=False, if externals: cmd[:0] = ['--config', 'hgsubversion.externals=%s' % externals] - dispatch.dispatch(cmd) + dispatch(cmd) return hg.repository(testui(), wc_path) @@ -229,12 +236,20 @@ def _verify_our_modules(): 'from the wrong path!' ) +def hgclone(ui, source, dest, update=True): + if getattr(hg, 'peer', None): + # Since 1.9 (d976542986d2) + src, dest = hg.clone(ui, {}, source, dest, update=update) + else: + src, dest = hg.clone(ui, source, dest, update=update) + return src, dest + class TestBase(unittest.TestCase): def setUp(self): _verify_our_modules() - self.oldenv = dict([(k, os.environ.get(k, None), ) for k in - ('LANG', 'LC_ALL', 'HGRCPATH', )]) + self.oldenv = dict([(k, os.environ.get(k, None),) for k in + ('LANG', 'LC_ALL', 'HGRCPATH',)]) self.oldt = i18n.t os.environ['LANG'] = os.environ['LC_ALL'] = 'C' i18n.t = gettext.translation('hg', i18n.localedir, fallback=True) @@ -351,6 +366,18 @@ class TestBase(unittest.TestCase): if p.returncode: raise Exception('svn co failed on %s: %r' % (svnpath, stderr)) + def svnpropget(self, path, prop, rev='HEAD'): + path = self.repo_path + '/' + path + path = util.normalize_url(fileurl(path)) + args = ['svn', 'propget', '-r', str(rev), prop, path] + p = subprocess.Popen(args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT) + stdout, stderr = p.communicate() + if p.returncode: + raise Exception('svn ls failed on %s: %r' % (path, stderr)) + return stdout.strip() + def commitchanges(self, changes, parent='tip', message='automated test'): """Commit changes to mercurial directory diff --git a/tests/test_utility_commands.py b/tests/test_utility_commands.py index b91613e..2de14bb 100644 --- a/tests/test_utility_commands.py +++ b/tests/test_utility_commands.py @@ -112,7 +112,7 @@ class UtilityTests(test_util.TestBase): self._load_fixture_and_fetch('two_heads.svndump') u = self.ui() u.pushbuffer() - parents = (self.repo['the_branch'].node(), revlog.nullid, ) + parents = (self.repo['the_branch'].node(), revlog.nullid,) def filectxfn(repo, memctx, path): return context.memfilectx(path=path, data='added', @@ -155,7 +155,7 @@ class UtilityTests(test_util.TestBase): def test_outgoing_output(self): self._load_fixture_and_fetch('two_heads.svndump') u = self.ui() - parents = (self.repo['the_branch'].node(), revlog.nullid, ) + parents = (self.repo['the_branch'].node(), revlog.nullid,) def filectxfn(repo, memctx, path): return context.memfilectx(path=path, data='added', @@ -185,7 +185,7 @@ class UtilityTests(test_util.TestBase): def test_rebase(self): self._load_fixture_and_fetch('two_revs.svndump') - parents = (self.repo[0].node(), revlog.nullid, ) + parents = (self.repo[0].node(), revlog.nullid,) def filectxfn(repo, memctx, path): return context.memfilectx(path=path, data='added', @@ -251,6 +251,6 @@ class UtilityTests(test_util.TestBase): def suite(): - all = [unittest.TestLoader().loadTestsFromTestCase(UtilityTests), + all_tests = [unittest.TestLoader().loadTestsFromTestCase(UtilityTests), ] - return unittest.TestSuite(all) + return unittest.TestSuite(all_tests) |