/loggerhead/trunk

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/loggerhead/trunk
411.4.5 by John Arbash Meinel
Add some tests for _iterate_sufficiently.
1
# Copyright (C) 2006-2011 Canonical Ltd.
185 by Martin Albisetti
* Clarify License and add copyright to all file headers (John Arbash Meinel)
2
#                     (Authored by Martin Albisetti <argentina@gmail.com>)
1 by Robey Pointer
initial checkin
3
# Copyright (C) 2006  Robey Pointer <robey@lag.net>
23 by Robey Pointer
lots of little changes:
4
# Copyright (C) 2006  Goffredo Baroncelli <kreijack@inwind.it>
48 by Robey Pointer
the big migration of branch-specific data to a BranchView object: actually
5
# Copyright (C) 2005  Jake Edge <jake@edge2.net>
6
# Copyright (C) 2005  Matt Mackall <mpm@selenic.com>
1 by Robey Pointer
initial checkin
7
#
8
# This program is free software; you can redistribute it and/or modify
9
# it under the terms of the GNU General Public License as published by
10
# the Free Software Foundation; either version 2 of the License, or
11
# (at your option) any later version.
12
#
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
# GNU General Public License for more details.
17
#
18
# You should have received a copy of the GNU General Public License
19
# along with this program; if not, write to the Free Software
20
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21
#
22
48 by Robey Pointer
the big migration of branch-specific data to a BranchView object: actually
23
#
24
# This file (and many of the web templates) contains work based on the
25
# "bazaar-webserve" project by Goffredo Baroncelli, which is in turn based
26
# on "hgweb" by Jake Edge and Matt Mackall.
27
#
28
29
41 by Robey Pointer
initial search ui (revid, date, and very slow text search)
30
import bisect
1 by Robey Pointer
initial checkin
31
import datetime
18 by Robey Pointer
add a caching system for revision/change entries, since those should never
32
import logging
23 by Robey Pointer
lots of little changes:
33
import re
1 by Robey Pointer
initial checkin
34
import textwrap
399.1.1 by Max Kanat-Alexander
Add locks so that we only build a revision graph cache once simultaneously
35
import threading
41 by Robey Pointer
initial search ui (revid, date, and very slow text search)
36
432.2.2 by John Arbash Meinel
Sort tags 'naturally', add tests for it.
37
from bzrlib import tag
1 by Robey Pointer
initial checkin
38
import bzrlib.branch
305.1.8 by Michael Hudson
finally, use iter_changes and report_changes, not changes_from.
39
import bzrlib.delta
1 by Robey Pointer
initial checkin
40
import bzrlib.errors
358.2.1 by Jelmer Vernooij
Provide foreign revision id information.
41
import bzrlib.foreign
128.1.60 by Michael Hudson
respond to review comments
42
import bzrlib.revision
381 by Michael Hudson
fix bug 382765 by deleting the ThreadSafeUIFactory. a purely cosmetic problem remains. also clean up some imports
43
389.2.1 by Matt Nordhoff
Some random PEP 8 and otehr stylistic changes.
44
from loggerhead import search
45
from loggerhead import util
46
from loggerhead.wholehistory import compute_whole_history_data
47
66 by Robey Pointer
do side-by-side diff on the revision page, making it the default.
48
74 by Robey Pointer
add the ability to auto-publish anything found under a particular folder.
49
def is_branch(folder):
50
    try:
51
        bzrlib.branch.Branch.open(folder)
52
        return True
53
    except:
54
        return False
55
56
97 by Robey Pointer
big checkpoint commit. added some functions to util for tracking browsing
57
def clean_message(message):
138.1.3 by Michael Hudson
clean up various comments and add docstrings
58
    """Clean up a commit message and return it and a short (1-line) version.
59
60
    Commit messages that are long single lines are reflowed using the textwrap
61
    module (Robey, the original author of this code, apparently favored this
62
    style of message).
63
    """
279.1.1 by Colin Watson
Leading blank lines in commit messages no longer result in an empty summary.
64
    message = message.lstrip().splitlines()
138.1.3 by Michael Hudson
clean up various comments and add docstrings
65
97 by Robey Pointer
big checkpoint commit. added some functions to util for tracking browsing
66
    if len(message) == 1:
67
        message = textwrap.wrap(message[0])
138.1.3 by Michael Hudson
clean up various comments and add docstrings
68
138.1.2 by Michael Hudson
test and fix for the problem
69
    if len(message) == 0:
138.1.3 by Michael Hudson
clean up various comments and add docstrings
70
        # We can end up where when (a) the commit message was empty or (b)
71
        # when the message consisted entirely of whitespace, in which case
72
        # textwrap.wrap() returns an empty list.
73
        return [''], ''
128.1.46 by Michael Hudson
delete-trailing-whitespace
74
138.1.3 by Michael Hudson
clean up various comments and add docstrings
75
    # Make short form of commit message.
97 by Robey Pointer
big checkpoint commit. added some functions to util for tracking browsing
76
    short_message = message[0]
128.6.12 by Michael Hudson
go back to 60 character 'short' commit messages
77
    if len(short_message) > 60:
78
        short_message = short_message[:60] + '...'
128.1.46 by Michael Hudson
delete-trailing-whitespace
79
97 by Robey Pointer
big checkpoint commit. added some functions to util for tracking browsing
80
    return message, short_message
81
82
128.2.7 by Robey Pointer
the diff cache isn't adding very much, and can grow very large. let's just
83
def rich_filename(path, kind):
84
    if kind == 'directory':
85
        path += '/'
86
    if kind == 'symlink':
87
        path += '@'
88
    return path
128.1.46 by Michael Hudson
delete-trailing-whitespace
89
128.2.7 by Robey Pointer
the diff cache isn't adding very much, and can grow very large. let's just
90
41 by Robey Pointer
initial search ui (revid, date, and very slow text search)
91
class _RevListToTimestamps(object):
92
    """This takes a list of revisions, and allows you to bisect by date"""
93
94
    __slots__ = ['revid_list', 'repository']
95
96
    def __init__(self, revid_list, repository):
97
        self.revid_list = revid_list
98
        self.repository = repository
99
100
    def __getitem__(self, index):
101
        """Get the date of the index'd item"""
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
102
        return datetime.datetime.fromtimestamp(self.repository.get_revision(
103
                   self.revid_list[index]).timestamp)
41 by Robey Pointer
initial search ui (revid, date, and very slow text search)
104
105
    def __len__(self):
106
        return len(self.revid_list)
107
305.1.8 by Michael Hudson
finally, use iter_changes and report_changes, not changes_from.
108
class FileChangeReporter(object):
329.1.2 by Matt Nordhoff
Remove unused imports in history.py, plus some syntax tweaks.
109
308.1.3 by Michael Hudson
push all handling of revision trees into history
110
    def __init__(self, old_inv, new_inv):
