~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/history.py

  • Committer: Matt Nordhoff
  • Date: 2009-04-16 21:49:48 UTC
  • Revision ID: mnordhoff@mattnordhoff.com-20090416214948-y6aan4mamt55x0wd
Fix incorrect calls to config.get_option() (bug #361238)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#
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>
34
34
import re
35
35
import textwrap
36
36
import threading
37
 
 
 
37
import time
 
38
import urllib
 
39
from StringIO import StringIO
 
40
 
 
41
from loggerhead import search
 
42
from loggerhead import util
 
43
from loggerhead.wholehistory import compute_whole_history_data
 
44
 
 
45
import bzrlib
38
46
import bzrlib.branch
39
47
import bzrlib.delta
 
48
import bzrlib.diff
40
49
import bzrlib.errors
41
 
import bzrlib.foreign
 
50
import bzrlib.progress
42
51
import bzrlib.revision
43
 
 
44
 
from loggerhead import search
45
 
from loggerhead import util
46
 
from loggerhead.wholehistory import compute_whole_history_data
47
 
 
 
52
import bzrlib.textfile
 
53
import bzrlib.tsort
 
54
import bzrlib.ui
 
55
 
 
56
# bzrlib's UIFactory is not thread-safe
 
57
uihack = threading.local()
 
58
 
 
59
 
 
60
class ThreadSafeUIFactory (bzrlib.ui.SilentUIFactory):
 
61
 
 
62
    def nested_progress_bar(self):
 
63
        if getattr(uihack, '_progress_bar_stack', None) is None:
 
64
            pbs = bzrlib.progress.ProgressBarStack(
 
65
                      klass=bzrlib.progress.DummyProgress)
 
66
            uihack._progress_bar_stack = pbs
 
67
        return uihack._progress_bar_stack.get_nested()
 
68
 
 
69
bzrlib.ui.ui_factory = ThreadSafeUIFactory()
48
70
 
49
71
def is_branch(folder):
50
72
    try:
88
110
    return path
89
111
 
90
112
 
 
113
# from bzrlib
 
114
 
 
115
 
91
116
class _RevListToTimestamps(object):
92
117
    """This takes a list of revisions, and allows you to bisect by date"""
93
118
 
106
131
        return len(self.revid_list)
107
132
 
108
133
class FileChangeReporter(object):
109
 
 
110
134
    def __init__(self, old_inv, new_inv):
111
135
        self.added = []
112
136
        self.modified = []
153
177
                file_id=file_id))
154
178
 
155
179
 
156
 
class RevInfoMemoryCache(object):
157
 
    """A store that validates values against the revids they were stored with.
158
 
 
159
 
    We use a unique key for each branch.
160
 
 
161
 
    The reason for not just using the revid as the key is so that when a new
162
 
    value is provided for a branch, we replace the old value used for the
163
 
    branch.
164
 
 
165
 
    There is another implementation of the same interface in
166
 
    loggerhead.changecache.RevInfoDiskCache.
167
 
    """
168
 
 
169
 
    def __init__(self, cache):
170
 
        self._cache = cache
171
 
 
172
 
    def get(self, key, revid):
173
 
        """Return the data associated with `key`, subject to a revid check.
174
 
 
175
 
        If a value was stored under `key`, with the same revid, return it.
176
 
        Otherwise return None.
177
 
        """
178
 
        cached = self._cache.get(key)
179
 
        if cached is None:
180
 
            return None
181
 
        stored_revid, data = cached
182
 
        if revid == stored_revid:
183
 
            return data
184
 
        else:
185
 
            return None
186
 
 
187
 
    def set(self, key, revid, data):
188
 
        """Store `data` under `key`, to be checked against `revid` on get().
189
 
        """
190
 
        self._cache[key] = (revid, data)
191
 
 
192
 
# Used to store locks that prevent multiple threads from building a 
193
 
# revision graph for the same branch at the same time, because that can
194
 
