##// END OF EJS Templates
convert: record the source revision in the changelog
Brendan Cully -
r4873:28b23b90 default
parent child Browse files
Show More
@@ -1,118 +1,121 b''
1 # common code for the convert extension
1 # common code for the convert extension
2
2
3 class NoRepo(Exception): pass
3 class NoRepo(Exception): pass
4
4
5 class commit(object):
5 class commit(object):
6 def __init__(self, **parts):
6 def __init__(self, **parts):
7 self.rev = None
8 self.branch = None
9
7 for x in "author date desc parents".split():
10 for x in "author date desc parents".split():
8 if not x in parts:
11 if not x in parts:
9 raise util.Abort("commit missing field %s" % x)
12 raise util.Abort("commit missing field %s" % x)
10 self.__dict__.update(parts)
13 self.__dict__.update(parts)
11 if not self.desc or self.desc.isspace():
14 if not self.desc or self.desc.isspace():
12 self.desc = '*** empty log message ***'
15 self.desc = '*** empty log message ***'
13
16
14 class converter_source(object):
17 class converter_source(object):
15 """Conversion source interface"""
18 """Conversion source interface"""
16
19
17 def __init__(self, ui, path, rev=None):
20 def __init__(self, ui, path, rev=None):
18 """Initialize conversion source (or raise NoRepo("message")
21 """Initialize conversion source (or raise NoRepo("message")
19 exception if path is not a valid repository)"""
22 exception if path is not a valid repository)"""
20 self.ui = ui
23 self.ui = ui
21 self.path = path
24 self.path = path
22 self.rev = rev
25 self.rev = rev
23
26
24 self.encoding = 'utf-8'
27 self.encoding = 'utf-8'
25
28
26 def setrevmap(self, revmap):
29 def setrevmap(self, revmap):
27 """set the map of already-converted revisions"""
30 """set the map of already-converted revisions"""
28 pass
31 pass
29
32
30 def getheads(self):
33 def getheads(self):
31 """Return a list of this repository's heads"""
34 """Return a list of this repository's heads"""
32 raise NotImplementedError()
35 raise NotImplementedError()
33
36
34 def getfile(self, name, rev):
37 def getfile(self, name, rev):
35 """Return file contents as a string"""
38 """Return file contents as a string"""
36 raise NotImplementedError()
39 raise NotImplementedError()
37
40
38 def getmode(self, name, rev):
41 def getmode(self, name, rev):
39 """Return file mode, eg. '', 'x', or 'l'"""
42 """Return file mode, eg. '', 'x', or 'l'"""
40 raise NotImplementedError()
43 raise NotImplementedError()
41
44
42 def getchanges(self, version):
45 def getchanges(self, version):
43 """Return sorted list of (filename, id) tuples for all files changed in rev.
46 """Return sorted list of (filename, id) tuples for all files changed in rev.
44
47
45 id just tells us which revision to return in getfile(), e.g. in
48 id just tells us which revision to return in getfile(), e.g. in
46 git it's an object hash."""
49 git it's an object hash."""
47 raise NotImplementedError()
50 raise NotImplementedError()
48
51
49 def getcommit(self, version):
52 def getcommit(self, version):
50 """Return the commit object for version"""
53 """Return the commit object for version"""
51 raise NotImplementedError()
54 raise NotImplementedError()
52
55
53 def gettags(self):
56 def gettags(self):
54 """Return the tags as a dictionary of name: revision"""
57 """Return the tags as a dictionary of name: revision"""
55 raise NotImplementedError()
58 raise NotImplementedError()
56
59
57 def recode(self, s, encoding=None):
60 def recode(self, s, encoding=None):
58 if not encoding:
61 if not encoding:
59 encoding = self.encoding or 'utf-8'
62 encoding = self.encoding or 'utf-8'
60
63
61 try:
64 try:
62 return s.decode(encoding).encode("utf-8")
65 return s.decode(encoding).encode("utf-8")
63 except:
66 except:
64 try:
67 try:
65 return s.decode("latin-1").encode("utf-8")
68 return s.decode("latin-1").encode("utf-8")
66 except:
69 except:
67 return s.decode(encoding, "replace").encode("utf-8")
70 return s.decode(encoding, "replace").encode("utf-8")
68
71
69 class converter_sink(object):
72 class converter_sink(object):
70 """Conversion sink (target) interface"""
73 """Conversion sink (target) interface"""
71
74
72 def __init__(self, ui, path):
75 def __init__(self, ui, path):
73 """Initialize conversion sink (or raise NoRepo("message")
76 """Initialize conversion sink (or raise NoRepo("message")
74 exception if path is not a valid repository)"""
77 exception if path is not a valid repository)"""
75 raise NotImplementedError()
78 raise NotImplementedError()
76
79
77 def getheads(self):
80 def getheads(self):
78 """Return a list of this repository's heads"""
81 """Return a list of this repository's heads"""
79 raise NotImplementedError()
82 raise NotImplementedError()
80
83
81 def mapfile(self):
84 def mapfile(self):
82 """Path to a file that will contain lines
85 """Path to a file that will contain lines
83 source_rev_id sink_rev_id
86 source_rev_id sink_rev_id
84 mapping equivalent revision identifiers for each system."""
87 mapping equivalent revision identifiers for each system."""
85 raise NotImplementedError()
88 raise NotImplementedError()
86
89
87 def authorfile(self):
90 def authorfile(self):
88 """Path to a file that will contain lines
91 """Path to a file that will contain lines
89 srcauthor=dstauthor
92 srcauthor=dstauthor
90 mapping equivalent authors identifiers for each system."""
93 mapping equivalent authors identifiers for each system."""
91 return None
94 return None
92
95
93 def putfile(self, f, e, data):
96 def putfile(self, f, e, data):
94 """Put file for next putcommit().
97 """Put file for next putcommit().
95 f: path to file
98 f: path to file
96 e: '', 'x', or 'l' (regular file, executable, or symlink)
99 e: '', 'x', or 'l' (regular file, executable, or symlink)
97 data: file contents"""
100 data: file contents"""
98 raise NotImplementedError()
101 raise NotImplementedError()
99
102
100 def delfile(self, f):
103 def delfile(self, f):
101 """Delete file for next putcommit().
104 """Delete file for next putcommit().
102 f: path to file"""
105 f: path to file"""
103 raise NotImplementedError()
106 raise NotImplementedError()
104
107
105 def putcommit(self, files, parents, commit):
108 def putcommit(self, files, parents, commit):
106 """Create a revision with all changed files listed in 'files'
109 """Create a revision with all changed files listed in 'files'
107 and having listed parents. 'commit' is a commit object containing
110 and having listed parents. 'commit' is a commit object containing
108 at a minimum the author, date, and message for this changeset.
111 at a minimum the author, date, and message for this changeset.
109 Called after putfile() and delfile() calls. Note that the sink
112 Called after putfile() and delfile() calls. Note that the sink
110 repository is not told to update itself to a particular revision
113 repository is not told to update itself to a particular revision
111 (or even what that revision would be) before it receives the
114 (or even what that revision would be) before it receives the
112 file data."""
115 file data."""
113 raise NotImplementedError()
116 raise NotImplementedError()
114
117
115 def puttags(self, tags):
118 def puttags(self, tags):
116 """Put tags into sink.
119 """Put tags into sink.
117 tags: {tagname: sink_rev_id, ...}"""
120 tags: {tagname: sink_rev_id, ...}"""
118 raise NotImplementedError()
121 raise NotImplementedError()
@@ -1,100 +1,101 b''
1 # git support for the convert extension
1 # git support for the convert extension
2
2
3 import os
3 import os
4
4
5 from common import NoRepo, commit, converter_source
5 from common import NoRepo, commit, converter_source
6
6
7 class convert_git(converter_source):
7 class convert_git(converter_source):
8 def gitcmd(self, s):
8 def gitcmd(self, s):
9 return os.popen('GIT_DIR=%s %s' % (self.path, s))
9 return os.popen('GIT_DIR=%s %s' % (self.path, s))
10
10
11 def __init__(self, ui, path, rev=None):
11 def __init__(self, ui, path, rev=None):
12 super(convert_git, self).__init__(ui, path, rev=rev)
12 super(convert_git, self).__init__(ui, path, rev=rev)
13
13
14 if os.path.isdir(path + "/.git"):
14 if os.path.isdir(path + "/.git"):
15 path += "/.git"
15 path += "/.git"
16 if not os.path.exists(path + "/objects"):
16 if not os.path.exists(path + "/objects"):
17 raise NoRepo("couldn't open GIT repo %s" % path)
17 raise NoRepo("couldn't open GIT repo %s" % path)
18 self.path = path
18 self.path = path
19
19
20 def getheads(self):
20 def getheads(self):
21 if not self.rev:
21 if not self.rev:
22 return self.gitcmd('git-rev-parse --branches').read().splitlines()
22 return self.gitcmd('git-rev-parse --branches').read().splitlines()
23 else:
23 else:
24 fh = self.gitcmd("git-rev-parse --verify %s" % self.rev)
24 fh = self.gitcmd("git-rev-parse --verify %s" % self.rev)
25 return [fh.read()[:-1]]
25 return [fh.read()[:-1]]
26
26
27 def catfile(self, rev, type):
27 def catfile(self, rev, type):
28 if rev == "0" * 40: raise IOError()
28 if rev == "0" * 40: raise IOError()
29 fh = self.gitcmd("git-cat-file %s %s 2>/dev/null" % (type, rev))
29 fh = self.gitcmd("git-cat-file %s %s 2>/dev/null" % (type, rev))
30 return fh.read()
30 return fh.read()
31
31
32 def getfile(self, name, rev):
32 def getfile(self, name, rev):
33 return self.catfile(rev, "blob")
33 return self.catfile(rev, "blob")
34
34
35 def getmode(self, name, rev):
35 def getmode(self, name, rev):
36 return self.modecache[(name, rev)]
36 return self.modecache[(name, rev)]
37
37
38 def getchanges(self, version):
38 def getchanges(self, version):
39 self.modecache = {}
39 self.modecache = {}
40 fh = self.gitcmd("git-diff-tree --root -m -r %s" % version)
40 fh = self.gitcmd("git-diff-tree --root -m -r %s" % version)
41 changes = []
41 changes = []
42 for l in fh:
42 for l in fh:
43 if "\t" not in l: continue
43 if "\t" not in l: continue
44 m, f = l[:-1].split("\t")
44 m, f = l[:-1].split("\t")
45 m = m.split()
45 m = m.split()
46 h = m[3]
46 h = m[3]
47 p = (m[1] == "100755")
47 p = (m[1] == "100755")
48 s = (m[1] == "120000")
48 s = (m[1] == "120000")
49 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
49 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
50 changes.append((f, h))
50 changes.append((f, h))
51 return changes
51 return changes
52
52
53 def getcommit(self, version):
53 def getcommit(self, version):
54 c = self.catfile(version, "commit") # read the commit hash
54 c = self.catfile(version, "commit") # read the commit hash
55 end = c.find("\n\n")
55 end = c.find("\n\n")
56 message = c[end+2:]
56 message = c[end+2:]
57 message = self.recode(message)
57 message = self.recode(message)
58 l = c[:end].splitlines()
58 l = c[:end].splitlines()
59 manifest = l[0].split()[1]
59 manifest = l[0].split()[1]
60 parents = []
60 parents = []
61 for e in l[1:]:
61 for e in l[1:]:
62 n, v = e.split(" ", 1)
62 n, v = e.split(" ", 1)
63 if n == "author":
63 if n == "author":
64 p = v.split()
64 p = v.split()
65 tm, tz = p[-2:]
65 tm, tz = p[-2:]
66 author = " ".join(p[:-2])
66 author = " ".join(p[:-2])
67 if author[0] == "<": author = author[1:-1]
67 if author[0] == "<": author = author[1:-1]
68 author = self.recode(author)
68 author = self.recode(author)
69 if n == "committer":
69 if n == "committer":
70 p = v.split()
70 p = v.split()
71 tm, tz = p[-2:]
71 tm, tz = p[-2:]
72 committer = " ".join(p[:-2])
72 committer = " ".join(p[:-2])
73 if committer[0] == "<": committer = committer[1:-1]
73 if committer[0] == "<": committer = committer[1:-1]
74 committer = self.recode(committer)
74 committer = self.recode(committer)
75 message += "\ncommitter: %s\n" % committer
75 message += "\ncommitter: %s\n" % committer
76 if n == "parent": parents.append(v)
76 if n == "parent": parents.append(v)
77
77
78 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
78 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
79 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
79 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
80 date = tm + " " + str(tz)
80 date = tm + " " + str(tz)
81 author = author or "unknown"
81 author = author or "unknown"
82
82
83 c = commit(parents=parents, date=date, author=author, desc=message)
83 c = commit(parents=parents, date=date, author=author, desc=message,
84 rev=version)
84 return c
85 return c
85
86
86 def gettags(self):
87 def gettags(self):
87 tags = {}
88 tags = {}
88 fh = self.gitcmd('git-ls-remote --tags "%s" 2>/dev/null' % self.path)
89 fh = self.gitcmd('git-ls-remote --tags "%s" 2>/dev/null' % self.path)
89 prefix = 'refs/tags/'
90 prefix = 'refs/tags/'
90 for line in fh:
91 for line in fh:
91 line = line.strip()
92 line = line.strip()
92 if not line.endswith("^{}"):
93 if not line.endswith("^{}"):
93 continue
94 continue
94 node, tag = line.split(None, 1)
95 node, tag = line.split(None, 1)
95 if not tag.startswith(prefix):
96 if not tag.startswith(prefix):
96 continue
97 continue
97 tag = tag[len(prefix):-3]
98 tag = tag[len(prefix):-3]
98 tags[tag] = node
99 tags[tag] = node
99
100
100 return tags
101 return tags
@@ -1,97 +1,97 b''
1 # hg backend for convert extension
1 # hg backend for convert extension
2
2
3 import os, time
3 import os, time
4 from mercurial import hg
4 from mercurial import hg
5
5
6 from common import NoRepo, converter_sink
6 from common import NoRepo, converter_sink
7
7
8 class convert_mercurial(converter_sink):
8 class convert_mercurial(converter_sink):
9 def __init__(self, ui, path):
9 def __init__(self, ui, path):
10 self.path = path
10 self.path = path
11 self.ui = ui
11 self.ui = ui
12 try:
12 try:
13 self.repo = hg.repository(self.ui, path)
13 self.repo = hg.repository(self.ui, path)
14 except:
14 except:
15 raise NoRepo("could open hg repo %s" % path)
15 raise NoRepo("could open hg repo %s" % path)
16
16
17 def mapfile(self):
17 def mapfile(self):
18 return os.path.join(self.path, ".hg", "shamap")
18 return os.path.join(self.path, ".hg", "shamap")
19
19
20 def authorfile(self):
20 def authorfile(self):
21 return os.path.join(self.path, ".hg", "authormap")
21 return os.path.join(self.path, ".hg", "authormap")
22
22
23 def getheads(self):
23 def getheads(self):
24 h = self.repo.changelog.heads()
24 h = self.repo.changelog.heads()
25 return [ hg.hex(x) for x in h ]
25 return [ hg.hex(x) for x in h ]
26
26
27 def putfile(self, f, e, data):
27 def putfile(self, f, e, data):
28 self.repo.wwrite(f, data, e)
28 self.repo.wwrite(f, data, e)
29 if self.repo.dirstate.state(f) == '?':
29 if self.repo.dirstate.state(f) == '?':
30 self.repo.dirstate.update([f], "a")
30 self.repo.dirstate.update([f], "a")
31
31
32 def copyfile(self, source, dest):
32 def copyfile(self, source, dest):
33 self.repo.copy(source, dest)
33 self.repo.copy(source, dest)
34
34
35 def delfile(self, f):
35 def delfile(self, f):
36 try:
36 try:
37 os.unlink(self.repo.wjoin(f))
37 os.unlink(self.repo.wjoin(f))
38 #self.repo.remove([f])
38 #self.repo.remove([f])
39 except:
39 except:
40 pass
40 pass
41
41
42 def putcommit(self, files, parents, commit):
42 def putcommit(self, files, parents, commit):
43 seen = {}
43 seen = {}
44 pl = []
44 pl = []
45 for p in parents:
45 for p in parents:
46 if p not in seen:
46 if p not in seen:
47 pl.append(p)
47 pl.append(p)
48 seen[p] = 1
48 seen[p] = 1
49 parents = pl
49 parents = pl
50
50
51 if len(parents) < 2: parents.append("0" * 40)
51 if len(parents) < 2: parents.append("0" * 40)
52 if len(parents) < 2: parents.append("0" * 40)
52 if len(parents) < 2: parents.append("0" * 40)
53 p2 = parents.pop(0)
53 p2 = parents.pop(0)
54
54
55 text = commit.desc
55 text = commit.desc
56 extra = {}
56 extra = {}
57 try:
57 if commit.branch:
58 extra["branch"] = commit.branch
58 extra['branch'] = commit.branch
59 except AttributeError:
59 if commit.rev:
60 pass
60 extra['convert_revision'] = commit.rev
61
61
62 while parents:
62 while parents:
63 p1 = p2
63 p1 = p2
64 p2 = parents.pop(0)
64 p2 = parents.pop(0)
65 a = self.repo.rawcommit(files, text, commit.author, commit.date,
65 a = self.repo.rawcommit(files, text, commit.author, commit.date,
66 hg.bin(p1), hg.bin(p2), extra=extra)
66 hg.bin(p1), hg.bin(p2), extra=extra)
67 text = "(octopus merge fixup)\n"
67 text = "(octopus merge fixup)\n"
68 p2 = hg.hex(self.repo.changelog.tip())
68 p2 = hg.hex(self.repo.changelog.tip())
69
69
70 return p2
70 return p2
71
71
72 def puttags(self, tags):
72 def puttags(self, tags):
73 try:
73 try:
74 old = self.repo.wfile(".hgtags").read()
74 old = self.repo.wfile(".hgtags").read()
75 oldlines = old.splitlines(1)
75 oldlines = old.splitlines(1)
76 oldlines.sort()
76 oldlines.sort()
77 except:
77 except:
78 oldlines = []
78 oldlines = []
79
79
80 k = tags.keys()
80 k = tags.keys()
81 k.sort()
81 k.sort()
82 newlines = []
82 newlines = []
83 for tag in k:
83 for tag in k:
84 newlines.append("%s %s\n" % (tags[tag], tag))
84 newlines.append("%s %s\n" % (tags[tag], tag))
85
85
86 newlines.sort()
86 newlines.sort()
87
87
88 if newlines != oldlines:
88 if newlines != oldlines:
89 self.ui.status("updating tags\n")
89 self.ui.status("updating tags\n")
90 f = self.repo.wfile(".hgtags", "w")
90 f = self.repo.wfile(".hgtags", "w")
91 f.write("".join(newlines))
91 f.write("".join(newlines))
92 f.close()
92 f.close()
93 if not oldlines: self.repo.add([".hgtags"])
93 if not oldlines: self.repo.add([".hgtags"])
94 date = "%s 0" % int(time.mktime(time.gmtime()))
94 date = "%s 0" % int(time.mktime(time.gmtime()))
95 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
95 self.repo.rawcommit([".hgtags"], "update tags", "convert-repo",
96 date, self.repo.changelog.tip(), hg.nullid)
96 date, self.repo.changelog.tip(), hg.nullid)
97 return hg.hex(self.repo.changelog.tip())
97 return hg.hex(self.repo.changelog.tip())
@@ -1,588 +1,589 b''
1 # Subversion 1.4/1.5 Python API backend
1 # Subversion 1.4/1.5 Python API backend
2 #
2 #
3 # Copyright(C) 2007 Daniel Holth et al
3 # Copyright(C) 2007 Daniel Holth et al
4
4
5 import pprint
5 import pprint
6 import locale
6 import locale
7
7
8 from mercurial import util
8 from mercurial import util
9
9
10 # Subversion stuff. Works best with very recent Python SVN bindings
10 # Subversion stuff. Works best with very recent Python SVN bindings
11 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
11 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
12 # these bindings.
12 # these bindings.
13
13
14 from cStringIO import StringIO
14 from cStringIO import StringIO
15
15
16 from common import NoRepo, commit, converter_source
16 from common import NoRepo, commit, converter_source
17
17
18 try:
18 try:
19 from svn.core import SubversionException, Pool
19 from svn.core import SubversionException, Pool
20 import svn.core
20 import svn.core
21 import svn.ra
21 import svn.ra
22 import svn.delta
22 import svn.delta
23 import svn
23 import svn
24 import transport
24 import transport
25 except ImportError:
25 except ImportError:
26 pass
26 pass
27
27
28 class CompatibilityException(Exception): pass
28 class CompatibilityException(Exception): pass
29
29
30 # SVN conversion code stolen from bzr-svn and tailor
30 # SVN conversion code stolen from bzr-svn and tailor
31 class convert_svn(converter_source):
31 class convert_svn(converter_source):
32 def __init__(self, ui, url, rev=None):
32 def __init__(self, ui, url, rev=None):
33 super(convert_svn, self).__init__(ui, url, rev=rev)
33 super(convert_svn, self).__init__(ui, url, rev=rev)
34
34
35 try:
35 try:
36 SubversionException
36 SubversionException
37 except NameError:
37 except NameError:
38 msg = 'subversion python bindings could not be loaded\n'
38 msg = 'subversion python bindings could not be loaded\n'
39 ui.warn(msg)
39 ui.warn(msg)
40 raise NoRepo(msg)
40 raise NoRepo(msg)
41
41
42 self.encoding = locale.getpreferredencoding()
42 self.encoding = locale.getpreferredencoding()
43 self.lastrevs = {}
43 self.lastrevs = {}
44
44
45 latest = None
45 latest = None
46 if rev:
46 if rev:
47 try:
47 try:
48 latest = int(rev)
48 latest = int(rev)
49 except ValueError:
49 except ValueError:
50 raise util.Abort('svn: revision %s is not an integer' % rev)
50 raise util.Abort('svn: revision %s is not an integer' % rev)
51 try:
51 try:
52 # Support file://path@rev syntax. Useful e.g. to convert
52 # Support file://path@rev syntax. Useful e.g. to convert
53 # deleted branches.
53 # deleted branches.
54 url, latest = url.rsplit("@", 1)
54 url, latest = url.rsplit("@", 1)
55 latest = int(latest)
55 latest = int(latest)
56 except ValueError, e:
56 except ValueError, e:
57 pass
57 pass
58 self.url = url
58 self.url = url
59 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
59 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
60 try:
60 try:
61 self.transport = transport.SvnRaTransport(url = url)
61 self.transport = transport.SvnRaTransport(url = url)
62 self.ra = self.transport.ra
62 self.ra = self.transport.ra
63 self.ctx = svn.client.create_context()
63 self.ctx = svn.client.create_context()
64 self.base = svn.ra.get_repos_root(self.ra)
64 self.base = svn.ra.get_repos_root(self.ra)
65 self.module = self.url[len(self.base):]
65 self.module = self.url[len(self.base):]
66 self.modulemap = {} # revision, module
66 self.modulemap = {} # revision, module
67 self.commits = {}
67 self.commits = {}
68 self.files = {}
68 self.files = {}
69 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
69 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
70 except SubversionException, e:
70 except SubversionException, e:
71 raise NoRepo("couldn't open SVN repo %s" % url)
71 raise NoRepo("couldn't open SVN repo %s" % url)
72
72
73 try:
73 try:
74 self.get_blacklist()
74 self.get_blacklist()
75 except IOError, e:
75 except IOError, e:
76 pass
76 pass
77
77
78 self.last_changed = self.latest(self.module, latest)
78 self.last_changed = self.latest(self.module, latest)
79
79
80 self.head = self.revid(self.last_changed)
80 self.head = self.revid(self.last_changed)
81
81
82 def setrevmap(self, revmap):
82 def setrevmap(self, revmap):
83 lastrevs = {}
83 lastrevs = {}
84 for revid in revmap.keys():
84 for revid in revmap.keys():
85 uuid, module, revnum = self.revsplit(revid)
85 uuid, module, revnum = self.revsplit(revid)
86 lastrevnum = lastrevs.setdefault(module, revnum)
86 lastrevnum = lastrevs.setdefault(module, revnum)
87 if revnum > lastrevnum:
87 if revnum > lastrevnum:
88 lastrevs[module] = revnum
88 lastrevs[module] = revnum
89 self.lastrevs = lastrevs
89 self.lastrevs = lastrevs
90
90
91 def getheads(self):
91 def getheads(self):
92 # detect standard /branches, /tags, /trunk layout
92 # detect standard /branches, /tags, /trunk layout
93 optrev = svn.core.svn_opt_revision_t()
93 optrev = svn.core.svn_opt_revision_t()
94 optrev.kind = svn.core.svn_opt_revision_number
94 optrev.kind = svn.core.svn_opt_revision_number
95 optrev.value.number = self.last_changed
95 optrev.value.number = self.last_changed
96 rpath = self.url.strip('/')
96 rpath = self.url.strip('/')
97 paths = svn.client.ls(rpath, optrev, False, self.ctx)
97 paths = svn.client.ls(rpath, optrev, False, self.ctx)
98 if 'branches' in paths and 'trunk' in paths:
98 if 'branches' in paths and 'trunk' in paths:
99 self.module += '/trunk'
99 self.module += '/trunk'
100 lt = self.latest(self.module, self.last_changed)
100 lt = self.latest(self.module, self.last_changed)
101 self.head = self.revid(lt)
101 self.head = self.revid(lt)
102 self.heads = [self.head]
102 self.heads = [self.head]
103 branches = svn.client.ls(rpath + '/branches', optrev, False, self.ctx)
103 branches = svn.client.ls(rpath + '/branches', optrev, False, self.ctx)
104 for branch in branches.keys():
104 for branch in branches.keys():
105 module = '/branches/' + branch
105 module = '/branches/' + branch
106 brevnum = self.latest(module, self.last_changed)
106 brevnum = self.latest(module, self.last_changed)
107 brev = self.revid(brevnum, module)
107 brev = self.revid(brevnum, module)
108 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
108 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
109 self.heads.append(brev)
109 self.heads.append(brev)
110 else:
110 else:
111 self.heads = [self.head]
111 self.heads = [self.head]
112 return self.heads
112 return self.heads
113
113
114 def getfile(self, file, rev):
114 def getfile(self, file, rev):
115 data, mode = self._getfile(file, rev)
115 data, mode = self._getfile(file, rev)
116 self.modecache[(file, rev)] = mode
116 self.modecache[(file, rev)] = mode
117 return data
117 return data
118
118
119 def getmode(self, file, rev):
119 def getmode(self, file, rev):
120 return self.modecache[(file, rev)]
120 return self.modecache[(file, rev)]
121
121
122 def getchanges(self, rev):
122 def getchanges(self, rev):
123 self.modecache = {}
123 self.modecache = {}
124 files = self.files[rev]
124 files = self.files[rev]
125 cl = files
125 cl = files
126 cl.sort()
126 cl.sort()
127 # caller caches the result, so free it here to release memory
127 # caller caches the result, so free it here to release memory
128 del self.files[rev]
128 del self.files[rev]
129 return cl
129 return cl
130
130
131 def getcommit(self, rev):
131 def getcommit(self, rev):
132 if rev not in self.commits:
132 if rev not in self.commits:
133 uuid, module, revnum = self.revsplit(rev)
133 uuid, module, revnum = self.revsplit(rev)
134 self.module = module
134 self.module = module
135 self.reparent(module)
135 self.reparent(module)
136 stop = self.lastrevs.get(module, 0)
136 stop = self.lastrevs.get(module, 0)
137 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
137 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
138 commit = self.commits[rev]
138 commit = self.commits[rev]
139 # caller caches the result, so free it here to release memory
139 # caller caches the result, so free it here to release memory
140 del self.commits[rev]
140 del self.commits[rev]
141 return commit
141 return commit
142
142
143 def gettags(self):
143 def gettags(self):
144 tags = {}
144 tags = {}
145 def parselogentry(*arg, **args):
145 def parselogentry(*arg, **args):
146 orig_paths, revnum, author, date, message, pool = arg
146 orig_paths, revnum, author, date, message, pool = arg
147 for path in orig_paths:
147 for path in orig_paths:
148 if not path.startswith('/tags/'):
148 if not path.startswith('/tags/'):
149 continue
149 continue
150 ent = orig_paths[path]
150 ent = orig_paths[path]
151 source = ent.copyfrom_path
151 source = ent.copyfrom_path
152 rev = ent.copyfrom_rev
152 rev = ent.copyfrom_rev
153 tag = path.split('/', 2)[2]
153 tag = path.split('/', 2)[2]
154 tags[tag] = self.revid(rev, module=source)
154 tags[tag] = self.revid(rev, module=source)
155
155
156 start = self.revnum(self.head)
156 start = self.revnum(self.head)
157 try:
157 try:
158 svn.ra.get_log(self.ra, ['/tags'], 0, start, 0, True, False,
158 svn.ra.get_log(self.ra, ['/tags'], 0, start, 0, True, False,
159 parselogentry)
159 parselogentry)
160 return tags
160 return tags
161 except SubversionException:
161 except SubversionException:
162 self.ui.note('no tags found at revision %d\n' % start)
162 self.ui.note('no tags found at revision %d\n' % start)
163 return {}
163 return {}
164
164
165 # -- helper functions --
165 # -- helper functions --
166
166
167 def revid(self, revnum, module=None):
167 def revid(self, revnum, module=None):
168 if not module:
168 if not module:
169 module = self.module
169 module = self.module
170 return (u"svn:%s%s@%s" % (self.uuid, module, revnum)).decode(self.encoding)
170 return (u"svn:%s%s@%s" % (self.uuid, module, revnum)).decode(self.encoding)
171
171
172 def revnum(self, rev):
172 def revnum(self, rev):
173 return int(rev.split('@')[-1])
173 return int(rev.split('@')[-1])
174
174
175 def revsplit(self, rev):
175 def revsplit(self, rev):
176 url, revnum = rev.encode(self.encoding).split('@', 1)
176 url, revnum = rev.encode(self.encoding).split('@', 1)
177 revnum = int(revnum)
177 revnum = int(revnum)
178 parts = url.split('/', 1)
178 parts = url.split('/', 1)
179 uuid = parts.pop(0)[4:]
179 uuid = parts.pop(0)[4:]
180 mod = ''
180 mod = ''
181 if parts:
181 if parts:
182 mod = '/' + parts[0]
182 mod = '/' + parts[0]
183 return uuid, mod, revnum
183 return uuid, mod, revnum
184
184
185 def latest(self, path, stop=0):
185 def latest(self, path, stop=0):
186 'find the latest revision affecting path, up to stop'
186 'find the latest revision affecting path, up to stop'
187 if not stop:
187 if not stop:
188 stop = svn.ra.get_latest_revnum(self.ra)
188 stop = svn.ra.get_latest_revnum(self.ra)
189 try:
189 try:
190 self.reparent('')
190 self.reparent('')
191 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
191 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
192 self.reparent(self.module)
192 self.reparent(self.module)
193 except SubversionException:
193 except SubversionException:
194 dirent = None
194 dirent = None
195 if not dirent:
195 if not dirent:
196 raise util.Abort('%s not found up to revision %d' \
196 raise util.Abort('%s not found up to revision %d' \
197 % (path, stop))
197 % (path, stop))
198
198
199 return dirent.created_rev
199 return dirent.created_rev
200
200
201 def get_blacklist(self):
201 def get_blacklist(self):
202 """Avoid certain revision numbers.
202 """Avoid certain revision numbers.
203 It is not uncommon for two nearby revisions to cancel each other
203 It is not uncommon for two nearby revisions to cancel each other
204 out, e.g. 'I copied trunk into a subdirectory of itself instead
204 out, e.g. 'I copied trunk into a subdirectory of itself instead
205 of making a branch'. The converted repository is significantly
205 of making a branch'. The converted repository is significantly
206 smaller if we ignore such revisions."""
206 smaller if we ignore such revisions."""
207 self.blacklist = set()
207 self.blacklist = set()
208 blacklist = self.blacklist
208 blacklist = self.blacklist
209 for line in file("blacklist.txt", "r"):
209 for line in file("blacklist.txt", "r"):
210 if not line.startswith("#"):
210 if not line.startswith("#"):
211 try:
211 try:
212 svn_rev = int(line.strip())
212 svn_rev = int(line.strip())
213 blacklist.add(svn_rev)
213 blacklist.add(svn_rev)
214 except ValueError, e:
214 except ValueError, e:
215 pass # not an integer or a comment
215 pass # not an integer or a comment
216
216
217 def is_blacklisted(self, svn_rev):
217 def is_blacklisted(self, svn_rev):
218 return svn_rev in self.blacklist
218 return svn_rev in self.blacklist
219
219
220 def reparent(self, module):
220 def reparent(self, module):
221 svn_url = self.base + module
221 svn_url = self.base + module
222 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
222 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
223 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
223 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
224
224
225 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
225 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
226 def get_entry_from_path(path, module=self.module):
226 def get_entry_from_path(path, module=self.module):
227 # Given the repository url of this wc, say
227 # Given the repository url of this wc, say
228 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
228 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
229 # extract the "entry" portion (a relative path) from what
229 # extract the "entry" portion (a relative path) from what
230 # svn log --xml says, ie
230 # svn log --xml says, ie
231 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
231 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
232 # that is to say "tests/PloneTestCase.py"
232 # that is to say "tests/PloneTestCase.py"
233
233
234 if path.startswith(module):
234 if path.startswith(module):
235 relative = path[len(module):]
235 relative = path[len(module):]
236 if relative.startswith('/'):
236 if relative.startswith('/'):
237 return relative[1:]
237 return relative[1:]
238 else:
238 else:
239 return relative
239 return relative
240
240
241 # The path is outside our tracked tree...
241 # The path is outside our tracked tree...
242 self.ui.debug('Ignoring %r since it is not under %r\n' % (path, module))
242 self.ui.debug('Ignoring %r since it is not under %r\n' % (path, module))
243 return None
243 return None
244
244
245 received = []
245 received = []
246 # svn.ra.get_log requires no other calls to the ra until it completes,
246 # svn.ra.get_log requires no other calls to the ra until it completes,
247 # so we just collect the log entries and parse them afterwards
247 # so we just collect the log entries and parse them afterwards
248 def receivelog(*arg, **args):
248 def receivelog(*arg, **args):
249 received.append(arg)
249 received.append(arg)
250
250
251 self.child_cset = None
251 self.child_cset = None
252 def parselogentry(*arg, **args):
252 def parselogentry(*arg, **args):
253 orig_paths, revnum, author, date, message, pool = arg
253 orig_paths, revnum, author, date, message, pool = arg
254
254
255 if self.is_blacklisted(revnum):
255 if self.is_blacklisted(revnum):
256 self.ui.note('skipping blacklisted revision %d\n' % revnum)
256 self.ui.note('skipping blacklisted revision %d\n' % revnum)
257 return
257 return
258
258
259 self.ui.debug("parsing revision %d\n" % revnum)
259 self.ui.debug("parsing revision %d\n" % revnum)
260
260
261 if orig_paths is None:
261 if orig_paths is None:
262 self.ui.debug('revision %d has no entries\n' % revnum)
262 self.ui.debug('revision %d has no entries\n' % revnum)
263 return
263 return
264
264
265 if revnum in self.modulemap:
265 if revnum in self.modulemap:
266 new_module = self.modulemap[revnum]
266 new_module = self.modulemap[revnum]
267 if new_module != self.module:
267 if new_module != self.module:
268 self.module = new_module
268 self.module = new_module
269 self.reparent(self.module)
269 self.reparent(self.module)
270
270
271 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
271 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
272 copies = {}
272 copies = {}
273 entries = []
273 entries = []
274 rev = self.revid(revnum)
274 rev = self.revid(revnum)
275 parents = []
275 parents = []
276
276
277 # branch log might return entries for a parent we already have
277 # branch log might return entries for a parent we already have
278 if (rev in self.commits or
278 if (rev in self.commits or
279 (revnum < self.lastrevs.get(self.module, 0))):
279 (revnum < self.lastrevs.get(self.module, 0))):
280 return
280 return
281
281
282 try:
282 try:
283 branch = self.module.split("/")[-1]
283 branch = self.module.split("/")[-1]
284 if branch == 'trunk':
284 if branch == 'trunk':
285 branch = ''
285 branch = ''
286 except IndexError:
286 except IndexError:
287 branch = None
287 branch = None
288
288
289 paths = orig_paths.keys()
289 paths = orig_paths.keys()
290 paths.sort()
290 paths.sort()
291 for path in paths:
291 for path in paths:
292 # self.ui.write("path %s\n" % path)
292 # self.ui.write("path %s\n" % path)
293 if path == self.module: # Follow branching back in history
293 if path == self.module: # Follow branching back in history
294 ent = orig_paths[path]
294 ent = orig_paths[path]
295 if ent:
295 if ent:
296 if ent.copyfrom_path:
296 if ent.copyfrom_path:
297 # ent.copyfrom_rev may not be the actual last revision
297 # ent.copyfrom_rev may not be the actual last revision
298 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
298 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
299 self.modulemap[prev] = ent.copyfrom_path
299 self.modulemap[prev] = ent.copyfrom_path
300 parents = [self.revid(prev, ent.copyfrom_path)]
300 parents = [self.revid(prev, ent.copyfrom_path)]
301 self.ui.note('found parent of branch %s at %d: %s\n' % \
301 self.ui.note('found parent of branch %s at %d: %s\n' % \
302 (self.module, prev, ent.copyfrom_path))
302 (self.module, prev, ent.copyfrom_path))
303 else:
303 else:
304 self.ui.debug("No copyfrom path, don't know what to do.\n")
304 self.ui.debug("No copyfrom path, don't know what to do.\n")
305 # Maybe it was added and there is no more history.
305 # Maybe it was added and there is no more history.
306 entrypath = get_entry_from_path(path, module=self.module)
306 entrypath = get_entry_from_path(path, module=self.module)
307 # self.ui.write("entrypath %s\n" % entrypath)
307 # self.ui.write("entrypath %s\n" % entrypath)
308 if entrypath is None:
308 if entrypath is None:
309 # Outside our area of interest
309 # Outside our area of interest
310 self.ui.debug("boring@%s: %s\n" % (revnum, path))
310 self.ui.debug("boring@%s: %s\n" % (revnum, path))
311 continue
311 continue
312 entry = entrypath.decode(self.encoding)
312 entry = entrypath.decode(self.encoding)
313 ent = orig_paths[path]
313 ent = orig_paths[path]
314
314
315 kind = svn.ra.check_path(self.ra, entrypath, revnum)
315 kind = svn.ra.check_path(self.ra, entrypath, revnum)
316 if kind == svn.core.svn_node_file:
316 if kind == svn.core.svn_node_file:
317 if ent.copyfrom_path:
317 if ent.copyfrom_path:
318 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
318 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
319 if copyfrom_path:
319 if copyfrom_path:
320 self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
320 self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
321 # It's probably important for hg that the source
321 # It's probably important for hg that the source
322 # exists in the revision's parent, not just the
322 # exists in the revision's parent, not just the
323 # ent.copyfrom_rev
323 # ent.copyfrom_rev
324 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
324 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
325 if fromkind != 0:
325 if fromkind != 0:
326 copies[self.recode(entry)] = self.recode(copyfrom_path)
326 copies[self.recode(entry)] = self.recode(copyfrom_path)
327 entries.append(self.recode(entry))
327 entries.append(self.recode(entry))
328 elif kind == 0: # gone, but had better be a deleted *file*
328 elif kind == 0: # gone, but had better be a deleted *file*
329 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
329 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
330
330
331 # if a branch is created but entries are removed in the same
331 # if a branch is created but entries are removed in the same
332 # changeset, get the right fromrev
332 # changeset, get the right fromrev
333 if parents:
333 if parents:
334 uuid, old_module, fromrev = self.revsplit(parents[0])
334 uuid, old_module, fromrev = self.revsplit(parents[0])
335 else:
335 else:
336 fromrev = revnum - 1
336 fromrev = revnum - 1
337 # might always need to be revnum - 1 in these 3 lines?
337 # might always need to be revnum - 1 in these 3 lines?
338 old_module = self.modulemap.get(fromrev, self.module)
338 old_module = self.modulemap.get(fromrev, self.module)
339
339
340 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
340 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
341 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
341 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
342
342
343 def lookup_parts(p):
343 def lookup_parts(p):
344 rc = None
344 rc = None
345 parts = p.split("/")
345 parts = p.split("/")
346 for i in range(len(parts)):
346 for i in range(len(parts)):
347 part = "/".join(parts[:i])
347 part = "/".join(parts[:i])
348 info = part, copyfrom.get(part, None)
348 info = part, copyfrom.get(part, None)
349 if info[1] is not None:
349 if info[1] is not None:
350 self.ui.debug("Found parent directory %s\n" % info[1])
350 self.ui.debug("Found parent directory %s\n" % info[1])
351 rc = info
351 rc = info
352 return rc
352 return rc
353
353
354 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
354 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
355
355
356 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
356 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
357
357
358 # need to remove fragment from lookup_parts and replace with copyfrom_path
358 # need to remove fragment from lookup_parts and replace with copyfrom_path
359 if frompath is not None:
359 if frompath is not None:
360 self.ui.debug("munge-o-matic\n")
360 self.ui.debug("munge-o-matic\n")
361 self.ui.debug(entrypath + '\n')
361 self.ui.debug(entrypath + '\n')
362 self.ui.debug(entrypath[len(frompath):] + '\n')
362 self.ui.debug(entrypath[len(frompath):] + '\n')
363 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
363 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
364 fromrev = froment.copyfrom_rev
364 fromrev = froment.copyfrom_rev
365 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
365 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
366
366
367 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
367 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
368 if fromkind == svn.core.svn_node_file: # a deleted file
368 if fromkind == svn.core.svn_node_file: # a deleted file
369 entries.append(self.recode(entry))
369 entries.append(self.recode(entry))
370 elif fromkind == svn.core.svn_node_dir:
370 elif fromkind == svn.core.svn_node_dir:
371 # print "Deleted/moved non-file:", revnum, path, ent
371 # print "Deleted/moved non-file:", revnum, path, ent
372 # children = self._find_children(path, revnum - 1)
372 # children = self._find_children(path, revnum - 1)
373 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
373 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
374 # Sometimes this is tricky. For example: in
374 # Sometimes this is tricky. For example: in
375 # The Subversion Repository revision 6940 a dir
375 # The Subversion Repository revision 6940 a dir
376 # was copied and one of its files was deleted
376 # was copied and one of its files was deleted
377 # from the new location in the same commit. This
377 # from the new location in the same commit. This
378 # code can't deal with that yet.
378 # code can't deal with that yet.
379 if ent.action == 'C':
379 if ent.action == 'C':
380 children = self._find_children(path, fromrev)
380 children = self._find_children(path, fromrev)
381 else:
381 else:
382 oroot = entrypath.strip('/')
382 oroot = entrypath.strip('/')
383 nroot = path.strip('/')
383 nroot = path.strip('/')
384 children = self._find_children(oroot, fromrev)
384 children = self._find_children(oroot, fromrev)
385 children = [s.replace(oroot,nroot) for s in children]
385 children = [s.replace(oroot,nroot) for s in children]
386 # Mark all [files, not directories] as deleted.
386 # Mark all [files, not directories] as deleted.
387 for child in children:
387 for child in children:
388 # Can we move a child directory and its
388 # Can we move a child directory and its
389 # parent in the same commit? (probably can). Could
389 # parent in the same commit? (probably can). Could
390 # cause problems if instead of revnum -1,
390 # cause problems if instead of revnum -1,
391 # we have to look in (copyfrom_path, revnum - 1)
391 # we have to look in (copyfrom_path, revnum - 1)
392 entrypath = get_entry_from_path("/" + child, module=old_module)
392 entrypath = get_entry_from_path("/" + child, module=old_module)
393 if entrypath:
393 if entrypath:
394 entry = self.recode(entrypath.decode(self.encoding))
394 entry = self.recode(entrypath.decode(self.encoding))
395 if entry in copies:
395 if entry in copies:
396 # deleted file within a copy
396 # deleted file within a copy
397 del copies[entry]
397 del copies[entry]
398 else:
398 else:
399 entries.append(entry)
399 entries.append(entry)
400 else:
400 else:
401 self.ui.debug('unknown path in revision %d: %s\n' % \
401 self.ui.debug('unknown path in revision %d: %s\n' % \
402 (revnum, path))
402 (revnum, path))
403 elif kind == svn.core.svn_node_dir:
403 elif kind == svn.core.svn_node_dir:
404 # Should probably synthesize normal file entries
404 # Should probably synthesize normal file entries
405 # and handle as above to clean up copy/rename handling.
405 # and handle as above to clean up copy/rename handling.
406
406
407 # If the directory just had a prop change,
407 # If the directory just had a prop change,
408 # then we shouldn't need to look for its children.
408 # then we shouldn't need to look for its children.
409 # Also this could create duplicate entries. Not sure
409 # Also this could create duplicate entries. Not sure
410 # whether this will matter. Maybe should make entries a set.
410 # whether this will matter. Maybe should make entries a set.
411 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
411 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
412 # This will fail if a directory was copied
412 # This will fail if a directory was copied
413 # from another branch and then some of its files
413 # from another branch and then some of its files
414 # were deleted in the same transaction.
414 # were deleted in the same transaction.
415 children = self._find_children(path, revnum)
415 children = self._find_children(path, revnum)
416 children.sort()
416 children.sort()
417 for child in children:
417 for child in children:
418 # Can we move a child directory and its
418 # Can we move a child directory and its
419 # parent in the same commit? (probably can). Could
419 # parent in the same commit? (probably can). Could
420 # cause problems if instead of revnum -1,
420 # cause problems if instead of revnum -1,
421 # we have to look in (copyfrom_path, revnum - 1)
421 # we have to look in (copyfrom_path, revnum - 1)
422 entrypath = get_entry_from_path("/" + child, module=self.module)
422 entrypath = get_entry_from_path("/" + child, module=self.module)
423 # print child, self.module, entrypath
423 # print child, self.module, entrypath
424 if entrypath:
424 if entrypath:
425 # Need to filter out directories here...
425 # Need to filter out directories here...
426 kind = svn.ra.check_path(self.ra, entrypath, revnum)
426 kind = svn.ra.check_path(self.ra, entrypath, revnum)
427 if kind != svn.core.svn_node_dir:
427 if kind != svn.core.svn_node_dir:
428 entries.append(self.recode(entrypath))
428 entries.append(self.recode(entrypath))
429
429
430 # Copies here (must copy all from source)
430 # Copies here (must copy all from source)
431 # Probably not a real problem for us if
431 # Probably not a real problem for us if
432 # source does not exist
432 # source does not exist
433
433
434 # Can do this with the copy command "hg copy"
434 # Can do this with the copy command "hg copy"
435 # if ent.copyfrom_path:
435 # if ent.copyfrom_path:
436 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
436 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
437 # module=self.module)
437 # module=self.module)
438 # copyto_entry = entrypath
438 # copyto_entry = entrypath
439 #
439 #
440 # print "copy directory", copyfrom_entry, 'to', copyto_entry
440 # print "copy directory", copyfrom_entry, 'to', copyto_entry
441 #
441 #
442 # copies.append((copyfrom_entry, copyto_entry))
442 # copies.append((copyfrom_entry, copyto_entry))
443
443
444 if ent.copyfrom_path:
444 if ent.copyfrom_path:
445 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
445 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
446 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
446 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
447 if copyfrom_entry:
447 if copyfrom_entry:
448 copyfrom[path] = ent
448 copyfrom[path] = ent
449 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
449 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
450
450
451 # Good, /probably/ a regular copy. Really should check
451 # Good, /probably/ a regular copy. Really should check
452 # to see whether the parent revision actually contains
452 # to see whether the parent revision actually contains
453 # the directory in question.
453 # the directory in question.
454 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
454 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
455 children.sort()
455 children.sort()
456 for child in children:
456 for child in children:
457 entrypath = get_entry_from_path("/" + child, module=self.module)
457 entrypath = get_entry_from_path("/" + child, module=self.module)
458 if entrypath:
458 if entrypath:
459 entry = entrypath.decode(self.encoding)
459 entry = entrypath.decode(self.encoding)
460 # print "COPY COPY From", copyfrom_entry, entry
460 # print "COPY COPY From", copyfrom_entry, entry
461 copyto_path = path + entry[len(copyfrom_entry):]
461 copyto_path = path + entry[len(copyfrom_entry):]
462 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
462 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
463 # print "COPY", entry, "COPY To", copyto_entry
463 # print "COPY", entry, "COPY To", copyto_entry
464 copies[self.recode(copyto_entry)] = self.recode(entry)
464 copies[self.recode(copyto_entry)] = self.recode(entry)
465 # copy from quux splort/quuxfile
465 # copy from quux splort/quuxfile
466
466
467 self.modulemap[revnum] = self.module # track backwards in time
467 self.modulemap[revnum] = self.module # track backwards in time
468 # a list of (filename, id) where id lets us retrieve the file.
468 # a list of (filename, id) where id lets us retrieve the file.
469 # eg in git, id is the object hash. for svn it'll be the
469 # eg in git, id is the object hash. for svn it'll be the
470 self.files[rev] = zip(entries, [rev] * len(entries))
470 self.files[rev] = zip(entries, [rev] * len(entries))
471 if not entries:
471 if not entries:
472 return
472 return
473
473
474 # Example SVN datetime. Includes microseconds.
474 # Example SVN datetime. Includes microseconds.
475 # ISO-8601 conformant
475 # ISO-8601 conformant
476 # '2007-01-04T17:35:00.902377Z'
476 # '2007-01-04T17:35:00.902377Z'
477 date = util.parsedate(date[:18] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
477 date = util.parsedate(date[:18] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
478
478
479 log = message and self.recode(message)
479 log = message and self.recode(message)
480 author = author and self.recode(author) or ''
480 author = author and self.recode(author) or ''
481
481
482 cset = commit(author=author,
482 cset = commit(author=author,
483 date=util.datestr(date),
483 date=util.datestr(date),
484 desc=log,
484 desc=log,
485 parents=parents,
485 parents=parents,
486 copies=copies,
486 copies=copies,
487 branch=branch)
487 branch=branch,
488 rev=rev.encode('utf-8'))
488
489
489 self.commits[rev] = cset
490 self.commits[rev] = cset
490 if self.child_cset and not self.child_cset.parents:
491 if self.child_cset and not self.child_cset.parents:
491 self.child_cset.parents = [rev]
492 self.child_cset.parents = [rev]
492 self.child_cset = cset
493 self.child_cset = cset
493
494
494 self.ui.note('fetching revision log for "%s" from %d to %d\n' % \
495 self.ui.note('fetching revision log for "%s" from %d to %d\n' % \
495 (self.module, from_revnum, to_revnum))
496 (self.module, from_revnum, to_revnum))
496
497
497 try:
498 try:
498 discover_changed_paths = True
499 discover_changed_paths = True
499 strict_node_history = False
500 strict_node_history = False
500 svn.ra.get_log(self.ra, [self.module], from_revnum, to_revnum, 0,
501 svn.ra.get_log(self.ra, [self.module], from_revnum, to_revnum, 0,
501 discover_changed_paths, strict_node_history,
502 discover_changed_paths, strict_node_history,
502 receivelog)
503 receivelog)
503 for entry in received:
504 for entry in received:
504 parselogentry(*entry)
505 parselogentry(*entry)
505 except SubversionException, (_, num):
506 except SubversionException, (_, num):
506 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
507 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
507 raise NoSuchRevision(branch=self,
508 raise NoSuchRevision(branch=self,
508 revision="Revision number %d" % to_revnum)
509 revision="Revision number %d" % to_revnum)
509 raise
510 raise
510
511
511 def _getfile(self, file, rev):
512 def _getfile(self, file, rev):
512 io = StringIO()
513 io = StringIO()
513 # TODO: ra.get_file transmits the whole file instead of diffs.
514 # TODO: ra.get_file transmits the whole file instead of diffs.
514 mode = ''
515 mode = ''
515 try:
516 try:
516 revnum = self.revnum(rev)
517 revnum = self.revnum(rev)
517 if self.module != self.modulemap[revnum]:
518 if self.module != self.modulemap[revnum]:
518 self.module = self.modulemap[revnum]
519 self.module = self.modulemap[revnum]
519 self.reparent(self.module)
520 self.reparent(self.module)
520 info = svn.ra.get_file(self.ra, file, revnum, io)
521 info = svn.ra.get_file(self.ra, file, revnum, io)
521 if isinstance(info, list):
522 if isinstance(info, list):
522 info = info[-1]
523 info = info[-1]
523 mode = ("svn:executable" in info) and 'x' or ''
524 mode = ("svn:executable" in info) and 'x' or ''
524 mode = ("svn:special" in info) and 'l' or mode
525 mode = ("svn:special" in info) and 'l' or mode
525 except SubversionException, e:
526 except SubversionException, e:
526 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
527 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
527 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
528 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
528 if e.apr_err in notfound: # File not found
529 if e.apr_err in notfound: # File not found
529 raise IOError()
530 raise IOError()
530 raise
531 raise
531 data = io.getvalue()
532 data = io.getvalue()
532 if mode == 'l':
533 if mode == 'l':
533 link_prefix = "link "
534 link_prefix = "link "
534 if data.startswith(link_prefix):
535 if data.startswith(link_prefix):
535 data = data[len(link_prefix):]
536 data = data[len(link_prefix):]
536 return data, mode
537 return data, mode
537
538
538 def _find_children(self, path, revnum):
539 def _find_children(self, path, revnum):
539 path = path.strip("/")
540 path = path.strip("/")
540
541
541 def _find_children_fallback(path, revnum):
542 def _find_children_fallback(path, revnum):
542 # SWIG python bindings for getdir are broken up to at least 1.4.3
543 # SWIG python bindings for getdir are broken up to at least 1.4.3
543 pool = Pool()
544 pool = Pool()
544 optrev = svn.core.svn_opt_revision_t()
545 optrev = svn.core.svn_opt_revision_t()
545 optrev.kind = svn.core.svn_opt_revision_number
546 optrev.kind = svn.core.svn_opt_revision_number
546 optrev.value.number = revnum
547 optrev.value.number = revnum
547 rpath = '/'.join([self.base, path]).strip('/')
548 rpath = '/'.join([self.base, path]).strip('/')
548 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev, True, self.ctx, pool).keys()]
549 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev, True, self.ctx, pool).keys()]
549
550
550 if hasattr(self, '_find_children_fallback'):
551 if hasattr(self, '_find_children_fallback'):
551 return _find_children_fallback(path, revnum)
552 return _find_children_fallback(path, revnum)
552
553
553 self.reparent("/" + path)
554 self.reparent("/" + path)
554 pool = Pool()
555 pool = Pool()
555
556
556 children = []
557 children = []
557 def find_children_inner(children, path, revnum = revnum):
558 def find_children_inner(children, path, revnum = revnum):
558 if hasattr(svn.ra, 'get_dir2'): # Since SVN 1.4
559 if hasattr(svn.ra, 'get_dir2'): # Since SVN 1.4
559 fields = 0xffffffff # Binding does not provide SVN_DIRENT_ALL
560 fields = 0xffffffff # Binding does not provide SVN_DIRENT_ALL
560 getdir = svn.ra.get_dir2(self.ra, path, revnum, fields, pool)
561 getdir = svn.ra.get_dir2(self.ra, path, revnum, fields, pool)
561 else:
562 else:
562 getdir = svn.ra.get_dir(self.ra, path, revnum, pool)
563 getdir = svn.ra.get_dir(self.ra, path, revnum, pool)
563 if type(getdir) == dict:
564 if type(getdir) == dict:
564 # python binding for getdir is broken up to at least 1.4.3
565 # python binding for getdir is broken up to at least 1.4.3
565 raise CompatibilityException()
566 raise CompatibilityException()
566 dirents = getdir[0]
567 dirents = getdir[0]
567 if type(dirents) == int:
568 if type(dirents) == int:
568 # got here once due to infinite recursion bug
569 # got here once due to infinite recursion bug
569 # pprint.pprint(getdir)
570 # pprint.pprint(getdir)
570 return
571 return
571 c = dirents.keys()
572 c = dirents.keys()
572 c.sort()
573 c.sort()
573 for child in c:
574 for child in c:
574 dirent = dirents[child]
575 dirent = dirents[child]
575 if dirent.kind == svn.core.svn_node_dir:
576 if dirent.kind == svn.core.svn_node_dir:
576 find_children_inner(children, (path + "/" + child).strip("/"))
577 find_children_inner(children, (path + "/" + child).strip("/"))
577 else:
578 else:
578 children.append((path + "/" + child).strip("/"))
579 children.append((path + "/" + child).strip("/"))
579
580
580 try:
581 try:
581 find_children_inner(children, "")
582 find_children_inner(children, "")
582 except CompatibilityException:
583 except CompatibilityException:
583 self._find_children_fallback = True
584 self._find_children_fallback = True
584 self.reparent(self.module)
585 self.reparent(self.module)
585 return _find_children_fallback(path, revnum)
586 return _find_children_fallback(path, revnum)
586
587
587 self.reparent(self.module)
588 self.reparent(self.module)
588 return [path + "/" + c for c in children]
589 return [path + "/" + c for c in children]
General Comments 0
You need to be logged in to leave comments. Login now