38
from bzrlib import tag
40
from StringIO import StringIO
42
from loggerhead import search
43
from loggerhead import util
44
from loggerhead.wholehistory import compute_whole_history_data
39
47
import bzrlib.branch
40
48
import bzrlib.delta
41
50
import bzrlib.errors
51
import bzrlib.lru_cache
52
import bzrlib.progress
43
53
import bzrlib.revision
45
from loggerhead import search
46
from loggerhead import util
47
from loggerhead.wholehistory import compute_whole_history_data
54
import bzrlib.textfile
58
# bzrlib's UIFactory is not thread-safe
59
uihack = threading.local()
62
class ThreadSafeUIFactory (bzrlib.ui.SilentUIFactory):
64
def nested_progress_bar(self):
65
if getattr(uihack, '_progress_bar_stack', None) is None:
66
pbs = bzrlib.progress.ProgressBarStack(
67
klass=bzrlib.progress.DummyProgress)
68
uihack._progress_bar_stack = pbs
69
return uihack._progress_bar_stack.get_nested()
71
bzrlib.ui.ui_factory = ThreadSafeUIFactory()
50
73
def is_branch(folder):
195
213
def set(self, key, revid, data):
196
214
"""Store `data` under `key`, to be checked against `revid` on get().
198
rev_info_memory_cache_lock.acquire()
200
self._cache[key] = (revid, data)
202
rev_info_memory_cache_lock.release()
204
# Used to store locks that prevent multiple threads from building a
205
# revision graph for the same branch at the same time, because that can
206
# cause severe performance issues that are so bad that the system seems
208
revision_graph_locks = {}
209
revision_graph_check_lock = threading.Lock()
211
class History(object):
216
self._cache[key] = (revid, data)
219
class History (object):
212
220
"""Decorate a branch to provide information for rendering.
214
222
History objects are expected to be short lived -- when serving a request
244
257
def update_missed_caches():
245
258
for cache in missed_caches:
246
259
cache.set(cache_key, self.last_revid, self._rev_info)
248
# Theoretically, it's possible for two threads to race in creating
249
# the Lock() object for their branch, so we put a lock around
250
# creating the per-branch Lock().
251
revision_graph_check_lock.acquire()
253
if cache_key not in revision_graph_locks:
254
revision_graph_locks[cache_key] = threading.Lock()
256
revision_graph_check_lock.release()
258
revision_graph_locks[cache_key].acquire()
261
data = cache.get(cache_key, self.last_revid)
263
self._rev_info = data
264
update_missed_caches()
267
missed_caches.append(cache)
269
whole_history_data = compute_whole_history_data(self._branch)
270
self._rev_info, self._rev_indices = whole_history_data
261
data = cache.get(cache_key, self.last_revid)
263
self._rev_info = data
271
264
update_missed_caches()
273
revision_graph_locks[cache_key].release()
267
missed_caches.append(cache)
269
whole_history_data = compute_whole_history_data(self._branch)
270
self._rev_info, self._rev_indices = whole_history_data
271
update_missed_caches()
275
273
if self._rev_indices is not None:
274
self._full_history = []
276
275
self._revno_revid = {}
277
276
for ((_, revid, _, revno_str, _), _, _) in self._rev_info:
278
277
self._revno_revid[revno_str] = revid
278
self._full_history.append(revid)
280
self._full_history = []
280
281
self._revno_revid = {}
281
282
self._rev_indices = {}
282
283
for ((seq, revid, _, revno_str, _), _, _) in self._rev_info:
283
284
self._rev_indices[revid] = seq
284
285
self._revno_revid[revno_str] = revid
286
self._full_history.append(revid)
286
def __init__(self, branch, whole_history_data_cache,
288
def __init__(self, branch, whole_history_data_cache, file_cache=None,
287
289
revinfo_disk_cache=None, cache_key=None):
288
290
assert branch.is_locked(), (
289
291
"Can only construct a History object with a read-locked branch.")
292
if file_cache is not None:
293
self._file_change_cache = file_cache
294
file_cache.history = self
296
self._file_change_cache = None
290
297
self._branch = branch
291
self._branch_tags = None
292
298
self._inventory_cache = {}
293
299
self._branch_nick = self._branch.get_config().get_nickname()
294
self.log = logging.getLogger('loggerhead.%s' % (self._branch_nick,))
300
self.log = logging.getLogger('loggerhead.%s' % self._branch_nick)
296
302
self.last_revid = branch.last_revision()
360
353
def get_short_revision_history_by_fileid(self, file_id):
361
354
# FIXME: would be awesome if we could get, for a folder, the list of
362
355
# revisions where items within that folder changed.i
363
possible_keys = [(file_id, revid) for revid in self._rev_indices]
364
get_parent_map = self._branch.repository.texts.get_parent_map
365
# We chunk the requests as this works better with GraphIndex.
366
# See _filter_revisions_touching_file_id in bzrlib/log.py
367
# for more information.
370
for start in xrange(0, len(possible_keys), chunk_size):
371
next_keys = possible_keys[start:start + chunk_size]
372
revids += [k[1] for k in get_parent_map(next_keys)]
373
del possible_keys, next_keys
357
# FIXME: Workaround for bzr versions prior to 1.6b3.
358
# Remove me eventually pretty please :)
359
w = self._branch.repository.weave_store.get_weave(
360
file_id, self._branch.repository.get_transaction())
361
w_revids = w.versions()
362
revids = [r for r in self._full_history if r in w_revids]
363
except AttributeError:
364
possible_keys = [(file_id, revid) for revid in self._full_history]
365
get_parent_map = self._branch.repository.texts.get_parent_map
366
# We chunk the requests as this works better with GraphIndex.
367
# See _filter_revisions_touching_file_id in bzrlib/log.py
368
# for more information.
371
for start in xrange(0, len(possible_keys), chunk_size):
372
next_keys = possible_keys[start:start + chunk_size]
373
revids += [k[1] for k in get_parent_map(next_keys)]
374
del possible_keys, next_keys
376
377
def get_revision_history_since(self, revid_list, date):
471
472
if revid is None:
472
473
revid = self.last_revid
473
474
if file_id is not None:
475
self.get_short_revision_history_by_fileid(file_id))
476
revlist = self.get_revids_from(revlist, revid)
475
# since revid is 'start_revid', possibly should start the path
476
# tracing from revid... FIXME
477
revlist = list(self.get_short_revision_history_by_fileid(file_id))
478
revlist = list(self.get_revids_from(revlist, revid))
478
revlist = self.get_revids_from(None, revid)
480
revlist = list(self.get_revids_from(None, revid))
482
def _iterate_sufficiently(iterable, stop_at, extra_rev_count):
483
"""Return a list of iterable.
485
If extra_rev_count is None, fully consume iterable.
486
Otherwise, stop at 'stop_at' + extra_rev_count.
489
iterate until you find stop_at, then iterate 10 more times.
491
if extra_rev_count is None:
492
return list(iterable)
501
for count, n in enumerate(iterable):
502
if count >= extra_rev_count:
507
def get_view(self, revid, start_revid, file_id, query=None,
508
extra_rev_count=None):
483
def get_view(self, revid, start_revid, file_id, query=None):
510
485
use the URL parameters (revid, start_revid, file_id, and query) to
511
486
determine the revision list we're viewing (start_revid, file_id, query)
539
510
if query is None:
540
511
revid_list = self.get_file_view(start_revid, file_id)
541
revid_list = self._iterate_sufficiently(revid_list, revid,
543
512
if revid is None:
544
513
revid = start_revid
545
514
if revid not in revid_list:
546
515
# if the given revid is not in the revlist, use a revlist that
547
516
# starts at the given revid.
548
517
revid_list = self.get_file_view(revid, file_id)
549
revid_list = self._iterate_sufficiently(revid_list, revid,
551
518
start_revid = revid
552
519
return revid, start_revid, revid_list
704
671
Given a bzrlib Revision, return a processed "change" for use in
674
commit_time = datetime.datetime.fromtimestamp(revision.timestamp)
676
parents = [util.Container(revid=r,
677
revno=self.get_revno(r)) for r in revision.parent_ids]
707
679
message, short_message = clean_message(revision.message)
709
if self._branch_tags is None:
710
self._branch_tags = self._branch.tags.get_reverse_tag_dict()
713
if revision.revision_id in self._branch_tags:
714
# tag.sort_* functions expect (tag, data) pairs, so we generate them,
715
# and then strip them
716
tags = [(t, None) for t in self._branch_tags[revision.revision_id]]
717
sort_func = getattr(tag, 'sort_natural', None)
718
if sort_func is None:
721
sort_func(self._branch, tags)
722
revtags = u', '.join([t[0] for t in tags])
682
authors = revision.get_apparent_authors()
683
except AttributeError:
684
authors = [revision.get_apparent_author()]
725
687
'revid': revision.revision_id,
726
'date': datetime.datetime.fromtimestamp(revision.timestamp),
727
'utc_date': datetime.datetime.utcfromtimestamp(revision.timestamp),
728
'committer': revision.committer,
729
'authors': revision.get_apparent_authors(),
730
690
'branch_nick': revision.properties.get('branch-nick', None),
731
691
'short_comment': short_message,
732
692
'comment': revision.message,
733
693
'comment_clean': [util.html_clean(s) for s in message],
734
694
'parents': revision.parent_ids,
735
'bugs': [bug.split()[0] for bug in revision.properties.get('bugs', '').splitlines()],
738
if isinstance(revision, bzrlib.foreign.ForeignRevision):
739
foreign_revid, mapping = (
740
revision.foreign_revid, revision.mapping)
741
elif ":" in revision.revision_id:
743
foreign_revid, mapping = \
744
bzrlib.foreign.foreign_vcs_registry.parse_revision_id(
745
revision.revision_id)
746
except bzrlib.errors.InvalidRevisionId:
751
if foreign_revid is not None:
752
entry["foreign_vcs"] = mapping.vcs.abbreviation
753
entry["foreign_revid"] = mapping.vcs.show_foreign_revid(foreign_revid)
754
696
return util.Container(entry)
756
def get_file_changes(self, entry):
698
def get_file_changes_uncached(self, entry):
699
repo = self._branch.repository
757
700
if entry.parents:
758
701
old_revid = entry.parents[0].revid
760
703
old_revid = bzrlib.revision.NULL_REVISION
761
704
return self.file_changes_for_revision_ids(old_revid, entry.revid)
706
def get_file_changes(self, entry):
707
if self._file_change_cache is None:
708
return self.get_file_changes_uncached(entry)
710
return self._file_change_cache.get_file_changes(entry)
763
712
def add_changes(self, entry):
764
713
changes = self.get_file_changes(entry)
765
714
entry.changes = changes
767
716
def get_file(self, file_id, revid):
768
"""Returns (path, filename, file contents)"""
717
"returns (path, filename, data)"
769
718
inv = self.get_inventory(revid)
770
719
inv_entry = inv[file_id]
771
720
rev_tree = self._branch.repository.revision_tree(inv_entry.revision)
800
749
bzrlib.delta.report_changes(new_tree.iter_changes(old_tree), reporter)
802
751
return util.Container(
803
added=sorted(reporter.added, key=lambda x: x.filename),
804
renamed=sorted(reporter.renamed, key=lambda x: x.new_filename),
805
removed=sorted(reporter.removed, key=lambda x: x.filename),
806
modified=sorted(reporter.modified, key=lambda x: x.filename),
807
text_changes=sorted(reporter.text_changes,
808
key=lambda x: x.filename))
752
added=sorted(reporter.added, key=lambda x:x.filename),
753
renamed=sorted(reporter.renamed, key=lambda x:x.new_filename),
754
removed=sorted(reporter.removed, key=lambda x:x.filename),
755
modified=sorted(reporter.modified, key=lambda x:x.filename),
756
text_changes=sorted(reporter.text_changes, key=lambda x:x.filename))