summaryrefslogtreecommitdiff
path: root/hgsubversion
diff options
context:
space:
mode:
authorQijiang Fan <fqj1994@gmail.com>2011-10-25 12:37:40 +0800
committerQijiang Fan <fqj1994@gmail.com>2011-10-25 12:37:40 +0800
commit4d96cae645ca33d56b4a4961f2b59bef82773b58 (patch)
tree3a7085b11447f986526447bceca48170e3602af4 /hgsubversion
parent6e8f1b6597c85527b4721f376e0360bb25543cc8 (diff)
Upstream version 1.3
Diffstat (limited to 'hgsubversion')
-rw-r--r--hgsubversion/__init__.py19
-rw-r--r--hgsubversion/editor.py43
-rw-r--r--hgsubversion/maps.py86
-rw-r--r--hgsubversion/pushmod.py11
-rw-r--r--hgsubversion/replay.py4
-rw-r--r--hgsubversion/stupid.py140
-rw-r--r--hgsubversion/svncommands.py14
-rw-r--r--hgsubversion/svnexternals.py27
-rw-r--r--hgsubversion/svnmeta.py16
-rw-r--r--hgsubversion/svnrepo.py27
-rw-r--r--hgsubversion/svnwrap/common.py2
-rw-r--r--hgsubversion/svnwrap/subvertpy_wrapper.py17
-rw-r--r--hgsubversion/svnwrap/svn_swig_wrapper.py4
-rw-r--r--hgsubversion/util.py78
-rw-r--r--hgsubversion/wrappers.py43
15 files changed, 380 insertions, 151 deletions
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]: