~malept/loggerhead/standalone-auth

« back to all changes in this revision

Viewing changes to loggerhead/history.py

  • Committer: Martin Albisetti
  • Date: 2008-07-22 00:40:03 UTC
  • mfrom: (182 loggerhead.search_integration)
  • mto: This revision was merged to the branch mainline in revision 187.
  • Revision ID: argentina@gmail.com-20080722004003-lx1fp0tthpp6kl92
Merged from trunk, resolved billions of conflicts

Show diffs side-by-side

added added

removed removed

Lines of Context:
35
35
import time
36
36
from StringIO import StringIO
37
37
 
 
38
from loggerhead import search
38
39
from loggerhead import util
39
 
from loggerhead.util import decorator
 
40
from loggerhead.wholehistory import compute_whole_history_data
40
41
 
41
42
import bzrlib
42
43
import bzrlib.branch
47
48
import bzrlib.tsort
48
49
import bzrlib.ui
49
50
 
50
 
 
51
 
with_branch_lock = util.with_lock('_lock', 'branch')
52
 
 
53
 
 
54
 
@decorator
55
 
def with_bzrlib_read_lock(unbound):
56
 
    def bzrlib_read_locked(self, *args, **kw):
57
 
        #self.log.debug('-> %r bzr lock', id(threading.currentThread()))
58
 
        self._branch.repository.lock_read()
59
 
        try:
60
 
            return unbound(self, *args, **kw)
61
 
        finally:
62
 
            self._branch.repository.unlock()
63
 
            #self.log.debug('<- %r bzr lock', id(threading.currentThread()))
64
 
    return bzrlib_read_locked
65
 
 
66
 
 
67
51
# bzrlib's UIFactory is not thread-safe
68
52
uihack = threading.local()
69
53
 
107
91
    out_chunk_list = []
108
92
    for chunk in chunk_list:
109
93
        line_list = []
110
 
        wrap_char = '<wbr/>'
111
94
        delete_list, insert_list = [], []
112
95
        for line in chunk.diff:
113
 
            # Add <wbr/> every X characters so we can wrap properly
114
 
            wrap_line = re.findall(r'.{%d}|.+$' % 78, line.line)
115
 
            wrap_lines = [util.html_clean(_line) for _line in wrap_line]
116
 
            wrapped_line = wrap_char.join(wrap_lines)
117
 
 
118
96
            if line.type == 'context':
119
97
                if len(delete_list) or len(insert_list):
120
 
                    _process_side_by_side_buffers(line_list, delete_list, 
121
 
                                                  insert_list)
 
98
                    _process_side_by_side_buffers(line_list, delete_list, insert_list)
122
99
                    delete_list, insert_list = [], []
123
 
                line_list.append(util.Container(old_lineno=line.old_lineno, 
124
 
                                                new_lineno=line.new_lineno,
125
 
                                                old_line=wrapped_line, 
126
 
                                                new_line=wrapped_line,
127
 
                                                old_type=line.type, 
128
 
                                                new_type=line.type))
 
100
                line_list.append(util.Container(old_lineno=line.old_lineno, new_lineno=line.new_lineno,
 
101
                                                old_line=line.line, new_line=line.line,
 
102
                                                old_type=line.type, new_type=line.type))
129
103
            elif line.type == 'delete':
130
 
                delete_list.append((line.old_lineno, wrapped_line, line.type))
 
104
                delete_list.append((line.old_lineno, line.line, line.type))
131
105
            elif line.type == 'insert':
132
 
                insert_list.append((line.new_lineno, wrapped_line, line.type))
 
106
                insert_list.append((line.new_lineno, line.line, line.type))
133
107
        if len(delete_list) or len(insert_list):
134
108
            _process_side_by_side_buffers(line_list, delete_list, insert_list)
135
109
        out_chunk_list.append(util.Container(diff=line_list))
198
172
 
199
173
 
200
174
class History (object):
201
 
 
202
 
    def __init__(self):
 
175
    """Decorate a branch to provide information for rendering.
 
176
 
 
177
    History objects are expected to be short lived -- when serving a request
 
178
    for a particular branch, open it, read-lock it, wrap a History object
 
179
    around it, serve the request, throw the History object away, unlock the
 
180
    branch and throw it away.
 
181
 
 
182
    :ivar _file_change_cache: xx
 
183
    """
 
184
 
 
185
    def __init__(self, branch, whole_history_data_cache):
 
186
        assert branch.is_locked(), (
 
187
            "Can only construct a History object with a read-locked branch.")
203
188
        self._file_change_cache = None
204
 
        self._lock = threading.RLock()
205
 
 
206
 
    @classmethod
207
 
    def from_branch(cls, branch):
208
 
        z = time.time()
209
 
        self = cls()
210
189
        self._branch = branch
211
 
        self._last_revid = self._branch.last_revision()
212
 
 
213
 
        self.log = logging.getLogger('loggerhead.%s' % (self._branch.nick,))
214
 
 
215
 
        graph = branch.repository.get_graph()
216
 
        parent_map = dict(((key, value) for key, value in
217
 
             graph.iter_ancestry([self._last_revid]) if value is not None))
218
 
 
219
 
        self._revision_graph = self._strip_NULL_ghosts(parent_map)
220
 
        self._full_history = []
221
 
        self._revision_info = {}
222
 
        self._revno_revid = {}
223
 
        if bzrlib.revision.is_null(self._last_revid):
224
 
            self._merge_sort = []
225
 
        else:
226
 
            self._merge_sort = bzrlib.tsort.merge_sort(
227
 
                self._revision_graph, self._last_revid, generate_revno=True)
228
 
 
229
 
        for (seq, revid, merge_depth, revno, end_of_merge) in self._merge_sort:
230
 
            self._full_history.append(revid)
231
 
            revno_str = '.'.join(str(n) for n in revno)
232
 
            self._revno_revid[revno_str] = revid
233
 
            self._revision_info[revid] = (
234
 
                seq, revid, merge_depth, revno_str, end_of_merge)
235
 
 
236
 
        # cache merge info
237
 
        self._where_merged = {}
238
 
 
239
 
        for revid in self._revision_graph.keys():
240
 
            if self._revision_info[revid][2] == 0:
241
 
                continue
242
 
            for parent in self._revision_graph[revid]:
243
 
                self._where_merged.setdefault(parent, set()).add(revid)
244
 
 
245
 
        self.log.info('built revision graph cache: %r secs' % (time.time() - z,))
246
 
        return self
247
 
 
248
 
    @staticmethod
249
 
    def _strip_NULL_ghosts(revision_graph):
250
 
        """
251
 
        Copied over from bzrlib meant as a temporary workaround deprecated 
252
 
        methods.
253
 
        """
254
 
 
255
 
        # Filter ghosts, and null:
256
 
        if bzrlib.revision.NULL_REVISION in revision_graph:
257
 
            del revision_graph[bzrlib.revision.NULL_REVISION]
258
 
        for key, parents in revision_graph.items():
259
 
            revision_graph[key] = tuple(parent for parent in parents if parent
260
 
                in revision_graph)
261
 
        return revision_graph
262
 
 
263
 
    @classmethod
264
 
    def from_folder(cls, path):
265
 
        b = bzrlib.branch.Branch.open(path)
266
 
        b.lock_read()
267
 
        try:
268
 
            return cls.from_branch(b)
269
 
        finally:
270
 
            b.unlock()
271
 
 
272
 
    @with_branch_lock
273
 
    def out_of_date(self):
274
 
        # the branch may have been upgraded on disk, in which case we're stale.
275
 
        newly_opened = bzrlib.branch.Branch.open(self._branch.base)
276
 
        if self._branch.__class__ is not \
277
 
               newly_opened.__class__:
278
 
            return True
279
 
        if self._branch.repository.__class__ is not \
280
 
               newly_opened.repository.__class__:
281
 
            return True
282
 
        return self._branch.last_revision() != self._last_revid
 
190
        self.log = logging.getLogger('loggerhead.%s' % (branch.nick,))
 
191
 
 
192
        self.last_revid = branch.last_revision()
 
193
 
 
194
        whole_history_data = whole_history_data_cache.get(self.last_revid)
 
195
        if whole_history_data is None:
 
196
            whole_history_data = compute_whole_history_data(branch)
 
197
            whole_history_data_cache[self.last_revid] = whole_history_data
 
198
 
 
199
        (self._revision_graph, self._full_history, self._revision_info,
 
200
         self._revno_revid, self._merge_sort, self._where_merged
 
201
         ) = whole_history_data
283
202
 
284
203
    def use_file_cache(self, cache):
285
204
        self._file_change_cache = cache
288
207
    def has_revisions(self):
289
208
        return not bzrlib.revision.is_null(self.last_revid)
290
209
 
291
 
    last_revid = property(lambda self: self._last_revid, None, None)
292
 
 
293
 
    @with_branch_lock
294
210
    def get_config(self):
295
211
        return self._branch.get_config()
296
212
 