# cause severe performance issues that are so bad that the system seems
195
 
# to hang.
196
 
revision_graph_locks = {}
197
 
revision_graph_check_lock = threading.Lock()
198
 
 
199
 
class History(object):
 
180
class History (object):
200
181
    """Decorate a branch to provide information for rendering.
201
182
 
202
183
    History objects are expected to be short lived -- when serving a request
204
185
    around it, serve the request, throw the History object away, unlock the
205
186
    branch and throw it away.
206
187
 
207
 
    :ivar _file_change_cache: An object that caches information about the
208
 
        files that changed between two revisions.
209
 
    :ivar _rev_info: A list of information about revisions.  This is by far
210
 
        the most cryptic data structure in loggerhead.  At the top level, it
211
 
        is a list of 3-tuples [(merge-info, where-merged, parents)].
212
 
        `merge-info` is (seq, revid, merge_depth, revno_str, end_of_merge) --
213
 
        like a merged sorted list, but the revno is stringified.
214
 
        `where-merged` is a tuple of revisions that have this revision as a
215
 
        non-lefthand parent.  Finally, `parents` is just the usual list of
216
 
        parents of this revision.
217
 
    :ivar _rev_indices: A dictionary mapping each revision id to the index of
218
 
        the information about it in _rev_info.
219
 
    :ivar _revno_revid: A dictionary mapping stringified revnos to revision
220
 
        ids.
 
188
    :ivar _file_change_cache: xx
221
189
    """
222
190
 
223
 
    def _load_whole_history_data(self, caches, cache_key):
224
 
        """Set the attributes relating to the whole history of the branch.
225
 
 
226
 
        :param caches: a list of caches with interfaces like
227
 
            `RevInfoMemoryCache` and be ordered from fastest to slowest.
228
 
        :param cache_key: the key to use with the caches.
229
 
        """
230
 
        self._rev_indices = None
231
 
        self._rev_info = None
232
 
 
233
 
        missed_caches = []
234
 
        def update_missed_caches():
235
 
            for cache in missed_caches:
236
 
                cache.set(cache_key, self.last_revid, self._rev_info)
237
 
 
238
 
        # Theoretically, it's possible for two threads to race in creating
239
 
        # the Lock() object for their branch, so we put a lock around
240
 
        # creating the per-branch Lock().
241
 
        revision_graph_check_lock.acquire()
242
 
        try:
243
 
            if cache_key not in revision_graph_locks:
244
 
                revision_graph_locks[cache_key] = threading.Lock()
245
 
        finally:
246
 
            revision_graph_check_lock.release()
247
 
 
248
 
        revision_graph_locks[cache_key].acquire()
249
 
        try:
250
 
            for cache in caches:
251
 
                data = cache.get(cache_key, self.last_revid)
252
 
                if data is not None:
253
 
                    self._rev_info = data
254
 
                    update_missed_caches()
255
 
                    break
256
 
                else:
257
 
                    missed_caches.append(cache)
258
 
            else:
259
 
                whole_history_data = compute_whole_history_data(self._branch)
260
 
                self._rev_info, self._rev_indices = whole_history_data
261
 
                update_missed_caches()
262
 
        finally:
263
 
            revision_graph_locks[cache_key].release()
264
 
 
265
 
        if self._rev_indices is not None:
266
 
            self._revno_revid = {}
267
 
            for ((_, revid, _, revno_str, _), _, _) in self._rev_info:
268
 
                self._revno_revid[revno_str] = revid
269
 
        else:
270
 
            self._revno_revid = {}
271
 
            self._rev_indices = {}
272
 
            for ((seq, revid, _, revno_str, _), _, _) in self._rev_info:
273
 
                self._rev_indices[revid] = seq
274
 
                self._revno_revid[revno_str] = revid
275
 
 
276
 
    def __init__(self, branch, whole_history_data_cache, file_cache=None,
277
 
                 revinfo_disk_cache=None, cache_key=None):
 
191
    def __init__(self, branch, whole_history_data_cache):