305.1.8 by Michael Hudson
finally, use iter_changes and report_changes, not changes_from.
111
        self.added = []
112
        self.modified = []
113
        self.renamed = []
114
        self.removed = []
115
        self.text_changes = []
308.1.3 by Michael Hudson
push all handling of revision trees into history
116
        self.old_inv = old_inv
117
        self.new_inv = new_inv
118
119
    def revid(self, inv, file_id):
120
        try:
121
            return inv[file_id].revision
122
        except bzrlib.errors.NoSuchId:
123
            return 'null:'
124
305.1.8 by Michael Hudson
finally, use iter_changes and report_changes, not changes_from.
125
    def report(self, file_id, paths, versioned, renamed, modified,
126
               exe_change, kind):
127
        if modified not in ('unchanged', 'kind changed'):
312 by Michael Hudson
fix thinko on revision pages for removed files
128
            if versioned == 'removed':
129
                filename = rich_filename(paths[0], kind[0])
130
            else:
131
                filename = rich_filename(paths[1], kind[1])
305.1.8 by Michael Hudson
finally, use iter_changes and report_changes, not changes_from.
132
            self.text_changes.append(util.Container(
312 by Michael Hudson
fix thinko on revision pages for removed files
133
                filename=filename, file_id=file_id,
308.1.3 by Michael Hudson
push all handling of revision trees into history
134
                old_revision=self.revid(self.old_inv, file_id),
135
                new_revision=self.revid(self.new_inv, file_id)))
305.1.8 by Michael Hudson
finally, use iter_changes and report_changes, not changes_from.
136
        if versioned == 'added':
137
            self.added.append(util.Container(
138
                filename=rich_filename(paths[1], kind),
139
                file_id=file_id, kind=kind[1]))
140
        elif versioned == 'removed':
141
            self.removed.append(util.Container(
305.1.15 by Michael Hudson
grr
142
                filename=rich_filename(paths[0], kind),
305.1.8 by Michael Hudson
finally, use iter_changes and report_changes, not changes_from.
143
                file_id=file_id, kind=kind[0]))
144
        elif renamed:
145
            self.renamed.append(util.Container(
146
                old_filename=rich_filename(paths[0], kind[0]),
147
                new_filename=rich_filename(paths[1], kind[1]),
148
                file_id=file_id,
149
                text_modified=modified == 'modified'))
150
        else:
151
            self.modified.append(util.Container(
152
                filename=rich_filename(paths[1], kind),
153
                file_id=file_id))
154
414.1.1 by Max Kanat-Alexander
jam rightly pointed out that the RevInfoMemoryCache lock needs to be global
155
# The lru_cache is not thread-safe, so we need a lock around it for
156
# all threads.
157
rev_info_memory_cache_lock = threading.RLock()
128.1.26 by Michael Hudson
same trick for revision.kid. still way too slow for big diffs though
158
332.2.5 by Michael Hudson
refactor in the direction of maybe making sense one day
159
class RevInfoMemoryCache(object):
332.2.8 by Michael Hudson
docstrings (omg!!)
160
    """A store that validates values against the revids they were stored with.
161
162
    We use a unique key for each branch.
163
164
    The reason for not just using the revid as the key is so that when a new
165
    value is provided for a branch, we replace the old value used for the
166
    branch.
167
168
    There is another implementation of the same interface in
169
    loggerhead.changecache.RevInfoDiskCache.
170
    """
171
332.2.5 by Michael Hudson
refactor in the direction of maybe making sense one day
172
    def __init__(self, cache):
173
        self._cache = cache
174
175
    def get(self, key, revid):
332.2.8 by Michael Hudson
docstrings (omg!!)
176
        """Return the data associated with `key`, subject to a revid check.
177
178
        If a value was stored under `key`, with the same revid, return it.
179
        Otherwise return None.
180
        """
414.1.1 by Max Kanat-Alexander
jam rightly pointed out that the RevInfoMemoryCache lock needs to be global
181
        rev_info_memory_cache_lock.acquire()
409.1.1 by Max Kanat-Alexander
Synchronize all access to RevInfoMemoryCache, because lru_cache is not thread-safe.
182
        try:
183
            cached = self._cache.get(key)
184
        finally:
414.1.1 by Max Kanat-Alexander
jam rightly pointed out that the RevInfoMemoryCache lock needs to be global
185
            rev_info_memory_cache_lock.release()
332.2.5 by Michael Hudson
refactor in the direction of maybe making sense one day
186
        if cached is None:
187
            return None
188
        stored_revid, data = cached
189
        if revid == stored_revid:
190
            return data
191
        else:
192
            return None
193
194
    def set(self, key, revid, data):
332.2.8 by Michael Hudson
docstrings (omg!!)
195
        """Store `data` under `key`, to be checked against `revid` on get().
196
        """
414.1.1 by Max Kanat-Alexander
jam rightly pointed out that the RevInfoMemoryCache lock needs to be global
197
        rev_info_memory_cache_lock.acquire()
409.1.1 by Max Kanat-Alexander
Synchronize all access to RevInfoMemoryCache, because lru_cache is not thread-safe.
198
        try:
199
            self._cache[key] = (revid, data)
200
        finally:
414.1.1 by Max Kanat-Alexander
jam rightly pointed out that the RevInfoMemoryCache lock needs to be global
201
            rev_info_memory_cache_lock.release()
332.2.5 by Michael Hudson
refactor in the direction of maybe making sense one day
202
399.1.2 by Max Kanat-Alexander
Address code review comments from mwhudson and Peng.
203
# Used to store locks that prevent multiple threads from building a 
204
# revision graph for the same branch at the same time, because that can
205
# cause severe performance issues that are so bad that the system seems
206
# to hang.
207
revision_graph_locks = {}
208
revision_graph_check_lock = threading.Lock()
332.2.5 by Michael Hudson
refactor in the direction of maybe making sense one day
209
329.1.2 by Matt Nordhoff
Remove unused imports in history.py, plus some syntax tweaks.
210
class History(object):
179.1.10 by Michael Hudson
rename some things, change History to be more conventionally constructed
211
    """Decorate a branch to provide information for rendering.
212
213
    History objects are expected to be short lived -- when serving a request
214
    for a particular branch, open it, read-lock it, wrap a History object
215
    around it, serve the request, throw the History object away, unlock the
216
    branch and throw it away.
179.1.12 by Michael Hudson
less repeated locking
217
332.2.8 by Michael Hudson
docstrings (omg!!)
218
    :ivar _rev_info: A list of information about revisions.  This is by far
219
        the most cryptic data structure in loggerhead.  At the top level, it
220
        is a list of 3-tuples [(merge-info, where-merged, parents)].
221
        `merge-info` is (seq, revid, merge_depth, revno_str, end_of_merge) --
222
        like a merged sorted list, but the revno is stringified.
223
        `where-merged` is a tuple of revisions that have this revision as a
224
        non-lefthand parent.  Finally, `parents` is just the usual list of
225
        parents of this revision.
226
    :ivar _rev_indices: A dictionary mapping each revision id to the index of
227
        the information about it in _rev_info.
228
    :ivar _revno_revid: A dictionary mapping stringified revnos to revision
229
        ids.
179.1.10 by Michael Hudson
rename some things, change History to be more conventionally constructed
230
    """
