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