~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/info.py

  • Committer: Patch Queue Manager
  • Date: 2011-10-14 16:54:26 UTC
  • mfrom: (6216.1.1 remove-this-file)
  • Revision ID: pqm@pqm.ubuntu.com-20111014165426-tjix4e6idryf1r2z
(jelmer) Remove an accidentally committed .THIS file. (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005 by Martin Pool
2
 
# Copyright (C) 2005 by Canonical Ltd
3
 
 
4
 
 
 
1
# Copyright (C) 2005-2010 Canonical Ltd
 
2
#
5
3
# This program is free software; you can redistribute it and/or modify
6
4
# it under the terms of the GNU General Public License as published by
7
5
# the Free Software Foundation; either version 2 of the License, or
8
6
# (at your option) any later version.
9
 
 
 
7
#
10
8
# This program is distributed in the hope that it will be useful,
11
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
12
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
11
# GNU General Public License for more details.
14
 
 
 
12
#
15
13
# You should have received a copy of the GNU General Public License
16
14
# along with this program; if not, write to the Free Software
17
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
 
 
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
__all__ = ['show_bzrdir_info']
 
18
 
 
19
from cStringIO import StringIO
19
20
import time
20
 
 
21
 
from bzrlib.osutils import format_date
22
 
 
23
 
 
24
 
def _countiter(it):
25
 
    # surely there's a builtin for this?
26
 
    i = 0
27
 
    for j in it:
28
 
        i += 1
29
 
    return i        
30
 
 
31
 
 
32
 
 
33
 
def show_info(b):
34
 
    import diff
35
 
    
36
 
    print 'branch format:', b.controlfile('branch-format', 'r').readline().rstrip('\n')
37
 
 
38
 
    def plural(n, base='', pl=None):
39
 
        if n == 1:
40
 
            return base
41
 
        elif pl != None:
42
 
            return pl
43
 
        else:
44
 
            return 's'
45
 
 
46
 
    count_version_dirs = 0
47
 
 
48
 
    basis = b.basis_tree()
49
 
    working = b.working_tree()
50
 
    work_inv = working.inventory
51
 
    delta = diff.compare_trees(basis, working, want_unchanged=True)
52
 
    
53
 
    print
54
 
    print 'in the working tree:'
55
 
    print '  %8s unchanged' % len(delta.unchanged)
56
 
    print '  %8d modified' % len(delta.modified)
57
 
    print '  %8d added' % len(delta.added)
58
 
    print '  %8d removed' % len(delta.removed)
59
 
    print '  %8d renamed' % len(delta.renamed)
 
21
import sys
 
22
 
 
23
from bzrlib import (
 
24
    bzrdir,
 
25
    controldir,
 
26
    errors,
 
27
    hooks as _mod_hooks,
 
28
    osutils,
 
29
    urlutils,
 
30
    )
 
31
from bzrlib.errors import (NoWorkingTree, NotBranchError,
 
32
                           NoRepositoryPresent, NotLocalUrl)
 
33
from bzrlib.missing import find_unmerged
 
34
 
 
35
 
 
36
def plural(n, base='', pl=None):
 
37
    if n == 1:
 
38
        return base
 
39
    elif pl is not None:
 
40
        return pl
 
41
    else:
 
42
        return 's'
 
43
 
 
44
 
 
45
class LocationList(object):
 
46
 
 
47
    def __init__(self, base_path):
 
48
        self.locs = []
 
49
        self.base_path = base_path
 
50
 
 
51
    def add_url(self, label, url):
 
52
        """Add a URL to the list, converting it to a path if possible"""
 
53
        if url is None:
 
54
            return
 
55
        try:
 
56
            path = urlutils.local_path_from_url(url)
 
57
        except errors.InvalidURL:
 
58
            self.locs.append((label, url))
 
59
        else:
 
60
            self.add_path(label, path)
 
61
 
 
62
    def add_path(self, label, path):
 
63
        """Add a path, converting it to a relative path if possible"""
 
64
        try:
 
65
            path = osutils.relpath(self.base_path, path)
 
66
        except errors.PathNotChild:
 
67
            pass
 
68
        else:
 
69
            if path == '':
 
70
                path = '.'
 
71
        if path != '/':
 
72
            path = path.rstrip('/')
 
73
        self.locs.append((label, path))
 
74
 
 
75
    def get_lines(self):
 
76
        max_len = max(len(l) for l, u in self.locs)
 
77
        return ["  %*s: %s\n" % (max_len, l, u) for l, u in self.locs ]
 
78
 
 
79
 
 
80
def gather_location_info(repository, branch=None, working=None):
 
81
    locs = {}
 
82
    repository_path = repository.user_url
 
83
    if branch is not None:
 
84
        branch_path = branch.user_url
 
85
        master_path = branch.get_bound_location()
 
86
        if master_path is None:
 
87
            master_path = branch_path
 
88
    else:
 
89
        branch_path = None
 
90
        master_path = None
 
91
    if working:
 
92
        working_path = working.user_url
 
93
        if working_path != branch_path:
 
94
            locs['light checkout root'] = working_path
 
95
        if master_path != branch_path:
 
96
            if repository.is_shared():
 
97
                locs['repository checkout root'] = branch_path
 
98
            else:
 
99
                locs['checkout root'] = branch_path
 
100
        if working_path != master_path:
 
101
            locs['checkout of branch'] = master_path
 
102
        elif repository.is_shared():
 
103
            locs['repository branch'] = branch_path
 
104
        elif branch_path is not None:
 
105
            # standalone
 
106
            locs['branch root'] = branch_path
 
107
    else:
 
108
        working_path = None
 
109
        if repository.is_shared():
 
110
            # lightweight checkout of branch in shared repository
 
111
            if branch_path is not None:
 
112
                locs['repository branch'] = branch_path
 
113
        elif branch_path is not None:
 
114
            # standalone
 
115
            locs['branch root'] = branch_path
 
116
            if master_path != branch_path:
 
117
                locs['bound to branch'] = master_path
 
118
        else:
 
119
            locs['repository'] = repository_path
 
120
    if repository.is_shared():
 
121
        # lightweight checkout of branch in shared repository
 
122
        locs['shared repository'] = repository_path
 
123
    order = ['light checkout root', 'repository checkout root',
 
124
             'checkout root', 'checkout of branch', 'shared repository',
 
125
             'repository', 'repository branch', 'branch root',
 
126
             'bound to branch']
 
127
    return [(n, locs[n]) for n in order if n in locs]
 
128
 
 
129
 
 
130
def _show_location_info(locs, outfile):
 
131
    """Show known locations for working, branch and repository."""
 
132
    outfile.write('Location:\n')
 
133
    path_list = LocationList(osutils.getcwd())
 
134
    for name, loc in locs:
 
135
        path_list.add_url(name, loc)
 
136
    outfile.writelines(path_list.get_lines())
 
137
 
 
138
 
 
139
def _gather_related_branches(branch):
 
140
    locs = LocationList(osutils.getcwd())
 
141
    locs.add_url('public branch', branch.get_public_branch())
 
142
    locs.add_url('push branch', branch.get_push_location())
 
143
    locs.add_url('parent branch', branch.get_parent())
 
144
    locs.add_url('submit branch', branch.get_submit_branch())
 
145
    try:
 
146
        locs.add_url('stacked on', branch.get_stacked_on_url())
 
147
    except (errors.UnstackableBranchFormat, errors.UnstackableRepositoryFormat,
 
148
        errors.NotStacked):
 
149
        pass
 
150
    return locs
 
151
 
 
152
 
 
153
def _show_related_info(branch, outfile):
 
154
    """Show parent and push location of branch."""
 
155
    locs = _gather_related_branches(branch)
 
156
    if len(locs.locs) > 0:
 
157
        outfile.write('\n')
 
158
        outfile.write('Related branches:\n')
 
159
        outfile.writelines(locs.get_lines())
 
160
 
 
161
 
 
162
def _show_format_info(control=None, repository=None, branch=None,
 
163
                      working=None, outfile=None):
 
164
    """Show known formats for control, working, branch and repository."""
 
165
    outfile.write('\n')
 
166
    outfile.write('Format:\n')
 
167
    if control:
 
168
        outfile.write('       control: %s\n' %
 
169
            control._format.get_format_description())
 
170
    if working:
 
171
        outfile.write('  working tree: %s\n' %
 
172
            working._format.get_format_description())
 
173
    if branch:
 
174
        outfile.write('        branch: %s\n' %
 
175
            branch._format.get_format_description())
 
176
    if repository:
 
177
        outfile.write('    repository: %s\n' %
 
178
            repository._format.get_format_description())
 
179
 
 
180
 
 
181
def _show_locking_info(repository, branch=None, working=None, outfile=None):
 
182
    """Show locking status of working, branch and repository."""
 
183
    if (repository.get_physical_lock_status() or
 
184
        (branch and branch.get_physical_lock_status()) or
 
185
        (working and working.get_physical_lock_status())):
 
186
        outfile.write('\n')
 
187
        outfile.write('Lock status:\n')
 
188
        if working:
 
189
            if working.get_physical_lock_status():
 
190
                status = 'locked'
 
191
            else:
 
192
                status = 'unlocked'
 
193
            outfile.write('  working tree: %s\n' % status)
 
194
        if branch:
 
195
            if branch.get_physical_lock_status():
 
196
                status = 'locked'
 
197
            else:
 
198
                status = 'unlocked'
 
199
            outfile.write('        branch: %s\n' % status)
 
200
        if repository:
 
201
            if repository.get_physical_lock_status():
 
202
                status = 'locked'
 
203
            else:
 
204
                status = 'unlocked'
 
205
            outfile.write('    repository: %s\n' % status)
 
206
 
 
207
 
 
208
def _show_missing_revisions_branch(branch, outfile):
 
209
    """Show missing master revisions in branch."""
 
210
    # Try with inaccessible branch ?
 
211
    master = branch.get_master_branch()
 
212
    if master:
 
213
        local_extra, remote_extra = find_unmerged(branch, master)
 
214
        if remote_extra:
 
215
            outfile.write('\n')
 
216
            outfile.write(('Branch is out of date: missing %d '
 
217
                'revision%s.\n') % (len(remote_extra),
 
218
                plural(len(remote_extra))))
 
219
 
 
220
 
 
221
def _show_missing_revisions_working(working, outfile):
 
222
    """Show missing revisions in working tree."""
 
223
    branch = working.branch
 
224
    basis = working.basis_tree()
 
225
    try:
 
226
        branch_revno, branch_last_revision = branch.last_revision_info()
 
227
    except errors.UnsupportedOperation:
 
228
        return
 
229
    try:
 
230
        tree_last_id = working.get_parent_ids()[0]
 
231
    except IndexError:
 
232
        tree_last_id = None
 
233
 
 
234
    if branch_revno and tree_last_id != branch_last_revision:
 
235
        tree_last_revno = branch.revision_id_to_revno(tree_last_id)
 
236
        missing_count = branch_revno - tree_last_revno
 
237
        outfile.write('\n')
 
238
        outfile.write(('Working tree is out of date: missing %d '
 
239
            'revision%s.\n') % (missing_count, plural(missing_count)))
 
240
 
 
241
 
 
242
def _show_working_stats(working, outfile):
 
243
    """Show statistics about a working tree."""
 
244
    basis = working.basis_tree()
 
245
    delta = working.changes_from(basis, want_unchanged=True)
 
246
 
 
247
    outfile.write('\n')
 
248
    outfile.write('In the working tree:\n')
 
249
    outfile.write('  %8s unchanged\n' % len(delta.unchanged))
 
250
    outfile.write('  %8d modified\n' % len(delta.modified))
 
251
    outfile.write('  %8d added\n' % len(delta.added))
 
252
    outfile.write('  %8d removed\n' % len(delta.removed))
 
253
    outfile.write('  %8d renamed\n' % len(delta.renamed))
60
254
 
61
255
    ignore_cnt = unknown_cnt = 0
62
256
    for path in working.extras():
64
258
            ignore_cnt += 1
65
259
        else:
66
260
            unknown_cnt += 1
67
 
 
68
 
    print '  %8d unknown' % unknown_cnt
69
 
    print '  %8d ignored' % ignore_cnt
 
261
    outfile.write('  %8d unknown\n' % unknown_cnt)
 
262
    outfile.write('  %8d ignored\n' % ignore_cnt)
70
263
 
71
264
    dir_cnt = 0
72
 
    for file_id in work_inv:
73
 
        if work_inv.get_file_kind(file_id) == 'directory':
 
265
    root_id = working.get_root_id()
 
266
    for path, entry in working.iter_entries_by_dir():
 
267
        if entry.kind == 'directory' and entry.file_id != root_id:
74
268
            dir_cnt += 1
75
 
    print '  %8d versioned %s' \
76
 
          % (dir_cnt,
77
 
             plural(dir_cnt, 'subdirectory', 'subdirectories'))
78
 
 
79
 
    print
80
 
    print 'branch history:'
81
 
    history = b.revision_history()
82
 
    revno = len(history)
83
 
    print '  %8d revision%s' % (revno, plural(revno))
84
 
    committers = {}
85
 
    for rev in history:
86
 
        committers[b.get_revision(rev).committer] = True
87
 
    print '  %8d committer%s' % (len(committers), plural(len(committers)))
88
 
    if revno > 0:
89
 
        firstrev = b.get_revision(history[0])
90
 
        age = int((time.time() - firstrev.timestamp) / 3600 / 24)
91
 
        print '  %8d day%s old' % (age, plural(age))
92
 
        print '   first revision: %s' % format_date(firstrev.timestamp,
93
 
                                                    firstrev.timezone)
94
 
 
95
 
        lastrev = b.get_revision(history[-1])
96
 
        print '  latest revision: %s' % format_date(lastrev.timestamp,
97
 
                                                    lastrev.timezone)
98
 
 
99
 
#     print
100
 
#     print 'text store:'
101
 
#     c, t = b.text_store.total_size()
102
 
#     print '  %8d file texts' % c
103
 
#     print '  %8d kB' % (t/1024)
104
 
 
105
 
    print
106
 
    print 'revision store:'
107
 
    c, t = b.revision_store.total_size()
108
 
    print '  %8d revisions' % c
109
 
    print '  %8d kB' % (t/1024)
110
 
 
111
 
 
112
 
#     print
113
 
#     print 'inventory store:'
114
 
#     c, t = b.inventory_store.total_size()
115
 
#     print '  %8d inventories' % c
116
 
#     print '  %8d kB' % (t/1024)
117
 
 
 
269
    outfile.write('  %8d versioned %s\n' % (dir_cnt,
 
270
        plural(dir_cnt, 'subdirectory', 'subdirectories')))
 
271
 
 
272
 
 
273
def _show_branch_stats(branch, verbose, outfile):
 
274
    """Show statistics about a branch."""
 
275
    try:
 
276
        revno, head = branch.last_revision_info()
 
277
    except errors.UnsupportedOperation:
 
278
        return {}
 
279
    outfile.write('\n')
 
280
    outfile.write('Branch history:\n')
 
281
    outfile.write('  %8d revision%s\n' % (revno, plural(revno)))
 
282
    stats = branch.repository.gather_stats(head, committers=verbose)
 
283
    if verbose:
 
284
        committers = stats['committers']
 
285
        outfile.write('  %8d committer%s\n' % (committers,
 
286
            plural(committers)))
 
287
    if revno:
 
288
        timestamp, timezone = stats['firstrev']
 
289
        age = int((time.time() - timestamp) / 3600 / 24)
 
290
        outfile.write('  %8d day%s old\n' % (age, plural(age)))
 
291
        outfile.write('   first revision: %s\n' %
 
292
            osutils.format_date(timestamp, timezone))
 
293
        timestamp, timezone = stats['latestrev']
 
294
        outfile.write('  latest revision: %s\n' %
 
295
            osutils.format_date(timestamp, timezone))
 
296
    return stats
 
297
 
 
298
 
 
299
def _show_repository_info(repository, outfile):
 
300
    """Show settings of a repository."""
 
301
    if repository.make_working_trees():
 
302
        outfile.write('\n')
 
303
        outfile.write('Create working tree for new branches inside '
 
304
            'the repository.\n')
 
305
 
 
306
 
 
307
def _show_repository_stats(repository, stats, outfile):
 
308
    """Show statistics about a repository."""
 
309
    f = StringIO()
 
310
    if 'revisions' in stats:
 
311
        revisions = stats['revisions']
 
312
        f.write('  %8d revision%s\n' % (revisions, plural(revisions)))
 
313
    if 'size' in stats:
 
314
        f.write('  %8d KiB\n' % (stats['size']/1024))
 
315
    for hook in hooks['repository']:
 
316
        hook(repository, stats, f)
 
317
    if f.getvalue() != "":
 
318
        outfile.write('\n')
 
319
        outfile.write('Repository:\n')
 
320
        outfile.write(f.getvalue())
 
321
 
 
322
 
 
323
def show_bzrdir_info(a_bzrdir, verbose=False, outfile=None):
 
324
    """Output to stdout the 'info' for a_bzrdir."""
 
325
    if outfile is None:
 
326
        outfile = sys.stdout
 
327
    try:
 
328
        tree = a_bzrdir.open_workingtree(
 
329
            recommend_upgrade=False)
 
330
    except (NoWorkingTree, NotLocalUrl):
 
331
        tree = None
 
332
        try:
 
333
            branch = a_bzrdir.open_branch()
 
334
        except NotBranchError:
 
335
            branch = None
 
336
            try:
 
337
                repository = a_bzrdir.open_repository()
 
338
            except NoRepositoryPresent:
 
339
                # Return silently; cmd_info already returned NotBranchError
 
340
                # if no controldir could be opened.
 
341
                return
 
342
            else:
 
343
                lockable = repository
 
344
        else:
 
345
            repository = branch.repository
 
346
            lockable = branch
 
347
    else:
 
348
        branch = tree.branch
 
349
        repository = branch.repository
 
350
        lockable = tree
 
351
 
 
352
    lockable.lock_read()
 
353
    try:
 
354
        show_component_info(a_bzrdir, repository, branch, tree, verbose,
 
355
                            outfile)
 
356
    finally:
 
357
        lockable.unlock()
 
358
 
 
359
 
 
360
def show_component_info(control, repository, branch=None, working=None,
 
361
    verbose=1, outfile=None):
 
362
    """Write info about all bzrdir components to stdout"""
 
363
    if outfile is None:
 
364
        outfile = sys.stdout
 
365
    if verbose is False:
 
366
        verbose = 1
 
367
    if verbose is True:
 
368
        verbose = 2
 
369
    layout = describe_layout(repository, branch, working)
 
370
    format = describe_format(control, repository, branch, working)
 
371
    outfile.write("%s (format: %s)\n" % (layout, format))
 
372
    _show_location_info(gather_location_info(repository, branch, working),
 
373
                        outfile)
 
374
    if branch is not None:
 
375
        _show_related_info(branch, outfile)
 
376
    if verbose == 0:
 
377
        return
 
378
    _show_format_info(control, repository, branch, working, outfile)
 
379
    _show_locking_info(repository, branch, working, outfile)
 
380
    if branch is not None:
 
381
        _show_missing_revisions_branch(branch, outfile)
 
382
    if working is not None:
 
383
        _show_missing_revisions_working(working, outfile)
 
384
        _show_working_stats(working, outfile)
 
385
    elif branch is not None:
 
386
        _show_missing_revisions_branch(branch, outfile)
 
387
    if branch is not None:
 
388
        show_committers = verbose >= 2
 
389
        stats = _show_branch_stats(branch, show_committers, outfile)
 
390
    else:
 
391
        stats = repository.gather_stats()
 
392
    if branch is None and working is None:
 
393
        _show_repository_info(repository, outfile)
 
394
    _show_repository_stats(repository, stats, outfile)
 
395
 
 
396
 
 
397
def describe_layout(repository=None, branch=None, tree=None):
 
398
    """Convert a control directory layout into a user-understandable term
 
399
 
 
400
    Common outputs include "Standalone tree", "Repository branch" and
 
401
    "Checkout".  Uncommon outputs include "Unshared repository with trees"
 
402
    and "Empty control directory"
 
403
    """
 
404
    if repository is None:
 
405
        return 'Empty control directory'
 
406
    if branch is None and tree is None:
 
407
        if repository.is_shared():
 
408
            phrase = 'Shared repository'
 
409
        else:
 
410
            phrase = 'Unshared repository'
 
411
        if repository.make_working_trees():
 
412
            phrase += ' with trees'
 
413
        return phrase
 
414
    else:
 
415
        if repository.is_shared():
 
416
            independence = "Repository "
 
417
        else:
 
418
            independence = "Standalone "
 
419
        if tree is not None:
 
420
            phrase = "tree"
 
421
        else:
 
422
            phrase = "branch"
 
423
        if branch is None and tree is not None:
 
424
            phrase = "branchless tree"
 
425
        else:
 
426
            if (tree is not None and tree.user_url !=
 
427
                branch.user_url):
 
428
                independence = ''
 
429
                phrase = "Lightweight checkout"
 
430
            elif branch.get_bound_location() is not None:
 
431
                if independence == 'Standalone ':
 
432
                    independence = ''
 
433
                if tree is None:
 
434
                    phrase = "Bound branch"
 
435
                else:
 
436
                    phrase = "Checkout"
 
437
        if independence != "":
 
438
            phrase = phrase.lower()
 
439
        return "%s%s" % (independence, phrase)
 
440
 
 
441
 
 
442
def describe_format(control, repository, branch, tree):
 
443
    """Determine the format of an existing control directory
 
444
 
 
445
    Several candidates may be found.  If so, the names are returned as a
 
446
    single string, separated by ' or '.
 
447
 
 
448
    If no matching candidate is found, "unnamed" is returned.
 
449
    """
 
450
    candidates  = []
 
451
    if (branch is not None and tree is not None and
 
452
        branch.user_url != tree.user_url):
 
453
        branch = None
 
454
        repository = None
 
455
    non_aliases = set(controldir.format_registry.keys())
 
456
    non_aliases.difference_update(controldir.format_registry.aliases())
 
457
    for key in non_aliases:
 
458
        format = controldir.format_registry.make_bzrdir(key)
 
459
        if isinstance(format, bzrdir.BzrDirMetaFormat1):
 
460
            if (tree and format.workingtree_format !=
 
461
                tree._format):
 
462
                continue
 
463
            if (branch and format.get_branch_format() !=
 
464
                branch._format):
 
465
                continue
 
466
            if (repository and format.repository_format !=
 
467
                repository._format):
 
468
                continue
 
469
        if format.__class__ is not control._format.__class__:
 
470
            continue
 
471
        candidates.append(key)
 
472
    if len(candidates) == 0:
 
473
        return 'unnamed'
 
474
    candidates.sort()
 
475
    new_candidates = [c for c in candidates if not
 
476
        controldir.format_registry.get_info(c).hidden]
 
477
    if len(new_candidates) > 0:
 
478
        # If there are any non-hidden formats that match, only return those to
 
479
        # avoid listing hidden formats except when only a hidden format will
 
480
        # do.
 
481
        candidates = new_candidates
 
482
    return ' or '.join(candidates)
 
483
 
 
484
 
 
485
class InfoHooks(_mod_hooks.Hooks):
 
486
    """Hooks for the info command."""
 
487
 
 
488
    def __init__(self):
 
489
        super(InfoHooks, self).__init__("bzrlib.info", "hooks")
 
490
        self.add_hook('repository',
 
491
            "Invoked when displaying the statistics for a repository. "
 
492
            "repository is called with a statistics dictionary as returned "
 
493
            "by the repository and a file-like object to write to.", (1, 15))
 
494
 
 
495
 
 
496
hooks = InfoHooks()