~loggerhead-team/loggerhead/trunk-rich

« back to all changes in this revision

Viewing changes to loggerhead/history.py

  • Committer: Matt Nordhoff
  • Date: 2009-05-02 14:01:05 UTC
  • Revision ID: mnordhoff@mattnordhoff.com-20090502140105-m07dxhtzfgsgu2ia
Make sure to close mkstemp's file descriptor (bug #370845)

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>
31
31
import bisect
32
32
import datetime
33
33
import logging
 
34
import marshal
34
35
import re
35
36
import textwrap
36
37
import threading
37
 
import tarfile
38
 
 
 
38
import time
 
39
import urllib
 
40
from StringIO import StringIO
 
41
 
 
42
from loggerhead import search
 
43
from loggerhead import util
 
44
from loggerhead.wholehistory import compute_whole_history_data
 
45
 
 
46
import bzrlib
39
47
import bzrlib.branch
40
48
import bzrlib.delta
 
49
import bzrlib.diff
41
50
import bzrlib.errors
42
 
import bzrlib.foreign
 
51
import bzrlib.lru_cache
 
52
import bzrlib.progress
43
53
import bzrlib.revision
44
 
 
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
49
 
 
 
54
import bzrlib.textfile
 
55
import bzrlib.tsort
 
56
import bzrlib.ui
 
57
 
 
58
# bzrlib's UIFactory is not thread-safe
 
59
uihack = threading.local()
 
60
 
 
61
 
 
62
class ThreadSafeUIFactory (bzrlib.ui.SilentUIFactory):
 
63
 
 
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()
 
70
 
 
71
bzrlib.ui.ui_factory = ThreadSafeUIFactory()
50
72
 
51
73
def is_branch(folder):
52
74
    try:
90
112
    return path
91
113
 
92
114
 
 
115
# from bzrlib
 
116
 
 
117
 
93
118
class _RevListToTimestamps(object):
94
119
    """This takes a list of revisions, and allows you to bisect by date"""
95
120
 
108
133
        return len(self.revid_list)
109
134
 
110
135
class FileChangeReporter(object):
111
 
 
112
136
    def __init__(self, old_inv, new_inv):
113
137
        self.added = []
114
138
        self.modified = []
154
178
                filename=rich_filename(paths[1], kind),
155
179
                file_id=file_id))
156
180
 
157
 
# The lru_cache is not thread-safe, so we need a lock around it for
158
 
# all threads.
159
 
rev_info_memory_cache_lock = threading.RLock()
160
181
 
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.
182
203
        """
183
 
        rev_info_memory_cache_lock.acquire()
184
 
        try:
185
 
            cached = self._cache.get(key)
186
 
        finally:
187
 
            rev_info_memory_cache_lock.release()
 
204
        cached = self._cache.get(key)
188
205
        if cached is None:
189
206
            return 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().
198
215
        """
199
 
        rev_info_memory_cache_lock.acquire()
200
 
        try:
201
 
            self._cache[key] = (revid, data)
202
 
        finally:
203
 
            rev_info_memory_cache_lock.release()
204
 
 
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
208
 
# to hang.
209
 
revision_graph_locks = {}
210
 
revision_graph_check_lock = threading.Lock()
211
 
 
212
 
class History(object):
 
216
        self._cache[key] = (revid, data)
 
217
 
 
218
 
 
219
class History (object):
213
220
    """Decorate a branch to provide information for rendering.
214
221
 
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
233
243
        ids.
234
244
    """
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)
250
 
 
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()
255
 
        try:
256
 
            if cache_key not in revision_graph_locks:
257
 
                revision_graph_locks[cache_key] = threading.Lock()
258
 
        finally:
259
 
            revision_graph_check_lock.release()
260
 
 
261
 
        revision_graph_locks[cache_key].acquire()
262
 
        try:
263
 
            for cache in caches:
264
 
                data = cache.get(cache_key, self.last_revid)
265
 
                if data is not None:
266
 
                    self._rev_info = data
267
 
                    update_missed_caches()
268
 
                    break
269
 
                else:
270
 
                    missed_caches.append(cache)
271
 
            else:
272
 
                whole_history_data = compute_whole_history_data(self._branch)
273
 
                self._rev_info, self._rev_indices = whole_history_data
 