231
332.2.5 by Michael Hudson
refactor in the direction of maybe making sense one day
232
    def _load_whole_history_data(self, caches, cache_key):
332.2.8 by Michael Hudson
docstrings (omg!!)
233
        """Set the attributes relating to the whole history of the branch.
234
235
        :param caches: a list of caches with interfaces like
236
            `RevInfoMemoryCache` and be ordered from fastest to slowest.
237
        :param cache_key: the key to use with the caches.
238
        """
332.2.5 by Michael Hudson
refactor in the direction of maybe making sense one day
239
        self._rev_indices = None
240
        self._rev_info = None
241
242
        missed_caches = []
243
        def update_missed_caches():
244
            for cache in missed_caches:
245
                cache.set(cache_key, self.last_revid, self._rev_info)
399.1.1 by Max Kanat-Alexander
Add locks so that we only build a revision graph cache once simultaneously
246
399.1.2 by Max Kanat-Alexander
Address code review comments from mwhudson and Peng.
247
        # Theoretically, it's possible for two threads to race in creating
248
        # the Lock() object for their branch, so we put a lock around
249
        # creating the per-branch Lock().
250
        revision_graph_check_lock.acquire()
399.1.3 by Max Kanat-Alexander
Be super-paranoid about releasing the revision_graph_check_lock.
251
        try:
252
            if cache_key not in revision_graph_locks:
253
                revision_graph_locks[cache_key] = threading.Lock()
254
        finally:
255
            revision_graph_check_lock.release()
256
399.1.2 by Max Kanat-Alexander
Address code review comments from mwhudson and Peng.
257
        revision_graph_locks[cache_key].acquire()
399.1.1 by Max Kanat-Alexander
Add locks so that we only build a revision graph cache once simultaneously
258
        try:
259
            for cache in caches:
260
                data = cache.get(cache_key, self.last_revid)
261
                if data is not None:
262
                    self._rev_info = data
263
                    update_missed_caches()
264
                    break
265
                else:
266
                    missed_caches.append(cache)
267
            else:
268
                whole_history_data = compute_whole_history_data(self._branch)
269
                self._rev_info, self._rev_indices = whole_history_data
332.2.5 by Michael Hudson
refactor in the direction of maybe making sense one day
270
                update_missed_caches()
399.1.1 by Max Kanat-Alexander
Add locks so that we only build a revision graph cache once simultaneously
271
        finally:
399.1.2 by Max Kanat-Alexander
Address code review comments from mwhudson and Peng.
272
            revision_graph_locks[cache_key].release()
332.2.5 by Michael Hudson
refactor in the direction of maybe making sense one day
273
274
        if self._rev_indices is not None:
275
            self._revno_revid = {}
276
            for ((_, revid, _, revno_str, _), _, _) in self._rev_info:
277
                self._revno_revid[revno_str] = revid
278
        else:
279
            self._revno_revid = {}
280
            self._rev_indices = {}
281
            for ((seq, revid, _, revno_str, _), _, _) in self._rev_info:
282
                self._rev_indices[revid] = seq
283
                self._revno_revid[revno_str] = revid
284
454.1.1 by Danilo Segan
Get rid of FileChangeCache and its uses.
285
    def __init__(self, branch, whole_history_data_cache,
332.2.5 by Michael Hudson
refactor in the direction of maybe making sense one day
286
                 revinfo_disk_cache=None, cache_key=None):
179.1.10 by Michael Hudson
rename some things, change History to be more conventionally constructed
287
        assert branch.is_locked(), (
288
            "Can only construct a History object with a read-locked branch.")
1 by Robey Pointer
initial checkin
289
        self._branch = branch
411.1.1 by John Arbash Meinel
Branch.tags.get_tag_dict doesn't cache, so cache for it.
290
        self._branch_tags = None
201.2.13 by Michael Hudson
have get_inventory cache the inventories
291
        self._inventory_cache = {}
247.1.1 by Martin Albisetti
Change API used to get branch nicks so they don't access the network
292
        self._branch_nick = self._branch.get_config().get_nickname()
329.1.14 by Matt Nordhoff
Always use a tuple in string formatting
293
        self.log = logging.getLogger('loggerhead.%s' % (self._branch_nick,))
179.1.1 by Michael Hudson
dearie me, all tests pass already!
294
179.1.11 by Michael Hudson
more tidying
295
        self.last_revid = branch.last_revision()
296
405.1.8 by John Arbash Meinel
Clear out the controversial updates.
297
        caches = [RevInfoMemoryCache(whole_history_data_cache)]
298
        if revinfo_disk_cache:
299
            caches.append(revinfo_disk_cache)
300
        self._load_whole_history_data(caches, cache_key)
128.1.55 by Michael Hudson
plumbing for a file change cache
301
144.1.1 by Michael Hudson
reduce the failures
302
    @property
303
    def has_revisions(self):
304
        return not bzrlib.revision.is_null(self.last_revid)
305
74 by Robey Pointer
add the ability to auto-publish anything found under a particular folder.
306
    def get_config(self):
307
        return self._branch.get_config()
128.1.46 by Michael Hudson
delete-trailing-whitespace
308
1 by Robey Pointer
initial checkin
309
    def get_revno(self, revid):
405.1.8 by John Arbash Meinel
Clear out the controversial updates.
310
        if revid not in self._rev_indices:
311
            # ghost parent?
312
            return 'unknown'
313
        seq = self._rev_indices[revid]
314
        revno = self._rev_info[seq][0][3]
315
        return revno
