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