14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
19
17
__all__ = ['show_bzrdir_info']
21
from io import StringIO
19
from cStringIO import StringIO
26
branch as _mod_branch,
29
26
hooks as _mod_hooks,
36
from .errors import (NoWorkingTree, NotBranchError,
37
NoRepositoryPresent, NotLocalUrl)
38
from .missing import find_unmerged
41
def plural(n, base=u'', pl=None):
30
from bzrlib.errors import (NoWorkingTree, NotBranchError,
31
NoRepositoryPresent, NotLocalUrl)
32
from bzrlib.missing import find_unmerged
35
def plural(n, base='', pl=None):
44
38
elif pl is not None:
50
44
class LocationList(object):
61
55
path = urlutils.local_path_from_url(url)
62
except urlutils.InvalidURL:
56
except errors.InvalidURL:
63
57
self.locs.append((label, url))
65
59
self.add_path(label, path)
80
74
def get_lines(self):
81
75
max_len = max(len(l) for l, u in self.locs)
82
return [" %*s: %s\n" % (max_len, l, u) for l, u in self.locs]
85
def gather_location_info(repository=None, branch=None, working=None,
76
return [" %*s: %s\n" % (max_len, l, u) for l, u in self.locs ]
79
def gather_location_info(repository, branch=None, working=None):
81
repository_path = repository.user_url
88
82
if branch is not None:
89
83
branch_path = branch.user_url
90
84
master_path = branch.get_bound_location()
109
98
locs['checkout root'] = branch_path
110
99
if working_path != master_path:
111
(master_path_base, params) = urlutils.split_segment_parameters(
113
if working_path == master_path_base:
114
locs['checkout of co-located branch'] = params['branch']
115
elif 'branch' in params:
116
locs['checkout of branch'] = "%s, branch %s" (
117
master_path_base, params['branch'])
119
locs['checkout of branch'] = master_path
100
locs['checkout of branch'] = master_path
120
101
elif repository.is_shared():
121
102
locs['repository branch'] = branch_path
122
103
elif branch_path is not None:
124
105
locs['branch root'] = branch_path
126
107
working_path = None
127
if repository is not None and repository.is_shared():
108
if repository.is_shared():
128
109
# lightweight checkout of branch in shared repository
129
110
if branch_path is not None:
130
111
locs['repository branch'] = branch_path
131
112
elif branch_path is not None:
133
114
locs['branch root'] = branch_path
134
elif repository is not None:
135
locs['repository'] = repository.user_url
136
elif control is not None:
137
locs['control directory'] = control.user_url
115
if master_path != branch_path:
116
locs['bound to branch'] = master_path
139
# Really, at least a control directory should be
140
# passed in for this method to be useful.
142
if master_path != branch_path:
143
locs['bound to branch'] = master_path
144
if repository is not None and repository.is_shared():
118
locs['repository'] = repository_path
119
if repository.is_shared():
145
120
# lightweight checkout of branch in shared repository
146
locs['shared repository'] = repository.user_url
147
order = ['control directory', 'light checkout root',
148
'repository checkout root', 'checkout root',
149
'checkout of branch', 'checkout of co-located branch',
150
'shared repository', 'repository', 'repository branch',
151
'branch root', 'bound to branch']
121
locs['shared repository'] = repository_path
122
order = ['light checkout root', 'repository checkout root',
123
'checkout root', 'checkout of branch', 'shared repository',
124
'repository', 'repository branch', 'branch root',
152
126
return [(n, locs[n]) for n in order if n in locs]
169
143
locs.add_url('submit branch', branch.get_submit_branch())
171
145
locs.add_url('stacked on', branch.get_stacked_on_url())
172
except (_mod_branch.UnstackableBranchFormat, errors.UnstackableRepositoryFormat,
146
except (errors.UnstackableBranchFormat, errors.UnstackableRepositoryFormat,
184
158
outfile.writelines(locs.get_lines())
187
def _show_control_dir_info(control, outfile):
188
"""Show control dir information."""
189
if control._format.colocated_branches:
191
outfile.write('Control directory:\n')
192
outfile.write(' %d branches\n' % len(control.list_branches()))
195
161
def _show_format_info(control=None, repository=None, branch=None,
196
162
working=None, outfile=None):
197
163
"""Show known formats for control, working, branch and repository."""
199
165
outfile.write('Format:\n')
201
167
outfile.write(' control: %s\n' %
202
control._format.get_format_description())
168
control._format.get_format_description())
204
170
outfile.write(' working tree: %s\n' %
205
working._format.get_format_description())
171
working._format.get_format_description())
207
173
outfile.write(' branch: %s\n' %
208
branch._format.get_format_description())
174
branch._format.get_format_description())
210
176
outfile.write(' repository: %s\n' %
211
repository._format.get_format_description())
214
def _show_locking_info(repository=None, branch=None, working=None,
177
repository._format.get_format_description())
180
def _show_locking_info(repository, branch=None, working=None, outfile=None):
216
181
"""Show locking status of working, branch and repository."""
217
if (repository and repository.get_physical_lock_status() or
182
if (repository.get_physical_lock_status() or
218
183
(branch and branch.get_physical_lock_status()) or
219
(working and working.get_physical_lock_status())):
184
(working and working.get_physical_lock_status())):
220
185
outfile.write('\n')
221
186
outfile.write('Lock status:\n')
249
214
outfile.write('\n')
250
215
outfile.write(('Branch is out of date: missing %d '
251
'revision%s.\n') % (len(remote_extra),
252
plural(len(remote_extra))))
216
'revision%s.\n') % (len(remote_extra),
217
plural(len(remote_extra))))
255
220
def _show_missing_revisions_working(working, outfile):
256
221
"""Show missing revisions in working tree."""
257
222
branch = working.branch
259
branch_revno, branch_last_revision = branch.last_revision_info()
260
except errors.UnsupportedOperation:
223
basis = working.basis_tree()
224
work_inv = working.inventory
225
branch_revno, branch_last_revision = branch.last_revision_info()
263
227
tree_last_id = working.get_parent_ids()[0]
264
228
except IndexError:
269
233
missing_count = branch_revno - tree_last_revno
270
234
outfile.write('\n')
271
235
outfile.write(('Working tree is out of date: missing %d '
272
'revision%s.\n') % (missing_count, plural(missing_count)))
236
'revision%s.\n') % (missing_count, plural(missing_count)))
275
239
def _show_working_stats(working, outfile):
276
240
"""Show statistics about a working tree."""
277
241
basis = working.basis_tree()
242
work_inv = working.inventory
278
243
delta = working.changes_from(basis, want_unchanged=True)
280
245
outfile.write('\n')
295
260
outfile.write(' %8d ignored\n' % ignore_cnt)
298
for path, entry in working.iter_entries_by_dir():
299
if entry.kind == 'directory' and path != '':
263
for file_id in work_inv:
264
if (work_inv.get_file_kind(file_id) == 'directory' and
265
not work_inv.is_root(file_id)):
301
267
outfile.write(' %8d versioned %s\n' % (dir_cnt,
302
plural(dir_cnt, 'subdirectory', 'subdirectories')))
268
plural(dir_cnt, 'subdirectory', 'subdirectories')))
305
271
def _show_branch_stats(branch, verbose, outfile):
306
272
"""Show statistics about a branch."""
308
revno, head = branch.last_revision_info()
309
except errors.UnsupportedOperation:
273
revno, head = branch.last_revision_info()
311
274
outfile.write('\n')
312
275
outfile.write('Branch history:\n')
313
276
outfile.write(' %8d revision%s\n' % (revno, plural(revno)))
316
279
committers = stats['committers']
317
280
outfile.write(' %8d committer%s\n' % (committers,
320
283
timestamp, timezone = stats['firstrev']
321
284
age = int((time.time() - timestamp) / 3600 / 24)
322
285
outfile.write(' %8d day%s old\n' % (age, plural(age)))
323
286
outfile.write(' first revision: %s\n' %
324
osutils.format_date(timestamp, timezone))
287
osutils.format_date(timestamp, timezone))
325
288
timestamp, timezone = stats['latestrev']
326
289
outfile.write(' latest revision: %s\n' %
327
osutils.format_date(timestamp, timezone))
290
osutils.format_date(timestamp, timezone))
343
306
revisions = stats['revisions']
344
307
f.write(' %8d revision%s\n' % (revisions, plural(revisions)))
345
308
if 'size' in stats:
346
f.write(' %8d KiB\n' % (stats['size'] / 1024))
309
f.write(' %8d KiB\n' % (stats['size']/1024))
347
310
for hook in hooks['repository']:
348
311
hook(repository, stats, f)
349
312
if f.getvalue() != "":
352
315
outfile.write(f.getvalue())
355
def show_bzrdir_info(a_controldir, verbose=False, outfile=None):
356
"""Output to stdout the 'info' for a_controldir."""
318
def show_bzrdir_info(a_bzrdir, verbose=False, outfile=None):
319
"""Output to stdout the 'info' for a_bzrdir."""
357
320
if outfile is None:
358
321
outfile = sys.stdout
360
tree = a_controldir.open_workingtree(
323
tree = a_bzrdir.open_workingtree(
361
324
recommend_upgrade=False)
362
except (NoWorkingTree, NotLocalUrl, NotBranchError):
325
except (NoWorkingTree, NotLocalUrl):
365
branch = a_controldir.open_branch(name="")
328
branch = a_bzrdir.open_branch()
366
329
except NotBranchError:
369
repository = a_controldir.open_repository()
332
repository = a_bzrdir.open_repository()
370
333
except NoRepositoryPresent:
334
# Return silently; cmd_info already returned NotBranchError
335
# if no bzrdir could be opened.
374
338
lockable = repository
380
344
repository = branch.repository
383
if lockable is not None:
386
show_component_info(a_controldir, repository, branch, tree, verbose,
349
show_component_info(a_bzrdir, repository, branch, tree, verbose,
389
if lockable is not None:
393
355
def show_component_info(control, repository, branch=None, working=None,
394
verbose=1, outfile=None):
356
verbose=1, outfile=None):
395
357
"""Write info about all bzrdir components to stdout"""
396
358
if outfile is None:
397
359
outfile = sys.stdout
400
362
if verbose is True:
402
layout = describe_layout(repository, branch, working, control)
364
layout = describe_layout(repository, branch, working)
403
365
format = describe_format(control, repository, branch, working)
404
366
outfile.write("%s (format: %s)\n" % (layout, format))
406
gather_location_info(control=control, repository=repository,
407
branch=branch, working=working),
367
_show_location_info(gather_location_info(repository, branch, working),
409
369
if branch is not None:
410
370
_show_related_info(branch, outfile)
413
373
_show_format_info(control, repository, branch, working, outfile)
414
374
_show_locking_info(repository, branch, working, outfile)
415
_show_control_dir_info(control, outfile)
416
375
if branch is not None:
417
376
_show_missing_revisions_branch(branch, outfile)
418
377
if working is not None:
423
382
if branch is not None:
424
383
show_committers = verbose >= 2
425
384
stats = _show_branch_stats(branch, show_committers, outfile)
426
elif repository is not None:
427
386
stats = repository.gather_stats()
428
if branch is None and working is None and repository is not None:
387
if branch is None and working is None:
429
388
_show_repository_info(repository, outfile)
430
if repository is not None:
431
_show_repository_stats(repository, stats, outfile)
434
def describe_layout(repository=None, branch=None, tree=None, control=None):
389
_show_repository_stats(repository, stats, outfile)
392
def describe_layout(repository=None, branch=None, tree=None):
435
393
"""Convert a control directory layout into a user-understandable term
437
395
Common outputs include "Standalone tree", "Repository branch" and
438
396
"Checkout". Uncommon outputs include "Unshared repository with trees"
439
397
and "Empty control directory"
441
if branch is None and control is not None:
443
branch_reference = control.get_branch_reference()
444
except NotBranchError:
447
if branch_reference is not None:
448
return "Dangling branch reference"
449
399
if repository is None:
450
400
return 'Empty control directory'
451
401
if branch is None and tree is None:
453
403
phrase = 'Shared repository'
455
405
phrase = 'Unshared repository'
457
406
if repository.make_working_trees():
458
extra.append('trees')
459
if len(control.get_branches()) > 0:
460
extra.append('colocated branches')
462
phrase += ' with ' + " and ".join(extra)
407
phrase += ' with trees'
465
410
if repository.is_shared():
473
418
if branch is None and tree is not None:
474
419
phrase = "branchless tree"
476
if (tree is not None and tree.controldir.control_url !=
477
branch.controldir.control_url):
421
if (tree is not None and tree.user_url !=
478
423
independence = ''
479
424
phrase = "Lightweight checkout"
480
425
elif branch.get_bound_location() is not None:
498
443
If no matching candidate is found, "unnamed" is returned.
501
446
if (branch is not None and tree is not None and
502
branch.user_url != tree.user_url):
447
branch.user_url != tree.user_url):
504
449
repository = None
505
non_aliases = set(controldir.format_registry.keys())
506
non_aliases.difference_update(controldir.format_registry.aliases())
450
non_aliases = set(bzrdir.format_registry.keys())
451
non_aliases.difference_update(bzrdir.format_registry.aliases())
507
452
for key in non_aliases:
508
format = controldir.format_registry.make_controldir(key)
453
format = bzrdir.format_registry.make_bzrdir(key)
509
454
if isinstance(format, bzrdir.BzrDirMetaFormat1):
510
455
if (tree and format.workingtree_format !=
513
458
if (branch and format.get_branch_format() !=
516
461
if (repository and format.repository_format !=
519
464
if format.__class__ is not control._format.__class__:
524
469
candidates.sort()
525
470
new_candidates = [c for c in candidates if not
526
controldir.format_registry.get_info(c).hidden]
471
bzrdir.format_registry.get_info(c).hidden]
527
472
if len(new_candidates) > 0:
528
473
# If there are any non-hidden formats that match, only return those to
529
474
# avoid listing hidden formats except when only a hidden format will
536
481
"""Hooks for the info command."""
538
483
def __init__(self):
539
super(InfoHooks, self).__init__("breezy.info", "hooks")
484
super(InfoHooks, self).__init__()
485
self.create_hook(_mod_hooks.HookPoint('repository',
542
486
"Invoked when displaying the statistics for a repository. "
543
487
"repository is called with a statistics dictionary as returned "
544
"by the repository and a file-like object to write to.", (1, 15))
488
"by the repository and a file-like object to write to.", (1, 15),
547
492
hooks = InfoHooks()