~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/log.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-10-06 20:45:48 UTC
  • mfrom: (4728.1.2 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20091006204548-bjnc3z4k256ppimz
MutableTree.has_changes() does not require a tree parameter anymore

Show diffs side-by-side

added added

removed removed

Lines of Context:
69
69
    config,
70
70
    diff,
71
71
    errors,
 
72
    foreign,
72
73
    repository as _mod_repository,
73
74
    revision as _mod_revision,
74
75
    revisionspec,
279
280
        'diff_type': diff_type,
280
281
        # Add 'private' attributes for features that may be deprecated
281
282
        '_match_using_deltas': _match_using_deltas,
282
 
        '_allow_single_merge_revision': True,
283
283
    }
284
284
 
285
285
 
348
348
            rqst['delta_type'] = None
349
349
        if not getattr(lf, 'supports_diff', False):
350
350
            rqst['diff_type'] = None
351
 
        if not getattr(lf, 'supports_merge_revisions', False):
352
 
            rqst['_allow_single_merge_revision'] = getattr(lf,
353
 
                'supports_single_merge_revision', False)
354
351
 
355
352
        # Find and print the interesting revisions
356
353
        generator = self._generator_factory(self.branch, rqst)
357
354
        for lr in generator.iter_log_revisions():
358
355
            lf.log_revision(lr)
 
356
        lf.show_advice()
359
357
 
360
358
    def _generator_factory(self, branch, rqst):
361
359
        """Make the LogGenerator object to use.
453
451
                rqst.get('limit') or self.start_rev_id or self.end_rev_id)
454
452
        view_revisions = _calc_view_revisions(self.branch, self.start_rev_id,
455
453
            self.end_rev_id, rqst.get('direction'), generate_merge_revisions,
456
 
            rqst.get('_allow_single_merge_revision'),
457
454
            delayed_graph_generation=delayed_graph_generation)
458
455
 
459
456
        # Apply the other filters
468
465
        # filter_revisions_touching_file_id() requires them ...
469
466
        rqst = self.rqst
470
467
        view_revisions = _calc_view_revisions(self.branch, self.start_rev_id,
471
 
            self.end_rev_id, rqst.get('direction'), True,
472
 
            rqst.get('_allow_single_merge_revision'))
 
468
            self.end_rev_id, rqst.get('direction'), True)
473
469
        if not isinstance(view_revisions, list):
474
470
            view_revisions = list(view_revisions)
475
471
        view_revisions = _filter_revisions_touching_file_id(self.branch,
480
476
 
481
477
 
482
478
def _calc_view_revisions(branch, start_rev_id, end_rev_id, direction,
483
 
    generate_merge_revisions, allow_single_merge_revision,
484
 
    delayed_graph_generation=False):
 
479
    generate_merge_revisions, delayed_graph_generation=False):
485
480
    """Calculate the revisions to view.
486
481
 
487
482
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
495
490
    generate_single_revision = (end_rev_id and start_rev_id == end_rev_id and
496
491
        (not generate_merge_revisions or not _has_merges(branch, end_rev_id)))
497
492
    if generate_single_revision:
498
 
        return _generate_one_revision(branch, end_rev_id, br_rev_id, br_revno,
499
 
            allow_single_merge_revision)
 
493
        return _generate_one_revision(branch, end_rev_id, br_rev_id, br_revno)
500
494
 
501
495
    # If we only want to see linear revisions, we can iterate ...
502
496
    if not generate_merge_revisions:
507
501
            direction, delayed_graph_generation)
508
502
 
509
503
 
510
 
def _generate_one_revision(branch, rev_id, br_rev_id, br_revno,
511
 
    allow_single_merge_revision):
 
504
def _generate_one_revision(branch, rev_id, br_rev_id, br_revno):
512
505
    if rev_id == br_rev_id:
513
506
        # It's the tip
514
507
        return [(br_rev_id, br_revno, 0)]
515
508
    else:
516
509
        revno = branch.revision_id_to_dotted_revno(rev_id)
517
 
        if len(revno) > 1 and not allow_single_merge_revision:
518
 
            # It's a merge revision and the log formatter is
519
 
            # completely brain dead. This "feature" of allowing
520
 
            # log formatters incapable of displaying dotted revnos
521
 
            # ought to be deprecated IMNSHO. IGC 20091022
522
 
            raise errors.BzrCommandError('Selected log formatter only'
523
 
                ' supports mainline revisions.')
524
510
        revno_str = '.'.join(str(n) for n in revno)
525
511
        return [(rev_id, revno_str, 0)]
526
512
 
682
668
 
683
669
 
684
670
def calculate_view_revisions(branch, start_revision, end_revision, direction,
685
 
        specific_fileid, generate_merge_revisions, allow_single_merge_revision):
 
671
        specific_fileid, generate_merge_revisions):