260
        for cache in caches:
 
261
            data = cache.get(cache_key, self.last_revid)
 
262
            if data is not None:
 
263
                self._rev_info = data
274
264
                update_missed_caches()
275
 
        finally:
276
 
            revision_graph_locks[cache_key].release()
 
265
                break
 
266
            else:
 
267
                missed_caches.append(cache)
 
268
        else:
 
269
            whole_history_data = compute_whole_history_data(self._branch)
 
270
            self._rev_info, self._rev_indices = whole_history_data
 
271
            update_missed_caches()
277
272
 
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)
282
279
        else:
 
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)
288
287
 
289
288
    def __init__(self, branch, whole_history_data_cache, file_cache=None,
290
289
                 revinfo_disk_cache=None, cache_key=None):
296
295
        else:
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)
303
301
 
304
302
        self.last_revid = branch.last_revision()
305
303
 
329
327
        revid in revid_list.
330
328
        """
331
329
        if revid_list is None:
332
 
            revid_list = [r[0][1] for r in self._rev_info]
 
330
            revid_list = self._full_history
333
331
        revid_set = set(revid_list)
334
332
        revid = start_revid
335
333
 
342
340
                r.add(self._rev_info[i][0][1])
343
341
                i += 1
344
342
            return r
345
 
        while True:
 
343
        while 1:
346
344
            if bzrlib.revision.is_null(revid):
347
345
                return
348
346
            if introduced_revisions(revid) & revid_set:
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.
363
 
        revids = []
364
 
        chunk_size = 1000
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
 
356
        try:
 
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.
 
369
            revids = []
 
370
            chunk_size = 1000
 
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
369
375
        return revids
370
376
 
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]
536
542
 
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])
588
 
            if revnos in d:
 
594
            if revnos in d.keys():
589
595
                m = d[revnos][0]
590
596
                if revnolast < m:
591
597
                    d[revnos] = (revnolast, revid)
592
598
            else:
593
599
                d[revnos] = (revnolast, revid)
594
600
 
595
 
        return [revid for (_, revid) in d.itervalues()]
 
601
        return [d[revnos][1] for revnos in d.keys()]
596
602
 
597
603
    def add_branch_nicks(self, change):
598
604
        """
665
671
        Given a bzrlib Revision, return a processed "change" for use in
666
672
        templates.
667
673
        """
 
674
        commit_time = datetime.datetime.fromtimestamp(revision.timestamp)
 
675
 
 
676
        parents = [util.Container(revid=r,
 
677
                   revno=self.get_revno(r)) for r in revision.parent_ids]
 
678
 
668
679
        message, short_message = clean_message(revision.message)
669
680
 
670
 
        if self._branch_tags is None:
671
 
            self._branch_tags = self._branch.tags.get_reverse_tag_dict()
672
 
 
673
 
        revtags = None
674
 
        if revision.revision_id in self._branch_tags:
675
 
          revtags = ', '.join(self._branch_tags[revision.revision_id])
 
681
        try:
 
682
            authors = revision.get_apparent_authors()
 
683
        except AttributeError:
 
684
            authors = [revision.get_apparent_author()]
676
685
 
677
686
        entry = {
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(),
 
688
            'date': commit_time,
 
689
            'authors': 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()],
688
 
            'tags': revtags,
689
695
        }
690
 
        if isinstance(revision, bzrlib.foreign.ForeignRevision):
691
 
            foreign_revid, mapping = (rev.foreign_revid, rev.mapping)
692
 
        elif ":" in revision.revision_id:
693
 
            try:
694
 
                foreign_revid, mapping = \
695
 
                    bzrlib.foreign.foreign_vcs_registry.parse_revision_id(
696
 
                        revision.revision_id)
697
 
            except bzrlib.errors.InvalidRevisionId:
698
 
                foreign_revid = None
699
 
                mapping = None
700
 
        else:
701
 
            foreign_revid = None
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)
706
697
 
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
710
702
        else:
722
714
        entry.changes = changes
723
715
 
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)),
746
738
        """
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])
752
744
        else:
757
749
        bzrlib.delta.report_changes(new_tree.iter_changes(old_tree), reporter)
758
750
 
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))
765
 
 
 
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))