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)
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)
72
62
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):
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=None, branch=None, working=None,
83
repository_path = repository.bzrdir.root_transport.base
84
83
if branch is not None:
85
branch_path = branch.bzrdir.root_transport.base
84
branch_path = branch.user_url
86
85
master_path = branch.get_bound_location()
87
86
if master_path is None:
88
87
master_path = branch_path
92
if control is not None and control.get_branch_reference():
93
locs['checkout of branch'] = control.get_branch_reference()
94
except NotBranchError:
93
working_path = working.bzrdir.root_transport.base
97
working_path = working.user_url
94
98
if working_path != branch_path:
95
99
locs['light checkout root'] = working_path
96
100
if master_path != branch_path:
101
105
if working_path != master_path:
102
106
locs['checkout of branch'] = master_path
103
107
elif repository.is_shared():
104
locs['repository branch'] = _repo_rel_url(repository_path,
108
locs['repository branch'] = branch_path
106
109
elif branch_path is not None:
108
111
locs['branch root'] = branch_path
110
113
working_path = None
111
if repository.is_shared():
114
if repository is not None and repository.is_shared():
112
115
# lightweight checkout of branch in shared repository
113
116
if branch_path is not None:
114
locs['repository branch'] = _repo_rel_url(repository_path,
117
locs['repository branch'] = branch_path
116
118
elif branch_path is not None:
118
120
locs['branch root'] = branch_path
119
if master_path != branch_path:
120
locs['bound to branch'] = master_path
121
elif repository is not None:
122
locs['repository'] = repository.user_url
123
elif control is not None:
124
locs['control directory'] = control.user_url
122
locs['repository'] = repository_path
123
if repository.is_shared():
126
# Really, at least a control directory should be
127
# passed in for this method to be useful.
129
if master_path != branch_path:
130
locs['bound to branch'] = master_path
131
if repository is not None and repository.is_shared():
124
132
# 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',
133
locs['shared repository'] = repository.user_url
134
order = ['control directory', 'light checkout root',
135
'repository checkout root', 'checkout root',
136
'checkout of branch', 'shared repository',
128
137
'repository', 'repository branch', 'branch root',
129
138
'bound to branch']
130
139
return [(n, locs[n]) for n in order if n in locs]
133
def _show_location_info(locs):
142
def _show_location_info(locs, outfile):
134
143
"""Show known locations for working, branch and repository."""
136
path_list = _UrlList()
144
outfile.write('Location:\n')
145
path_list = LocationList(osutils.getcwd())
137
146
for name, loc in locs:
138
147
path_list.add_url(name, loc)
139
path_list.print_lines()
142
def _show_related_info(branch):
148
outfile.writelines(path_list.get_lines())
151
def _gather_related_branches(branch):
152
locs = LocationList(osutils.getcwd())
153
locs.add_url('public branch', branch.get_public_branch())
154
locs.add_url('push branch', branch.get_push_location())
155
locs.add_url('parent branch', branch.get_parent())
156
locs.add_url('submit branch', branch.get_submit_branch())
158
locs.add_url('stacked on', branch.get_stacked_on_url())
159
except (errors.UnstackableBranchFormat, errors.UnstackableRepositoryFormat,
165
def _show_related_info(branch, outfile):
143
166
"""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):
167
locs = _gather_related_branches(branch)
168
if len(locs.locs) > 0:
170
outfile.write('Related branches:\n')
171
outfile.writelines(locs.get_lines())
174
def _show_control_dir_info(control, outfile):
175
"""Show control dir information."""
176
if control._format.colocated_branches:
178
outfile.write('Control directory:\n')
179
outfile.write(' %d branches\n' % len(control.list_branches()))
182
def _show_format_info(control=None, repository=None, branch=None,
183
working=None, outfile=None):
157
184
"""Show known formats for control, working, branch and repository."""
186
outfile.write('Format:\n')
161
print ' control: %s' % control._format.get_format_description()
188
outfile.write(' control: %s\n' %
189
control._format.get_format_description())
163
print ' working tree: %s' % working._format.get_format_description()
191
outfile.write(' working tree: %s\n' %
192
working._format.get_format_description())
165
print ' branch: %s' % branch._format.get_format_description()
194
outfile.write(' branch: %s\n' %
195
branch._format.get_format_description())
167
print ' repository: %s' % repository._format.get_format_description()
170
def _show_locking_info(repository, branch=None, working=None):
197
outfile.write(' repository: %s\n' %
198
repository._format.get_format_description())
201
def _show_locking_info(repository, branch=None, working=None, outfile=None):
171
202
"""Show locking status of working, branch and repository."""
172
203
if (repository.get_physical_lock_status() or
173
204
(branch and branch.get_physical_lock_status()) or
174
205
(working and working.get_physical_lock_status())):
207
outfile.write('Lock status:\n')
178
209
if working.get_physical_lock_status():
179
210
status = 'locked'
181
212
status = 'unlocked'
182
print ' working tree: %s' % status
213
outfile.write(' working tree: %s\n' % status)
184
215
if branch.get_physical_lock_status():
185
216
status = 'locked'
187
218
status = 'unlocked'
188
print ' branch: %s' % status
219
outfile.write(' branch: %s\n' % status)
190
221
if repository.get_physical_lock_status():
191
222
status = 'locked'
193
224
status = 'unlocked'
194
print ' repository: %s' % status
197
def _show_missing_revisions_branch(branch):
225
outfile.write(' repository: %s\n' % status)
228
def _show_missing_revisions_branch(branch, outfile):
198
229
"""Show missing master revisions in branch."""
199
230
# Try with inaccessible branch ?
200
231
master = branch.get_master_branch()
202
233
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):
236
outfile.write(('Branch is out of date: missing %d '
237
'revision%s.\n') % (len(remote_extra),
238
plural(len(remote_extra))))
241
def _show_missing_revisions_working(working, outfile):
210
242
"""Show missing revisions in working tree."""
211
243
branch = working.branch
212
244
basis = working.basis_tree()
213
work_inv = working.inventory
214
branch_revno, branch_last_revision = branch.last_revision_info()
246
branch_revno, branch_last_revision = branch.last_revision_info()
247
except errors.UnsupportedOperation:
216
250
tree_last_id = working.get_parent_ids()[0]
217
251
except IndexError:
220
254
if branch_revno and tree_last_id != branch_last_revision:
221
255
tree_last_revno = branch.revision_id_to_revno(tree_last_id)
222
256
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):
258
outfile.write(('Working tree is out of date: missing %d '
259
'revision%s.\n') % (missing_count, plural(missing_count)))
262
def _show_working_stats(working, outfile):
229
263
"""Show statistics about a working tree."""
230
264
basis = working.basis_tree()
231
work_inv = working.inventory
232
265
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)
268
outfile.write('In the working tree:\n')
269
outfile.write(' %8s unchanged\n' % len(delta.unchanged))
270
outfile.write(' %8d modified\n' % len(delta.modified))
271
outfile.write(' %8d added\n' % len(delta.added))
272
outfile.write(' %8d removed\n' % len(delta.removed))
273
outfile.write(' %8d renamed\n' % len(delta.renamed))
242
275
ignore_cnt = unknown_cnt = 0
243
276
for path in working.extras():
248
print ' %8d unknown' % unknown_cnt
249
print ' %8d ignored' % ignore_cnt
281
outfile.write(' %8d unknown\n' % unknown_cnt)
282
outfile.write(' %8d ignored\n' % 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)):
285
root_id = working.get_root_id()
286
for path, entry in working.iter_entries_by_dir():
287
if entry.kind == 'directory' and entry.file_id != root_id:
256
print ' %8d versioned %s' \
258
plural(dir_cnt, 'subdirectory', 'subdirectories'))
261
def _show_branch_stats(branch, verbose):
289
outfile.write(' %8d versioned %s\n' % (dir_cnt,
290
plural(dir_cnt, 'subdirectory', 'subdirectories')))
293
def _show_branch_stats(branch, verbose, outfile):
262
294
"""Show statistics about a branch."""
263
revno, head = branch.last_revision_info()
265
print 'Branch history:'
266
print ' %8d revision%s' % (revno, plural(revno))
296
revno, head = branch.last_revision_info()
297
except errors.UnsupportedOperation:
300
outfile.write('Branch history:\n')
301
outfile.write(' %8d revision%s\n' % (revno, plural(revno)))
267
302
stats = branch.repository.gather_stats(head, committers=verbose)
269
304
committers = stats['committers']
270
print ' %8d committer%s' % (committers, plural(committers))
305
outfile.write(' %8d committer%s\n' % (committers,
272
308
timestamp, timezone = stats['firstrev']
273
309
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,
310
outfile.write(' %8d day%s old\n' % (age, plural(age)))
311
outfile.write(' first revision: %s\n' %
312
osutils.format_date(timestamp, timezone))
277
313
timestamp, timezone = stats['latestrev']
278
print ' latest revision: %s' % osutils.format_date(timestamp,
314
outfile.write(' latest revision: %s\n' %
315
osutils.format_date(timestamp, timezone))
283
def _show_repository_info(repository):
319
def _show_repository_info(repository, outfile):
284
320
"""Show settings of a repository."""
285
321
if repository.make_working_trees():
287
print 'Create working tree for new branches inside the repository.'
290
def _show_repository_stats(stats):
323
outfile.write('Create working tree for new branches inside '
327
def _show_repository_stats(repository, stats, outfile):
291
328
"""Show statistics about a repository."""
292
if 'revisions' in stats or 'size' in stats:
295
330
if 'revisions' in stats:
296
331
revisions = stats['revisions']
297
print ' %8d revision%s' % (revisions, plural(revisions))
332
f.write(' %8d revision%s\n' % (revisions, plural(revisions)))
298
333
if 'size' in stats:
299
print ' %8d KiB' % (stats['size']/1024)
301
def show_bzrdir_info(a_bzrdir, verbose=False):
334
f.write(' %8d KiB\n' % (stats['size']/1024))
335
for hook in hooks['repository']:
336
hook(repository, stats, f)
337
if f.getvalue() != "":
339
outfile.write('Repository:\n')
340
outfile.write(f.getvalue())
343
def show_bzrdir_info(a_bzrdir, verbose=False, outfile=None):
302
344
"""Output to stdout the 'info' for a_bzrdir."""
304
348
tree = a_bzrdir.open_workingtree(
305
349
recommend_upgrade=False)
306
except (NoWorkingTree, NotLocalUrl):
350
except (NoWorkingTree, NotLocalUrl, NotBranchError):
309
353
branch = a_bzrdir.open_branch()
325
368
repository = branch.repository
371
if lockable is not None:
330
show_component_info(a_bzrdir, repository, branch, tree, verbose)
374
show_component_info(a_bzrdir, repository, branch, tree, verbose,
377
if lockable is not None:
335
381
def show_component_info(control, repository, branch=None, working=None,
382
verbose=1, outfile=None):
337
383
"""Write info about all bzrdir components to stdout"""
338
386
if verbose is False:
340
388
if verbose is True:
342
layout = describe_layout(repository, branch, working)
390
layout = describe_layout(repository, branch, working, control)
343
391
format = describe_format(control, repository, branch, working)
344
print "%s (format: %s)" % (layout, format)
345
_show_location_info(gather_location_info(repository, branch, working))
392
outfile.write("%s (format: %s)\n" % (layout, format))
394
gather_location_info(control=control, repository=repository,
395
branch=branch, working=working),
397
if branch is not None:
398
_show_related_info(branch, outfile)
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)
401
_show_format_info(control, repository, branch, working, outfile)
402
_show_locking_info(repository, branch, working, outfile)
403
_show_control_dir_info(control, outfile)
404
if branch is not None:
405
_show_missing_revisions_branch(branch, outfile)
354
406
if working is not None:
355
_show_missing_revisions_working(working)
356
_show_working_stats(working)
407
_show_missing_revisions_working(working, outfile)
408
_show_working_stats(working, outfile)
357
409
elif branch is not None:
358
_show_missing_revisions_branch(branch)
410
_show_missing_revisions_branch(branch, outfile)
359
411
if branch is not None:
360
stats = _show_branch_stats(branch, verbose==2)
412
show_committers = verbose >= 2
413
stats = _show_branch_stats(branch, show_committers, outfile)
362
415
stats = repository.gather_stats()
363
416
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):
417
_show_repository_info(repository, outfile)
418
_show_repository_stats(repository, stats, outfile)
421
def describe_layout(repository=None, branch=None, tree=None, control=None):
369
422
"""Convert a control directory layout into a user-understandable term
371
424
Common outputs include "Standalone tree", "Repository branch" and
372
425
"Checkout". Uncommon outputs include "Unshared repository with trees"
373
426
and "Empty control directory"
428
if branch is None and control is not None:
430
branch_reference = control.get_branch_reference()
431
except NotBranchError:
434
if branch_reference is not None:
435
return "Dangling branch reference"
375
436
if repository is None:
376
437
return 'Empty control directory'
377
438
if branch is None and tree is None:
441
503
candidates.append(key)
442
504
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
507
new_candidates = [c for c in candidates if not
448
bzrdir.format_registry.get_info(c).hidden]
508
controldir.format_registry.get_info(c).hidden]
449
509
if len(new_candidates) > 0:
510
# If there are any non-hidden formats that match, only return those to
511
# avoid listing hidden formats except when only a hidden format will
450
513
candidates = new_candidates
451
514
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)
517
class InfoHooks(_mod_hooks.Hooks):
518
"""Hooks for the info command."""
521
super(InfoHooks, self).__init__("bzrlib.info", "hooks")
522
self.add_hook('repository',
523
"Invoked when displaying the statistics for a repository. "
524
"repository is called with a statistics dictionary as returned "
525
"by the repository and a file-like object to write to.", (1, 15))