~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

  • Committer: Vincent Ladeuil
  • Date: 2011-08-12 09:49:24 UTC
  • mfrom: (6015.9.10 2.4)
  • mto: This revision was merged to the branch mainline in revision 6066.
  • Revision ID: v.ladeuil+lp@free.fr-20110812094924-knc5s0g7vs31a2f1
Merge 2.4 into trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
89
89
    get_terminal_encoding,
90
90
    terminal_width,
91
91
    )
92
 
from bzrlib.symbol_versioning import (
93
 
    deprecated_function,
94
 
    deprecated_in,
95
 
    )
96
92
 
97
93
 
98
94
def find_touching_revisions(branch, file_id):
157
153
             end_revision=None,
158
154
             search=None,
159
155
             limit=None,
160
 
             show_diff=False):
 
156
             show_diff=False,
 
157
             match=None):
161
158
    """Write out human-readable log of commits to this branch.
162
159
 
163
160
    This function is being retained for backwards compatibility but
186
183
        if None or 0.
187
184
 
188
185
    :param show_diff: If True, output a diff after each revision.
 
186
 
 
187
    :param match: Dictionary of search lists to use when matching revision
 
188
      properties.
189
189
    """
190
190
    # Convert old-style parameters to new-style parameters
191
191
    if specific_fileid is not None:
