##// END OF EJS Templates
convert: fix SVN date parser dropping the final whole second digit
David J. Mellor -
r5617:924fd86f default
parent child Browse files
Show More
@@ -1,666 +1,666
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 # Configuration options:
5 # Configuration options:
6 #
6 #
7 # convert.svn.trunk
7 # convert.svn.trunk
8 # Relative path to the trunk (default: "trunk")
8 # Relative path to the trunk (default: "trunk")
9 # convert.svn.branches
9 # convert.svn.branches
10 # Relative path to tree of branches (default: "branches")
10 # Relative path to tree of branches (default: "branches")
11 # convert.svn.tags
11 # convert.svn.tags
12 # Relative path to tree of tags (default: "tags")
12 # Relative path to tree of tags (default: "tags")
13 #
13 #
14 # Set these in a hgrc, or on the command line as follows:
14 # Set these in a hgrc, or on the command line as follows:
15 #
15 #
16 # hg convert --config convert.svn.trunk=wackoname [...]
16 # hg convert --config convert.svn.trunk=wackoname [...]
17
17
18 import locale
18 import locale
19 import os
19 import os
20 import sys
20 import sys
21 import cPickle as pickle
21 import cPickle as pickle
22 from mercurial import util
22 from mercurial import util
23
23
24 # Subversion stuff. Works best with very recent Python SVN bindings
24 # Subversion stuff. Works best with very recent Python SVN bindings
25 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
25 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
26 # these bindings.
26 # these bindings.
27
27
28 from cStringIO import StringIO
28 from cStringIO import StringIO
29
29
30 from common import NoRepo, commit, converter_source, encodeargs, decodeargs
30 from common import NoRepo, commit, converter_source, encodeargs, decodeargs
31
31
32 try:
32 try:
33 from svn.core import SubversionException, Pool
33 from svn.core import SubversionException, Pool
34 import svn
34 import svn
35 import svn.client
35 import svn.client
36 import svn.core
36 import svn.core
37 import svn.ra
37 import svn.ra
38 import svn.delta
38 import svn.delta
39 import transport
39 import transport
40 except ImportError:
40 except ImportError:
41 pass
41 pass
42
42
43 def geturl(path):
43 def geturl(path):
44 try:
44 try:
45 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
45 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
46 except SubversionException:
46 except SubversionException:
47 pass
47 pass
48 if os.path.isdir(path):
48 if os.path.isdir(path):
49 return 'file://%s' % os.path.normpath(os.path.abspath(path))
49 return 'file://%s' % os.path.normpath(os.path.abspath(path))
50 return path
50 return path
51
51
52 def optrev(number):
52 def optrev(number):
53 optrev = svn.core.svn_opt_revision_t()
53 optrev = svn.core.svn_opt_revision_t()
54 optrev.kind = svn.core.svn_opt_revision_number
54 optrev.kind = svn.core.svn_opt_revision_number
55 optrev.value.number = number
55 optrev.value.number = number
56 return optrev
56 return optrev
57
57
58 class changedpath(object):
58 class changedpath(object):
59 def __init__(self, p):
59 def __init__(self, p):
60 self.copyfrom_path = p.copyfrom_path
60 self.copyfrom_path = p.copyfrom_path
61 self.copyfrom_rev = p.copyfrom_rev
61 self.copyfrom_rev = p.copyfrom_rev
62 self.action = p.action
62 self.action = p.action
63
63
64 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
64 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
65 strict_node_history=False):
65 strict_node_history=False):
66 protocol = -1
66 protocol = -1
67 def receiver(orig_paths, revnum, author, date, message, pool):
67 def receiver(orig_paths, revnum, author, date, message, pool):
68 if orig_paths is not None:
68 if orig_paths is not None:
69 for k, v in orig_paths.iteritems():
69 for k, v in orig_paths.iteritems():
70 orig_paths[k] = changedpath(v)
70 orig_paths[k] = changedpath(v)
71 pickle.dump((orig_paths, revnum, author, date, message),
71 pickle.dump((orig_paths, revnum, author, date, message),
72 fp, protocol)
72 fp, protocol)
73
73
74 try:
74 try:
75 # Use an ra of our own so that our parent can consume
75 # Use an ra of our own so that our parent can consume
76 # our results without confusing the server.
76 # our results without confusing the server.
77 t = transport.SvnRaTransport(url=url)
77 t = transport.SvnRaTransport(url=url)
78 svn.ra.get_log(t.ra, paths, start, end, limit,
78 svn.ra.get_log(t.ra, paths, start, end, limit,
79 discover_changed_paths,
79 discover_changed_paths,
80 strict_node_history,
80 strict_node_history,
81 receiver)
81 receiver)
82 except SubversionException, (inst, num):
82 except SubversionException, (inst, num):
83 pickle.dump(num, fp, protocol)
83 pickle.dump(num, fp, protocol)
84 else:
84 else:
85 pickle.dump(None, fp, protocol)
85 pickle.dump(None, fp, protocol)
86 fp.close()
86 fp.close()
87
87
88 def debugsvnlog(ui, **opts):
88 def debugsvnlog(ui, **opts):
89 """Fetch SVN log in a subprocess and channel them back to parent to
89 """Fetch SVN log in a subprocess and channel them back to parent to
90 avoid memory collection issues.
90 avoid memory collection issues.
91 """
91 """
92 util.set_binary(sys.stdin)
92 util.set_binary(sys.stdin)
93 util.set_binary(sys.stdout)
93 util.set_binary(sys.stdout)
94 args = decodeargs(sys.stdin.read())
94 args = decodeargs(sys.stdin.read())
95 get_log_child(sys.stdout, *args)
95 get_log_child(sys.stdout, *args)
96
96
97 # SVN conversion code stolen from bzr-svn and tailor
97 # SVN conversion code stolen from bzr-svn and tailor
98 class svn_source(converter_source):
98 class svn_source(converter_source):
99 def __init__(self, ui, url, rev=None):
99 def __init__(self, ui, url, rev=None):
100 super(svn_source, self).__init__(ui, url, rev=rev)
100 super(svn_source, self).__init__(ui, url, rev=rev)
101
101
102 try:
102 try:
103 SubversionException
103 SubversionException
104 except NameError:
104 except NameError:
105 raise NoRepo('Subversion python bindings could not be loaded')
105 raise NoRepo('Subversion python bindings could not be loaded')
106
106
107 self.encoding = locale.getpreferredencoding()
107 self.encoding = locale.getpreferredencoding()
108 self.lastrevs = {}
108 self.lastrevs = {}
109
109
110 latest = None
110 latest = None
111 try:
111 try:
112 # Support file://path@rev syntax. Useful e.g. to convert
112 # Support file://path@rev syntax. Useful e.g. to convert
113 # deleted branches.
113 # deleted branches.
114 at = url.rfind('@')
114 at = url.rfind('@')
115 if at >= 0:
115 if at >= 0:
116 latest = int(url[at+1:])
116 latest = int(url[at+1:])
117 url = url[:at]
117 url = url[:at]
118 except ValueError, e:
118 except ValueError, e:
119 pass
119 pass
120 self.url = geturl(url)
120 self.url = geturl(url)
121 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
121 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
122 try:
122 try:
123 self.transport = transport.SvnRaTransport(url=self.url)
123 self.transport = transport.SvnRaTransport(url=self.url)
124 self.ra = self.transport.ra
124 self.ra = self.transport.ra
125 self.ctx = self.transport.client
125 self.ctx = self.transport.client
126 self.base = svn.ra.get_repos_root(self.ra)
126 self.base = svn.ra.get_repos_root(self.ra)
127 self.module = self.url[len(self.base):]
127 self.module = self.url[len(self.base):]
128 self.modulemap = {} # revision, module
128 self.modulemap = {} # revision, module
129 self.commits = {}
129 self.commits = {}
130 self.paths = {}
130 self.paths = {}
131 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
131 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
132 except SubversionException, e:
132 except SubversionException, e:
133 ui.print_exc()
133 ui.print_exc()
134 raise NoRepo("%s does not look like a Subversion repo" % self.url)
134 raise NoRepo("%s does not look like a Subversion repo" % self.url)
135
135
136 if rev:
136 if rev:
137 try:
137 try:
138 latest = int(rev)
138 latest = int(rev)
139 except ValueError:
139 except ValueError:
140 raise util.Abort('svn: revision %s is not an integer' % rev)
140 raise util.Abort('svn: revision %s is not an integer' % rev)
141
141
142 try:
142 try:
143 self.get_blacklist()
143 self.get_blacklist()
144 except IOError, e:
144 except IOError, e:
145 pass
145 pass
146
146
147 self.last_changed = self.latest(self.module, latest)
147 self.last_changed = self.latest(self.module, latest)
148
148
149 self.head = self.revid(self.last_changed)
149 self.head = self.revid(self.last_changed)
150 self._changescache = None
150 self._changescache = None
151
151
152 def setrevmap(self, revmap, order):
152 def setrevmap(self, revmap, order):
153 lastrevs = {}
153 lastrevs = {}
154 for revid in revmap.keys():
154 for revid in revmap.keys():
155 uuid, module, revnum = self.revsplit(revid)
155 uuid, module, revnum = self.revsplit(revid)
156 lastrevnum = lastrevs.setdefault(module, revnum)
156 lastrevnum = lastrevs.setdefault(module, revnum)
157 if revnum > lastrevnum:
157 if revnum > lastrevnum:
158 lastrevs[module] = revnum
158 lastrevs[module] = revnum
159 self.lastrevs = lastrevs
159 self.lastrevs = lastrevs
160
160
161 def exists(self, path, optrev):
161 def exists(self, path, optrev):
162 try:
162 try:
163 svn.client.ls(self.url.rstrip('/') + '/' + path,
163 svn.client.ls(self.url.rstrip('/') + '/' + path,
164 optrev, False, self.ctx)
164 optrev, False, self.ctx)
165 return True
165 return True
166 except SubversionException, err:
166 except SubversionException, err:
167 return False
167 return False
168
168
169 def getheads(self):
169 def getheads(self):
170 # detect standard /branches, /tags, /trunk layout
170 # detect standard /branches, /tags, /trunk layout
171 rev = optrev(self.last_changed)
171 rev = optrev(self.last_changed)
172 rpath = self.url.strip('/')
172 rpath = self.url.strip('/')
173 cfgtrunk = self.ui.config('convert', 'svn.trunk')
173 cfgtrunk = self.ui.config('convert', 'svn.trunk')
174 cfgbranches = self.ui.config('convert', 'svn.branches')
174 cfgbranches = self.ui.config('convert', 'svn.branches')
175 cfgtags = self.ui.config('convert', 'svn.tags')
175 cfgtags = self.ui.config('convert', 'svn.tags')
176 trunk = (cfgtrunk or 'trunk').strip('/')
176 trunk = (cfgtrunk or 'trunk').strip('/')
177 branches = (cfgbranches or 'branches').strip('/')
177 branches = (cfgbranches or 'branches').strip('/')
178 tags = (cfgtags or 'tags').strip('/')
178 tags = (cfgtags or 'tags').strip('/')
179 if self.exists(trunk, rev) and self.exists(branches, rev) and self.exists(tags, rev):
179 if self.exists(trunk, rev) and self.exists(branches, rev) and self.exists(tags, rev):
180 self.ui.note('found trunk at %r, branches at %r and tags at %r\n' %
180 self.ui.note('found trunk at %r, branches at %r and tags at %r\n' %
181 (trunk, branches, tags))
181 (trunk, branches, tags))
182 oldmodule = self.module
182 oldmodule = self.module
183 self.module += '/' + trunk
183 self.module += '/' + trunk
184 lt = self.latest(self.module, self.last_changed)
184 lt = self.latest(self.module, self.last_changed)
185 self.head = self.revid(lt)
185 self.head = self.revid(lt)
186 self.heads = [self.head]
186 self.heads = [self.head]
187 branchnames = svn.client.ls(rpath + '/' + branches, rev, False,
187 branchnames = svn.client.ls(rpath + '/' + branches, rev, False,
188 self.ctx)
188 self.ctx)
189 for branch in branchnames.keys():
189 for branch in branchnames.keys():
190 if oldmodule:
190 if oldmodule:
191 module = oldmodule + '/' + branches + '/' + branch
191 module = oldmodule + '/' + branches + '/' + branch
192 else:
192 else:
193 module = '/' + branches + '/' + branch
193 module = '/' + branches + '/' + branch
194 brevnum = self.latest(module, self.last_changed)
194 brevnum = self.latest(module, self.last_changed)
195 brev = self.revid(brevnum, module)
195 brev = self.revid(brevnum, module)
196 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
196 self.ui.note('found branch %s at %d\n' % (branch, brevnum))
197 self.heads.append(brev)
197 self.heads.append(brev)
198
198
199 if oldmodule:
199 if oldmodule:
200 self.tags = '%s/%s' % (oldmodule, tags)
200 self.tags = '%s/%s' % (oldmodule, tags)
201 else:
201 else:
202 self.tags = '/%s' % tags
202 self.tags = '/%s' % tags
203
203
204 elif cfgtrunk or cfgbranches or cfgtags:
204 elif cfgtrunk or cfgbranches or cfgtags:
205 raise util.Abort('trunk/branch/tags layout expected, but not found')
205 raise util.Abort('trunk/branch/tags layout expected, but not found')
206 else:
206 else:
207 self.ui.note('working with one branch\n')
207 self.ui.note('working with one branch\n')
208 self.heads = [self.head]
208 self.heads = [self.head]
209 self.tags = tags
209 self.tags = tags
210 return self.heads
210 return self.heads
211
211
212 def getfile(self, file, rev):
212 def getfile(self, file, rev):
213 data, mode = self._getfile(file, rev)
213 data, mode = self._getfile(file, rev)
214 self.modecache[(file, rev)] = mode
214 self.modecache[(file, rev)] = mode
215 return data
215 return data
216
216
217 def getmode(self, file, rev):
217 def getmode(self, file, rev):
218 return self.modecache[(file, rev)]
218 return self.modecache[(file, rev)]
219
219
220 def getchanges(self, rev):
220 def getchanges(self, rev):
221 if self._changescache and self._changescache[0] == rev:
221 if self._changescache and self._changescache[0] == rev:
222 return self._changescache[1]
222 return self._changescache[1]
223 self._changescache = None
223 self._changescache = None
224 self.modecache = {}
224 self.modecache = {}
225 (paths, parents) = self.paths[rev]
225 (paths, parents) = self.paths[rev]
226 files, copies = self.expandpaths(rev, paths, parents)
226 files, copies = self.expandpaths(rev, paths, parents)
227 files.sort()
227 files.sort()
228 files = zip(files, [rev] * len(files))
228 files = zip(files, [rev] * len(files))
229
229
230 # caller caches the result, so free it here to release memory
230 # caller caches the result, so free it here to release memory
231 del self.paths[rev]
231 del self.paths[rev]
232 return (files, copies)
232 return (files, copies)
233
233
234 def getchangedfiles(self, rev, i):
234 def getchangedfiles(self, rev, i):
235 changes = self.getchanges(rev)
235 changes = self.getchanges(rev)
236 self._changescache = (rev, changes)
236 self._changescache = (rev, changes)
237 return [f[0] for f in changes[0]]
237 return [f[0] for f in changes[0]]
238
238
239 def getcommit(self, rev):
239 def getcommit(self, rev):
240 if rev not in self.commits:
240 if rev not in self.commits:
241 uuid, module, revnum = self.revsplit(rev)
241 uuid, module, revnum = self.revsplit(rev)
242 self.module = module
242 self.module = module
243 self.reparent(module)
243 self.reparent(module)
244 stop = self.lastrevs.get(module, 0)
244 stop = self.lastrevs.get(module, 0)
245 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
245 self._fetch_revisions(from_revnum=revnum, to_revnum=stop)
246 commit = self.commits[rev]
246 commit = self.commits[rev]
247 # caller caches the result, so free it here to release memory
247 # caller caches the result, so free it here to release memory
248 del self.commits[rev]
248 del self.commits[rev]
249 return commit
249 return commit
250
250
251 def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
251 def get_log(self, paths, start, end, limit=0, discover_changed_paths=True,
252 strict_node_history=False):
252 strict_node_history=False):
253
253
254 def parent(fp):
254 def parent(fp):
255 while True:
255 while True:
256 entry = pickle.load(fp)
256 entry = pickle.load(fp)
257 try:
257 try:
258 orig_paths, revnum, author, date, message = entry
258 orig_paths, revnum, author, date, message = entry
259 except:
259 except:
260 if entry is None:
260 if entry is None:
261 break
261 break
262 raise SubversionException("child raised exception", entry)
262 raise SubversionException("child raised exception", entry)
263 yield entry
263 yield entry
264
264
265 args = [self.url, paths, start, end, limit, discover_changed_paths,
265 args = [self.url, paths, start, end, limit, discover_changed_paths,
266 strict_node_history]
266 strict_node_history]
267 arg = encodeargs(args)
267 arg = encodeargs(args)
268 hgexe = util.hgexecutable()
268 hgexe = util.hgexecutable()
269 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
269 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
270 stdin, stdout = os.popen2(cmd, 'b')
270 stdin, stdout = os.popen2(cmd, 'b')
271
271
272 stdin.write(arg)
272 stdin.write(arg)
273 stdin.close()
273 stdin.close()
274
274
275 for p in parent(stdout):
275 for p in parent(stdout):
276 yield p
276 yield p
277
277
278 def gettags(self):
278 def gettags(self):
279 tags = {}
279 tags = {}
280 start = self.revnum(self.head)
280 start = self.revnum(self.head)
281 try:
281 try:
282 for entry in self.get_log([self.tags], 0, start):
282 for entry in self.get_log([self.tags], 0, start):
283 orig_paths, revnum, author, date, message = entry
283 orig_paths, revnum, author, date, message = entry
284 for path in orig_paths:
284 for path in orig_paths:
285 if not path.startswith(self.tags+'/'):
285 if not path.startswith(self.tags+'/'):
286 continue
286 continue
287 ent = orig_paths[path]
287 ent = orig_paths[path]
288 source = ent.copyfrom_path
288 source = ent.copyfrom_path
289 rev = ent.copyfrom_rev
289 rev = ent.copyfrom_rev
290 tag = path.split('/')[-1]
290 tag = path.split('/')[-1]
291 tags[tag] = self.revid(rev, module=source)
291 tags[tag] = self.revid(rev, module=source)
292 except SubversionException, (inst, num):
292 except SubversionException, (inst, num):
293 self.ui.note('no tags found at revision %d\n' % start)
293 self.ui.note('no tags found at revision %d\n' % start)
294 return tags
294 return tags
295
295
296 # -- helper functions --
296 # -- helper functions --
297
297
298 def revid(self, revnum, module=None):
298 def revid(self, revnum, module=None):
299 if not module:
299 if not module:
300 module = self.module
300 module = self.module
301 return u"svn:%s%s@%s" % (self.uuid, module.decode(self.encoding),
301 return u"svn:%s%s@%s" % (self.uuid, module.decode(self.encoding),
302 revnum)
302 revnum)
303
303
304 def revnum(self, rev):
304 def revnum(self, rev):
305 return int(rev.split('@')[-1])
305 return int(rev.split('@')[-1])
306
306
307 def revsplit(self, rev):
307 def revsplit(self, rev):
308 url, revnum = rev.encode(self.encoding).split('@', 1)
308 url, revnum = rev.encode(self.encoding).split('@', 1)
309 revnum = int(revnum)
309 revnum = int(revnum)
310 parts = url.split('/', 1)
310 parts = url.split('/', 1)
311 uuid = parts.pop(0)[4:]
311 uuid = parts.pop(0)[4:]
312 mod = ''
312 mod = ''
313 if parts:
313 if parts:
314 mod = '/' + parts[0]
314 mod = '/' + parts[0]
315 return uuid, mod, revnum
315 return uuid, mod, revnum
316
316
317 def latest(self, path, stop=0):
317 def latest(self, path, stop=0):
318 'find the latest revision affecting path, up to stop'
318 'find the latest revision affecting path, up to stop'
319 if not stop:
319 if not stop:
320 stop = svn.ra.get_latest_revnum(self.ra)
320 stop = svn.ra.get_latest_revnum(self.ra)
321 try:
321 try:
322 self.reparent('')
322 self.reparent('')
323 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
323 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
324 self.reparent(self.module)
324 self.reparent(self.module)
325 except SubversionException:
325 except SubversionException:
326 dirent = None
326 dirent = None
327 if not dirent:
327 if not dirent:
328 raise util.Abort('%s not found up to revision %d' % (path, stop))
328 raise util.Abort('%s not found up to revision %d' % (path, stop))
329
329
330 return dirent.created_rev
330 return dirent.created_rev
331
331
332 def get_blacklist(self):
332 def get_blacklist(self):
333 """Avoid certain revision numbers.
333 """Avoid certain revision numbers.
334 It is not uncommon for two nearby revisions to cancel each other
334 It is not uncommon for two nearby revisions to cancel each other
335 out, e.g. 'I copied trunk into a subdirectory of itself instead
335 out, e.g. 'I copied trunk into a subdirectory of itself instead
336 of making a branch'. The converted repository is significantly
336 of making a branch'. The converted repository is significantly
337 smaller if we ignore such revisions."""
337 smaller if we ignore such revisions."""
338 self.blacklist = util.set()
338 self.blacklist = util.set()
339 blacklist = self.blacklist
339 blacklist = self.blacklist
340 for line in file("blacklist.txt", "r"):
340 for line in file("blacklist.txt", "r"):
341 if not line.startswith("#"):
341 if not line.startswith("#"):
342 try:
342 try:
343 svn_rev = int(line.strip())
343 svn_rev = int(line.strip())
344 blacklist.add(svn_rev)
344 blacklist.add(svn_rev)
345 except ValueError, e:
345 except ValueError, e:
346 pass # not an integer or a comment
346 pass # not an integer or a comment
347
347
348 def is_blacklisted(self, svn_rev):
348 def is_blacklisted(self, svn_rev):
349 return svn_rev in self.blacklist
349 return svn_rev in self.blacklist
350
350
351 def reparent(self, module):
351 def reparent(self, module):
352 svn_url = self.base + module
352 svn_url = self.base + module
353 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
353 self.ui.debug("reparent to %s\n" % svn_url.encode(self.encoding))
354 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
354 svn.ra.reparent(self.ra, svn_url.encode(self.encoding))
355
355
356 def expandpaths(self, rev, paths, parents):
356 def expandpaths(self, rev, paths, parents):
357 def get_entry_from_path(path, module=self.module):
357 def get_entry_from_path(path, module=self.module):
358 # Given the repository url of this wc, say
358 # Given the repository url of this wc, say
359 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
359 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
360 # extract the "entry" portion (a relative path) from what
360 # extract the "entry" portion (a relative path) from what
361 # svn log --xml says, ie
361 # svn log --xml says, ie
362 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
362 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
363 # that is to say "tests/PloneTestCase.py"
363 # that is to say "tests/PloneTestCase.py"
364 if path.startswith(module):
364 if path.startswith(module):
365 relative = path[len(module):]
365 relative = path[len(module):]
366 if relative.startswith('/'):
366 if relative.startswith('/'):
367 return relative[1:]
367 return relative[1:]
368 else:
368 else:
369 return relative
369 return relative
370
370
371 # The path is outside our tracked tree...
371 # The path is outside our tracked tree...
372 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
372 self.ui.debug('%r is not under %r, ignoring\n' % (path, module))
373 return None
373 return None
374
374
375 entries = []
375 entries = []
376 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
376 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
377 copies = {}
377 copies = {}
378 revnum = self.revnum(rev)
378 revnum = self.revnum(rev)
379
379
380 if revnum in self.modulemap:
380 if revnum in self.modulemap:
381 new_module = self.modulemap[revnum]
381 new_module = self.modulemap[revnum]
382 if new_module != self.module:
382 if new_module != self.module:
383 self.module = new_module
383 self.module = new_module
384 self.reparent(self.module)
384 self.reparent(self.module)
385
385
386 for path, ent in paths:
386 for path, ent in paths:
387 entrypath = get_entry_from_path(path, module=self.module)
387 entrypath = get_entry_from_path(path, module=self.module)
388 entry = entrypath.decode(self.encoding)
388 entry = entrypath.decode(self.encoding)
389
389
390 kind = svn.ra.check_path(self.ra, entrypath, revnum)
390 kind = svn.ra.check_path(self.ra, entrypath, revnum)
391 if kind == svn.core.svn_node_file:
391 if kind == svn.core.svn_node_file:
392 if ent.copyfrom_path:
392 if ent.copyfrom_path:
393 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
393 copyfrom_path = get_entry_from_path(ent.copyfrom_path)
394 if copyfrom_path:
394 if copyfrom_path:
395 self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
395 self.ui.debug("Copied to %s from %s@%s\n" % (entry, copyfrom_path, ent.copyfrom_rev))
396 # It's probably important for hg that the source
396 # It's probably important for hg that the source
397 # exists in the revision's parent, not just the
397 # exists in the revision's parent, not just the
398 # ent.copyfrom_rev
398 # ent.copyfrom_rev
399 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
399 fromkind = svn.ra.check_path(self.ra, copyfrom_path, ent.copyfrom_rev)
400 if fromkind != 0:
400 if fromkind != 0:
401 copies[self.recode(entry)] = self.recode(copyfrom_path)
401 copies[self.recode(entry)] = self.recode(copyfrom_path)
402 entries.append(self.recode(entry))
402 entries.append(self.recode(entry))
403 elif kind == 0: # gone, but had better be a deleted *file*
403 elif kind == 0: # gone, but had better be a deleted *file*
404 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
404 self.ui.debug("gone from %s\n" % ent.copyfrom_rev)
405
405
406 # if a branch is created but entries are removed in the same
406 # if a branch is created but entries are removed in the same
407 # changeset, get the right fromrev
407 # changeset, get the right fromrev
408 if parents:
408 if parents:
409 uuid, old_module, fromrev = self.revsplit(parents[0])
409 uuid, old_module, fromrev = self.revsplit(parents[0])
410 else:
410 else:
411 fromrev = revnum - 1
411 fromrev = revnum - 1
412 # might always need to be revnum - 1 in these 3 lines?
412 # might always need to be revnum - 1 in these 3 lines?
413 old_module = self.modulemap.get(fromrev, self.module)
413 old_module = self.modulemap.get(fromrev, self.module)
414
414
415 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
415 basepath = old_module + "/" + get_entry_from_path(path, module=self.module)
416 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
416 entrypath = old_module + "/" + get_entry_from_path(path, module=self.module)
417
417
418 def lookup_parts(p):
418 def lookup_parts(p):
419 rc = None
419 rc = None
420 parts = p.split("/")
420 parts = p.split("/")
421 for i in range(len(parts)):
421 for i in range(len(parts)):
422 part = "/".join(parts[:i])
422 part = "/".join(parts[:i])
423 info = part, copyfrom.get(part, None)
423 info = part, copyfrom.get(part, None)
424 if info[1] is not None:
424 if info[1] is not None:
425 self.ui.debug("Found parent directory %s\n" % info[1])
425 self.ui.debug("Found parent directory %s\n" % info[1])
426 rc = info
426 rc = info
427 return rc
427 return rc
428
428
429 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
429 self.ui.debug("base, entry %s %s\n" % (basepath, entrypath))
430
430
431 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
431 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
432
432
433 # need to remove fragment from lookup_parts and replace with copyfrom_path
433 # need to remove fragment from lookup_parts and replace with copyfrom_path
434 if frompath is not None:
434 if frompath is not None:
435 self.ui.debug("munge-o-matic\n")
435 self.ui.debug("munge-o-matic\n")
436 self.ui.debug(entrypath + '\n')
436 self.ui.debug(entrypath + '\n')
437 self.ui.debug(entrypath[len(frompath):] + '\n')
437 self.ui.debug(entrypath[len(frompath):] + '\n')
438 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
438 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
439 fromrev = froment.copyfrom_rev
439 fromrev = froment.copyfrom_rev
440 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
440 self.ui.debug("Info: %s %s %s %s\n" % (frompath, froment, ent, entrypath))
441
441
442 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
442 fromkind = svn.ra.check_path(self.ra, entrypath, fromrev)
443 if fromkind == svn.core.svn_node_file: # a deleted file
443 if fromkind == svn.core.svn_node_file: # a deleted file
444 entries.append(self.recode(entry))
444 entries.append(self.recode(entry))
445 elif fromkind == svn.core.svn_node_dir:
445 elif fromkind == svn.core.svn_node_dir:
446 # print "Deleted/moved non-file:", revnum, path, ent
446 # print "Deleted/moved non-file:", revnum, path, ent
447 # children = self._find_children(path, revnum - 1)
447 # children = self._find_children(path, revnum - 1)
448 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
448 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
449 # Sometimes this is tricky. For example: in
449 # Sometimes this is tricky. For example: in
450 # The Subversion Repository revision 6940 a dir
450 # The Subversion Repository revision 6940 a dir
451 # was copied and one of its files was deleted
451 # was copied and one of its files was deleted
452 # from the new location in the same commit. This
452 # from the new location in the same commit. This
453 # code can't deal with that yet.
453 # code can't deal with that yet.
454 if ent.action == 'C':
454 if ent.action == 'C':
455 children = self._find_children(path, fromrev)
455 children = self._find_children(path, fromrev)
456 else:
456 else:
457 oroot = entrypath.strip('/')
457 oroot = entrypath.strip('/')
458 nroot = path.strip('/')
458 nroot = path.strip('/')
459 children = self._find_children(oroot, fromrev)
459 children = self._find_children(oroot, fromrev)
460 children = [s.replace(oroot,nroot) for s in children]
460 children = [s.replace(oroot,nroot) for s in children]
461 # Mark all [files, not directories] as deleted.
461 # Mark all [files, not directories] as deleted.
462 for child in children:
462 for child in children:
463 # Can we move a child directory and its
463 # Can we move a child directory and its
464 # parent in the same commit? (probably can). Could
464 # parent in the same commit? (probably can). Could
465 # cause problems if instead of revnum -1,
465 # cause problems if instead of revnum -1,
466 # we have to look in (copyfrom_path, revnum - 1)
466 # we have to look in (copyfrom_path, revnum - 1)
467 entrypath = get_entry_from_path("/" + child, module=old_module)
467 entrypath = get_entry_from_path("/" + child, module=old_module)
468 if entrypath:
468 if entrypath:
469 entry = self.recode(entrypath.decode(self.encoding))
469 entry = self.recode(entrypath.decode(self.encoding))
470 if entry in copies:
470 if entry in copies:
471 # deleted file within a copy
471 # deleted file within a copy
472 del copies[entry]
472 del copies[entry]
473 else:
473 else:
474 entries.append(entry)
474 entries.append(entry)
475 else:
475 else:
476 self.ui.debug('unknown path in revision %d: %s\n' % \
476 self.ui.debug('unknown path in revision %d: %s\n' % \
477 (revnum, path))
477 (revnum, path))
478 elif kind == svn.core.svn_node_dir:
478 elif kind == svn.core.svn_node_dir:
479 # Should probably synthesize normal file entries
479 # Should probably synthesize normal file entries
480 # and handle as above to clean up copy/rename handling.
480 # and handle as above to clean up copy/rename handling.
481
481
482 # If the directory just had a prop change,
482 # If the directory just had a prop change,
483 # then we shouldn't need to look for its children.
483 # then we shouldn't need to look for its children.
484 # Also this could create duplicate entries. Not sure
484 # Also this could create duplicate entries. Not sure
485 # whether this will matter. Maybe should make entries a set.
485 # whether this will matter. Maybe should make entries a set.
486 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
486 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
487 # This will fail if a directory was copied
487 # This will fail if a directory was copied
488 # from another branch and then some of its files
488 # from another branch and then some of its files
489 # were deleted in the same transaction.
489 # were deleted in the same transaction.
490 children = self._find_children(path, revnum)
490 children = self._find_children(path, revnum)
491 children.sort()
491 children.sort()
492 for child in children:
492 for child in children:
493 # Can we move a child directory and its
493 # Can we move a child directory and its
494 # parent in the same commit? (probably can). Could
494 # parent in the same commit? (probably can). Could
495 # cause problems if instead of revnum -1,
495 # cause problems if instead of revnum -1,
496 # we have to look in (copyfrom_path, revnum - 1)
496 # we have to look in (copyfrom_path, revnum - 1)
497 entrypath = get_entry_from_path("/" + child, module=self.module)
497 entrypath = get_entry_from_path("/" + child, module=self.module)
498 # print child, self.module, entrypath
498 # print child, self.module, entrypath
499 if entrypath:
499 if entrypath:
500 # Need to filter out directories here...
500 # Need to filter out directories here...
501 kind = svn.ra.check_path(self.ra, entrypath, revnum)
501 kind = svn.ra.check_path(self.ra, entrypath, revnum)
502 if kind != svn.core.svn_node_dir:
502 if kind != svn.core.svn_node_dir:
503 entries.append(self.recode(entrypath))
503 entries.append(self.recode(entrypath))
504
504
505 # Copies here (must copy all from source)
505 # Copies here (must copy all from source)
506 # Probably not a real problem for us if
506 # Probably not a real problem for us if
507 # source does not exist
507 # source does not exist
508
508
509 # Can do this with the copy command "hg copy"
509 # Can do this with the copy command "hg copy"
510 # if ent.copyfrom_path:
510 # if ent.copyfrom_path:
511 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
511 # copyfrom_entry = get_entry_from_path(ent.copyfrom_path.decode(self.encoding),
512 # module=self.module)
512 # module=self.module)
513 # copyto_entry = entrypath
513 # copyto_entry = entrypath
514 #
514 #
515 # print "copy directory", copyfrom_entry, 'to', copyto_entry
515 # print "copy directory", copyfrom_entry, 'to', copyto_entry
516 #
516 #
517 # copies.append((copyfrom_entry, copyto_entry))
517 # copies.append((copyfrom_entry, copyto_entry))
518
518
519 if ent.copyfrom_path:
519 if ent.copyfrom_path:
520 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
520 copyfrom_path = ent.copyfrom_path.decode(self.encoding)
521 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
521 copyfrom_entry = get_entry_from_path(copyfrom_path, module=self.module)
522 if copyfrom_entry:
522 if copyfrom_entry:
523 copyfrom[path] = ent
523 copyfrom[path] = ent
524 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
524 self.ui.debug("mark %s came from %s\n" % (path, copyfrom[path]))
525
525
526 # Good, /probably/ a regular copy. Really should check
526 # Good, /probably/ a regular copy. Really should check
527 # to see whether the parent revision actually contains
527 # to see whether the parent revision actually contains
528 # the directory in question.
528 # the directory in question.
529 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
529 children = self._find_children(self.recode(copyfrom_path), ent.copyfrom_rev)
530 children.sort()
530 children.sort()
531 for child in children:
531 for child in children:
532 entrypath = get_entry_from_path("/" + child, module=self.module)
532 entrypath = get_entry_from_path("/" + child, module=self.module)
533 if entrypath:
533 if entrypath:
534 entry = entrypath.decode(self.encoding)
534 entry = entrypath.decode(self.encoding)
535 # print "COPY COPY From", copyfrom_entry, entry
535 # print "COPY COPY From", copyfrom_entry, entry
536 copyto_path = path + entry[len(copyfrom_entry):]
536 copyto_path = path + entry[len(copyfrom_entry):]
537 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
537 copyto_entry = get_entry_from_path(copyto_path, module=self.module)
538 # print "COPY", entry, "COPY To", copyto_entry
538 # print "COPY", entry, "COPY To", copyto_entry
539 copies[self.recode(copyto_entry)] = self.recode(entry)
539 copies[self.recode(copyto_entry)] = self.recode(entry)
540 # copy from quux splort/quuxfile
540 # copy from quux splort/quuxfile
541
541
542 return (entries, copies)
542 return (entries, copies)
543
543
544 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
544 def _fetch_revisions(self, from_revnum = 0, to_revnum = 347):
545 self.child_cset = None
545 self.child_cset = None
546 def parselogentry(orig_paths, revnum, author, date, message):
546 def parselogentry(orig_paths, revnum, author, date, message):
547 self.ui.debug("parsing revision %d (%d changes)\n" %
547 self.ui.debug("parsing revision %d (%d changes)\n" %
548 (revnum, len(orig_paths)))
548 (revnum, len(orig_paths)))
549
549
550 if revnum in self.modulemap:
550 if revnum in self.modulemap:
551 new_module = self.modulemap[revnum]
551 new_module = self.modulemap[revnum]
552 if new_module != self.module:
552 if new_module != self.module:
553 self.module = new_module
553 self.module = new_module
554 self.reparent(self.module)
554 self.reparent(self.module)
555
555
556 rev = self.revid(revnum)
556 rev = self.revid(revnum)
557 # branch log might return entries for a parent we already have
557 # branch log might return entries for a parent we already have
558 if (rev in self.commits or
558 if (rev in self.commits or
559 (revnum < self.lastrevs.get(self.module, 0))):
559 (revnum < self.lastrevs.get(self.module, 0))):
560 return
560 return
561
561
562 parents = []
562 parents = []
563 # check whether this revision is the start of a branch
563 # check whether this revision is the start of a branch
564 if self.module in orig_paths:
564 if self.module in orig_paths:
565 ent = orig_paths[self.module]
565 ent = orig_paths[self.module]
566 if ent.copyfrom_path:
566 if ent.copyfrom_path:
567 # ent.copyfrom_rev may not be the actual last revision
567 # ent.copyfrom_rev may not be the actual last revision
568 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
568 prev = self.latest(ent.copyfrom_path, ent.copyfrom_rev)
569 self.modulemap[prev] = ent.copyfrom_path
569 self.modulemap[prev] = ent.copyfrom_path
570 parents = [self.revid(prev, ent.copyfrom_path)]
570 parents = [self.revid(prev, ent.copyfrom_path)]
571 self.ui.note('found parent of branch %s at %d: %s\n' % \
571 self.ui.note('found parent of branch %s at %d: %s\n' % \
572 (self.module, prev, ent.copyfrom_path))
572 (self.module, prev, ent.copyfrom_path))
573 else:
573 else:
574 self.ui.debug("No copyfrom path, don't know what to do.\n")
574 self.ui.debug("No copyfrom path, don't know what to do.\n")
575
575
576 self.modulemap[revnum] = self.module # track backwards in time
576 self.modulemap[revnum] = self.module # track backwards in time
577
577
578 orig_paths = orig_paths.items()
578 orig_paths = orig_paths.items()
579 orig_paths.sort()
579 orig_paths.sort()
580 paths = []
580 paths = []
581 # filter out unrelated paths
581 # filter out unrelated paths
582 for path, ent in orig_paths:
582 for path, ent in orig_paths:
583 if not path.startswith(self.module):
583 if not path.startswith(self.module):
584 self.ui.debug("boring@%s: %s\n" % (revnum, path))
584 self.ui.debug("boring@%s: %s\n" % (revnum, path))
585 continue
585 continue
586 paths.append((path, ent))
586 paths.append((path, ent))
587
587
588 self.paths[rev] = (paths, parents)
588 self.paths[rev] = (paths, parents)
589
589
590 # Example SVN datetime. Includes microseconds.
590 # Example SVN datetime. Includes microseconds.
591 # ISO-8601 conformant
591 # ISO-8601 conformant
592 # '2007-01-04T17:35:00.902377Z'
592 # '2007-01-04T17:35:00.902377Z'
593 date = util.parsedate(date[:18] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
593 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
594
594
595 log = message and self.recode(message)
595 log = message and self.recode(message)
596 author = author and self.recode(author) or ''
596 author = author and self.recode(author) or ''
597 try:
597 try:
598 branch = self.module.split("/")[-1]
598 branch = self.module.split("/")[-1]
599 if branch == 'trunk':
599 if branch == 'trunk':
600 branch = ''
600 branch = ''
601 except IndexError:
601 except IndexError:
602 branch = None
602 branch = None
603
603
604 cset = commit(author=author,
604 cset = commit(author=author,
605 date=util.datestr(date),
605 date=util.datestr(date),
606 desc=log,
606 desc=log,
607 parents=parents,
607 parents=parents,
608 branch=branch,
608 branch=branch,
609 rev=rev.encode('utf-8'))
609 rev=rev.encode('utf-8'))
610
610
611 self.commits[rev] = cset
611 self.commits[rev] = cset
612 if self.child_cset and not self.child_cset.parents:
612 if self.child_cset and not self.child_cset.parents:
613 self.child_cset.parents = [rev]
613 self.child_cset.parents = [rev]
614 self.child_cset = cset
614 self.child_cset = cset
615
615
616 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
616 self.ui.note('fetching revision log for "%s" from %d to %d\n' %
617 (self.module, from_revnum, to_revnum))
617 (self.module, from_revnum, to_revnum))
618
618
619 try:
619 try:
620 for entry in self.get_log([self.module], from_revnum, to_revnum):
620 for entry in self.get_log([self.module], from_revnum, to_revnum):
621 orig_paths, revnum, author, date, message = entry
621 orig_paths, revnum, author, date, message = entry
622 if self.is_blacklisted(revnum):
622 if self.is_blacklisted(revnum):
623 self.ui.note('skipping blacklisted revision %d\n' % revnum)
623 self.ui.note('skipping blacklisted revision %d\n' % revnum)
624 continue
624 continue
625 if orig_paths is None:
625 if orig_paths is None:
626 self.ui.debug('revision %d has no entries\n' % revnum)
626 self.ui.debug('revision %d has no entries\n' % revnum)
627 continue
627 continue
628 parselogentry(orig_paths, revnum, author, date, message)
628 parselogentry(orig_paths, revnum, author, date, message)
629 except SubversionException, (inst, num):
629 except SubversionException, (inst, num):
630 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
630 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
631 raise NoSuchRevision(branch=self,
631 raise NoSuchRevision(branch=self,
632 revision="Revision number %d" % to_revnum)
632 revision="Revision number %d" % to_revnum)
633 raise
633 raise
634
634
635 def _getfile(self, file, rev):
635 def _getfile(self, file, rev):
636 io = StringIO()
636 io = StringIO()
637 # TODO: ra.get_file transmits the whole file instead of diffs.
637 # TODO: ra.get_file transmits the whole file instead of diffs.
638 mode = ''
638 mode = ''
639 try:
639 try:
640 revnum = self.revnum(rev)
640 revnum = self.revnum(rev)
641 if self.module != self.modulemap[revnum]:
641 if self.module != self.modulemap[revnum]:
642 self.module = self.modulemap[revnum]
642 self.module = self.modulemap[revnum]
643 self.reparent(self.module)
643 self.reparent(self.module)
644 info = svn.ra.get_file(self.ra, file, revnum, io)
644 info = svn.ra.get_file(self.ra, file, revnum, io)
645 if isinstance(info, list):
645 if isinstance(info, list):
646 info = info[-1]
646 info = info[-1]
647 mode = ("svn:executable" in info) and 'x' or ''
647 mode = ("svn:executable" in info) and 'x' or ''
648 mode = ("svn:special" in info) and 'l' or mode
648 mode = ("svn:special" in info) and 'l' or mode
649 except SubversionException, e:
649 except SubversionException, e:
650 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
650 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
651 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
651 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
652 if e.apr_err in notfound: # File not found
652 if e.apr_err in notfound: # File not found
653 raise IOError()
653 raise IOError()
654 raise
654 raise
655 data = io.getvalue()
655 data = io.getvalue()
656 if mode == 'l':
656 if mode == 'l':
657 link_prefix = "link "
657 link_prefix = "link "
658 if data.startswith(link_prefix):
658 if data.startswith(link_prefix):
659 data = data[len(link_prefix):]
659 data = data[len(link_prefix):]
660 return data, mode
660 return data, mode
661
661
662 def _find_children(self, path, revnum):
662 def _find_children(self, path, revnum):
663 path = path.strip('/')
663 path = path.strip('/')
664 pool = Pool()
664 pool = Pool()
665 rpath = '/'.join([self.base, path]).strip('/')
665 rpath = '/'.join([self.base, path]).strip('/')
666 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
666 return ['%s/%s' % (path, x) for x in svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
General Comments 0
You need to be logged in to leave comments. Login now