100
169
def __getitem__(self, index):
101
170
"""Get the date of the index'd item"""
102
return datetime.datetime.fromtimestamp(self.repository.get_revision(
103
self.revid_list[index]).timestamp)
171
return datetime.datetime.fromtimestamp(self.repository.get_revision(self.revid_list[index]).timestamp)
105
173
def __len__(self):
106
174
return len(self.revid_list)
108
class FileChangeReporter(object):
110
def __init__(self, old_inv, new_inv):
115
self.text_changes = []
116
self.old_inv = old_inv
117
self.new_inv = new_inv
119
def revid(self, inv, file_id):
121
return inv[file_id].revision
122
except bzrlib.errors.NoSuchId:
125
def report(self, file_id, paths, versioned, renamed, modified,
127
if modified not in ('unchanged', 'kind changed'):
128
if versioned == 'removed':
129
filename = rich_filename(paths[0], kind[0])
131
filename = rich_filename(paths[1], kind[1])
132
self.text_changes.append(util.Container(
133
filename=filename, file_id=file_id,
134
old_revision=self.revid(self.old_inv, file_id),
135
new_revision=self.revid(self.new_inv, file_id)))
136
if versioned == 'added':
137
self.added.append(util.Container(
138
filename=rich_filename(paths[1], kind),
139
file_id=file_id, kind=kind[1]))
140
elif versioned == 'removed':
141
self.removed.append(util.Container(
142
filename=rich_filename(paths[0], kind),
143
file_id=file_id, kind=kind[0]))
145
self.renamed.append(util.Container(
146
old_filename=rich_filename(paths[0], kind[0]),
147
new_filename=rich_filename(paths[1], kind[1]),
149
text_modified=modified == 'modified'))
151
self.modified.append(util.Container(
152
filename=rich_filename(paths[1], kind),
155
# The lru_cache is not thread-safe, so we need a lock around it for
157
rev_info_memory_cache_lock = threading.RLock()
159
class RevInfoMemoryCache(object):
160
"""A store that validates values against the revids they were stored with.
162
We use a unique key for each branch.
164
The reason for not just using the revid as the key is so that when a new
165
value is provided for a branch, we replace the old value used for the
168
There is another implementation of the same interface in
169
loggerhead.changecache.RevInfoDiskCache.
172
def __init__(self, cache):
175
def get(self, key, revid):
176
"""Return the data associated with `key`, subject to a revid check.
178
If a value was stored under `key`, with the same revid, return it.
179
Otherwise return None.
181
rev_info_memory_cache_lock.acquire()
183
cached = self._cache.get(key)
185
rev_info_memory_cache_lock.release()
188
stored_revid, data = cached
189
if revid == stored_revid:
194
def set(self, key, revid, data):
195
"""Store `data` under `key`, to be checked against `revid` on get().
197
rev_info_memory_cache_lock.acquire()
199
self._cache[key] = (revid, data)
201
rev_info_memory_cache_lock.release()
203
# Used to store locks that prevent multiple threads from building a
204
# revision graph for the same branch at the same time, because that can
205
# cause severe performance issues that are so bad that the system seems
207
revision_graph_locks = {}
208
revision_graph_check_lock = threading.Lock()
210
class History(object):
211
"""Decorate a branch to provide information for rendering.
213
History objects are expected to be short lived -- when serving a request
214
for a particular branch, open it, read-lock it, wrap a History object
215
around it, serve the request, throw the History object away, unlock the
216
branch and throw it away.
218
:ivar _rev_info: A list of information about revisions. This is by far
219
the most cryptic data structure in loggerhead. At the top level, it
220
is a list of 3-tuples [(merge-info, where-merged, parents)].
221
`merge-info` is (seq, revid, merge_depth, revno_str, end_of_merge) --
222
like a merged sorted list, but the revno is stringified.
223
`where-merged` is a tuple of revisions that have this revision as a
224
non-lefthand parent. Finally, `parents` is just the usual list of
225
parents of this revision.
226
:ivar _rev_indices: A dictionary mapping each revision id to the index of
227
the information about it in _rev_info.
228
:ivar _revno_revid: A dictionary mapping stringified revnos to revision
232
def _load_whole_history_data(self, caches, cache_key):
233
"""Set the attributes relating to the whole history of the branch.
235
:param caches: a list of caches with interfaces like
236
`RevInfoMemoryCache` and be ordered from fastest to slowest.
237
:param cache_key: the key to use with the caches.
239
self._rev_indices = None
240
self._rev_info = None
243
def update_missed_caches():
244
for cache in missed_caches:
245
cache.set(cache_key, self.last_revid, self._rev_info)
247
# Theoretically, it's possible for two threads to race in creating
248
# the Lock() object for their branch, so we put a lock around
249
# creating the per-branch Lock().
250
revision_graph_check_lock.acquire()
252
if cache_key not in revision_graph_locks:
253
revision_graph_locks[cache_key] = threading.Lock()
255
revision_graph_check_lock.release()
257
revision_graph_locks[cache_key].acquire()
260
data = cache.get(cache_key, self.last_revid)
262
self._rev_info = data
263
update_missed_caches()
266
missed_caches.append(cache)
268
whole_history_data = compute_whole_history_data(self._branch)
269
self._rev_info, self._rev_indices = whole_history_data
270
update_missed_caches()
272
revision_graph_locks[cache_key].release()
274
if self._rev_indices is not None:
275
self._revno_revid = {}
276
for ((_, revid, _, revno_str, _), _, _) in self._rev_info:
277
self._revno_revid[revno_str] = revid
279
self._revno_revid = {}
280
self._rev_indices = {}
281
for ((seq, revid, _, revno_str, _), _, _) in self._rev_info:
282
self._rev_indices[revid] = seq
283
self._revno_revid[revno_str] = revid
285
def __init__(self, branch, whole_history_data_cache,
286
revinfo_disk_cache=None, cache_key=None):
287
assert branch.is_locked(), (
288
"Can only construct a History object with a read-locked branch.")
177
class History (object):
180
self._change_cache = None
181
self._file_change_cache = None
183
self._lock = threading.RLock()
186
def from_branch(cls, branch, name=None):
289
189
self._branch = branch
290
self._branch_tags = None
291
self._inventory_cache = {}
292
self._branch_nick = self._branch.get_config().get_nickname()
293
self.log = logging.getLogger('loggerhead.%s' % (self._branch_nick,))
295
self.last_revid = branch.last_revision()
297
caches = [RevInfoMemoryCache(whole_history_data_cache)]
298
if revinfo_disk_cache:
299
caches.append(revinfo_disk_cache)
300
self._load_whole_history_data(caches, cache_key)
303
def has_revisions(self):
304
return not bzrlib.revision.is_null(self.last_revid)
190
self._last_revid = self._branch.last_revision()
191
if self._last_revid is not None:
192
self._revision_graph = branch.repository.get_revision_graph(self._last_revid)
194
self._revision_graph = {}
197
name = self._branch.nick
199
self.log = logging.getLogger('loggerhead.%s' % (name,))
201
self._full_history = []
202
self._revision_info = {}
203
self._revno_revid = {}
204
self._merge_sort = bzrlib.tsort.merge_sort(self._revision_graph, self._last_revid, generate_revno=True)
205
for (seq, revid, merge_depth, revno, end_of_merge) in self._merge_sort:
206
self._full_history.append(revid)
207
revno_str = '.'.join(str(n) for n in revno)
208
self._revno_revid[revno_str] = revid
209
self._revision_info[revid] = (seq, revid, merge_depth, revno_str, end_of_merge)
212
self._where_merged = {}
213
for revid in self._revision_graph.keys():
214
if not revid in self._full_history:
216
for parent in self._revision_graph[revid]:
217
self._where_merged.setdefault(parent, set()).add(revid)
219
self.log.info('built revision graph cache: %r secs' % (time.time() - z,))
223
def from_folder(cls, path, name=None):
224
b = bzrlib.branch.Branch.open(path)
225
return cls.from_branch(b, name)
228
def out_of_date(self):
229
# the branch may have been upgraded on disk, in which case we're stale.
230
if self._branch.__class__ is not \
231
bzrlib.branch.Branch.open(self._branch.base).__class__:
233
return self._branch.last_revision() != self._last_revid
235
def use_cache(self, cache):
236
self._change_cache = cache
238
def use_file_cache(self, cache):
239
self._file_change_cache = cache
241
def use_search_index(self, index):
246
# called when a new history object needs to be created, because the
247
# branch history has changed. we need to immediately close and stop
248
# using our caches, because a new history object will be created to
249
# replace us, using the same cache files.
250
# (may also be called during server shutdown.)
251
if self._change_cache is not None:
252
self._change_cache.close()
253
self._change_cache = None
254
if self._index is not None:
258
def flush_cache(self):
259
if self._change_cache is None:
261
self._change_cache.flush()
263
def check_rebuild(self):
264
if self._change_cache is not None:
265
self._change_cache.check_rebuild()
266
if self._index is not None:
267
self._index.check_rebuild()
269
last_revid = property(lambda self: self._last_revid, None, None)
306
272
def get_config(self):
307
273
return self._branch.get_config()
309
276
def get_revno(self, revid):
310
if revid not in self._rev_indices:
277
if revid not in self._revision_info:
313
seq = self._rev_indices[revid]
314
revno = self._rev_info[seq][0][3]
317
def get_revids_from(self, revid_list, start_revid):
319
Yield the mainline (wrt start_revid) revisions that merged each
322
if revid_list is None:
323
# Just yield the mainline, starting at start_revid
325
is_null = bzrlib.revision.is_null
326
while not is_null(revid):
280
seq, revid, merge_depth, revno_str, end_of_merge = self._revision_info[revid]
283
def get_revision_history(self):
284
return self._full_history
286
def get_revids_from(self, revid_list, revid):
288
given a list of revision ids, yield revisions in graph order,
289
starting from revid. the list can be None if you just want to travel
290
across all revisions.
293
if (revid_list is None) or (revid in revid_list):
328
parents = self._rev_info[self._rev_indices[revid]][2]
333
revid_set = set(revid_list)
336
def introduced_revisions(revid):
338
seq = self._rev_indices[revid]
339
md = self._rev_info[seq][0][2]
341
while i < len(self._rev_info) and self._rev_info[i][0][2] > md:
342
r.add(self._rev_info[i][0][1])
346
if bzrlib.revision.is_null(revid):
295
if not self._revision_graph.has_key(revid):
348
rev_introduced = introduced_revisions(revid)
349
matching = rev_introduced.intersection(revid_set)
351
# We don't need to look for these anymore.
352
revid_set.difference_update(matching)
354
parents = self._rev_info[self._rev_indices[revid]][2]
297
parents = self._revision_graph[revid]
355
298
if len(parents) == 0:
357
300
revid = parents[0]
359
303
def get_short_revision_history_by_fileid(self, file_id):
304
# wow. is this really the only way we can get this list? by
305
# man-handling the weave store directly? :-0
360
306
# FIXME: would be awesome if we could get, for a folder, the list of
361
# revisions where items within that folder changed.i
362
possible_keys = [(file_id, revid) for revid in self._rev_indices]
363
get_parent_map = self._branch.repository.texts.get_parent_map
364
# We chunk the requests as this works better with GraphIndex.
365
# See _filter_revisions_touching_file_id in bzrlib/log.py
366
# for more information.
369
for start in xrange(0, len(possible_keys), chunk_size):
370
next_keys = possible_keys[start:start + chunk_size]
371
revids += [k[1] for k in get_parent_map(next_keys)]
372
del possible_keys, next_keys
307
# revisions where items within that folder changed.
308
w = self._branch.repository.weave_store.get_weave(file_id, self._branch.repository.get_transaction())
309
w_revids = w.versions()
310
revids = [r for r in self._full_history if r in w_revids]
375
314
def get_revision_history_since(self, revid_list, date):
376
315
# if a user asks for revisions starting at 01-sep, they mean inclusive,
377
316
# so start at midnight on 02-sep.
378
317
date = date + datetime.timedelta(days=1)
379
# our revid list is sorted in REVERSE date order,
380
# so go thru some hoops here...
318
# our revid list is sorted in REVERSE date order, so go thru some hoops here...
381
319
revid_list.reverse()
382
index = bisect.bisect(_RevListToTimestamps(revid_list,
383
self._branch.repository),
320
index = bisect.bisect(_RevListToTimestamps(revid_list, self._branch.repository), date)
387
323
revid_list.reverse()
389
325
return revid_list[index:]
328
def get_revision_history_matching(self, revid_list, text):
329
self.log.debug('searching %d revisions for %r', len(revid_list), text)
331
# this is going to be painfully slow. :(
334
for revid in revid_list:
335
change = self.get_changes([ revid ])[0]
336
if text in change.comment.lower():
338
self.log.debug('searched %d revisions for %r in %r secs', len(revid_list), text, time.time() - z)
341
def get_revision_history_matching_indexed(self, revid_list, text):
342
self.log.debug('searching %d revisions for %r', len(revid_list), text)
344
if self._index is None:
345
return self.get_revision_history_matching(revid_list, text)
346
out = self._index.find(text, revid_list)
347
self.log.debug('searched %d revisions for %r in %r secs: %d results', len(revid_list), text, time.time() - z, len(out))
348
# put them in some coherent order :)
349
out = [r for r in self._full_history if r in out]
391
353
def get_search_revid_list(self, query, revid_list):
393
355
given a "quick-search" query, try a few obvious possible meanings:
395
357
- revision id or # ("128.1.3")
396
- date (US style "mm/dd/yy", earth style "dd-mm-yy", or \
397
iso style "yyyy-mm-dd")
358
- date (US style "mm/dd/yy", earth style "dd-mm-yy", or iso style "yyyy-mm-dd")
398
359
- comment text as a fallback
400
361
and return a revid list that matches.
468
422
If file_id is None, the entire revision history is the list scope.
470
424
if revid is None:
471
revid = self.last_revid
425
revid = self._last_revid
472
426
if file_id is not None:
474
self.get_short_revision_history_by_fileid(file_id))
475
revlist = self.get_revids_from(revlist, revid)
427
# since revid is 'start_revid', possibly should start the path
428
# tracing from revid... FIXME
429
revlist = list(self.get_short_revision_history_by_fileid(file_id))
430
revlist = list(self.get_revids_from(revlist, revid))
477
revlist = self.get_revids_from(None, revid)
432
revlist = list(self.get_revids_from(None, revid))
481
def _iterate_sufficiently(iterable, stop_at, extra_rev_count):
482
"""Return a list of iterable.
484
If extra_rev_count is None, fully consume iterable.
485
Otherwise, stop at 'stop_at' + extra_rev_count.
488
iterate until you find stop_at, then iterate 10 more times.
490
if extra_rev_count is None:
491
return list(iterable)
500
for count, n in enumerate(iterable):
501
if count >= extra_rev_count:
506
def get_view(self, revid, start_revid, file_id, query=None,
507
extra_rev_count=None):
436
def get_view(self, revid, start_revid, file_id, query=None):
509
438
use the URL parameters (revid, start_revid, file_id, and query) to
510
439
determine the revision list we're viewing (start_revid, file_id, query)
511
440
and where we are in it (revid).
513
- if a query is given, we're viewing query results.
514
- if a file_id is given, we're viewing revisions for a specific
516
- if a start_revid is given, we're viewing the branch from a
517
specific revision up the tree.
518
- if extra_rev_count is given, find the view from start_revid =>
519
revid, and continue an additional 'extra_rev_count'. If not
520
given, then revid_list will contain the full history of
523
these may be combined to view revisions for a specific file, from
524
a specific revision, with a specific search query.
526
returns a new (revid, start_revid, revid_list) where:
442
if a query is given, we're viewing query results.
443
if a file_id is given, we're viewing revisions for a specific file.
444
if a start_revid is given, we're viewing the branch from a
445
specific revision up the tree.
446
(these may be combined to view revisions for a specific file, from
447
a specific revision, with a specific search query.)
449
returns a new (revid, start_revid, revid_list, scan_list) where:
528
451
- revid: current position within the view
529
452
- start_revid: starting revision of this view
687
def get_changes_uncached(self, revid_list):
688
# FIXME: deprecated method in getting a null revision
689
revid_list = filter(lambda revid: not bzrlib.revision.is_null(revid),
691
parent_map = self._branch.repository.get_graph().get_parent_map(
693
# We need to return the answer in the same order as the input,
695
present_revids = [revid for revid in revid_list
696
if revid in parent_map]
697
rev_list = self._branch.repository.get_revisions(present_revids)
699
return [self._change_from_revision(rev) for rev in rev_list]
701
def _change_from_revision(self, revision):
703
Given a bzrlib Revision, return a processed "change" for use in
603
# alright, let's profile this sucka.
604
def _get_changes_profiled(self, revid_list, get_diffs=False):
605
from loggerhead.lsprof import profile
607
ret, stats = profile(self.get_changes_uncached, revid_list, get_diffs)
610
cPickle.dump(stats, open('lsprof.stats', 'w'), 2)
611
self.log.info('lsprof complete!')
614
def _get_deltas_for_revisions_with_trees(self, entries):
615
"""Produce a generator of revision deltas.
617
Note that the input is a sequence of REVISIONS, not revision_ids.
618
Trees will be held in memory until the generator exits.
619
Each delta is relative to the revision's lefthand predecessor.
621
required_trees = set()
622
for entry in entries:
623
required_trees.add(entry.revid)
624
required_trees.update([p.revid for p in entry.parents[:1]])
625
trees = dict((t.get_revision_id(), t) for
626
t in self._branch.repository.revision_trees(required_trees))
628
self._branch.repository.lock_read()
630
for entry in entries:
631
if not entry.parents:
632
old_tree = self._branch.repository.revision_tree("null:")
634
old_tree = trees[entry.parents[0].revid]
635
tree = trees[entry.revid]
636
ret.append(tree.changes_from(old_tree))
639
self._branch.repository.unlock()
641
def entry_from_revision(self, revision):
642
commit_time = datetime.datetime.fromtimestamp(revision.timestamp)
644
parents = [util.Container(revid=r, revno=self.get_revno(r)) for r in revision.parent_ids]
706
646
message, short_message = clean_message(revision.message)
708
if self._branch_tags is None:
709
self._branch_tags = self._branch.tags.get_reverse_tag_dict()
712
if revision.revision_id in self._branch_tags:
713
# tag.sort_* functions expect (tag, data) pairs, so we generate them,
714
# and then strip them
715
tags = [(t, None) for t in self._branch_tags[revision.revision_id]]
716
sort_func = getattr(tag, 'sort_natural', None)
717
if sort_func is None:
720
sort_func(self._branch, tags)
721
revtags = u', '.join([t[0] for t in tags])
724
649
'revid': revision.revision_id,
725
'date': datetime.datetime.fromtimestamp(revision.timestamp),
726
'utc_date': datetime.datetime.utcfromtimestamp(revision.timestamp),
727
'committer': revision.committer,
728
'authors': revision.get_apparent_authors(),
651
'author': revision.committer,
729
652
'branch_nick': revision.properties.get('branch-nick', None),
730
653
'short_comment': short_message,
731
654
'comment': revision.message,
732
655
'comment_clean': [util.html_clean(s) for s in message],
733
'parents': revision.parent_ids,
734
'bugs': [bug.split()[0] for bug in revision.properties.get('bugs', '').splitlines()],
737
if isinstance(revision, bzrlib.foreign.ForeignRevision):
738
foreign_revid, mapping = (
739
revision.foreign_revid, revision.mapping)
740
elif ":" in revision.revision_id:
742
foreign_revid, mapping = \
743
bzrlib.foreign.foreign_vcs_registry.parse_revision_id(
744
revision.revision_id)
745
except bzrlib.errors.InvalidRevisionId:
750
if foreign_revid is not None:
751
entry["foreign_vcs"] = mapping.vcs.abbreviation
752
entry["foreign_revid"] = mapping.vcs.show_foreign_revid(foreign_revid)
753
658
return util.Container(entry)
755
def get_file_changes(self, entry):
757
old_revid = entry.parents[0].revid
661
def get_changes_uncached(self, revid_list):
662
# Because we may loop and call get_revisions multiple times (to throw
663
# out dud revids), we grab a read lock.
664
self._branch.lock_read()
668
rev_list = self._branch.repository.get_revisions(revid_list)
669
except (KeyError, bzrlib.errors.NoSuchRevision), e:
670
# this sometimes happens with arch-converted branches.
671
# i don't know why. :(
672
self.log.debug('No such revision (skipping): %s', e)
673
revid_list.remove(e.revision)
677
return [self.entry_from_revision(rev) for rev in rev_list]
679
self._branch.unlock()
681
def get_file_changes_uncached(self, entries):
682
delta_list = self._get_deltas_for_revisions_with_trees(entries)
684
return [self.parse_delta(delta) for delta in delta_list]
687
def get_file_changes(self, entries):
688
if self._file_change_cache is None:
689
return self.get_file_changes_uncached(entries)
759
old_revid = bzrlib.revision.NULL_REVISION
760
return self.file_changes_for_revision_ids(old_revid, entry.revid)
762
def add_changes(self, entry):
763
changes = self.get_file_changes(entry)
764
entry.changes = changes
691
return self._file_change_cache.get_file_changes(entries)
693
def add_changes(self, entries):
694
changes_list = self.get_file_changes(entries)
696
for entry, changes in zip(entries, changes_list):
697
entry.changes = changes
700
def get_change_with_diff(self, revid, compare_revid=None):
701
entry = self.get_changes([revid])[0]
703
if compare_revid is None:
705
compare_revid = entry.parents[0].revid
707
compare_revid = 'null:'
709
rev_tree1 = self._branch.repository.revision_tree(compare_revid)
710
rev_tree2 = self._branch.repository.revision_tree(revid)
711
delta = rev_tree2.changes_from(rev_tree1)
713
entry.changes = self.parse_delta(delta)
715
entry.changes.modified = self._parse_diffs(rev_tree1, rev_tree2, delta)
766
720
def get_file(self, file_id, revid):
767
"""Returns (path, filename, file contents)"""
721
"returns (path, filename, data)"
768
722
inv = self.get_inventory(revid)
769
723
inv_entry = inv[file_id]
770
724
rev_tree = self._branch.repository.revision_tree(inv_entry.revision)
787
text_changes: list((filename, file_id)),
789
repo = self._branch.repository
790
if (bzrlib.revision.is_null(old_revid) or
791
bzrlib.revision.is_null(new_revid)):
792
old_tree, new_tree = map(
793
repo.revision_tree, [old_revid, new_revid])
795
old_tree, new_tree = repo.revision_trees([old_revid, new_revid])
797
reporter = FileChangeReporter(old_tree.inventory, new_tree.inventory)
799
bzrlib.delta.report_changes(new_tree.iter_changes(old_tree), reporter)
801
return util.Container(
802
added=sorted(reporter.added, key=lambda x: x.filename),
803
renamed=sorted(reporter.renamed, key=lambda x: x.new_filename),
804
removed=sorted(reporter.removed, key=lambda x: x.filename),
805
modified=sorted(reporter.modified, key=lambda x: x.filename),
806
text_changes=sorted(reporter.text_changes, key=lambda x: x.filename))
829
for path, fid, kind in delta.added:
830
added.append((rich_filename(path, kind), fid))
832
for path, fid, kind, text_modified, meta_modified in delta.modified:
833
modified.append(util.Container(filename=rich_filename(path, kind), file_id=fid))
835
for old_path, new_path, fid, kind, text_modified, meta_modified in delta.renamed:
836
renamed.append((rich_filename(old_path, kind), rich_filename(new_path, kind), fid))
837
if meta_modified or text_modified:
838
modified.append(util.Container(filename=rich_filename(new_path, kind), file_id=fid))
840
for path, fid, kind in delta.removed:
841
removed.append((rich_filename(path, kind), fid))
843
return util.Container(added=added, renamed=renamed, removed=removed, modified=modified)
846
def add_side_by_side(changes):
847
# FIXME: this is a rotten API.
848
for change in changes:
849
for m in change.changes.modified:
850
m.sbs_chunks = _make_side_by_side(m.chunks)
853
def get_filelist(self, inv, file_id, sort_type=None):
855
return the list of all files (and their attributes) within a given
859
dir_ie = inv[file_id]
860
path = inv.id2path(file_id)
865
for filename, entry in dir_ie.children.iteritems():
866
revid_set.add(entry.revision)
869
for change in self.get_changes(list(revid_set)):
870
change_dict[change.revid] = change
872
for filename, entry in dir_ie.children.iteritems():
874
if entry.kind == 'directory':
877
revid = entry.revision
879
file = util.Container(
880
filename=filename, executable=entry.executable, kind=entry.kind,
881
pathname=pathname, file_id=entry.file_id, size=entry.text_size,
882
revid=revid, change=change_dict[revid])
883
file_list.append(file)
885
if sort_type == 'filename' or sort_type is None:
886
file_list.sort(key=lambda x: x.filename)
887
elif sort_type == 'size':
888
file_list.sort(key=lambda x: x.size)
889
elif sort_type == 'date':
890
file_list.sort(key=lambda x: x.change.date)
893
for file in file_list:
900
_BADCHARS_RE = re.compile(ur'[\x00-\x08\x0b\x0e-\x1f]')
903
def annotate_file(self, file_id, revid):
908
file_revid = self.get_inventory(revid)[file_id].revision
911
# because we cache revision metadata ourselves, it's actually much
912
# faster to call 'annotate_iter' on the weave directly than it is to
913
# ask bzrlib to annotate for us.
914
w = self._branch.repository.weave_store.get_weave(file_id, self._branch.repository.get_transaction())
917
for line_revid, text in w.annotate_iter(file_revid):
918
revid_set.add(line_revid)
919
if self._BADCHARS_RE.match(text):
920
# bail out; this isn't displayable text
921
yield util.Container(parity=0, lineno=1, status='same',
922
text='(This is a binary file.)',
923
change=util.Container())
925
change_cache = dict([(c.revid, c) for c in self.get_changes(list(revid_set))])
927
last_line_revid = None
928
for line_revid, text in w.annotate_iter(file_revid):
929
if line_revid == last_line_revid:
930
# remember which lines have a new revno and which don't
935
last_line_revid = line_revid
936
change = change_cache[line_revid]
937
trunc_revno = change.revno
938
if len(trunc_revno) > 10:
939
trunc_revno = trunc_revno[:9] + '...'
941
yield util.Container(parity=parity, lineno=lineno, status=status,
942
change=change, text=util.fixed_width(text))
945
self.log.debug('annotate: %r secs' % (time.time() - z,))
948
def get_bundle(self, revid, compare_revid=None):
949
if compare_revid is None:
950
parents = self._revision_graph[revid]
952
compare_revid = parents[0]
956
bzrlib.bundle.serializer.write_bundle(self._branch.repository, revid, compare_revid, s)