301
217
        seq, revid, merge_depth, revno_str, end_of_merge = self._revision_info[revid]
302
218
        return revno_str
303
219
 
304
 
    def get_revision_history(self):
305
 
        return self._full_history
306
 
 
307
220
    def get_revids_from(self, revid_list, start_revid):
308
221
        """
309
222
        Yield the mainline (wrt start_revid) revisions that merged each
331
244
                return
332
245
            revid = parents[0]
333
246
 
334
 
    @with_branch_lock
335
247
    def get_short_revision_history_by_fileid(self, file_id):
336
248
        # wow.  is this really the only way we can get this list?  by
337
249
        # man-handling the weave store directly? :-0
338
250
        # FIXME: would be awesome if we could get, for a folder, the list of
339
251
        # revisions where items within that folder changed.
340
 
        w = self._branch.repository.weave_store.get_weave(file_id, self._branch.repository.get_transaction())
341
 
        w_revids = w.versions()
342
 
        revids = [r for r in self._full_history if r in w_revids]
343
 
        return revids
 
252
        possible_keys = [(file_id, revid) for revid in self._full_history]
 
253
        existing_keys = self._branch.repository.texts.get_parent_map(possible_keys)
 
254
        return [revid for _, revid in existing_keys.iterkeys()]
344
255
 
345
 
    @with_branch_lock
346
256
    def get_revision_history_since(self, revid_list, date):
347
257
        # if a user asks for revisions starting at 01-sep, they mean inclusive,
348
258
        # so start at midnight on 02-sep.
356
266
        index = -index
357
267
        return revid_list[index:]
358
268
 
359
 
    @with_branch_lock
360
269
    def get_search_revid_list(self, query, revid_list):
361
270
        """
362
271
        given a "quick-search" query, try a few obvious possible meanings:
395
304
        if date is not None:
396
305
            if revid_list is None:
397
306
                # if no limit to the query was given, search only the direct-parent path.
398
 
                revid_list = list(self.get_revids_from(None, self._last_revid))
 
307
                revid_list = list(self.get_revids_from(None, self.last_revid))
399
308
            return self.get_revision_history_since(revid_list, date)
400
309
 
401
310
    revno_re = re.compile(r'^[\d\.]+$')
411
320
        if revid is None:
412
321
            return revid
413
322
        if revid == 'head:':
414
 
            return self._last_revid
 
323
            return self.last_revid
415
324
        if self.revno_re.match(revid):
416
325
            revid = self._revno_revid[revid]
417
326
        return revid
418
327
 
419
 
    @with_branch_lock
420
328
    def get_file_view(self, revid, file_id):
421
329
        """
422
330
        Given a revid and optional path, return a (revlist, revid) for
426
334
        If file_id is None, the entire revision history is the list scope.
427
335
        """
428
336
        if revid is None:
429
 
            revid = self._last_revid
 
337
            revid = self.last_revid
430
338
        if file_id is not None:
431
339
            # since revid is 'start_revid', possibly should start the path
432
340
            # tracing from revid... FIXME
436
344
            revlist = list(self.get_revids_from(None, revid))
437
345
        return revlist
438
346
 
439
 
    @with_branch_lock
440
347
    def get_view(self, revid, start_revid, file_id, query=None):
441
348
        """
442
349
        use the URL parameters (revid, start_revid, file_id, and query) to
462
369
        contain vital context for future url navigation.
463
370
        """
464
371
        if start_revid is None:
465
 
            start_revid = self._last_revid
 
372
            start_revid = self.last_revid
466
373
 
467
374
        if query is None:
468
375
            revid_list = self.get_file_view(start_revid, file_id)
480
387
            revid_list = self.get_file_view(start_revid, file_id)
481
388
        else:
482
389
            revid_list = None
483
 
 
484
 
        revid_list = self.get_search_revid_list(query, revid_list)
 
390
        revid_list = search.search_revisions(self._branch, query)
485
391
        if revid_list and len(revid_list) > 0:
486
392
            if revid not in revid_list:
487
393
                revid = revid_list[0]
488
394
            return revid, start_revid, revid_list
489
395
        else:
 
396
            # XXX: This should return a message saying that the search could
 
397
            # not be completed due to either missing the plugin or missing a
 
398
            # search index.
490
399
            return None, None, []
491
400
 
492
 
    @with_branch_lock
493
401
    def get_inventory(self, revid):
494
402
        return self._branch.repository.get_revision_inventory(revid)
495
403
 
496
 
    @with_branch_lock
497
404
    def get_path(self, revid, file_id):
498
405
        if (file_id is None) or (file_id == ''):
