1
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
 
3
# This program is free software; you can redistribute it and/or modify
 
 
4
# it under the terms of the GNU General Public License as published by
 
 
5
# the Free Software Foundation; either version 2 of the License, or
 
 
6
# (at your option) any later version.
 
 
8
# This program is distributed in the hope that it will be useful,
 
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
 
11
# GNU General Public License for more details.
 
 
13
# You should have received a copy of the GNU General Public License
 
 
14
# along with this program; if not, write to the Free Software
 
 
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 
17
__all__ = ['show_bzrdir_info']
 
 
28
from bzrlib.errors import (NoWorkingTree, NotBranchError,
 
 
29
                           NoRepositoryPresent, NotLocalUrl)
 
 
30
from bzrlib.missing import find_unmerged
 
 
31
from bzrlib.symbol_versioning import (deprecated_function,
 
 
32
        zero_eight, zero_eighteen)
 
 
35
def plural(n, base='', pl=None):
 
 
44
def _repo_rel_url(repo_url, inner_url):
 
 
45
    """Return path with common prefix of repository path removed.
 
 
47
    If path is not part of the repository, the original path is returned.
 
 
48
    If path is equal to the repository, the current directory marker '.' is
 
 
50
    Otherwise, a relative path is returned, with trailing '/' stripped.
 
 
52
    inner_url = urlutils.normalize_url(inner_url)
 
 
53
    repo_url = urlutils.normalize_url(repo_url)
 
 
54
    if inner_url == repo_url:
 
 
56
    result = urlutils.relative_url(repo_url, inner_url)
 
 
57
    if result != inner_url:
 
 
58
        result = result.rstrip('/')
 
 
61
class _UrlList(object):
 
 
66
    def add_url(self, label, url):
 
 
67
        self.add_path(label, urlutils.unescape_for_display(url, 'ascii'))
 
 
69
    def add_url(self, label, url):
 
 
70
        self.add_path(label, url)
 
 
72
    def add_path(self, label, path):
 
 
73
        self.urls.append((label, path))
 
 
75
    def print_lines(self):
 
 
76
        max_len = max(len(l) for l, u in self.urls)
 
 
77
        for label, url in self.urls:
 
 
78
            print "  %*s: %s" % (max_len, label, url)
 
 
81
def gather_location_info(repository, branch=None, working=None):
 
 
83
    repository_path = repository.bzrdir.root_transport.base
 
 
84
    if branch is not None:
 
 
85
        branch_path = branch.bzrdir.root_transport.base
 
 
86
        master_path = branch.get_bound_location()
 
 
87
        if master_path is None:
 
 
88
            master_path = branch_path
 
 
93
        working_path = working.bzrdir.root_transport.base
 
 
94
        if working_path != branch_path:
 
 
95
            locs['light checkout root'] = working_path
 
 
96
        if master_path != branch_path:
 
 
97
            if repository.is_shared():
 
 
98
                locs['repository checkout root'] = branch_path
 
 
100
                locs['checkout root'] = branch_path
 
 
101
        if working_path != master_path:
 
 
102
            locs['checkout of branch'] = master_path
 
 
103
        elif repository.is_shared():
 
 
104
            locs['repository branch'] = _repo_rel_url(repository_path,
 
 
106
        elif branch_path is not None:
 
 
108
            locs['branch root'] = branch_path
 
 
111
        if repository.is_shared():
 
 
112
            # lightweight checkout of branch in shared repository
 
 
113
            if branch_path is not None:
 
 
114
                locs['repository branch'] = _repo_rel_url(repository_path,
 
 
116
        elif branch_path is not None:
 
 
118
            locs['branch root'] = branch_path
 
 
119
            if master_path != branch_path:
 
 
120
                locs['bound to branch'] = master_path
 
 
122
            locs['repository'] = repository_path
 
 
123
    if repository.is_shared():
 
 
124
        # lightweight checkout of branch in shared repository
 
 
125
        locs['shared repository'] = repository_path
 
 
126
    order = ['light checkout root', 'repository checkout root',
 
 
127
             'checkout root', 'checkout of branch', 'shared repository',
 
 
128
             'repository', 'repository branch', 'branch root',
 
 
130
    return [(n, locs[n]) for n in order if n in locs]
 
 
133
def _show_location_info(locs):
 
 
134
    """Show known locations for working, branch and repository."""
 
 
136
    path_list = _UrlList()
 
 
137
    for name, loc in locs:
 
 
138
        path_list.add_url(name, loc)
 
 
139
    path_list.print_lines()
 
 
142
def _show_related_info(branch):
 
 
143
    """Show parent and push location of branch."""
 
 
144
    if branch.get_parent() or branch.get_push_location():
 
 
146
        print 'Related branches:'
 
 
147
        if branch.get_parent():
 
 
148
            if branch.get_push_location():
 
 
149
                print '      parent branch: %s' % branch.get_parent()
 
 
151
                print '  parent branch: %s' % branch.get_parent()
 
 
152
        if branch.get_push_location():
 
 
153
            print '  publish to branch: %s' % branch.get_push_location()
 
 
156
def _show_format_info(control=None, repository=None, branch=None, working=None):
 
 
157
    """Show known formats for control, working, branch and repository."""
 
 
161
        print '       control: %s' % control._format.get_format_description()
 
 
163
        print '  working tree: %s' % working._format.get_format_description()
 
 
165
        print '        branch: %s' % branch._format.get_format_description()
 
 
167
        print '    repository: %s' % repository._format.get_format_description()
 
 
170
def _show_locking_info(repository, branch=None, working=None):
 
 
171
    """Show locking status of working, branch and repository."""
 
 
172
    if (repository.get_physical_lock_status() or
 
 
173
        (branch and branch.get_physical_lock_status()) or
 
 
174
        (working and working.get_physical_lock_status())):
 
 
178
            if working.get_physical_lock_status():
 
 
182
            print '  working tree: %s' % status
 
 
184
            if branch.get_physical_lock_status():
 
 
188
            print '        branch: %s' % status
 
 
190
            if repository.get_physical_lock_status():
 
 
194
            print '    repository: %s' % status
 
 
197
def _show_missing_revisions_branch(branch):
 
 
198
    """Show missing master revisions in branch."""
 
 
199
    # Try with inaccessible branch ?
 
 
200
    master = branch.get_master_branch()
 
 
202
        local_extra, remote_extra = find_unmerged(branch, master)
 
 
205
            print 'Branch is out of date: missing %d revision%s.' % (
 
 
206
                len(remote_extra), plural(len(remote_extra)))
 
 
209
def _show_missing_revisions_working(working):
 
 
210
    """Show missing revisions in working tree."""
 
 
211
    branch = working.branch
 
 
212
    basis = working.basis_tree()
 
 
213
    work_inv = working.inventory
 
 
214
    branch_revno, branch_last_revision = branch.last_revision_info()
 
 
216
        tree_last_id = working.get_parent_ids()[0]
 
 
220
    if branch_revno and tree_last_id != branch_last_revision:
 
 
221
        tree_last_revno = branch.revision_id_to_revno(tree_last_id)
 
 
222
        missing_count = branch_revno - tree_last_revno
 
 
224
        print 'Working tree is out of date: missing %d revision%s.' % (
 
 
225
            missing_count, plural(missing_count))
 
 
228
def _show_working_stats(working):
 
 
229
    """Show statistics about a working tree."""
 
 
230
    basis = working.basis_tree()
 
 
231
    work_inv = working.inventory
 
 
232
    delta = working.changes_from(basis, want_unchanged=True)
 
 
235
    print 'In the working tree:'
 
 
236
    print '  %8s unchanged' % len(delta.unchanged)
 
 
237
    print '  %8d modified' % len(delta.modified)
 
 
238
    print '  %8d added' % len(delta.added)
 
 
239
    print '  %8d removed' % len(delta.removed)
 
 
240
    print '  %8d renamed' % len(delta.renamed)
 
 
242
    ignore_cnt = unknown_cnt = 0
 
 
243
    for path in working.extras():
 
 
244
        if working.is_ignored(path):
 
 
248
    print '  %8d unknown' % unknown_cnt
 
 
249
    print '  %8d ignored' % ignore_cnt
 
 
252
    for file_id in work_inv:
 
 
253
        if (work_inv.get_file_kind(file_id) == 'directory' and 
 
 
254
            not work_inv.is_root(file_id)):
 
 
256
    print '  %8d versioned %s' \
 
 
258
             plural(dir_cnt, 'subdirectory', 'subdirectories'))
 
 
261
def _show_branch_stats(branch, verbose):
 
 
262
    """Show statistics about a branch."""
 
 
263
    revno, head = branch.last_revision_info()
 
 
265
    print 'Branch history:'
 
 
266
    print '  %8d revision%s' % (revno, plural(revno))
 
 
267
    stats = branch.repository.gather_stats(head, committers=verbose)
 
 
269
        committers = stats['committers']
 
 
270
        print '  %8d committer%s' % (committers, plural(committers))
 
 
272
        timestamp, timezone = stats['firstrev']
 
 
273
        age = int((time.time() - timestamp) / 3600 / 24)
 
 
274
        print '  %8d day%s old' % (age, plural(age))
 
 
275
        print '   first revision: %s' % osutils.format_date(timestamp,
 
 
277
        timestamp, timezone = stats['latestrev']
 
 
278
        print '  latest revision: %s' % osutils.format_date(timestamp,
 
 
283
def _show_repository_info(repository):
 
 
284
    """Show settings of a repository."""
 
 
285
    if repository.make_working_trees():
 
 
287
        print 'Create working tree for new branches inside the repository.'
 
 
290
def _show_repository_stats(stats):
 
 
291
    """Show statistics about a repository."""
 
 
292
    if 'revisions' in stats or 'size' in stats:
 
 
295
    if 'revisions' in stats:
 
 
296
        revisions = stats['revisions']
 
 
297
        print '  %8d revision%s' % (revisions, plural(revisions))
 
 
299
        print '  %8d KiB' % (stats['size']/1024)
 
 
301
def show_bzrdir_info(a_bzrdir, verbose=False):
 
 
302
    """Output to stdout the 'info' for a_bzrdir."""
 
 
304
        tree = a_bzrdir.open_workingtree(
 
 
305
            recommend_upgrade=False)
 
 
306
    except (NoWorkingTree, NotLocalUrl):
 
 
309
            branch = a_bzrdir.open_branch()
 
 
310
        except NotBranchError:
 
 
313
                repository = a_bzrdir.open_repository()
 
 
314
            except NoRepositoryPresent:
 
 
315
                # Return silently; cmd_info already returned NotBranchError
 
 
316
                # if no bzrdir could be opened.
 
 
319
                lockable = repository
 
 
321
            repository = branch.repository
 
 
325
        repository = branch.repository
 
 
330
        show_component_info(a_bzrdir, repository, branch, tree, verbose)
 
 
335
def show_component_info(control, repository, branch=None, working=None,
 
 
337
    """Write info about all bzrdir components to stdout"""
 
 
342
    layout = describe_layout(repository, branch, working)
 
 
343
    format = describe_format(control, repository, branch, working)
 
 
344
    print "%s (format: %s)" % (layout, format)
 
 
345
    _show_location_info(gather_location_info(repository, branch, working))
 
 
348
    if branch is not None:
 
 
349
        _show_related_info(branch)
 
 
350
    _show_format_info(control, repository, branch, working)
 
 
351
    _show_locking_info(repository, branch, working)
 
 
352
    if branch is not None:
 
 
353
        _show_missing_revisions_branch(branch)
 
 
354
    if working is not None:
 
 
355
        _show_missing_revisions_working(working)
 
 
356
        _show_working_stats(working)
 
 
357
    elif branch is not None:
 
 
358
        _show_missing_revisions_branch(branch)
 
 
359
    if branch is not None:
 
 
360
        stats = _show_branch_stats(branch, verbose==2)
 
 
362
        stats = repository.gather_stats()
 
 
363
    if branch is None and working is None:
 
 
364
        _show_repository_info(repository)
 
 
365
    _show_repository_stats(stats)
 
 
368
def describe_layout(repository=None, branch=None, tree=None):
 
 
369
    """Convert a control directory layout into a user-understandable term
 
 
