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()
116
105
locs['branch root'] = branch_path
118
107
working_path = None
119
if repository is not None and repository.is_shared():
108
if repository.is_shared():
120
109
# lightweight checkout of branch in shared repository
121
110
if branch_path is not None:
122
111
locs['repository branch'] = branch_path
123
112
elif branch_path is not None:
125
114
locs['branch root'] = branch_path
126
elif repository is not None:
127
locs['repository'] = repository.user_url
128
elif control is not None:
129
locs['control directory'] = control.user_url
115
if master_path != branch_path:
116
locs['bound to branch'] = master_path
131
# Really, at least a control directory should be
132
# passed in for this method to be useful.
134
if master_path != branch_path:
135
locs['bound to branch'] = master_path
136
if repository is not None and repository.is_shared():
118
locs['repository'] = repository_path
119
if repository.is_shared():
137
120
# lightweight checkout of branch in shared repository
138
locs['shared repository'] = repository.user_url
139
order = ['control directory', 'light checkout root',
140
'repository checkout root', 'checkout root',
141
'checkout of branch', 'shared repository',
121
locs['shared repository'] = repository_path
122
order = ['light checkout root', 'repository checkout root',
123
'checkout root', 'checkout of branch', 'shared repository',
142
124
'repository', 'repository branch', 'branch root',
143
125
'bound to branch']
144
126
return [(n, locs[n]) for n in order if n in locs]
161
143
locs.add_url('submit branch', branch.get_submit_branch())
163
145
locs.add_url('stacked on', branch.get_stacked_on_url())
164
except (_mod_branch.UnstackableBranchFormat, errors.UnstackableRepositoryFormat,
146
except (errors.UnstackableBranchFormat, errors.UnstackableRepositoryFormat,
176
158
outfile.writelines(locs.get_lines())
179
def _show_control_dir_info(control, outfile):
180
"""Show control dir information."""
181
if control._format.colocated_branches:
183
outfile.write('Control directory:\n')
184
outfile.write(' %d branches\n' % len(control.list_branches()))
187
161
def _show_format_info(control=None, repository=None, branch=None,
188
162
working=None, outfile=None):
189
163
"""Show known formats for control, working, branch and repository."""
191
165
outfile.write('Format:\n')
193
167
outfile.write(' control: %s\n' %
194
control._format.get_format_description())
168
control._format.get_format_description())
196
170
outfile.write(' working tree: %s\n' %
197
working._format.get_format_description())
171
working._format.get_format_description())
199
173
outfile.write(' branch: %s\n' %
200
branch._format.get_format_description())
174
branch._format.get_format_description())
202
176
outfile.write(' repository: %s\n' %
203
repository._format.get_format_description())
206
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):
208
181
"""Show locking status of working, branch and repository."""
209
if (repository and repository.get_physical_lock_status() or
182
if (repository.get_physical_lock_status() or
210
183
(branch and branch.get_physical_lock_status()) or
211
(working and working.get_physical_lock_status())):
184
(working and working.get_physical_lock_status())):
212
185
outfile.write('\n')
213
186
outfile.write('Lock status:\n')
241
214
outfile.write('\n')
242
215
outfile.write(('Branch is out of date: missing %d '
243
'revision%s.\n') % (len(remote_extra),
244
plural(len(remote_extra))))
216
'revision%s.\n') % (len(remote_extra),
217
plural(len(remote_extra))))
247
220
def _show_missing_revisions_working(working, outfile):
248
221
"""Show missing revisions in working tree."""
249
222
branch = working.branch
251
branch_revno, branch_last_revision = branch.last_revision_info()
252
except errors.UnsupportedOperation:
223
basis = working.basis_tree()
224
work_inv = working.inventory
225
branch_revno, branch_last_revision = branch.last_revision_info()
255
227
tree_last_id = working.get_parent_ids()[0]
256
228
except IndexError:
261
233
missing_count = branch_revno - tree_last_revno
262
234
outfile.write('\n')
263
235
outfile.write(('Working tree is out of date: missing %d '
264
'revision%s.\n') % (missing_count, plural(missing_count)))
236
'revision%s.\n') % (missing_count, plural(missing_count)))
267
239
def _show_working_stats(working, outfile):
268
240
"""Show statistics about a working tree."""
269
241
basis = working.basis_tree()
242
work_inv = working.inventory
270
243
delta = working.changes_from(basis, want_unchanged=True)
272
245
outfile.write('\n')
287
260
outfile.write(' %8d ignored\n' % ignore_cnt)
290
for path, entry in working.iter_entries_by_dir():
291
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)):
293
267
outfile.write(' %8d versioned %s\n' % (dir_cnt,
294
plural(dir_cnt, 'subdirectory', 'subdirectories')))
268
plural(dir_cnt, 'subdirectory', 'subdirectories')))
297
271
def _show_branch_stats(branch, verbose, outfile):
298
272
"""Show statistics about a branch."""
300
revno, head = branch.last_revision_info()
301
except errors.UnsupportedOperation:
273
revno, head = branch.last_revision_info()
303
274
outfile.write('\n')
304
275
outfile.write('Branch history:\n')
305
276
outfile.write(' %8d revision%s\n' % (revno, plural(revno)))
308
279
committers = stats['committers']
309
280
outfile.write(' %8d committer%s\n' % (committers,
312
283
timestamp, timezone = stats['firstrev']
313
284
age = int((time.time() - timestamp) / 3600 / 24)
314
285
outfile.write(' %8d day%s old\n' % (age, plural(age)))
315
286
outfile.write(' first revision: %s\n' %
316
osutils.format_date(timestamp, timezone))
287
osutils.format_date(timestamp, timezone))
317
288
timestamp, timezone = stats['latestrev']
318
289
outfile.write(' latest revision: %s\n' %
319
osutils.format_date(timestamp, timezone))
290
osutils.format_date(timestamp, timezone))
335
306
revisions = stats['revisions']
336
307
f.write(' %8d revision%s\n' % (revisions, plural(revisions)))
337
308
if 'size' in stats:
338
f.write(' %8d KiB\n' % (stats['size'] / 1024))
309
f.write(' %8d KiB\n' % (stats['size']/1024))
339
310
for hook in hooks['repository']:
340
311
hook(repository, stats, f)
341
312
if f.getvalue() != "":
344
315
outfile.write(f.getvalue())
347
def show_bzrdir_info(a_controldir, verbose=False, outfile=None):
348
"""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."""
349
320
if outfile is None:
350
321
outfile = sys.stdout
352
tree = a_controldir.open_workingtree(
323
tree = a_bzrdir.open_workingtree(
353
324
recommend_upgrade=False)
354
except (NoWorkingTree, NotLocalUrl, NotBranchError):
325
except (NoWorkingTree, NotLocalUrl):
357
branch = a_controldir.open_branch(name="")
328
branch = a_bzrdir.open_branch()
358
329
except NotBranchError:
361
repository = a_controldir.open_repository()
332
repository = a_bzrdir.open_repository()
362
333
except NoRepositoryPresent:
334
# Return silently; cmd_info already returned NotBranchError
335
# if no bzrdir could be opened.
366
338
lockable = repository
372
344
repository = branch.repository
375
if lockable is not None:
378
show_component_info(a_controldir, repository, branch, tree, verbose,
349
show_component_info(a_bzrdir, repository, branch, tree, verbose,
381
if lockable is not None:
385
355
def show_component_info(control, repository, branch=None, working=None,
386
verbose=1, outfile=None):
356
verbose=1, outfile=None):
387
357
"""Write info about all bzrdir components to stdout"""
388
358
if outfile is None:
389
359
outfile = sys.stdout
392
362
if verbose is True:
394
layout = describe_layout(repository, branch, working, control)
364
layout = describe_layout(repository, branch, working)
395
365
format = describe_format(control, repository, branch, working)
396
366
outfile.write("%s (format: %s)\n" % (layout, format))
398
gather_location_info(control=control, repository=repository,
399
branch=branch, working=working),
367
_show_location_info(gather_location_info(repository, branch, working),
401
369
if branch is not None:
402
370
_show_related_info(branch, outfile)
405
373
_show_format_info(control, repository, branch, working, outfile)
406
374
_show_locking_info(repository, branch, working, outfile)
407
_show_control_dir_info(control, outfile)
408
375
if branch is not None:
409
376
_show_missing_revisions_branch(branch, outfile)
410
377
if working is not None:
415
382
if branch is not None:
416
383
show_committers = verbose >= 2
417
384
stats = _show_branch_stats(branch, show_committers, outfile)
418
elif repository is not None:
419
386
stats = repository.gather_stats()
420
if branch is None and working is None and repository is not None:
387
if branch is None and working is None:
421
388
_show_repository_info(repository, outfile)
422
if repository is not None:
423
_show_repository_stats(repository, stats, outfile)
426
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):
427
393
"""Convert a control directory layout into a user-understandable term
429
395
Common outputs include "Standalone tree", "Repository branch" and
430
396
"Checkout". Uncommon outputs include "Unshared repository with trees"
431
397
and "Empty control directory"
433
if branch is None and control is not None:
435
branch_reference = control.get_branch_reference()
436
except NotBranchError:
439
if branch_reference is not None:
440
return "Dangling branch reference"
441
399
if repository is None:
442
400
return 'Empty control directory'
443
401
if branch is None and tree is None:
445
403
phrase = 'Shared repository'
447
405
phrase = 'Unshared repository'
449
406
if repository.make_working_trees():
450
extra.append('trees')
451
if len(control.get_branches()) > 0:
452
extra.append('colocated branches')
454
phrase += ' with ' + " and ".join(extra)
407
phrase += ' with trees'
457
410
if repository.is_shared():
465
418
if branch is None and tree is not None:
466
419
phrase = "branchless tree"
468
if (tree is not None and tree.controldir.control_url !=
469
branch.controldir.control_url):
421
if (tree is not None and tree.user_url !=
470
423
independence = ''
471
424
phrase = "Lightweight checkout"
472
425
elif branch.get_bound_location() is not None:
490
443
If no matching candidate is found, "unnamed" is returned.
493
446
if (branch is not None and tree is not None and
494
branch.user_url != tree.user_url):
447
branch.user_url != tree.user_url):
496
449
repository = None
497
non_aliases = set(controldir.format_registry.keys())
498
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())
499
452
for key in non_aliases:
500
format = controldir.format_registry.make_controldir(key)
453
format = bzrdir.format_registry.make_bzrdir(key)
501
454
if isinstance(format, bzrdir.BzrDirMetaFormat1):
502
455
if (tree and format.workingtree_format !=
505
458
if (branch and format.get_branch_format() !=
508
461
if (repository and format.repository_format !=
511
464
if format.__class__ is not control._format.__class__:
516
469
candidates.sort()
517
470
new_candidates = [c for c in candidates if not
518
controldir.format_registry.get_info(c).hidden]
471
bzrdir.format_registry.get_info(c).hidden]
519
472
if len(new_candidates) > 0:
520
473
# If there are any non-hidden formats that match, only return those to
521
474
# avoid listing hidden formats except when only a hidden format will
528
481
"""Hooks for the info command."""
530
483
def __init__(self):
531
super(InfoHooks, self).__init__("breezy.info", "hooks")
484
super(InfoHooks, self).__init__()
485
self.create_hook(_mod_hooks.HookPoint('repository',
534
486
"Invoked when displaying the statistics for a repository. "
535
487
"repository is called with a statistics dictionary as returned "
536
"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),
539
492
hooks = InfoHooks()