1
# Copyright (C) 2004, 2005 by Martin Pool
2
# Copyright (C) 2005 by Canonical Ltd
1
# Copyright (C) 2005-2010 Canonical Ltd
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.
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.
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
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
__all__ = ['show_bzrdir_info']
19
from cStringIO import StringIO
21
from bzrlib.osutils import format_date
25
# surely there's a builtin for this?
36
print 'branch format:', b.controlfile('branch-format', 'r').readline().rstrip('\n')
38
def plural(n, base='', pl=None):
46
count_version_dirs = 0
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)
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)
31
from bzrlib.errors import (NoWorkingTree, NotBranchError,
32
NoRepositoryPresent, NotLocalUrl)
33
from bzrlib.missing import find_unmerged
36
def plural(n, base='', pl=None):
45
class LocationList(object):
47
def __init__(self, base_path):
49
self.base_path = base_path
51
def add_url(self, label, url):
52
"""Add a URL to the list, converting it to a path if possible"""
56
path = urlutils.local_path_from_url(url)
57
except errors.InvalidURL:
58
self.locs.append((label, url))
60
self.add_path(label, path)
62
def add_path(self, label, path):
63
"""Add a path, converting it to a relative path if possible"""
65
path = osutils.relpath(self.base_path, path)
66
except errors.PathNotChild:
72
path = path.rstrip('/')
73
self.locs.append((label, path))
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 ]
80
def gather_location_info(repository, branch=None, working=None):
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
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
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:
106
locs['branch root'] = branch_path
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:
115
locs['branch root'] = branch_path
116
if master_path != branch_path:
117
locs['bound to branch'] = master_path
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',
127
return [(n, locs[n]) for n in order if n in locs]
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())
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())
146
locs.add_url('stacked on', branch.get_stacked_on_url())
147
except (errors.UnstackableBranchFormat, errors.UnstackableRepositoryFormat,
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:
158
outfile.write('Related branches:\n')
159
outfile.writelines(locs.get_lines())
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."""
166
outfile.write('Format:\n')
168
outfile.write(' control: %s\n' %
169
control._format.get_format_description())
171
outfile.write(' working tree: %s\n' %
172
working._format.get_format_description())
174
outfile.write(' branch: %s\n' %
175
branch._format.get_format_description())
177
outfile.write(' repository: %s\n' %
178
repository._format.get_format_description())
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())):
187
outfile.write('Lock status:\n')
189
if working.get_physical_lock_status():
193
outfile.write(' working tree: %s\n' % status)
195
if branch.get_physical_lock_status():
199
outfile.write(' branch: %s\n' % status)
201
if repository.get_physical_lock_status():
205
outfile.write(' repository: %s\n' % status)
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()
213
local_extra, remote_extra = find_unmerged(branch, master)
216
outfile.write(('Branch is out of date: missing %d '
217
'revision%s.\n') % (len(remote_extra),
218
plural(len(remote_extra))))
221
def _show_missing_revisions_working(working, outfile):
222
"""Show missing revisions in working tree."""
223
branch = working.branch
224
basis = working.basis_tree()
226
branch_revno, branch_last_revision = branch.last_revision_info()
227
except errors.UnsupportedOperation:
230
tree_last_id = working.get_parent_ids()[0]
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
238
outfile.write(('Working tree is out of date: missing %d '
239
'revision%s.\n') % (missing_count, plural(missing_count)))
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)
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))
61
255
ignore_cnt = unknown_cnt = 0
62
256
for path in working.extras():
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)
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:
75
print ' %8d versioned %s' \
77
plural(dir_cnt, 'subdirectory', 'subdirectories'))
80
print 'branch history:'
81
history = b.revision_history()
83
print ' %8d revision%s' % (revno, plural(revno))
86
committers[b.get_revision(rev).committer] = True
87
print ' %8d committer%s' % (len(committers), plural(len(committers)))
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,
95
lastrev = b.get_revision(history[-1])
96
print ' latest revision: %s' % format_date(lastrev.timestamp,
100
# print 'text store:'
101
# c, t = b.text_store.total_size()
102
# print ' %8d file texts' % c
103
# print ' %8d kB' % (t/1024)
106
print 'revision store:'
107
c, t = b.revision_store.total_size()
108
print ' %8d revisions' % c
109
print ' %8d kB' % (t/1024)
113
# print 'inventory store:'
114
# c, t = b.inventory_store.total_size()
115
# print ' %8d inventories' % c
116
# print ' %8d kB' % (t/1024)
269
outfile.write(' %8d versioned %s\n' % (dir_cnt,
270
plural(dir_cnt, 'subdirectory', 'subdirectories')))
273
def _show_branch_stats(branch, verbose, outfile):
274
"""Show statistics about a branch."""
276
revno, head = branch.last_revision_info()
277
except errors.UnsupportedOperation:
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)
284
committers = stats['committers']
285
outfile.write(' %8d committer%s\n' % (committers,
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))
299
def _show_repository_info(repository, outfile):
300
"""Show settings of a repository."""
301
if repository.make_working_trees():
303
outfile.write('Create working tree for new branches inside '
307
def _show_repository_stats(repository, stats, outfile):
308
"""Show statistics about a repository."""
310
if 'revisions' in stats:
311
revisions = stats['revisions']
312
f.write(' %8d revision%s\n' % (revisions, plural(revisions)))
314
f.write(' %8d KiB\n' % (stats['size']/1024))
315
for hook in hooks['repository']:
316
hook(repository, stats, f)
317
if f.getvalue() != "":
319
outfile.write('Repository:\n')
320
outfile.write(f.getvalue())
323
def show_bzrdir_info(a_bzrdir, verbose=False, outfile=None):
324
"""Output to stdout the 'info' for a_bzrdir."""
328
tree = a_bzrdir.open_workingtree(
329
recommend_upgrade=False)
330
except (NoWorkingTree, NotLocalUrl):
333
branch = a_bzrdir.open_branch()
334
except NotBranchError:
337
repository = a_bzrdir.open_repository()
338
except NoRepositoryPresent:
339
# Return silently; cmd_info already returned NotBranchError
340
# if no controldir could be opened.
343
lockable = repository
345
repository = branch.repository
349
repository = branch.repository
354
show_component_info(a_bzrdir, repository, branch, tree, verbose,
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"""
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),
374
if branch is not None:
375
_show_related_info(branch, outfile)
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)
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)
397
def describe_layout(repository=None, branch=None, tree=None):
398
"""Convert a control directory layout into a user-understandable term
400
Common outputs include "Standalone tree", "Repository branch" and
401
"Checkout". Uncommon outputs include "Unshared repository with trees"
402
and "Empty control directory"
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'
410
phrase = 'Unshared repository'
411
if repository.make_working_trees():
412
phrase += ' with trees'
415
if repository.is_shared():
416
independence = "Repository "
418
independence = "Standalone "
423
if branch is None and tree is not None:
424
phrase = "branchless tree"
426
if (tree is not None and tree.user_url !=
429
phrase = "Lightweight checkout"
430
elif branch.get_bound_location() is not None:
431
if independence == 'Standalone ':
434
phrase = "Bound branch"
437
if independence != "":
438
phrase = phrase.lower()
439
return "%s%s" % (independence, phrase)
442
def describe_format(control, repository, branch, tree):
443
"""Determine the format of an existing control directory
445
Several candidates may be found. If so, the names are returned as a
446
single string, separated by ' or '.
448
If no matching candidate is found, "unnamed" is returned.
451
if (branch is not None and tree is not None and
452
branch.user_url != tree.user_url):
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 !=
463
if (branch and format.get_branch_format() !=
466
if (repository and format.repository_format !=
469
if format.__class__ is not control._format.__class__:
471
candidates.append(key)
472
if len(candidates) == 0:
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
481
candidates = new_candidates
482
return ' or '.join(candidates)
485
class InfoHooks(_mod_hooks.Hooks):
486
"""Hooks for the info command."""
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))