1 by Robey Pointer
initial checkin
316
151.1.3 by Michael Hudson
when filtering on a file_id, show the mainline (relative to the start_revid
317
    def get_revids_from(self, revid_list, start_revid):
318
        """
319
        Yield the mainline (wrt start_revid) revisions that merged each
320
        revid in revid_list.
321
        """
322
        if revid_list is None:
411.3.1 by John Arbash Meinel
Shortcut get_revids_from when we aren't actually checking merges.
323
            # Just yield the mainline, starting at start_revid
324
            revid = start_revid
325
            is_null = bzrlib.revision.is_null
326
            while not is_null(revid):
327
                yield revid
328
                parents = self._rev_info[self._rev_indices[revid]][2]
329
                if not parents:
330
                    return
331
                revid = parents[0]
332
            return
151.1.3 by Michael Hudson
when filtering on a file_id, show the mainline (relative to the start_revid
333
        revid_set = set(revid_list)
405.1.8 by John Arbash Meinel
Clear out the controversial updates.
334
        revid = start_revid
335
336
        def introduced_revisions(revid):
337
            r = set([revid])
338
            seq = self._rev_indices[revid]
339
            md = self._rev_info[seq][0][2]
340
            i = seq + 1
341
            while i < len(self._rev_info) and self._rev_info[i][0][2] > md:
342
                r.add(self._rev_info[i][0][1])
343
                i += 1
344
            return r
411.4.6 by John Arbash Meinel
Test some edge cases for get_revids_from and test_iter.
345
        while revid_set:
405.1.8 by John Arbash Meinel
Clear out the controversial updates.
346
            if bzrlib.revision.is_null(revid):
347
                return
411.4.6 by John Arbash Meinel
Test some edge cases for get_revids_from and test_iter.
348
            rev_introduced = introduced_revisions(revid)
349
            matching = rev_introduced.intersection(revid_set)
350
            if matching:
351
                # We don't need to look for these anymore.
352
                revid_set.difference_update(matching)
405.1.8 by John Arbash Meinel
Clear out the controversial updates.
353
                yield revid
354
            parents = self._rev_info[self._rev_indices[revid]][2]
355
            if len(parents) == 0:
356
                return
357
            revid = parents[0]
358
359
    def get_short_revision_history_by_fileid(self, file_id):
9 by Robey Pointer
starting work on the inventory page, and some starting work on getting a changelog per-path
360
        # FIXME: would be awesome if we could get, for a folder, the list of
405.1.8 by John Arbash Meinel
Clear out the controversial updates.
361
        # revisions where items within that folder changed.i
362
        possible_keys = [(file_id, revid) for revid in self._rev_indices]
363
        get_parent_map = self._branch.repository.texts.get_parent_map
364
        # We chunk the requests as this works better with GraphIndex.
365
        # See _filter_revisions_touching_file_id in bzrlib/log.py
366
        # for more information.
367
        revids = []
368
        chunk_size = 1000
369
        for start in xrange(0, len(possible_keys), chunk_size):
370
            next_keys = possible_keys[start:start + chunk_size]
371
            revids += [k[1] for k in get_parent_map(next_keys)]
372
        del possible_keys, next_keys
373
        return revids
13 by Robey Pointer
clean up revision navigation so that the "revlist" you're browsing is
374
405.1.8 by John Arbash Meinel
Clear out the controversial updates.
375
    def get_revision_history_since(self, revid_list, date):
41 by Robey Pointer
initial search ui (revid, date, and very slow text search)
376
        # if a user asks for revisions starting at 01-sep, they mean inclusive,
377
        # so start at midnight on 02-sep.
378
        date = date + datetime.timedelta(days=1)
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
379
        # our revid list is sorted in REVERSE date order,
380
        # so go thru some hoops here...
41 by Robey Pointer
initial search ui (revid, date, and very slow text search)
381
        revid_list.reverse()
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
382
        index = bisect.bisect(_RevListToTimestamps(revid_list,
383
                                                   self._branch.repository),
384
                              date)
41 by Robey Pointer
initial search ui (revid, date, and very slow text search)
385
        if index == 0:
386
            return []
387
        revid_list.reverse()
388
        index = -index
389
        return revid_list[index:]
128.1.46 by Michael Hudson
delete-trailing-whitespace
390
405.1.8 by John Arbash Meinel
Clear out the controversial updates.
391
    def get_search_revid_list(self, query, revid_list):
41 by Robey Pointer
initial search ui (revid, date, and very slow text search)
392
        """
393
        given a "quick-search" query, try a few obvious possible meanings:
128.1.46 by Michael Hudson
delete-trailing-whitespace
394
41 by Robey Pointer
initial search ui (revid, date, and very slow text search)
395
            - revision id or # ("128.1.3")
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
396
            - date (US style "mm/dd/yy", earth style "dd-mm-yy", or \
397
iso style "yyyy-mm-dd")
41 by Robey Pointer
initial search ui (revid, date, and very slow text search)
398
            - comment text as a fallback
399
400
        and return a revid list that matches.
401
        """
402
        # FIXME: there is some silliness in this action.  we have to look up
403
        # all the relevant changes (time-consuming) only to return a list of
404
        # revids which will be used to fetch a set of changes again.
128.1.46 by Michael Hudson
delete-trailing-whitespace
405
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
406
        # if they entered a revid, just jump straight there;
407
        # ignore the passed-in revid_list
41 by Robey Pointer
initial search ui (revid, date, and very slow text search)
408
        revid = self.fix_revid(query)
409
        if revid is not None:
128.4.4 by Michael Hudson
use the sqlite not-shelf for the change cache too
410
            if isinstance(revid, unicode):
411
                revid = revid.encode('utf-8')
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
412
            changes = self.get_changes([revid])
41 by Robey Pointer
initial search ui (revid, date, and very slow text search)
413
            if (changes is not None) and (len(changes) > 0):
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
414
                return [revid]
128.1.46 by Michael Hudson
delete-trailing-whitespace
415
41 by Robey Pointer
initial search ui (revid, date, and very slow text search)
416
        date = None
417
        m = self.us_date_re.match(query)
418
        if m is not None:
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
419
            date = datetime.datetime(util.fix_year(int(m.group(3))),
420
                                     int(m.group(1)),
421
                                     int(m.group(2)))
41 by Robey Pointer
initial search ui (revid, date, and very slow text search)
422
        else:
423
            m = self.earth_date_re.match(query)
424
            if m is not None:
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
425
                date = datetime.datetime(util.fix_year(int(m.group(3))),
426
                                         int(m.group(2)),
427
                                         int(m.group(1)))
41 by Robey Pointer
initial search ui (revid, date, and very slow text search)
428
            else:
429
                m = self.iso_date_re.match(query)
430
                if m is not None:
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
431
                    date = datetime.datetime(util.fix_year(int(m.group(1))),
432
                                             int(m.group(2)),
433
                                             int(m.group(3)))
41 by Robey Pointer
initial search ui (revid, date, and very slow text search)
434
        if date is not None:
435
            if revid_list is None:
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
436
                # if no limit to the query was given,
437
                # search only the direct-parent path.
179.1.11 by Michael Hudson
more tidying
438
                revid_list = list(self.get_revids_from(None, self.last_revid))
41 by Robey Pointer
initial search ui (revid, date, and very slow text search)
439
            return self.get_revision_history_since(revid_list, date)
128.1.46 by Michael Hudson
delete-trailing-whitespace
440
23 by Robey Pointer
lots of little changes:
441
    revno_re = re.compile(r'^[\d\.]+$')
47 by Robey Pointer
slowly moving the branch-specific stuff into a common structure...
442
    # the date regex are without a final '$' so that queries like
443
    # "2006-11-30 12:15" still mostly work.  (i think it's better to give
444
    # them 90% of what they want instead of nothing at all.)
445
    us_date_re = re.compile(r'^(\d{1,2})/(\d{1,2})/(\d\d(\d\d?))')
446
    earth_date_re = re.compile(r'^(\d{1,2})-(\d{1,2})-(\d\d(\d\d?))')
447
    iso_date_re = re.compile(r'^(\d\d\d\d)-(\d\d)-(\d\d)')
23 by Robey Pointer
lots of little changes:
448
449
    def fix_revid(self, revid):
450
        # if a "revid" is actually a dotted revno, convert it to a revid
451
        if revid is None:
452
            return revid
126 by Robey Pointer
bug 98826: allow "head:" to be used as a valid revid to represent the current
453
        if revid == 'head:':
179.1.11 by Michael Hudson
more tidying
454
            return self.last_revid
217.1.11 by Guillermo Gonzalez
* History.fix_revid raise bzrlib.errors.NoSuchRevision, instead of KeyError
455
        try:
456
            if self.revno_re.match(revid):
405.1.8 by John Arbash Meinel
Clear out the controversial updates.
457
                revid = self._revno_revid[revid]
217.1.11 by Guillermo Gonzalez
* History.fix_revid raise bzrlib.errors.NoSuchRevision, instead of KeyError
458
        except KeyError:
247.1.1 by Martin Albisetti
Change API used to get branch nicks so they don't access the network
459
            raise bzrlib.errors.NoSuchRevision(self._branch_nick, revid)
23 by Robey Pointer
lots of little changes:
460
        return revid
128.1.46 by Michael Hudson
delete-trailing-whitespace
461
43 by Robey Pointer
fix up the other (non-changelog) pages to work with search queries by
462
    def get_file_view(self, revid, file_id):
13 by Robey Pointer
clean up revision navigation so that the "revlist" you're browsing is
463
        """
128.1.39 by Michael Hudson
make history.get_file_view have a clearer interface
464
        Given a revid and optional path, return a (revlist, revid) for
465
        navigation through the current scope: from the revid (or the latest
466
        revision) back to the original revision.
128.1.46 by Michael Hudson
delete-trailing-whitespace
467
38 by Robey Pointer
another pile of semi-related changes:
468
        If file_id is None, the entire revision history is the list scope.
13 by Robey Pointer
clean up revision navigation so that the "revlist" you're browsing is
469
        """
17 by Robey Pointer
think harder about revision traversal, and make it work more deterministic-
470
        if revid is None:
179.1.11 by Michael Hudson
more tidying
471
            revid = self.last_revid
38 by Robey Pointer
another pile of semi-related changes:
472
        if file_id is not None:
411.4.1 by John Arbash Meinel
Only load enough history to show what we need.
473
            revlist = list(
411.4.2 by John Arbash Meinel
Fix the api call.
474
                self.get_short_revision_history_by_fileid(file_id))
411.4.1 by John Arbash Meinel
Only load enough history to show what we need.
475
            revlist = self.get_revids_from(revlist, revid)
13 by Robey Pointer
clean up revision navigation so that the "revlist" you're browsing is
476
        else:
411.4.1 by John Arbash Meinel
Only load enough history to show what we need.
477
            revlist = self.get_revids_from(None, revid)
128.1.39 by Michael Hudson
make history.get_file_view have a clearer interface
478
        return revlist
128.1.46 by Michael Hudson
delete-trailing-whitespace
479
411.4.5 by John Arbash Meinel
Add some tests for _iterate_sufficiently.
480
    @staticmethod
481
    def _iterate_sufficiently(iterable, stop_at, extra_rev_count):
411.4.1 by John Arbash Meinel
Only load enough history to show what we need.
482
        """Return a list of iterable.
483
484
        If extra_rev_count is None, fully consume iterable.
485
        Otherwise, stop at 'stop_at' + extra_rev_count.
486
487
        Example:
488
          iterate until you find stop_at, then iterate 10 more times.
489
        """
490
        if extra_rev_count is None:
491
            return list(iterable)
492
        result = []
493
        found = False
494
        for n in iterable:
495
            result.append(n)
496
            if n == stop_at:
497
                found = True
498
                break
499
        if found:
500
            for count, n in enumerate(iterable):
411.4.5 by John Arbash Meinel
Add some tests for _iterate_sufficiently.
501
                if count >= extra_rev_count:
502
                    break
411.4.1 by John Arbash Meinel
Only load enough history to show what we need.
503
                result.append(n)
504
        return result
505
506
    def get_view(self, revid, start_revid, file_id, query=None,
507
                 extra_rev_count=None):
42 by Robey Pointer
add text substring indexer
508
        """
509
        use the URL parameters (revid, start_revid, file_id, and query) to
510
        determine the revision list we're viewing (start_revid, file_id, query)
511
        and where we are in it (revid).
128.6.16 by Michael Hudson
d-t-w in history.py
512
128.2.24 by Robey Pointer
clean up epydoc for history.get_view().
513
            - if a query is given, we're viewing query results.
514
            - if a file_id is given, we're viewing revisions for a specific
515
              file.
516
            - if a start_revid is given, we're viewing the branch from a
517
              specific revision up the tree.
411.4.1 by John Arbash Meinel
Only load enough history to show what we need.
518
            - if extra_rev_count is given, find the view from start_revid =>
519
              revid, and continue an additional 'extra_rev_count'. If not
520
              given, then revid_list will contain the full history of
521
              start_revid
128.2.24 by Robey Pointer
clean up epydoc for history.get_view().
522
523
        these may be combined to view revisions for a specific file, from
524
        a specific revision, with a specific search query.
144.1.10 by Michael Hudson
run reindent.py over the loggerhead package
525
128.2.24 by Robey Pointer
clean up epydoc for history.get_view().
526
        returns a new (revid, start_revid, revid_list) where:
128.6.16 by Michael Hudson
d-t-w in history.py
527
42 by Robey Pointer
add text substring indexer
528
            - revid: current position within the view
529
            - start_revid: starting revision of this view
530
            - revid_list: list of revision ids for this view
128.1.46 by Michael Hudson
delete-trailing-whitespace
531
42 by Robey Pointer
add text substring indexer
532
        file_id and query are never changed so aren't returned, but they may
533
        contain vital context for future url navigation.
534
        """
128.1.39 by Michael Hudson
make history.get_file_view have a clearer interface
535
        if start_revid is None:
179.1.11 by Michael Hudson
more tidying
536
            start_revid = self.last_revid
128.1.39 by Michael Hudson
make history.get_file_view have a clearer interface
537
42 by Robey Pointer
add text substring indexer
538
        if query is None:
405.1.8 by John Arbash Meinel
Clear out the controversial updates.
539
            revid_list = self.get_file_view(start_revid, file_id)
411.4.1 by John Arbash Meinel
Only load enough history to show what we need.
540
            revid_list = self._iterate_sufficiently(revid_list, revid,
541
                                                    extra_rev_count)
42 by Robey Pointer
add text substring indexer
542
            if revid is None:
543
                revid = start_revid
544
            if revid not in revid_list:
545
                # if the given revid is not in the revlist, use a revlist that
546
                # starts at the given revid.
405.1.8 by John Arbash Meinel
Clear out the controversial updates.
547
                revid_list = self.get_file_view(revid, file_id)
411.4.1 by John Arbash Meinel
Only load enough history to show what we need.
548
                revid_list = self._iterate_sufficiently(revid_list, revid,
549
                                                        extra_rev_count)
128.1.39 by Michael Hudson
make history.get_file_view have a clearer interface
550
                start_revid = revid
43 by Robey Pointer
fix up the other (non-changelog) pages to work with search queries by
551
            return revid, start_revid, revid_list
128.1.46 by Michael Hudson
delete-trailing-whitespace
552
42 by Robey Pointer
add text substring indexer
553
        # potentially limit the search
405.1.8 by John Arbash Meinel
Clear out the controversial updates.
554
        if file_id is not None:
555
            revid_list = self.get_file_view(start_revid, file_id)
556
        else:
557
            revid_list = None
128.10.14 by Martin Albisetti
* Fix searching
558
        revid_list = search.search_revisions(self._branch, query)
159.1.5 by Michael Hudson
restore fix_year, get rid of try:except:
559
        if revid_list and len(revid_list) > 0:
560
            if revid not in revid_list:
561
                revid = revid_list[0]
562
            return revid, start_revid, revid_list
563
        else:
128.12.1 by Robert Collins
Make bzr-search be an optional dependency and avoid errors when there is no search index.
564
            # XXX: This should return a message saying that the search could
565
            # not be completed due to either missing the plugin or missing a
566
            # search index.
42 by Robey Pointer
add text substring indexer
567
            return None, None, []
9 by Robey Pointer
starting work on the inventory page, and some starting work on getting a changelog per-path
568
569
    def get_inventory(self, revid):
201.2.13 by Michael Hudson
have get_inventory cache the inventories
570
        if revid not in self._inventory_cache:
571
            self._inventory_cache[revid] = (
201.4.1 by Matt Nordhoff
Repository.get_revision_inventory() was removed in bzr 2.2; use get_revision instead.
572
                self._branch.repository.get_inventory(revid))
201.2.13 by Michael Hudson
have get_inventory cache the inventories
573
        return self._inventory_cache[revid]
1 by Robey Pointer
initial checkin
574
38 by Robey Pointer
another pile of semi-related changes:
575
    def get_path(self, revid, file_id):
576
        if (file_id is None) or (file_id == ''):
577
            return ''
201.2.12 by Michael Hudson
direct all calls to get_revision_inventory through History.get_inventory
578
        path = self.get_inventory(revid).id2path(file_id)
38 by Robey Pointer
another pile of semi-related changes:
579
        if (len(path) > 0) and not path.startswith('/'):
580
            path = '/' + path
581
        return path
128.1.46 by Michael Hudson
delete-trailing-whitespace
582
125 by Robey Pointer
bug 98826: allow 'annotate' to take a path on the URL line instead of a
583
    def get_file_id(self, revid, path):
584
        if (len(path) > 0) and not path.startswith('/'):
585
            path = '/' + path
201.2.12 by Michael Hudson
direct all calls to get_revision_inventory through History.get_inventory
586
        return self.get_inventory(revid).path2id(path)
128.1.46 by Michael Hudson
delete-trailing-whitespace
587
1 by Robey Pointer
initial checkin
588
    def get_merge_point_list(self, revid):
589
        """
590
        Return the list of revids that have merged this node.
591
        """
128.1.21 by Michael Hudson
kill more dead code (this might help startup time a bit, even)
592
        if '.' not in self.get_revno(revid):
1 by Robey Pointer
initial checkin
593
            return []
128.1.46 by Michael Hudson
delete-trailing-whitespace
594
1 by Robey Pointer
initial checkin
595
        merge_point = []
596
        while True:
332.1.1 by Michael Hudson
eliminate some of the more egregious memory usage.
597
            children = self._rev_info[self._rev_indices[revid]][1]
1 by Robey Pointer
initial checkin
598
            nexts = []
599
            for child in children:
332.1.1 by Michael Hudson
eliminate some of the more egregious memory usage.
600
                child_parents = self._rev_info[self._rev_indices[child]][2]
1 by Robey Pointer
initial checkin
601
                if child_parents[0] == revid:
602
                    nexts.append(child)
603
                else:
604
                    merge_point.append(child)
605
606
            if len(nexts) == 0:
607
                # only merge
608
                return merge_point
609
610
            while len(nexts) > 1:
611
                # branch
612
                next = nexts.pop()
613
                merge_point_next = self.get_merge_point_list(next)
614
                merge_point.extend(merge_point_next)
615
616
            revid = nexts[0]
128.1.46 by Michael Hudson
delete-trailing-whitespace
617
1 by Robey Pointer
initial checkin
618
    def simplify_merge_point_list(self, revids):
619
        """if a revision is already merged, don't show further merge points"""
620
        d = {}
621
        for revid in revids:
622
            revno = self.get_revno(revid)
623
            revnol = revno.split(".")
624
            revnos = ".".join(revnol[:-2])
625
            revnolast = int(revnol[-1])
389 by Matt Nordhoff
Avoid using "dict.keys()" and "dict.items()" in a couple places, in favor of "dict.iter*()".
626
            if revnos in d:
1 by Robey Pointer
initial checkin
627
                m = d[revnos][0]
628
                if revnolast < m:
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
629
                    d[revnos] = (revnolast, revid)
1 by Robey Pointer
initial checkin
630
            else:
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
631
                d[revnos] = (revnolast, revid)
1 by Robey Pointer
initial checkin
632
389.2.1 by Matt Nordhoff
Some random PEP 8 and otehr stylistic changes.
633
        return [revid for (_, revid) in d.itervalues()]
35 by Robey Pointer
makeover of revision fetching, based on some hints on the bazaar mailing
634
305.1.13 by Michael Hudson
* actually finish getting file_changes of the change object.
635
    def add_branch_nicks(self, change):
35 by Robey Pointer
makeover of revision fetching, based on some hints on the bazaar mailing
636
        """
305.1.13 by Michael Hudson
* actually finish getting file_changes of the change object.
637
        given a 'change', fill in the branch nicks on all parents and merge
638
        points.
35 by Robey Pointer
makeover of revision fetching, based on some hints on the bazaar mailing
639
        """
640
        fetch_set = set()
305.1.13 by Michael Hudson
* actually finish getting file_changes of the change object.
641
        for p in change.parents:
642
            fetch_set.add(p.revid)
643
        for p in change.merge_points:
644
            fetch_set.add(p.revid)
35 by Robey Pointer
makeover of revision fetching, based on some hints on the bazaar mailing
645
        p_changes = self.get_changes(list(fetch_set))
646
        p_change_dict = dict([(c.revid, c) for c in p_changes])
305.1.13 by Michael Hudson
* actually finish getting file_changes of the change object.
647
        for p in change.parents:
648
            if p.revid in p_change_dict:
649
                p.branch_nick = p_change_dict[p.revid].branch_nick
650
            else:
651
                p.branch_nick = '(missing)'
652
        for p in change.merge_points:
653
            if p.revid in p_change_dict:
654
                p.branch_nick = p_change_dict[p.revid].branch_nick
655
            else:
656
                p.branch_nick = '(missing)'
128.1.46 by Michael Hudson
delete-trailing-whitespace
657
128.1.47 by Michael Hudson
kill the get_diffs argument to get_changes entirely.
658
    def get_changes(self, revid_list):
144.1.7 by Michael Hudson
add just one docstring
659
        """Return a list of changes objects for the given revids.
660
661
        Revisions not present and NULL_REVISION will be ignored.
662
        """
128.11.1 by Martin Albisetti
* Remove text index caching
663
        changes = self.get_changes_uncached(revid_list)
128.1.15 by Michael Hudson
fix bug #92435, but by hacking not by understanding
664
        if len(changes) == 0:
41 by Robey Pointer
initial search ui (revid, date, and very slow text search)
665
            return changes
128.1.46 by Michael Hudson
delete-trailing-whitespace
666
35 by Robey Pointer
makeover of revision fetching, based on some hints on the bazaar mailing
667
        # some data needs to be recalculated each time, because it may
668
        # change as new revisions are added.
405.1.8 by John Arbash Meinel
Clear out the controversial updates.
669
        for change in changes:
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
670
            merge_revids = self.simplify_merge_point_list(
405.1.8 by John Arbash Meinel
Clear out the controversial updates.
671
                               self.get_merge_point_list(change.revid))
672
            change.merge_points = [
673
                util.Container(revid=r,
674
                revno=self.get_revno(r)) for r in merge_revids]
128.2.20 by Robey Pointer
unfiled bug that i just noticed today:
675
            if len(change.parents) > 0:
405.1.8 by John Arbash Meinel
Clear out the controversial updates.
676
                change.parents = [util.Container(revid=r,
677
                    revno=self.get_revno(r)) for r in change.parents]
128.2.12 by Robey Pointer
don't save the revno in the revision cache, because if two branches use
678
            change.revno = self.get_revno(change.revid)
113.1.3 by Kent Gibson
Rework changes page to provide single line summary per change.
679
680
        parity = 0
681
        for change in changes:
682
            change.parity = parity
683
            parity ^= 1
128.1.46 by Michael Hudson
delete-trailing-whitespace
684
35 by Robey Pointer
makeover of revision fetching, based on some hints on the bazaar mailing
685
        return changes
21 by Robey Pointer
fix a thread-unsafety bug in bzrlib's progress bar system that was vexing
686
128.2.18 by Robey Pointer
rename get_diff to get_change_relative_to (since it returns the entire
687
    def get_changes_uncached(self, revid_list):
128.8.1 by Martin Albisetti
Merge in changes with trunk
688
        # FIXME: deprecated method in getting a null revision
144.1.3 by Michael Hudson
fix two more tests
689
        revid_list = filter(lambda revid: not bzrlib.revision.is_null(revid),
690
                            revid_list)
230.1.1 by Steve 'Ashcrow' Milner
Updated to follow pep8.
691
        parent_map = self._branch.repository.get_graph().get_parent_map(
692
                         revid_list)
148.1.3 by Michael Hudson
use a list comprehension instead
693
        # We need to return the answer in the same order as the input,
694
        # less any ghosts.
695
        present_revids = [revid for revid in revid_list
696
                          if revid in parent_map]
128.11.1 by Martin Albisetti
* Remove text index caching
697
        rev_list = self._branch.repository.get_revisions(present_revids)
128.2.31 by Robey Pointer
fix bugs introduced by incorrectly resolving the previous conflicts. (i
698
144.1.2 by Michael Hudson
use less dumb method of filtering revisions
699
        return [self._change_from_revision(rev) for rev in rev_list]
128.1.46 by Michael Hudson
delete-trailing-whitespace
700
128.2.18 by Robey Pointer
rename get_diff to get_change_relative_to (since it returns the entire
701
    def _change_from_revision(self, revision):
702
        """
703
        Given a bzrlib Revision, return a processed "change" for use in
704
        templates.
705
        """
97 by Robey Pointer
big checkpoint commit. added some functions to util for tracking browsing
706
        message, short_message = clean_message(revision.message)
707
411.1.1 by John Arbash Meinel
Branch.tags.get_tag_dict doesn't cache, so cache for it.
708
        if self._branch_tags is None:
709
            self._branch_tags = self._branch.tags.get_reverse_tag_dict()
366.1.1 by Cris Boylan
Add tags information (bug #246739)
710
711
        revtags = None
411.1.1 by John Arbash Meinel
Branch.tags.get_tag_dict doesn't cache, so cache for it.
712
        if revision.revision_id in self._branch_tags:
432.2.2 by John Arbash Meinel
Sort tags 'naturally', add tests for it.
713
          # tag.sort_* functions expect (tag, data) pairs, so we generate them,
714
          # and then strip them
715
          tags = [(t, None) for t in self._branch_tags[revision.revision_id]]
442.1.1 by John Arbash Meinel
Fix bug #742390, don't use sort_natural
716
          sort_func = getattr(tag, 'sort_natural', None)
717
          if sort_func is None:
718
              tags.sort()
719
          else:
720
              sort_func(self._branch, tags)
432.2.2 by John Arbash Meinel
Sort tags 'naturally', add tests for it.
721
          revtags = u', '.join([t[0] for t in tags])
366.1.1 by Cris Boylan
Add tags information (bug #246739)
722
97 by Robey Pointer
big checkpoint commit. added some functions to util for tracking browsing
723
        entry = {
724
            'revid': revision.revision_id,
345.2.3 by Matt Nordhoff
Simple hack to use UTC times in the feed
725
            'date': datetime.datetime.fromtimestamp(revision.timestamp),
726
            'utc_date': datetime.datetime.utcfromtimestamp(revision.timestamp),
432.2.3 by John Arbash Meinel
Add 'committer' to the change container.
727
            'committer': revision.committer,
369 by Matt Nordhoff
Remove Revision.get_apparent_author() compatibility code for bzr < 1.13
728
            'authors': revision.get_apparent_authors(),
97 by Robey Pointer
big checkpoint commit. added some functions to util for tracking browsing
729
            'branch_nick': revision.properties.get('branch-nick', None),
730
            'short_comment': short_message,
731
            'comment': revision.message,
732
            'comment_clean': [util.html_clean(s) for s in message],
128.2.20 by Robey Pointer
unfiled bug that i just noticed today:
733
            'parents': revision.parent_ids,
356.2.1 by Alexandre Garnier
Add bug link in revision informations
734
            'bugs': [bug.split()[0] for bug in revision.properties.get('bugs', '').splitlines()],
366.1.1 by Cris Boylan
Add tags information (bug #246739)
735
            'tags': revtags,
97 by Robey Pointer
big checkpoint commit. added some functions to util for tracking browsing
736
        }
358.2.1 by Jelmer Vernooij
Provide foreign revision id information.
737
        if isinstance(revision, bzrlib.foreign.ForeignRevision):
442.2.1 by Jelmer Vernooij
Fix showing of foreign revision ids.
738
            foreign_revid, mapping = (
739
                revision.foreign_revid, revision.mapping)
358.2.1 by Jelmer Vernooij
Provide foreign revision id information.
740
        elif ":" in revision.revision_id:
741
            try:
358.2.2 by Jelmer Vernooij
Provide key/value string dictionary with foreign revision id information rather than the raw objects.
742
                foreign_revid, mapping = \
358.2.1 by Jelmer Vernooij
Provide foreign revision id information.
743
                    bzrlib.foreign.foreign_vcs_registry.parse_revision_id(
744
                        revision.revision_id)
745
            except bzrlib.errors.InvalidRevisionId:
358.2.2 by Jelmer Vernooij
Provide key/value string dictionary with foreign revision id information rather than the raw objects.
746
                foreign_revid = None
747
                mapping = None
358.2.3 by Jelmer Vernooij
Make sure foreign_revid is initialized.
748
        else:
749
            foreign_revid = None
358.2.2 by Jelmer Vernooij
Provide key/value string dictionary with foreign revision id information rather than the raw objects.
750
        if foreign_revid is not None:
358.2.5 by Jelmer Vernooij
Show svn/git/hg revision ids in loggerhead revision view.
751
            entry["foreign_vcs"] = mapping.vcs.abbreviation
752
            entry["foreign_revid"] = mapping.vcs.show_foreign_revid(foreign_revid)
97 by Robey Pointer
big checkpoint commit. added some functions to util for tracking browsing
753
        return util.Container(entry)
754
454.1.1 by Danilo Segan
Get rid of FileChangeCache and its uses.
755
    def get_file_changes(self, entry):
305.1.5 by Michael Hudson
ooh nice, remove gobs of code. me happy
756
        if entry.parents:
308.1.3 by Michael Hudson
push all handling of revision trees into history
757
            old_revid = entry.parents[0].revid
305.1.5 by Michael Hudson
ooh nice, remove gobs of code. me happy
758
        else:
308.1.3 by Michael Hudson
push all handling of revision trees into history
759
            old_revid = bzrlib.revision.NULL_REVISION
308.1.4 by Michael Hudson
allow common-case revision page to get its information from the cache
760
        return self.file_changes_for_revision_ids(old_revid, entry.revid)
305.1.5 by Michael Hudson
ooh nice, remove gobs of code. me happy
761
305.1.4 by Michael Hudson
being outside in simplification of interface
762
    def add_changes(self, entry):
305.1.5 by Michael Hudson
ooh nice, remove gobs of code. me happy
763
        changes = self.get_file_changes(entry)
305.1.4 by Michael Hudson
being outside in simplification of interface
764
        entry.changes = changes
128.1.46 by Michael Hudson
delete-trailing-whitespace
765
38 by Robey Pointer
another pile of semi-related changes:
766
    def get_file(self, file_id, revid):
389.2.3 by Matt Nordhoff
moar
767
        """Returns (path, filename, file contents)"""
106 by Robey Pointer
oops, fix up the download logging for real.
768
        inv = self.get_inventory(revid)
769
        inv_entry = inv[file_id]
37 by Robey Pointer
don't bother to rebuild the cache when it's full
770
        rev_tree = self._branch.repository.revision_tree(inv_entry.revision)
106 by Robey Pointer
oops, fix up the download logging for real.
771
        path = inv.id2path(file_id)
772
        if not path.startswith('/'):
773
            path = '/' + path
774
        return path, inv_entry.name, rev_tree.get_file_text(file_id)
128.1.46 by Michael Hudson
delete-trailing-whitespace
775
308.1.4 by Michael Hudson
allow common-case revision page to get its information from the cache
776
    def file_changes_for_revision_ids(self, old_revid, new_revid):
128.2.7 by Robey Pointer
the diff cache isn't adding very much, and can grow very large. let's just
777
        """
778
        Return a nested data structure containing the changes in a delta::
128.1.46 by Michael Hudson
delete-trailing-whitespace
779
128.2.7 by Robey Pointer
the diff cache isn't adding very much, and can grow very large. let's just
780
            added: list((filename, file_id)),
781
            renamed: list((old_filename, new_filename, file_id)),
782
            deleted: list((filename, file_id)),
783
            modified: list(
784
                filename: str,
785
                file_id: str,
305.1.3 by Michael Hudson
Less repitition in the delta handling code and less tangled
786
            ),
787
            text_changes: list((filename, file_id)),
128.2.7 by Robey Pointer
the diff cache isn't adding very much, and can grow very large. let's just
788
        """
308.1.7 by Michael Hudson
work for the initial revision
789
        repo = self._branch.repository
329.1.2 by Matt Nordhoff
Remove unused imports in history.py, plus some syntax tweaks.
790
        if (bzrlib.revision.is_null(old_revid) or
791
            bzrlib.revision.is_null(new_revid)):
308.1.7 by Michael Hudson
work for the initial revision
792
            old_tree, new_tree = map(
793
                repo.revision_tree, [old_revid, new_revid])
794
        else:
795
            old_tree, new_tree = repo.revision_trees([old_revid, new_revid])
308.1.3 by Michael Hudson
push all handling of revision trees into history
796
797
        reporter = FileChangeReporter(old_tree.inventory, new_tree.inventory)
305.1.8 by Michael Hudson
finally, use iter_changes and report_changes, not changes_from.
798
799
        bzrlib.delta.report_changes(new_tree.iter_changes(old_tree), reporter)
305.1.3 by Michael Hudson
Less repitition in the delta handling code and less tangled
800
801
        return util.Container(
389.2.3 by Matt Nordhoff
moar
802
            added=sorted(reporter.added, key=lambda x: x.filename),
803
            renamed=sorted(reporter.renamed, key=lambda x: x.new_filename),
804
            removed=sorted(reporter.removed, key=lambda x: x.filename),
805
            modified=sorted(reporter.modified, key=lambda x: x.filename),
806
            text_changes=sorted(reporter.text_changes, key=lambda x: x.filename))