686
672
    """Calculate the revisions to view.
687
673
 
688
674
    :return: An iterator of (revision_id, dotted_revno, merge_depth) tuples OR
694
680
    start_rev_id, end_rev_id = _get_revision_limits(branch, start_revision,
695
681
        end_revision)
696
682
    view_revisions = list(_calc_view_revisions(branch, start_rev_id, end_rev_id,
697
 
        direction, generate_merge_revisions or specific_fileid,
698
 
        allow_single_merge_revision))
 
683
        direction, generate_merge_revisions or specific_fileid))
699
684
    if specific_fileid:
700
685
        view_revisions = _filter_revisions_touching_file_id(branch,
701
686
            specific_fileid, view_revisions,
1283
1268
        one (2) should be used.
1284
1269
 
1285
1270
    - supports_merge_revisions must be True if this log formatter supports
1286
 
        merge revisions.  If not, and if supports_single_merge_revision is
1287
 
        also not True, then only mainline revisions will be passed to the
1288
 
        formatter.
 
1271
        merge revisions.  If not, then only mainline revisions will be passed
 
1272
        to the formatter.
1289
1273
 
1290
1274
    - preferred_levels is the number of levels this formatter defaults to.
1291
1275
        The default value is zero meaning display all levels.
1292
1276
        This value is only relevant if supports_merge_revisions is True.
1293
1277
 
1294
 
    - supports_single_merge_revision must be True if this log formatter
1295
 
        supports logging only a single merge revision.  This flag is
1296
 
        only relevant if supports_merge_revisions is not True.
1297
 
 
1298
1278
    - supports_tags must be True if this log formatter supports tags.
1299
1279
        Otherwise the tags attribute may not be populated.
1300
1280
 
1311
1291
    preferred_levels = 0
1312
1292
 
1313
1293
    def __init__(self, to_file, show_ids=False, show_timezone='original',
1314
 
                 delta_format=None, levels=None):
 
1294
                 delta_format=None, levels=None, show_advice=False):
1315
1295
        """Create a LogFormatter.
1316
1296
 
1317
1297
        :param to_file: the file to output to
1318
1298
        :param show_ids: if True, revision-ids are to be displayed
1319
1299
        :param show_timezone: the timezone to use
1320
1300
        :param delta_format: the level of delta information to display
1321
 
          or None to leave it u to the formatter to decide
 
1301
          or None to leave it to the formatter to decide
1322
1302
        :param levels: the number of levels to display; None or -1 to
1323
1303
          let the log formatter decide.
 
1304
        :param show_advice: whether to show advice at the end of the
 
1305
          log or not
1324
1306
        """
1325
1307
        self.to_file = to_file
1326
1308
        # 'exact' stream used to show diff, it should print content 'as is'
1333
1315
            delta_format = 2 # long format
1334
1316
        self.delta_format = delta_format
1335
1317
        self.levels = levels
 
1318
        self._show_advice = show_advice
 
1319
        self._merge_count = 0
1336
1320
 
1337
1321
    def get_levels(self):
1338
1322
        """Get the number of levels to display or 0 for all."""
1339
1323
        if getattr(self, 'supports_merge_revisions', False):
1340
1324
            if self.levels is None or self.levels == -1:
1341
 
                return self.preferred_levels
1342
 
            else:
1343
 
                return self.levels
1344
 
        return 1
 
1325
                self.levels = self.preferred_levels
 
1326
        else:
 
1327
            self.levels = 1
 
1328
        return self.levels
1345
1329
 
1346
1330
    def log_revision(self, revision):
1347
1331
        """Log a revision.
1350
1334
        """
1351
1335
        raise NotImplementedError('not implemented in abstract base')
1352
1336
 
 
1337
    def show_advice(self):
 
1338
        """Output user advice, if any, when the log is completed."""
 
1339
        if self._show_advice and self.levels == 1 and self._merge_count > 0:
 
1340
            advice_sep = self.get_advice_separator()
 
1341
            if advice_sep:
 
1342
                self.to_file.write(advice_sep)
 
1343
            self.to_file.write(
 
1344
                "Use --include-merges or -n0 to see merged revisions.\n")
 
1345
 
 
1346
    def get_advice_separator(self):
 
1347
        """Get the text separating the log from the closing advice."""
 
1348
        return ''
 
1349
 
1353
1350
    def short_committer(self, rev):
1354
1351
        name, address = config.parse_username(rev.committer)
1355
1352
        if name:
1362
1359
            return name
1363
1360
        return address
1364
1361
 
 
1362
    def merge_marker(self, revision):
 
1363
        """Get the merge marker to include in the output or '' if none."""
 
1364
        if len(revision.rev.parent_ids) > 1:
 
1365
            self._merge_count += 1
 
1366
            return ' [merge]'
 
1367
        else:
 
1368
            return ''
 
1369
 
 
1370
    def show_foreign_info(self, rev, indent):
 
1371
        """Custom log displayer for foreign revision identifiers.
 
1372
 
 
1373
        :param rev: Revision object.
 
