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