499
406
            return ''
502
409
            path = '/' + path
503
410
        return path
504
411
 
505
 
    @with_branch_lock
506
412
    def get_file_id(self, revid, path):
507
413
        if (len(path) > 0) and not path.startswith('/'):
508
414
            path = '/' + path
581
487
                else:
582
488
                    p.branch_nick = '(missing)'
583
489
 
584
 
    @with_branch_lock
585
490
    def get_changes(self, revid_list):
586
491
        """Return a list of changes objects for the given revids.
587
492
 
608
513
 
609
514
        return changes
610
515
 
611
 
    @with_branch_lock
612
 
    @with_bzrlib_read_lock
613
516
    def get_changes_uncached(self, revid_list):
614
517
        # FIXME: deprecated method in getting a null revision
615
518
        revid_list = filter(lambda revid: not bzrlib.revision.is_null(revid),
680
583
 
681
584
        return [self.parse_delta(delta) for delta in delta_list]
682
585
 
683
 
    @with_branch_lock
684
586
    def get_file_changes(self, entries):
685
587
        if self._file_change_cache is None:
686
588
            return self.get_file_changes_uncached(entries)
693
595
        for entry, changes in zip(entries, changes_list):
694
596
            entry.changes = changes
695
597
 
696
 
    @with_branch_lock
697
598
    def get_change_with_diff(self, revid, compare_revid=None):
698
599
        change = self.get_changes([revid])[0]
699
600
 
712
613
 
713
614
        return change
714
615
 
715
 
    @with_branch_lock
716
616
    def get_file(self, file_id, revid):
717
617
        "returns (path, filename, data)"
718
618
        inv = self.get_inventory(revid)
786
686
                old_lineno = lines[0]
787
687
                new_lineno = lines[1]
788
688
            elif line.startswith(' '):
789
 
                chunk.diff.append(util.Container(old_lineno=old_lineno, 
790
 
                                                 new_lineno=new_lineno,
791
 
                                                 type='context', 
792
 
                                                 line=line[1:]))
 
689
                chunk.diff.append(util.Container(old_lineno=old_lineno, new_lineno=new_lineno,
 
690
                                                 type='context', line=util.fixed_width(line[1:])))
793
691
                old_lineno += 1
794
692
                new_lineno += 1
795
693
            elif line.startswith('+'):
796
 
                chunk.diff.append(util.Container(old_lineno=None, 
797
 
                                                 new_lineno=new_lineno,
798
 
                                                 type='insert', line=line[1:]))
 
694
                chunk.diff.append(util.Container(old_lineno=None, new_lineno=new_lineno,
 
695
                                                 type='insert', line=util.fixed_width(line[1:])))
799
696
                new_lineno += 1
800
697
            elif line.startswith('-'):
801
 
                chunk.diff.append(util.Container(old_lineno=old_lineno, 
802
 
                                                 new_lineno=None,
803
 
                                                 type='delete', line=line[1:]))
 
698
                chunk.diff.append(util.Container(old_lineno=old_lineno, new_lineno=None,
 
699
                                                 type='delete', line=util.fixed_width(line[1:])))
804
700
                old_lineno += 1
805
701
            else:
806
 
                chunk.diff.append(util.Container(old_lineno=None, 
807
 
                                                 new_lineno=None,
808
 
                                                 type='unknown', 
809
 
                                                 line=repr(line)))
 
702
                chunk.diff.append(util.Container(old_lineno=None, new_lineno=None,
 
703
                                                 type='unknown', line=util.fixed_width(repr(line))))
810
704
        if chunk is not None:
811
705
            chunks.append(chunk)
812
706
        return chunks
851
745
            for m in change.changes.modified:
852
746
                m.sbs_chunks = _make_side_by_side(m.chunks)
853
747
 
854
 
    @with_branch_lock
855
748
    def get_filelist(self, inv, file_id, sort_type=None):
856
749
        """
857
750
        return the list of all files (and their attributes) within a given
890
783
            file_list.sort(key=lambda x: x.size)
891
784
        elif sort_type == 'date':
892
785
            file_list.sort(key=lambda x: x.change.date)
 
786
        
893
787
        # Always sort by kind to get directories first
894
788
        file_list.sort(key=lambda x: x.kind != 'directory')
895
789
 
903
797
 
904
798
    _BADCHARS_RE = re.compile(ur'[\x00-\x08\x0b\x0e-\x1f]')
905
799
 
906
 
    @with_branch_lock
907
800
    def annotate_file(self, file_id, revid):
908
801
        z = time.time()
909
802
        lineno = 1