1374
        """
 
1375
        # Revision comes directly from a foreign repository
 
1376
        if isinstance(rev, foreign.ForeignRevision):
 
1377
            self._write_properties(indent, rev.mapping.vcs.show_foreign_revid(
 
1378
                rev.foreign_revid))
 
1379
            return
 
1380
 
 
1381
        # Imported foreign revision revision ids always contain :
 
1382
        if not ":" in rev.revision_id:
 
1383
            return
 
1384
 
 
1385
        # Revision was once imported from a foreign repository
 
1386
        try:
 
1387
            foreign_revid, mapping = \
 
1388
                foreign.foreign_vcs_registry.parse_revision_id(rev.revision_id)
 
1389
        except errors.InvalidRevisionId:
 
1390
            return
 
1391
 
 
1392
        self._write_properties(indent, 
 
1393
            mapping.vcs.show_foreign_revid(foreign_revid))
 
1394
 
1365
1395
    def show_properties(self, revision, indent):
1366
1396
        """Displays the custom properties returned by each registered handler.
1367
1397
 
1368
1398
        If a registered handler raises an error it is propagated.
1369
1399
        """
1370
1400
        for key, handler in properties_handler_registry.iteritems():
1371
 
            for key, value in handler(revision).items():
1372
 
                self.to_file.write(indent + key + ': ' + value + '\n')
 
1401
            self._write_properties(indent, handler(revision))
 
1402
 
 
1403
    def _write_properties(self, indent, properties):
 
1404
        for key, value in properties.items():
 
1405
            self.to_file.write(indent + key + ': ' + value + '\n')
1373
1406
 
1374
1407
    def show_diff(self, to_file, diff, indent):
1375
1408
        for l in diff.rstrip().split('\n'):
1379
1412
class LongLogFormatter(LogFormatter):
1380
1413
 
1381
1414
    supports_merge_revisions = True
 
1415
    preferred_levels = 1
1382
1416
    supports_delta = True
1383
1417
    supports_tags = True
1384
1418
    supports_diff = True
1389
1423
        to_file = self.to_file
1390
1424
        to_file.write(indent + '-' * 60 + '\n')
1391
1425
        if revision.revno is not None:
1392
 
            to_file.write(indent + 'revno: %s\n' % (revision.revno,))
 
1426
            to_file.write(indent + 'revno: %s%s\n' % (revision.revno,
 
1427
                self.merge_marker(revision)))
1393
1428
        if revision.tags:
1394
1429
            to_file.write(indent + 'tags: %s\n' % (', '.join(revision.tags)))
1395
1430
        if self.show_ids:
1397
1432
            to_file.write('\n')
1398
1433
            for parent_id in revision.rev.parent_ids:
1399
1434
                to_file.write(indent + 'parent: %s\n' % (parent_id,))
 
1435
        self.show_foreign_info(revision.rev, indent)
1400
1436
        self.show_properties(revision.rev, indent)
1401
1437
 
1402
1438
        committer = revision.rev.committer
1431
1467
            # revision information) so that the output can be fed to patch -p0
1432
1468
            self.show_diff(self.to_exact_file, revision.diff, indent)
1433
1469
 
 
1470
    def get_advice_separator(self):
 
1471
        """Get the text separating the log from the closing advice."""
 
1472
        return '-' * 60 + '\n'
 
1473
 
1434
1474
 
1435
1475
class ShortLogFormatter(LogFormatter):
1436
1476
 
1465
1505
        offset = ' ' * (revno_width + 1)
1466
1506
 
1467
1507
        to_file = self.to_file
1468
 
        is_merge = ''
1469
 
        if len(revision.rev.parent_ids) > 1:
1470
 
            is_merge = ' [merge]'
1471
1508
        tags = ''
1472
1509
        if revision.tags:
1473
1510
            tags = ' {%s}' % (', '.join(revision.tags))
1477
1514
                            revision.rev.timezone or 0,
1478
1515
                            self.show_timezone, date_fmt="%Y-%m-%d",
1479
1516
                            show_offset=False),
1480
 
                tags, is_merge))
 
1517
                tags, self.merge_marker(revision)))
 
1518
        self.show_foreign_info(revision.rev, indent+offset)
1481
1519
        self.show_properties(revision.rev, indent+offset)
1482
1520
        if self.show_ids:
1483
1521
            to_file.write(indent + offset + 'revision-id:%s\n'
1826
1864
    info_list = []
1827
1865
    start_rev_info, end_rev_info = _get_revision_range(revisionspec_list, b,
1828
1866
        "log")
 
1867
    if relpaths in ([], [u'']):
 
1868
        return b, [], start_rev_info, end_rev_info
1829
1869
    if start_rev_info is None and end_rev_info is None:
1830
1870
        if tree is None:
1831
1871
            tree = b.basis_tree()
1892
1932
 
1893
1933
 
1894
1934
properties_handler_registry = registry.Registry()
1895
 
properties_handler_registry.register_lazy("foreign",
1896
 
                                          "bzrlib.foreign",
1897
 
                                          "show_foreign_properties")
1898
1935
 
1899
1936
 
1900
1937
# adapters which revision ids to log are filtered. When log is called, the