278
192
        assert branch.is_locked(), (
279
193
            "Can only construct a History object with a read-locked branch.")
280
 
        if file_cache is not None:
281
 
            self._file_change_cache = file_cache
282
 
            file_cache.history = self
283
 
        else:
284
 
            self._file_change_cache = None
 
194
        self._file_change_cache = None
285
195
        self._branch = branch
286
196
        self._inventory_cache = {}
287
197
        self._branch_nick = self._branch.get_config().get_nickname()
288
 
        self.log = logging.getLogger('loggerhead.%s' % (self._branch_nick,))
 
198
        self.log = logging.getLogger('loggerhead.%s' % self._branch_nick)
289
199
 
290
200
        self.last_revid = branch.last_revision()
291
201
 
292
 
        caches = [RevInfoMemoryCache(whole_history_data_cache)]
293
 
        if revinfo_disk_cache:
294
 
            caches.append(revinfo_disk_cache)
295
 
        self._load_whole_history_data(caches, cache_key)
 
202
        whole_history_data = whole_history_data_cache.get(self.last_revid)
 
203
        if whole_history_data is None:
 
204
            whole_history_data = compute_whole_history_data(branch)
 
205
            whole_history_data_cache[self.last_revid] = whole_history_data
 
206
 
 
207
        (self._revision_graph, self._full_history, self._revision_info,
 
208
         self._revno_revid, self._merge_sort, self._where_merged,
 
209
         ) = whole_history_data
 
210
 
 
211
    def use_file_cache(self, cache):
 
212
        self._file_change_cache = cache
296
213
 
297
214
    @property
298
215
    def has_revisions(self):
302
219
        return self._branch.get_config()
303
220
 
304
221
    def get_revno(self, revid):
305
 
        if revid not in self._rev_indices:
 
222
        if revid not in self._revision_info:
306
223
            # ghost parent?
307
224
            return 'unknown'
308
 
        seq = self._rev_indices[revid]
309
 
        revno = self._rev_info[seq][0][3]
310
 
        return revno
 
225
        (seq, revid, merge_depth,
 
226
         revno_str, end_of_merge) = self._revision_info[revid]
 
227
        return revno_str
311
228
 
312
229
    def get_revids_from(self, revid_list, start_revid):
313
230
        """
315
232
        revid in revid_list.
316
233
        """
317
234
        if revid_list is None:
318
 
            revid_list = [r[0][1] for r in self._rev_info]
 
235
            revid_list = self._full_history
319
236
        revid_set = set(revid_list)
320
237
        revid = start_revid
321
238
 
322
239
        def introduced_revisions(revid):
323
240
            r = set([revid])
324
 
            seq = self._rev_indices[revid]
325
 
            md = self._rev_info[seq][0][2]
 
241
            seq, revid, md, revno, end_of_merge = self._revision_info[revid]
326
242
            i = seq + 1
327
 
            while i < len(self._rev_info) and self._rev_info[i][0][2] > md:
328
 
                r.add(self._rev_info[i][0][1])
 
243
            while i < len(self._merge_sort) and self._merge_sort[i][2] > md:
 
244
                r.add(self._merge_sort[i][1])
329
245
                i += 1
330
246
            return r
331
 
        while True:
 
247
        while 1:
332
248
            if bzrlib.revision.is_null(revid):
333
249
                return
334
250
            if introduced_revisions(revid) & revid_set:
335
251
                yield revid
336
 
            parents = self._rev_info[self._rev_indices[revid]][2]
 
252
            parents = self._revision_graph[revid]
337
253
            if len(parents) == 0:
338
254
                return
339
255
            revid = parents[0]
341
257
    def get_short_revision_history_by_fileid(self, file_id):
342
258
        # FIXME: would be awesome if we could get, for a folder, the list of
343
259
        # revisions where items within that folder changed.i
344
 
        possible_keys = [(file_id, revid) for revid in self._rev_indices]