371
    Common outputs include "Standalone tree", "Repository branch" and
 
 
372
    "Checkout".  Uncommon outputs include "Unshared repository with trees"
 
 
373
    and "Empty control directory"
 
 
375
    if repository is None:
 
 
376
        return 'Empty control directory'
 
 
377
    if branch is None and tree is None:
 
 
378
        if repository.is_shared():
 
 
379
            phrase = 'Shared repository'
 
 
381
            phrase = 'Unshared repository'
 
 
382
        if repository.make_working_trees():
 
 
383
            phrase += ' with trees'
 
 
386
        if repository.is_shared():
 
 
387
            independence = "Repository "
 
 
389
            independence = "Standalone "
 
 
394
        if branch is None and tree is not None:
 
 
395
            phrase = "branchless tree"
 
 
397
            if (tree is not None and tree.bzrdir.root_transport.base !=
 
 
398
                branch.bzrdir.root_transport.base):
 
 
400
                phrase = "Lightweight checkout"
 
 
401
            elif branch.get_bound_location() is not None:
 
 
402
                if independence == 'Standalone ':
 
 
405
                    phrase = "Bound branch"
 
 
408
        if independence != "":
 
 
409
            phrase = phrase.lower()
 
 
410
        return "%s%s" % (independence, phrase)
 
 
