47
48
import bzrlib.tsort
51
with_branch_lock = util.with_lock('_lock', 'branch')
55
def with_bzrlib_read_lock(unbound):
56
def bzrlib_read_locked(self, *args, **kw):
57
#self.log.debug('-> %r bzr lock', id(threading.currentThread()))
58
self._branch.repository.lock_read()
60
return unbound(self, *args, **kw)
62
self._branch.repository.unlock()
63
#self.log.debug('<- %r bzr lock', id(threading.currentThread()))
64
return bzrlib_read_locked
67
51
# bzrlib's UIFactory is not thread-safe
68
52
uihack = threading.local()
107
91
out_chunk_list = []
108
92
for chunk in chunk_list:
111
94
delete_list, insert_list = [], []
112
95
for line in chunk.diff:
113
# Add <wbr/> every X characters so we can wrap properly
114
wrap_line = re.findall(r'.{%d}|.+$' % 78, line.line)
115
wrap_lines = [util.html_clean(_line) for _line in wrap_line]
116
wrapped_line = wrap_char.join(wrap_lines)
118
96
if line.type == 'context':
119
97
if len(delete_list) or len(insert_list):
120
_process_side_by_side_buffers(line_list, delete_list,
98
_process_side_by_side_buffers(line_list, delete_list, insert_list)
122
99
delete_list, insert_list = [], []
123
line_list.append(util.Container(old_lineno=line.old_lineno,
124
new_lineno=line.new_lineno,
125
old_line=wrapped_line,
126
new_line=wrapped_line,
100
line_list.append(util.Container(old_lineno=line.old_lineno, new_lineno=line.new_lineno,
101
old_line=line.line, new_line=line.line,
102
old_type=line.type, new_type=line.type))
129
103
elif line.type == 'delete':
130
delete_list.append((line.old_lineno, wrapped_line, line.type))
104
delete_list.append((line.old_lineno, line.line, line.type))
131
105
elif line.type == 'insert':
132
insert_list.append((line.new_lineno, wrapped_line, line.type))
106
insert_list.append((line.new_lineno, line.line, line.type))
133
107
if len(delete_list) or len(insert_list):
134
108
_process_side_by_side_buffers(line_list, delete_list, insert_list)
135
109
out_chunk_list.append(util.Container(diff=line_list))
200
174
class History (object):
175
"""Decorate a branch to provide information for rendering.
177
History objects are expected to be short lived -- when serving a request
178
for a particular branch, open it, read-lock it, wrap a History object
179
around it, serve the request, throw the History object away, unlock the
180
branch and throw it away.
182
:ivar _file_change_cache: xx
185
def __init__(self, branch, whole_history_data_cache):
186
assert branch.is_locked(), (
187
"Can only construct a History object with a read-locked branch.")
203
188
self._file_change_cache = None
204
self._lock = threading.RLock()
207
def from_branch(cls, branch):
210
189
self._branch = branch
211
self._last_revid = self._branch.last_revision()
213
self.log = logging.getLogger('loggerhead.%s' % (self._branch.nick,))
215
graph = branch.repository.get_graph()
216
parent_map = dict(((key, value) for key, value in
217
graph.iter_ancestry([self._last_revid]) if value is not None))
219
self._revision_graph = self._strip_NULL_ghosts(parent_map)
220
self._full_history = []
221
self._revision_info = {}
222
self._revno_revid = {}
223
if bzrlib.revision.is_null(self._last_revid):
224
self._merge_sort = []
226
self._merge_sort = bzrlib.tsort.merge_sort(
227
self._revision_graph, self._last_revid, generate_revno=True)
229
for (seq, revid, merge_depth, revno, end_of_merge) in self._merge_sort:
230
self._full_history.append(revid)
231
revno_str = '.'.join(str(n) for n in revno)
232
self._revno_revid[revno_str] = revid
233
self._revision_info[revid] = (
234
seq, revid, merge_depth, revno_str, end_of_merge)
237
self._where_merged = {}
239
for revid in self._revision_graph.keys():
240
if self._revision_info[revid][2] == 0:
242
for parent in self._revision_graph[revid]:
243
self._where_merged.setdefault(parent, set()).add(revid)
245
self.log.info('built revision graph cache: %r secs' % (time.time() - z,))
249
def _strip_NULL_ghosts(revision_graph):
251
Copied over from bzrlib meant as a temporary workaround deprecated
255
# Filter ghosts, and null:
256
if bzrlib.revision.NULL_REVISION in revision_graph:
257
del revision_graph[bzrlib.revision.NULL_REVISION]
258
for key, parents in revision_graph.items():
259
revision_graph[key] = tuple(parent for parent in parents if parent
261
return revision_graph
264
def from_folder(cls, path):
265
b = bzrlib.branch.Branch.open(path)
268
return cls.from_branch(b)
273
def out_of_date(self):
274
# the branch may have been upgraded on disk, in which case we're stale.
275
newly_opened = bzrlib.branch.Branch.open(self._branch.base)
276
if self._branch.__class__ is not \
277
newly_opened.__class__:
279
if self._branch.repository.__class__ is not \
280
newly_opened.repository.__class__:
282
return self._branch.last_revision() != self._last_revid
190
self.log = logging.getLogger('loggerhead.%s' % (branch.nick,))
192
self.last_revid = branch.last_revision()
194
whole_history_data = whole_history_data_cache.get(self.last_revid)
195
if whole_history_data is None:
196
whole_history_data = compute_whole_history_data(branch)
197
whole_history_data_cache[self.last_revid] = whole_history_data
199
(self._revision_graph, self._full_history, self._revision_info,
200
self._revno_revid, self._merge_sort, self._where_merged
201
) = whole_history_data
284
203
def use_file_cache(self, cache):
285
204
self._file_change_cache = cache
332
245
revid = parents[0]
335
247
def get_short_revision_history_by_fileid(self, file_id):
336
248
# wow. is this really the only way we can get this list? by
337
249
# man-handling the weave store directly? :-0
338
250
# FIXME: would be awesome if we could get, for a folder, the list of
339
251
# revisions where items within that folder changed.
340
w = self._branch.repository.weave_store.get_weave(file_id, self._branch.repository.get_transaction())
341
w_revids = w.versions()
342
revids = [r for r in self._full_history if r in w_revids]
252
possible_keys = [(file_id, revid) for revid in self._full_history]
253
existing_keys = self._branch.repository.texts.get_parent_map(possible_keys)
254
return [revid for _, revid in existing_keys.iterkeys()]
346
256
def get_revision_history_since(self, revid_list, date):
347
257
# if a user asks for revisions starting at 01-sep, they mean inclusive,
348
258
# so start at midnight on 02-sep.
480
387
revid_list = self.get_file_view(start_revid, file_id)
482
389
revid_list = None
484
revid_list = self.get_search_revid_list(query, revid_list)
390
revid_list = search.search_revisions(self._branch, query)
485
391
if revid_list and len(revid_list) > 0:
486
392
if revid not in revid_list:
487
393
revid = revid_list[0]
488
394
return revid, start_revid, revid_list
396
# XXX: This should return a message saying that the search could
397
# not be completed due to either missing the plugin or missing a
490
399
return None, None, []
493
401
def get_inventory(self, revid):
494
402
return self._branch.repository.get_revision_inventory(revid)
497
404
def get_path(self, revid, file_id):
498
405
if (file_id is None) or (file_id == ''):
786
686
old_lineno = lines[0]
787
687
new_lineno = lines[1]
788
688
elif line.startswith(' '):
789
chunk.diff.append(util.Container(old_lineno=old_lineno,
790
new_lineno=new_lineno,
689
chunk.diff.append(util.Container(old_lineno=old_lineno, new_lineno=new_lineno,
690
type='context', line=util.fixed_width(line[1:])))
795
693
elif line.startswith('+'):
796
chunk.diff.append(util.Container(old_lineno=None,
797
new_lineno=new_lineno,
798
type='insert', line=line[1:]))
694
chunk.diff.append(util.Container(old_lineno=None, new_lineno=new_lineno,
695
type='insert', line=util.fixed_width(line[1:])))
800
697
elif line.startswith('-'):
801
chunk.diff.append(util.Container(old_lineno=old_lineno,
803
type='delete', line=line[1:]))
698
chunk.diff.append(util.Container(old_lineno=old_lineno, new_lineno=None,
699
type='delete', line=util.fixed_width(line[1:])))
806
chunk.diff.append(util.Container(old_lineno=None,
702
chunk.diff.append(util.Container(old_lineno=None, new_lineno=None,
703
type='unknown', line=util.fixed_width(repr(line))))
810
704
if chunk is not None:
811
705
chunks.append(chunk)