345
 
        get_parent_map = self._branch.repository.texts.get_parent_map
346
 
        # We chunk the requests as this works better with GraphIndex.
347
 
        # See _filter_revisions_touching_file_id in bzrlib/log.py
348
 
        # for more information.
349
 
        revids = []
350
 
        chunk_size = 1000
351
 
        for start in xrange(0, len(possible_keys), chunk_size):
352
 
            next_keys = possible_keys[start:start + chunk_size]
353
 
            revids += [k[1] for k in get_parent_map(next_keys)]
354
 
        del possible_keys, next_keys
 
260
        try:
 
261
            # FIXME: Workaround for bzr versions prior to 1.6b3.
 
262
            # Remove me eventually pretty please  :)
 
263
            w = self._branch.repository.weave_store.get_weave(
 
264
                    file_id, self._branch.repository.get_transaction())
 
265
            w_revids = w.versions()
 
266
            revids = [r for r in self._full_history if r in w_revids]
 
267
        except AttributeError:
 
268
            possible_keys = [(file_id, revid) for revid in self._full_history]
 
269
            get_parent_map = self._branch.repository.texts.get_parent_map
 
270
            # We chunk the requests as this works better with GraphIndex.
 
271
            # See _filter_revisions_touching_file_id in bzrlib/log.py
 
272
            # for more information.
 
273
            revids = []
 
274
            chunk_size = 1000
 
275
            for start in xrange(0, len(possible_keys), chunk_size):
 
276
                next_keys = possible_keys[start:start + chunk_size]
 
277
                revids += [k[1] for k in get_parent_map(next_keys)]
 
278
            del possible_keys, next_keys
355
279
        return revids
356
280
 
357
281
    def get_revision_history_since(self, revid_list, date):
517
441
    def get_inventory(self, revid):
518
442
        if revid not in self._inventory_cache:
519
443
            self._inventory_cache[revid] = (
520
 
                self._branch.repository.get_inventory(revid))
 
444
                self._branch.repository.get_revision_inventory(revid))
521
445
        return self._inventory_cache[revid]
522
446
 
523
447
    def get_path(self, revid, file_id):
542
466
 
543
467
        merge_point = []
544
468
        while True:
545
 
            children = self._rev_info[self._rev_indices[revid]][1]
 
469
            children = self._where_merged.get(revid, [])
546
470
            nexts = []
547
471
            for child in children:
548
 
                child_parents = self._rev_info[self._rev_indices[child]][2]
 
472
                child_parents = self._revision_graph[child]
549
473
                if child_parents[0] == revid:
550
474
                    nexts.append(child)
551
475
                else:
571
495
            revnol = revno.split(".")
572
496
            revnos = ".".join(revnol[:-2])
573
497
            revnolast = int(revnol[-1])
574
 
            if revnos in d:
 
498
            if revnos in d.keys():
575
499
                m = d[revnos][0]
576
500
                if revnolast < m:
577
501
                    d[revnos] = (revnolast, revid)
578
502
            else:
579
503
                d[revnos] = (revnolast, revid)
580
504
 
581
 
        return [revid for (_, revid) in d.itervalues()]
 
505
        return [d[revnos][1] for revnos in d.keys()]
582
506
 
583
507
    def add_branch_nicks(self, change):
584
508
        """
651
575
        Given a bzrlib Revision, return a processed "change" for use in
652
576
        templates.
653
577
        """
 
578
        commit_time = datetime.datetime.fromtimestamp(revision.timestamp)
 
579
 
 
580
        parents = [util.Container(revid=r,
 
581
                   revno=self.get_revno(r)) for r in revision.parent_ids]
 
582
 
654
583
        message, short_message = clean_message(revision.message)
655
584
 
656
 
        tags = self._branch.tags.get_reverse_tag_dict()
657
 
 
658
 
        revtags = None
659
 
        if tags.has_key(revision.revision_id):
660
 
          revtags = ', '.join(tags[revision.revision_id])
 
