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