219
219
# make_log_request_dict() below
220
220
_DEFAULT_REQUEST_PARAMS = {
221
221
    'direction': 'reverse',
222
 
    'levels': 1,
 
222
    'levels': None,
223
223
    'generate_tags': True,
224
224
    'exclude_common_ancestry': False,
225
225
    '_match_using_deltas': True,
228
228
 
229
229
def make_log_request_dict(direction='reverse', specific_fileids=None,
230
230
                          start_revision=None, end_revision=None, limit=None,
231
 
                          message_search=None, levels=1, generate_tags=True,
 
231
                          message_search=None, levels=None, generate_tags=True,
232
232
                          delta_type=None,
233
233
                          diff_type=None, _match_using_deltas=True,
234
 
                          exclude_common_ancestry=False,
 
234
                          exclude_common_ancestry=False, match=None,
235
235
                          signature=False,
236
236
                          ):
237
237
    """Convenience function for making a logging request dictionary.
259
259
      matching commit messages
260
260
 
261
261
    :param levels: the number of levels of revisions to
262
 
      generate; 1 for just the mainline; 0 for all levels.
 
262
      generate; 1 for just the mainline; 0 for all levels, or None for
 
263
      a sensible default.
263
264
 
264
265
    :param generate_tags: If True, include tags for matched revisions.
265
266
`
282
283
      range operator or as a graph difference.
283
284
 
284
285
    :param signature: show digital signature information
 
286
 
 
287
    :param match: Dictionary of list of search strings to use when filtering
 
288
      revisions. Keys can be 'message', 'author', 'committer', 'bugs' or
 
289
      the empty string to match any of the preceding properties.
 
290
 
285
291
    """
 
292
    # Take care of old style message_search parameter
 
293
    if message_search:
 
294
        if match:
 
295
            if 'message' in match:
 
296
                match['message'].append(message_search)
 
297
            else:
 
298
                match['message'] = [message_search]
 
299
        else:
 
300
            match={ 'message': [message_search] }
286
301
    return {
287
302
        'direction': direction,
288
303
        'specific_fileids': specific_fileids,
289
304
        'start_revision': start_revision,
290
305
        'end_revision': end_revision,
291
306
        'limit': limit,
292
 
        'message_search': message_search,
293
307
        'levels': levels,
294
308
        'generate_tags': generate_tags,
295
309
        'delta_type': delta_type,
296
310
        'diff_type': diff_type,
297
311
        'exclude_common_ancestry': exclude_common_ancestry,
298
312
        'signature': signature,
 
313
        'match': match,
299
314
        # Add 'private' attributes for features that may be deprecated
300
315
        '_match_using_deltas': _match_using_deltas,
301
316
    }
311
326
 
312
327
def format_signature_validity(rev_id, repo):
313
328
    """get the signature validity
314
 
    
 
329
 
315
330
    :param rev_id: revision id to validate
316
331
    :param repo: repository of revision
317
332
    :return: human readable string to print to log
380
395
        # Tweak the LogRequest based on what the LogFormatter can handle.
381
396
        # (There's no point generating stuff if the formatter can't display it.)
382
397
        rqst = self.rqst
383
 
        rqst['levels'] = lf.get_levels()
 
398
        if rqst['levels'] is None or lf.get_levels() > rqst['levels']:
 
399
            # user didn't specify levels, use whatever the LF can handle:
 
400
            rqst['levels'] = lf.get_levels()
 
401
 
384
402
        if not getattr(lf, 'supports_tags', False):
385
403
            rqst['generate_tags'] = False
386
404
        if not getattr(lf, 'supports_delta', False):
398
416
 
399
417
    def _generator_factory(self, branch, rqst):
400
418
        """Make the LogGenerator object to use.
401
 
        
 
419
 
402
420
        Subclasses may wish to override this.
403
421
        """
404
422
        return _DefaultLogGenerator(branch, rqst)
507
525
 
508
526
        # Apply the other filters
509
527
        return make_log_rev_iterator(self.branch, view_revisions,
510
 
            rqst.get('delta_type'), rqst.get('message_search'),
 
528
            rqst.get('delta_type'), rqst.get('match'),
511
529
            file_ids=rqst.get('specific_fileids'),
512
530
            direction=rqst.get('direction'))
513
531
 
526
544
            rqst.get('specific_fileids')[0], view_revisions,
527
545
            include_merges=rqst.get('levels') != 1)
528
546
        return make_log_rev_iterator(self.branch, view_revisions,
529
 
            rqst.get('delta_type'), rqst.get('message_search'))
 
547
            rqst.get('delta_type'), rqst.get('match'))
530
548
 
531
549
 
532
550
def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
783
801
            yield rev_id, '.'.join(map(str, revno)), merge_depth
784
802
 
785
803
 
786
 
@deprecated_function(deprecated_in((2, 2, 0)))
787
 
def calculate_view_revisions(branch, start_revision, end_revision, direction,
788
 
        specific_fileid, generate_merge_revisions):
789
 
    """Calculate the revisions to view.
790
 
 
791
 
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
792
 
             a list of the same tuples.
793
 
    """
794
 
    start_rev_id, end_rev_id = _get_revision_limits(branch, start_revision,
795
 
        end_revision)
796
 
    view_revisions = list(_calc_view_revisions(branch, start_rev_id, end_rev_id,
797
 
        direction, generate_merge_revisions or specific_fileid))
798
 
    if specific_fileid:
799
 
        view_revisions = _filter_revisions_touching_file_id(branch,
800
 
            specific_fileid, view_revisions,
801
 
            include_merges=generate_merge_revisions)
802
 
    return _rebase_merge_depth(view_revisions)
803
 
 
804
 
 
805
804
def _rebase_merge_depth(view_revisions):
806
805
    """Adjust depths upwards so the top level is 0."""
807
806
    # If either the first or last revision have a merge_depth of 0, we're done
851
850
    return log_rev_iterator
852
851
 
853
852
 
854
 
def _make_search_filter(branch, generate_delta, search, log_rev_iterator):
 
853
def _make_search_filter(branch, generate_delta, match, log_rev_iterator):
855
854
    """Create a filtered iterator of log_rev_iterator matching on a regex.
856
855
 
857
856
    :param branch: The branch being logged.
858
857
    :param generate_delta: Whether to generate a delta for each revision.
859
 
    :param search: A user text search string.
 
858
    :param match: A dictionary with properties as keys and lists of strings
 
859
        as values. To match, a revision may match any of the supplied strings
 
860
        within a single property but must match at least one string for each
 
861
        property.
860
862
    :param log_rev_iterator: An input iterator containing all revisions that
861
863
        could be displayed, in lists.
862
864
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
863
865
        delta).
