/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/info.py

  • Committer: Martin
  • Date: 2018-11-16 19:10:17 UTC
  • mto: This revision was merged to the branch mainline in revision 7177.
  • Revision ID: gzlist@googlemail.com-20181116191017-kyedz1qck0ovon3h
Remove lazy_regexp reset in bt.test_source

Show diffs side-by-side

added added

removed removed

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