413
def describe_format(control, repository, branch, tree):
 
 
414
    """Determine the format of an existing control directory
 
 
416
    Several candidates may be found.  If so, the names are returned as a
 
 
417
    single string, separated by ' or '.
 
 
419
    If no matching candidate is found, "unnamed" is returned.
 
 
422
    if (branch is not None and tree is not None and
 
 
423
        branch.bzrdir.root_transport.base !=
 
 
424
        tree.bzrdir.root_transport.base):
 
 
427
    for key in bzrdir.format_registry.keys():
 
 
428
        format = bzrdir.format_registry.make_bzrdir(key)
 
 
429
        if isinstance(format, bzrdir.BzrDirMetaFormat1):
 
 
430
            if (tree and format.workingtree_format !=
 
 
433
            if (branch and format.get_branch_format() !=
 
 
436
            if (repository and format.repository_format !=
 
 
439
        if format.__class__ is not control._format.__class__:
 
 
441
        candidates.append(key)
 
 
442
    if len(candidates) == 0:
 
 
444
    new_candidates = [c for c in candidates if c != 'default']
 
 
445
    if len(new_candidates) > 0:
 
 
446
        candidates = new_candidates
 
 
447
    new_candidates = [c for c in candidates if not
 
 
448
        bzrdir.format_registry.get_info(c).hidden]
 
 
449
    if len(new_candidates) > 0:
 
 
450
        candidates = new_candidates
 
 
451
    return ' or '.join(candidates)
 
 
453
@deprecated_function(zero_eight)
 
 
455
    """Please see show_bzrdir_info."""
 
 
456
    return show_bzrdir_info(b.bzrdir)
 
 
459
@deprecated_function(zero_eighteen)
 
 
460
def show_tree_info(working, verbose):
 
 
461
    """Output to stdout the 'info' for working."""
 
 
462
    branch = working.branch
 
 
463
    repository = branch.repository
 
 
464
    control = working.bzrdir
 
 
465
    show_component_info(control, repository, branch, working, verbose)
 
 
468
@deprecated_function(zero_eighteen)
 
 
469
def show_branch_info(branch, verbose):
 
 
470
    """Output to stdout the 'info' for branch."""
 
 
471
    repository = branch.repository
 
 
472
    control = branch.bzrdir
 
 
473
    show_component_info(control, repository, branch, verbose=verbose)
 
 
476
@deprecated_function(zero_eighteen)
 
 
477
def show_repository_info(repository, verbose):
 
 
478
    """Output to stdout the 'info' for repository."""
 
 
479
    control = repository.bzrdir
 
 
480
    show_component_info(control, repository, verbose=verbose)