864
866
    """
865
 
    if search is None:
 
867
    if match is None:
866
868
        return log_rev_iterator
867
 
    searchRE = lazy_regex.lazy_compile(search, re.IGNORECASE)
868
 
    return _filter_message_re(searchRE, log_rev_iterator)
869
 
 
870
 
 
871
 
def _filter_message_re(searchRE, log_rev_iterator):
 
869
    searchRE = [(k, [re.compile(x, re.IGNORECASE) for x in v])
 
870
                for (k,v) in match.iteritems()]
 
871
    return _filter_re(searchRE, log_rev_iterator)
 
872
 
 
873
 
 
874
def _filter_re(searchRE, log_rev_iterator):
872
875
    for revs in log_rev_iterator:
873
 
        new_revs = []
874
 
        for (rev_id, revno, merge_depth), rev, delta in revs:
875
 
            if searchRE.search(rev.message):
876
 
                new_revs.append(((rev_id, revno, merge_depth), rev, delta))
877
 
        yield new_revs
878
 
 
 
876
        new_revs = [rev for rev in revs if _match_filter(searchRE, rev[1])]
 
877
        if new_revs:
 
878
            yield new_revs
 
879
 
 
880
def _match_filter(searchRE, rev):
 
881
    strings = {
 
882
               'message': (rev.message,),
 
883
               'committer': (rev.committer,),
 
884
               'author': (rev.get_apparent_authors()),
 
885
               'bugs': list(rev.iter_bugs())
 
886
               }
 
887
    strings[''] = [item for inner_list in strings.itervalues()
 
888
                   for item in inner_list]
 
889
    for (k,v) in searchRE:
 
890
        if k in strings and not _match_any_filter(strings[k], v):
 
891
            return False
 
892
    return True
 
893
 
 
894
def _match_any_filter(strings, res):
 
895
    return any([filter(None, map(re.search, strings)) for re in res])
879
896
 
880
897
def _make_delta_filter(branch, generate_delta, search, log_rev_iterator,
881
898
    fileids=None, direction='reverse'):
954
971
 
955
972
def _update_fileids(delta, fileids, stop_on):
956
973
    """Update the set of file-ids to search based on file lifecycle events.
957
 
    
 
974
 
958
975
    :param fileids: a set of fileids to update
959
976
    :param stop_on: either 'add' or 'remove' - take file-ids out of the
960
977
      fileids set once their add or remove entry is detected respectively
1001
1018
    :return: An iterator over lists of ((rev_id, revno, merge_depth), rev,
1002
1019
        delta).
1003
1020
    """
1004
 
    repository = branch.repository
1005
1021
    num = 9
1006
1022
    for batch in log_rev_iterator:
1007
1023
        batch = iter(batch)
1146
1162
    return mainline_revs, rev_nos, start_rev_id, end_rev_id
1147
1163
 
1148
1164
 
1149
 
@deprecated_function(deprecated_in((2, 2, 0)))
1150
 
def _filter_revision_range(view_revisions, start_rev_id, end_rev_id):
1151
 
    """Filter view_revisions based on revision ranges.
1152
 
 
1153
 
    :param view_revisions: A list of (revision_id, dotted_revno, merge_depth)
1154
 
            tuples to be filtered.
1155
 
 
1156
 
    :param start_rev_id: If not NONE specifies the first revision to be logged.
1157
 
            If NONE then all revisions up to the end_rev_id are logged.
1158
 
 
1159
 
    :param end_rev_id: If not NONE specifies the last revision to be logged.
1160
 
            If NONE then all revisions up to the end of the log are logged.
1161
 
 
1162
 
    :return: The filtered view_revisions.
1163
 
    """
1164
 
    if start_rev_id or end_rev_id:
1165
 
        revision_ids = [r for r, n, d in view_revisions]
1166
 
        if start_rev_id:
1167
 
            start_index = revision_ids.index(start_rev_id)
1168
 
        else:
1169
 
            start_index = 0
1170
 
        if start_rev_id == end_rev_id:
1171
 
            end_index = start_index
1172
 
        else:
1173
 
            if end_rev_id:
1174
 
                end_index = revision_ids.index(end_rev_id)
1175
 
            else:
1176
 
                end_index = len(view_revisions) - 1
1177
 
        # To include the revisions merged into the last revision,
1178
 
        # extend end_rev_id down to, but not including, the next rev
1179
 
        # with the same or lesser merge_depth
1180
 
        end_merge_depth = view_revisions[end_index][2]
1181
 
        try:
1182
 
            for index in xrange(end_index+1, len(view_revisions)+1):
1183
 
                if view_revisions[index][2] <= end_merge_depth:
1184
 
                    end_index = index - 1
1185
 
                    break
1186
 
        except IndexError:
1187
 
            # if the search falls off the end then log to the end as well
1188
 
            end_index = len(view_revisions) - 1
1189
 
        view_revisions = view_revisions[start_index:end_index+1]
1190
 
    return view_revisions
1191
 
 
1192
 
 
1193
1165
def _filter_revisions_touching_file_id(branch, file_id, view_revisions,
1194
1166
    include_merges=True):
1195
1167
    r"""Return the list of revision ids which touch a given file id.
1274
1246
    return result
1275
1247
 