585
        try:
 
586
            authors = revision.get_apparent_authors()
 
587
        except AttributeError:
 
588
            authors = [revision.get_apparent_author()]
661
589
 
662
590
        entry = {
663
591
            'revid': revision.revision_id,
664
 
            'date': datetime.datetime.fromtimestamp(revision.timestamp),
665
 
            'utc_date': datetime.datetime.utcfromtimestamp(revision.timestamp),
666
 
            'authors': revision.get_apparent_authors(),
 
592
            'date': commit_time,
 
593
            'authors': authors,
667
594
            'branch_nick': revision.properties.get('branch-nick', None),
668
595
            'short_comment': short_message,
669
596
            'comment': revision.message,
670
597
            'comment_clean': [util.html_clean(s) for s in message],
671
598
            'parents': revision.parent_ids,
672
 
            'bugs': [bug.split()[0] for bug in revision.properties.get('bugs', '').splitlines()],
673
 
            'tags': revtags,
674
599
        }
675
 
        if isinstance(revision, bzrlib.foreign.ForeignRevision):
676
 
            foreign_revid, mapping = (rev.foreign_revid, rev.mapping)
677
 
        elif ":" in revision.revision_id:
678
 
            try:
679
 
                foreign_revid, mapping = \
680
 
                    bzrlib.foreign.foreign_vcs_registry.parse_revision_id(
681
 
                        revision.revision_id)
682
 
            except bzrlib.errors.InvalidRevisionId:
683
 
                foreign_revid = None
684
 
                mapping = None
685
 
        else:
686
 
            foreign_revid = None
687
 
        if foreign_revid is not None:
688
 
            entry["foreign_vcs"] = mapping.vcs.abbreviation
689
 
            entry["foreign_revid"] = mapping.vcs.show_foreign_revid(foreign_revid)
690
600
        return util.Container(entry)
691
601
 
692
602
    def get_file_changes_uncached(self, entry):
 
603
        repo = self._branch.repository
693
604
        if entry.parents:
694
605
            old_revid = entry.parents[0].revid
695
606
        else:
707
618
        entry.changes = changes
708
619
 
709
620
    def get_file(self, file_id, revid):
710
 
        """Returns (path, filename, file contents)"""
 
621
        "returns (path, filename, data)"
711
622
        inv = self.get_inventory(revid)
712
623
        inv_entry = inv[file_id]
713
624
        rev_tree = self._branch.repository.revision_tree(inv_entry.revision)
730
641
            text_changes: list((filename, file_id)),
731
642
        """
732
643
        repo = self._branch.repository
733
 
        if (bzrlib.revision.is_null(old_revid) or
734
 
            bzrlib.revision.is_null(new_revid)):
 
644
        if bzrlib.revision.is_null(old_revid) or \
 
645
               bzrlib.revision.is_null(new_revid):
735
646
            old_tree, new_tree = map(
736
647
                repo.revision_tree, [old_revid, new_revid])
737
648
        else:
742
653
        bzrlib.delta.report_changes(new_tree.iter_changes(old_tree), reporter)
743
654
 
744
655
        return util.Container(
745
 
            added=sorted(reporter.added, key=lambda x: x.filename),
746
 
            renamed=sorted(reporter.renamed, key=lambda x: x.new_filename),
747
 
            removed=sorted(reporter.removed, key=lambda x: x.filename),
748
 
            modified=sorted(reporter.modified, key=lambda x: x.filename),
749
 
            text_changes=sorted(reporter.text_changes, key=lambda x: x.filename))
 
656
            added=sorted(reporter.added, key=lambda x:x.filename),
 
657
            renamed=sorted(reporter.renamed, key=lambda x:x.new_filename),
 
658
            removed=sorted(reporter.removed, key=lambda x:x.filename),
 
659
            modified=sorted(reporter.modified, key=lambda x:x.filename),
 
660
            text_changes=sorted(reporter.text_changes, key=lambda x:x.filename))