1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 from itertools import izip
20 from tempfile import TemporaryFile
21 from zipfile import ZipFile, ZIP_DEFLATED
22
23 from genshi.builder import tag
24
25 from trac.resource import ResourceNotFound
26 from trac.util import content_disposition, create_zipinfo
27 from trac.util.datefmt import http_date
28 from trac.util.translation import tag_, _
29 from trac.versioncontrol.api import EmptyChangeset, NoSuchChangeset, \
30 NoSuchNode
31 from trac.web.api import RequestDone
32
33 __all__ = ['content_closing', 'get_changes', 'get_path_links',
34 'get_existing_node', 'get_allowed_node', 'make_log_graph',
35 'render_zip']
36
37
38 -class content_closing(object):
39
40 - def __init__(self, content):
41 self.content = content
42
43 - def __enter__(self):
45
46 - def __exit__(self, *exc_info):
47 if hasattr(self.content, 'close'):
48 self.content.close()
49
50
65
66
68 desc = desc or None
69 links = [{'name': 'source:',
70 'href': href.browser(rev=rev if reponame == '' else None,
71 order=order, desc=desc)}]
72 if reponame:
73 links.append({
74 'name': reponame,
75 'href': href.browser(reponame, rev=rev, order=order, desc=desc)})
76 partial_path = ''
77 for part in [p for p in path.split('/') if p]:
78 partial_path += part + '/'
79 links.append({
80 'name': part,
81 'href': href.browser(reponame or None, partial_path, rev=rev,
82 order=order, desc=desc)
83 })
84 return links
85
86
88 try:
89 return repos.get_node(path, rev)
90 except NoSuchNode as e:
91
92 search_a = tag.a(_("search"),
93 href=req.href.log(repos.reponame or None, path,
94 rev=rev, mode='path_history'))
95 raise ResourceNotFound(tag(
96 tag.p(e, class_="message"),
97 tag.p(tag_("You can %(search)s in the repository history to see "
98 "if that path existed but was later removed",
99 search=search_a))))
100
101
110
111
113 """Generate graph information for the given revisions.
114
115 Returns a tuple `(threads, vertices, columns)`, where:
116
117 * `threads`: List of paint command lists `[(type, column, line)]`, where
118 `type` is either 0 for "move to" or 1 for "line to", and `column` and
119 `line` are coordinates.
120 * `vertices`: List of `(column, thread_index)` tuples, where the `i`th
121 item specifies the column in which to draw the dot in line `i` and the
122 corresponding thread.
123 * `columns`: Maximum width of the graph.
124 """
125 threads = []
126 vertices = []
127 columns = 0
128 revs = iter(revs)
129
130 def add_edge(thread, column, line):
131 if thread and thread[-1][:2] == [1, column] \
132 and thread[-2][1] == column:
133 thread[-1][2] = line
134 else:
135 thread.append([1, column, line])
136
137 try:
138 next_rev = revs.next()
139 line = 0
140 active = []
141 active_thread = []
142 while True:
143 rev = next_rev
144 if rev not in active:
145
146 threads.append([[0, len(active), line]])
147 active_thread.append(threads[-1])
148 active.append(rev)
149
150 columns = max(columns, len(active))
151 column = active.index(rev)
152 vertices.append((column, threads.index(active_thread[column])))
153
154 next_rev = revs.next()
155 next = active[:]
156 parents = list(repos.parent_revs(rev))
157
158
159 new_parents = [p for p in parents if p not in active]
160 next[column:column + 1] = new_parents
161
162
163 for col, (r, thread) in enumerate(izip(active, active_thread)):
164 if r in next:
165 add_edge(thread, next.index(r), line + 1)
166 elif r == rev:
167 if new_parents:
168 parents.remove(new_parents[0])
169 parents.append(new_parents[0])
170 for parent in parents:
171 if parent != parents[0]:
172 thread.append([0, col, line])
173 add_edge(thread, next.index(parent), line + 1)
174
175 if not new_parents:
176 del active_thread[column]
177 else:
178 base = len(threads)
179 threads.extend([[0, column + 1 + i, line + 1]]
180 for i in xrange(len(new_parents) - 1))
181 active_thread[column + 1:column + 1] = threads[base:]
182
183 active = next
184 line += 1
185 except StopIteration:
186 pass
187 return threads, vertices, columns
188
189
190 -def render_zip(req, filename, repos, root_node, iter_nodes):
191 """Send a ZIP file containing the data corresponding to the `nodes`
192 iterable.
193
194 :type root_node: `~trac.versioncontrol.api.Node`
195 :param root_node: optional ancestor for all the *nodes*
196
197 :param iter_nodes: callable taking the optional *root_node* as input
198 and generating the `~trac.versioncontrol.api.Node`
199 for which the content should be added into the zip.
200 """
201 req.send_response(200)
202 req.send_header('Content-Type', 'application/zip')
203 req.send_header('Content-Disposition',
204 content_disposition('inline', filename))
205 if root_node:
206 req.send_header('Last-Modified', http_date(root_node.last_modified))
207 root_path = root_node.path.rstrip('/')
208 else:
209 root_path = ''
210 if root_path:
211 root_path += '/'
212 root_name = root_node.name + '/'
213 else:
214 root_name = ''
215 root_len = len(root_path)
216 req.end_headers()
217
218 def write_partial(fileobj, start):
219 end = fileobj.tell()
220 fileobj.seek(start, 0)
221 remaining = end - start
222 while remaining > 0:
223 chunk = fileobj.read(min(remaining, 4096))
224 req.write(chunk)
225 remaining -= len(chunk)
226 fileobj.seek(end, 0)
227 return end
228
229 pos = 0
230 fileobj = TemporaryFile(prefix='trac-', suffix='.zip')
231 try:
232 zipfile = ZipFile(fileobj, 'w', ZIP_DEFLATED)
233 for node in iter_nodes(root_node):
234 if node is root_node:
235 continue
236 path = node.path.strip('/')
237 assert path.startswith(root_path)
238 path = root_name + path[root_len:]
239 kwargs = {'mtime': node.last_modified}
240 data = None
241 if node.isfile:
242 with content_closing(
243 node.get_processed_content(eol_hint='CRLF')) \
244 as content:
245 data = content.read()
246 properties = node.get_properties()
247
248 if 'svn:special' in properties and data.startswith('link '):
249 data = data[5:]
250 kwargs['symlink'] = True
251 if 'svn:executable' in properties:
252 kwargs['executable'] = True
253 elif node.isdir and path:
254 kwargs['dir'] = True
255 data = ''
256 if data is not None:
257 zipfile.writestr(create_zipinfo(path, **kwargs), data)
258 pos = write_partial(fileobj, pos)
259 finally:
260 try:
261 zipfile.close()
262 write_partial(fileobj, pos)
263 finally:
264 fileobj.close()
265 raise RequestDone
266