1276
1248
 
1277
 
@deprecated_function(deprecated_in((2, 2, 0)))
1278
 
def get_view_revisions(mainline_revs, rev_nos, branch, direction,
1279
 
                       include_merges=True):
1280
 
    """Produce an iterator of revisions to show
1281
 
    :return: an iterator of (revision_id, revno, merge_depth)
1282
 
    (if there is no revno for a revision, None is supplied)
1283
 
    """
1284
 
    if not include_merges:
1285
 
        revision_ids = mainline_revs[1:]
1286
 
        if direction == 'reverse':
1287
 
            revision_ids.reverse()
1288
 
        for revision_id in revision_ids:
1289
 
            yield revision_id, str(rev_nos[revision_id]), 0
1290
 
        return
1291
 
    graph = branch.repository.get_graph()
1292
 
    # This asks for all mainline revisions, which means we only have to spider
1293
 
    # sideways, rather than depth history. That said, its still size-of-history
1294
 
    # and should be addressed.
1295
 
    # mainline_revisions always includes an extra revision at the beginning, so
1296
 
    # don't request it.
1297
 
    parent_map = dict(((key, value) for key, value in
1298
 
        graph.iter_ancestry(mainline_revs[1:]) if value is not None))
1299
 
    # filter out ghosts; merge_sort errors on ghosts.
1300
 
    rev_graph = _mod_repository._strip_NULL_ghosts(parent_map)
1301
 
    merge_sorted_revisions = tsort.merge_sort(
1302
 
        rev_graph,
1303
 
        mainline_revs[-1],
1304
 
        mainline_revs,
1305
 
        generate_revno=True)
1306
 
 
1307
 
    if direction == 'forward':
1308
 
        # forward means oldest first.
1309
 
        merge_sorted_revisions = reverse_by_depth(merge_sorted_revisions)
1310
 
    elif direction != 'reverse':
1311
 
        raise ValueError('invalid direction %r' % direction)
1312
 
 
1313
 
    for (sequence, rev_id, merge_depth, revno, end_of_merge
1314
 
         ) in merge_sorted_revisions:
1315
 
        yield rev_id, '.'.join(map(str, revno)), merge_depth
1316
 
 
1317
 
 
1318
1249
def reverse_by_depth(merge_sorted_revisions, _depth=0):
1319
1250
    """Reverse revisions by depth.
1320
1251
 
1417
1348
        """Create a LogFormatter.
1418
1349
 
1419
1350
        :param to_file: the file to output to
1420
 
        :param to_exact_file: if set, gives an output stream to which 
 
1351
        :param to_exact_file: if set, gives an output stream to which
1421
1352
             non-Unicode diffs are written.
1422
1353
        :param show_ids: if True, revision-ids are to be displayed
1423
1354
        :param show_timezone: the timezone to use
1659
1590
        if revision.delta is not None:
1660
1591
            # Use the standard status output to display changes
1661
1592
            from bzrlib.delta import report_delta
1662
 
            report_delta(to_file, revision.delta, short_status=False, 
 
1593
            report_delta(to_file, revision.delta, short_status=False,
1663
1594
                         show_ids=self.show_ids, indent=indent)
1664
1595
        if revision.diff is not None:
1665
1596
            to_file.write(indent + 'diff:\n')
1731
1662
        if revision.delta is not None:
1732
1663
            # Use the standard status output to display changes
1733
1664
            from bzrlib.delta import report_delta
1734
 
            report_delta(to_file, revision.delta, 
1735
 
                         short_status=self.delta_format==1, 
 
1665
            report_delta(to_file, revision.delta,
 
1666
                         short_status=self.delta_format==1,
1736
1667
                         show_ids=self.show_ids, indent=indent + offset)
1737
1668
        if revision.diff is not None:
1738
1669
            self.show_diff(self.to_exact_file, revision.diff, '      ')
1918
1849
                              'The committer')
1919
1850
 
1920
1851
 
1921
 
def show_one_log(revno, rev, delta, verbose, to_file, show_timezone):
1922
 
    # deprecated; for compatibility
1923
 
    lf = LongLogFormatter(to_file=to_file, show_timezone=show_timezone)
1924
 
    lf.show(revno, rev, delta)
1925
 
 
1926
 
 
1927
1852
def show_changed_revisions(branch, old_rh, new_rh, to_file=None,
1928
1853
                           log_format='long'):
1929
1854
    """Show the change in revision history comparing the old revision history to the new one.