2
# Copyright (C) 2008, 2009 Canonical Ltd.
2
# Copyright (C) 2008 Canonical Ltd.
3
3
# (Authored by Martin Albisetti <argentina@gmail.com>)
4
4
# Copyright (C) 2006 Robey Pointer <robey@lag.net>
5
5
# Copyright (C) 2006 Goffredo Baroncelli <kreijack@inwind.it>
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
48
from bzrlib.export.tar_exporter import export_tarball
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()
51
73
def is_branch(folder):
154
178
filename=rich_filename(paths[1], kind),
155
179
file_id=file_id))
157
# The lru_cache is not thread-safe, so we need a lock around it for
159
rev_info_memory_cache_lock = threading.RLock()
161
182
class RevInfoMemoryCache(object):
162
183
"""A store that validates values against the revids they were stored with.
180
201
If a value was stored under `key`, with the same revid, return it.
181
202
Otherwise return None.
183
rev_info_memory_cache_lock.acquire()
185
cached = self._cache.get(key)
187
rev_info_memory_cache_lock.release()
204
cached = self._cache.get(key)
188
205
if cached is None:
190
207
stored_revid, data = cached
196
213
def set(self, key, revid, data):
197
214
"""Store `data` under `key`, to be checked against `revid` on get().
199
rev_info_memory_cache_lock.acquire()
201
self._cache[key] = (revid, data)
203
rev_info_memory_cache_lock.release()
205
# Used to store locks that prevent multiple threads from building a
206
# revision graph for the same branch at the same time, because that can
207
# cause severe performance issues that are so bad that the system seems
209
revision_graph_locks = {}
210
revision_graph_check_lock = threading.Lock()
212
class History(object):
216
self._cache[key] = (revid, data)
219
class History (object):
213
220
"""Decorate a branch to provide information for rendering.
215
222
History objects are expected to be short lived -- when serving a request
229
236
parents of this revision.
230
237
:ivar _rev_indices: A dictionary mapping each revision id to the index of
231
238
the information about it in _rev_info.
239
:ivar _full_history: A list of all revision ids in the ancestry of the
240
branch, in merge-sorted order. This is a bit silly, and shouldn't
241
really be stored on the instance...
232
242
:ivar _revno_revid: A dictionary mapping stringified revnos to revision
247
257
def update_missed_caches():
248
258
for cache in missed_caches:
249
259
cache.set(cache_key, self.last_revid, self._rev_info)
251
# Theoretically, it's possible for two threads to race in creating
252
# the Lock() object for their branch, so we put a lock around
253
# creating the per-branch Lock().
254
revision_graph_check_lock.acquire()
256
if cache_key not in revision_graph_locks:
257
revision_graph_locks[cache_key] = threading.Lock()
259
revision_graph_check_lock.release()
261
revision_graph_locks[cache_key].acquire()
264
data = cache.get(cache_key, self.last_revid)
266
self._rev_info = data
267
update_missed_caches()
270
missed_caches.append(cache)
272
whole_history_data = compute_whole_history_data(self._branch)
273
self._rev_info, self._rev_indices = whole_history_data
261
data = cache.get(cache_key, self.last_revid)
263
self._rev_info = data
274
264
update_missed_caches()
276
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()
278
273
if self._rev_indices is not None:
274
self._full_history = []
279
275
self._revno_revid = {}
280
276
for ((_, revid, _, revno_str, _), _, _) in self._rev_info:
281
277
self._revno_revid[revno_str] = revid
278
self._full_history.append(revid)
280
self._full_history = []
283
281
self._revno_revid = {}
284
282
self._rev_indices = {}
285
283
for ((seq, revid, _, revno_str, _), _, _) in self._rev_info:
286
284
self._rev_indices[revid] = seq
287
285
self._revno_revid[revno_str] = revid
286
self._full_history.append(revid)
289
288
def __init__(self, branch, whole_history_data_cache, file_cache=None,
290
289
revinfo_disk_cache=None, cache_key=None):
297
296
self._file_change_cache = None
298
297
self._branch = branch
299
self._branch_tags = None
300
298
self._inventory_cache = {}
301
299
self._branch_nick = self._branch.get_config().get_nickname()
302
self.log = logging.getLogger('loggerhead.%s' % (self._branch_nick,))
300
self.log = logging.getLogger('loggerhead.%s' % self._branch_nick)
304
302
self.last_revid = branch.last_revision()
355
353
def get_short_revision_history_by_fileid(self, file_id):
356
354
# FIXME: would be awesome if we could get, for a folder, the list of
357
355
# revisions where items within that folder changed.i
358
possible_keys = [(file_id, revid) for revid in self._rev_indices]
359
get_parent_map = self._branch.repository.texts.get_parent_map
360
# We chunk the requests as this works better with GraphIndex.
361
# See _filter_revisions_touching_file_id in bzrlib/log.py
362
# for more information.
365
for start in xrange(0, len(possible_keys), chunk_size):
366
next_keys = possible_keys[start:start + chunk_size]
367
revids += [k[1] for k in get_parent_map(next_keys)]
368
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
371
377
def get_revision_history_since(self, revid_list, date):
531
537
def get_inventory(self, revid):
532
538
if revid not in self._inventory_cache:
533
539
self._inventory_cache[revid] = (
534
self._branch.repository.get_inventory(revid))
540
self._branch.repository.get_revision_inventory(revid))
535
541
return self._inventory_cache[revid]
537
543
def get_path(self, revid, file_id):
585
591
revnol = revno.split(".")
586
592
revnos = ".".join(revnol[:-2])
587
593
revnolast = int(revnol[-1])
594
if revnos in d.keys():
590
596
if revnolast < m:
591
597
d[revnos] = (revnolast, revid)
593
599
d[revnos] = (revnolast, revid)
595
return [revid for (_, revid) in d.itervalues()]
601
return [d[revnos][1] for revnos in d.keys()]
597
603
def add_branch_nicks(self, change):
665
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]
668
679
message, short_message = clean_message(revision.message)
670
if self._branch_tags is None:
671
self._branch_tags = self._branch.tags.get_reverse_tag_dict()
674
if revision.revision_id in self._branch_tags:
675
revtags = ', '.join(self._branch_tags[revision.revision_id])
682
authors = revision.get_apparent_authors()
683
except AttributeError:
684
authors = [revision.get_apparent_author()]
678
687
'revid': revision.revision_id,
679
'date': datetime.datetime.fromtimestamp(revision.timestamp),
680
'utc_date': datetime.datetime.utcfromtimestamp(revision.timestamp),
681
'authors': revision.get_apparent_authors(),
682
690
'branch_nick': revision.properties.get('branch-nick', None),
683
691
'short_comment': short_message,
684
692
'comment': revision.message,
685
693
'comment_clean': [util.html_clean(s) for s in message],
686
694
'parents': revision.parent_ids,
687
'bugs': [bug.split()[0] for bug in revision.properties.get('bugs', '').splitlines()],
690
if isinstance(revision, bzrlib.foreign.ForeignRevision):
691
foreign_revid, mapping = (rev.foreign_revid, rev.mapping)
692
elif ":" in revision.revision_id:
694
foreign_revid, mapping = \
695
bzrlib.foreign.foreign_vcs_registry.parse_revision_id(
696
revision.revision_id)
697
except bzrlib.errors.InvalidRevisionId:
702
if foreign_revid is not None:
703
entry["foreign_vcs"] = mapping.vcs.abbreviation
704
entry["foreign_revid"] = mapping.vcs.show_foreign_revid(foreign_revid)
705
696
return util.Container(entry)
707
698
def get_file_changes_uncached(self, entry):
699
repo = self._branch.repository
708
700
if entry.parents:
709
701
old_revid = entry.parents[0].revid
722
714
entry.changes = changes
724
716
def get_file(self, file_id, revid):
725
"""Returns (path, filename, file contents)"""
717
"returns (path, filename, data)"
726
718
inv = self.get_inventory(revid)
727
719
inv_entry = inv[file_id]
728
720
rev_tree = self._branch.repository.revision_tree(inv_entry.revision)
745
737
text_changes: list((filename, file_id)),
747
739
repo = self._branch.repository
748
if (bzrlib.revision.is_null(old_revid) or
749
bzrlib.revision.is_null(new_revid)):
740
if bzrlib.revision.is_null(old_revid) or \
741
bzrlib.revision.is_null(new_revid):
750
742
old_tree, new_tree = map(
751
743
repo.revision_tree, [old_revid, new_revid])
757
749
bzrlib.delta.report_changes(new_tree.iter_changes(old_tree), reporter)
759
751
return util.Container(
760
added=sorted(reporter.added, key=lambda x: x.filename),
761
renamed=sorted(reporter.renamed, key=lambda x: x.new_filename),
762
removed=sorted(reporter.removed, key=lambda x: x.filename),
763
modified=sorted(reporter.modified, key=lambda x: x.filename),
764
text_changes=sorted(reporter.text_changes, 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))