~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/status.py

(jameinel) Allow 'bzr serve' to interpret SIGHUP as a graceful shutdown.
 (bug #795025) (John A Meinel)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2005-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# 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
16
16
 
17
17
import sys
18
18
 
19
19
from bzrlib import (
20
20
    delta as _mod_delta,
 
21
    hooks as _mod_hooks,
21
22
    log,
22
23
    osutils,
23
 
    tree,
24
24
    tsort,
25
25
    revision as _mod_revision,
26
26
    )
27
27
import bzrlib.errors as errors
28
 
from bzrlib.osutils import is_inside_any
29
 
from bzrlib.symbol_versioning import (deprecated_function,
30
 
        )
31
28
from bzrlib.trace import mutter, warning
32
29
 
33
30
# TODO: when showing single-line logs, truncate to the width of the terminal
34
31
# if known, but only if really going to the terminal (not into a file)
35
32
 
36
33
 
 
34
def report_changes(to_file, old, new, specific_files, 
 
35
                   show_short_reporter, show_long_callback, 
 
36
                   short=False, want_unchanged=False, 
 
37
                   want_unversioned=False, show_ids=False, classify=True):
 
38
    """Display summary of changes.
 
39
 
 
40
    This compares two trees with regards to a list of files, and delegates 
 
41
    the display to underlying elements.
 
42
 
 
43
    For short output, it creates an iterator on all changes, and lets a given
 
44
    reporter display these changes.
 
45
 
 
46
    For stantard output, it creates a delta of the changes, and forwards it
 
47
    to a callback
 
48
 
 
49
    :param to_file: If set, write to this file (default stdout.)
 
50
    :param old: Start tree for the comparison
 
51
    :param end: End tree for the comparison
 
52
    :param specific_files: If set, a list of filenames whose status should be
 
53
        shown.  It is an error to give a filename that is not in the working
 
54
        tree, or in the working inventory or in the basis inventory.
 
55
    :param show_short_reporter: Reporter in charge of display for short output
 
56
    :param show_long_callback: Callback in charge of display for normal output
 
57
    :param short: If True, gives short SVN-style status lines.
 
58
    :param want_unchanged: Deprecated parameter. If set, includes unchanged
 
59
        files.
 
60
    :param show_ids: If set, includes each file's id.
 
61
    :param want_unversioned: If False, only shows versioned files.
 
62
    :param classify: Add special symbols to indicate file kind.
 
63
    """
 
64
 
 
65
    if short:
 
66
        changes = new.iter_changes(old, want_unchanged, specific_files,
 
67
            require_versioned=False, want_unversioned=want_unversioned)
 
68
        _mod_delta.report_changes(changes, show_short_reporter)
 
69
        
 
70
    else:
 
71
        delta = new.changes_from(old, want_unchanged=want_unchanged,
 
72
                              specific_files=specific_files,
 
73
                              want_unversioned=want_unversioned)
 
74
        # filter out unknown files. We may want a tree method for
 
75
        # this
 
76
        delta.unversioned = [unversioned for unversioned in
 
77
            delta.unversioned if not new.is_ignored(unversioned[0])]
 
78
        show_long_callback(to_file, delta, 
 
79
                           show_ids=show_ids,
 
80
                           show_unchanged=want_unchanged,
 
81
                           classify=classify)
 
82
 
 
83
 
37
84
def show_tree_status(wt, show_unchanged=None,
38
85
                     specific_files=None,
39
86
                     show_ids=False,
42
89
                     revision=None,
43
90
                     short=False,
44
91
                     verbose=False,
45
 
                     versioned=False):
 
92
                     versioned=False,
 
93
                     classify=True,
 
94
                     show_long_callback=_mod_delta.report_delta):
46
95
    """Display summary of changes.
47
96
 
48
 
    By default this compares the working tree to a previous revision. 
49
 
    If the revision argument is given, summarizes changes between the 
 
97
    By default this compares the working tree to a previous revision.
 
98
    If the revision argument is given, summarizes changes between the
50
99
    working tree and another, or between two revisions.
51
100
 
52
 
    The result is written out as Unicode and to_file should be able 
 
101
    The result is written out as Unicode and to_file should be able
53
102
    to encode that.
54
103
 
55
104
    If showing the status of a working tree, extra information is included
56
105
    about unknown files, conflicts, and pending merges.
57
106
 
58
 
    :param show_unchanged: Deprecated parameter. If set, includes unchanged 
 
107
    :param show_unchanged: Deprecated parameter. If set, includes unchanged
59
108
        files.
60
109
    :param specific_files: If set, a list of filenames whose status should be
61
 
        shown.  It is an error to give a filename that is not in the working 
 
110
        shown.  It is an error to give a filename that is not in the working
62
111
        tree, or in the working inventory or in the basis inventory.
63
112
    :param show_ids: If set, includes each file's id.
64
113
    :param to_file: If set, write to this file (default stdout.)
71
120
    :param verbose: If True, show all merged revisions, not just
72
121
        the merge tips
73
122
    :param versioned: If True, only shows versioned files.
 
123
    :param classify: Add special symbols to indicate file kind.
 
124
    :param show_long_callback: A callback: message = show_long_callback(to_file, delta, 
 
125
        show_ids, show_unchanged, indent, filter), only used with the long output
74
126
    """
75
127
    if show_unchanged is not None:
76
128
        warn("show_tree_status with show_unchanged has been deprecated "
78
130
 
79
131
    if to_file is None:
80
132
        to_file = sys.stdout
81
 
    
 
133
 
82
134
    wt.lock_read()
83
135
    try:
84
136
        new_is_working_tree = True
103
155
        old.lock_read()
104
156
        new.lock_read()
105
157
        try:
 
158
            for hook in hooks['pre_status']:
 
159
                hook(StatusHookParams(old, new, to_file, versioned,
 
160
                    show_ids, short, verbose))
 
161
 
106
162
            specific_files, nonexistents \
107
163
                = _filter_nonexistent(specific_files, old, new)
108
164
            want_unversioned = not versioned
109
 
            if short:
110
 
                changes = new.iter_changes(old, show_unchanged, specific_files,
111
 
                    require_versioned=False, want_unversioned=want_unversioned)
112
 
                reporter = _mod_delta._ChangeReporter(output_file=to_file,
113
 
                    unversioned_filter=new.is_ignored)
114
 
                _mod_delta.report_changes(changes, reporter)
115
 
            else:
116
 
                delta = new.changes_from(old, want_unchanged=show_unchanged,
117
 
                                      specific_files=specific_files,
118
 
                                      want_unversioned=want_unversioned)
119
 
                # filter out unknown files. We may want a tree method for
120
 
                # this
121
 
                delta.unversioned = [unversioned for unversioned in
122
 
                    delta.unversioned if not new.is_ignored(unversioned[0])]
123
 
                delta.show(to_file,
124
 
                           show_ids=show_ids,
125
 
                           show_unchanged=show_unchanged,
126
 
                           short_status=False)
 
165
 
 
166
            # Reporter used for short outputs
 
167
            reporter = _mod_delta._ChangeReporter(output_file=to_file,
 
168
                unversioned_filter=new.is_ignored, classify=classify)
 
169
            report_changes(to_file, old, new, specific_files, 
 
170
                           reporter, show_long_callback, 
 
171
                           short=short, want_unchanged=show_unchanged, 
 
172
                           want_unversioned=want_unversioned, show_ids=show_ids,
 
173
                           classify=classify)
 
174
 
 
175
            # show the ignored files among specific files (i.e. show the files
 
176
            # identified from input that we choose to ignore). 
 
177
            if specific_files is not None:
 
178
                # Ignored files is sorted because specific_files is already sorted
 
179
                ignored_files = [specific for specific in
 
180
                    specific_files if new.is_ignored(specific)]
 
181
                if len(ignored_files) > 0 and not short:
 
182
                    to_file.write("ignored:\n")
 
183
                    prefix = ' '
 
184
                else:
 
185
                    prefix = 'I  '
 
186
                for ignored_file in ignored_files:
 
187
                    to_file.write("%s %s\n" % (prefix, ignored_file))
 
188
 
127
189
            # show the new conflicts only for now. XXX: get them from the
128
190
            # delta.
129
191
            conflicts = new.conflicts()
137
199
                    prefix = 'C  '
138
200
                else:
139
201
                    prefix = ' '
140
 
                to_file.write("%s %s\n" % (prefix, conflict))
 
202
                to_file.write("%s %s\n" % (prefix, unicode(conflict)))
141
203
            # Show files that were requested but don't exist (and are
142
204
            # not versioned).  We don't involve delta in this; these
143
205
            # paths are really the province of just the status
156
218
                to_file.write("%s %s\n" % (prefix, nonexistent))
157
219
            if (new_is_working_tree and show_pending):
158
220
                show_pending_merges(new, to_file, short, verbose=verbose)
 
221
            if nonexistents:
 
222
                raise errors.PathsDoNotExist(nonexistents)
 
223
            for hook in hooks['post_status']:
 
224
                hook(StatusHookParams(old, new, to_file, versioned,
 
225
                    show_ids, short, verbose))
159
226
        finally:
160
227
            old.unlock()
161
228
            new.unlock()
162
 
            if nonexistents:
163
 
              raise errors.PathsDoNotExist(nonexistents)
164
229
    finally:
165
230
        wt.unlock()
166
231
 
197
262
    if len(parents) < 2:
198
263
        return
199
264
 
200
 
    # we need one extra space for terminals that wrap on last char
201
 
    term_width = osutils.terminal_width() - 1
 
265
    term_width = osutils.terminal_width()
 
266
    if term_width is not None:
 
267
        # we need one extra space for terminals that wrap on last char
 
268
        term_width = term_width - 1
202
269
    if short:
203
270
        first_prefix = 'P   '
204
271
        sub_prefix = 'P.   '
206
273
        first_prefix = '  '
207
274
        sub_prefix = '    '
208
275
 
 
276
    def show_log_message(rev, prefix):
 
277
        if term_width is None:
 
278
            width = term_width
 
279
        else:
 
280
            width = term_width - len(prefix)
 
281
        log_message = log_formatter.log_string(None, rev, width, prefix=prefix)
 
282
        to_file.write(log_message + '\n')
 
283
 
209
284
    pending = parents[1:]
210
285
    branch = new.branch
211
286
    last_revision = parents[0]
213
288
        if verbose:
214
289
            to_file.write('pending merges:\n')
215
290
        else:
216
 
            to_file.write('pending merge tips: (use -v to see all merge revisions)\n')
 
291
            to_file.write('pending merge tips:'
 
292
                          ' (use -v to see all merge revisions)\n')
217
293
    graph = branch.repository.get_graph()
218
294
    other_revisions = [last_revision]
219
295
    log_formatter = log.LineLogFormatter(to_file)
227
303
            continue
228
304
 
229
305
        # Log the merge, as it gets a slightly different formatting
230
 
        log_message = log_formatter.log_string(None, rev,
231
 
                        term_width - len(first_prefix))
232
 
        to_file.write(first_prefix + log_message + '\n')
 
306
        show_log_message(rev, first_prefix)
233
307
        if not verbose:
234
308
            continue
235
309
 
267
341
            if rev is None:
268
342
                to_file.write(sub_prefix + '(ghost) ' + sub_merge + '\n')
269
343
                continue
270
 
            log_message = log_formatter.log_string(None,
271
 
                            revisions[sub_merge],
272
 
                            term_width - len(sub_prefix))
273
 
            to_file.write(sub_prefix + log_message + '\n')
 
344
            show_log_message(revisions[sub_merge], sub_prefix)
274
345
 
275
346
 
276
347
def _filter_nonexistent(orig_paths, old_tree, new_tree):
298
369
    # their groups individually.  But for consistency of this
299
370
    # function's API, it's better to sort both than just 'nonexistent'.
300
371
    return sorted(remaining), sorted(nonexistent)
 
372
 
 
373
 
 
374
class StatusHooks(_mod_hooks.Hooks):
 
375
    """A dictionary mapping hook name to a list of callables for status hooks.
 
376
 
 
377
    e.g. ['post_status'] Is the list of items to be called when the
 
378
    status command has finished printing the status.
 
379
    """
 
380
 
 
381
    def __init__(self):
 
382
        """Create the default hooks.
 
383
 
 
384
        These are all empty initially, because by default nothing should get
 
385
        notified.
 
386
        """
 
387
        _mod_hooks.Hooks.__init__(self, "bzrlib.status", "hooks")
 
388
        self.add_hook('post_status',
 
389
            "Called with argument StatusHookParams after Bazaar has "
 
390
            "displayed the status. StatusHookParams has the attributes "
 
391
            "(old_tree, new_tree, to_file, versioned, show_ids, short, "
 
392
            "verbose). The last four arguments correspond to the command "
 
393
            "line options specified by the user for the status command. "
 
394
            "to_file is the output stream for writing.",
 
395
            (2, 3))
 
396
        self.add_hook('pre_status',
 
397
            "Called with argument StatusHookParams before Bazaar "
 
398
            "displays the status. StatusHookParams has the attributes "
 
399
            "(old_tree, new_tree, to_file, versioned, show_ids, short, "
 
400
            "verbose). The last four arguments correspond to the command "
 
401
            "line options specified by the user for the status command. "
 
402
            "to_file is the output stream for writing.",
 
403
            (2, 3))
 
404
 
 
405
 
 
406
class StatusHookParams(object):
 
407
    """Object holding parameters passed to post_status hooks.
 
408
 
 
409
    :ivar old_tree: Start tree (basis tree) for comparison.
 
410
    :ivar new_tree: Working tree.
 
411
    :ivar to_file: If set, write to this file.
 
412
    :ivar versioned: Show only versioned files.
 
413
    :ivar show_ids: Show internal object ids.
 
414
    :ivar short: Use short status indicators.
 
415
    :ivar verbose: Verbose flag.
 
416
    """
 
417
 
 
418
    def __init__(self, old_tree, new_tree, to_file, versioned, show_ids,
 
419
            short, verbose):
 
420
        """Create a group of post_status hook parameters.
 
421
 
 
422
        :param old_tree: Start tree (basis tree) for comparison.
 
423
        :param new_tree: Working tree.
 
424
        :param to_file: If set, write to this file.
 
425
        :param versioned: Show only versioned files.
 
426
        :param show_ids: Show internal object ids.
 
427
        :param short: Use short status indicators.
 
428
        :param verbose: Verbose flag.
 
429
        """
 
430
        self.old_tree = old_tree
 
431
        self.new_tree = new_tree
 
432
        self.to_file = to_file
 
433
        self.versioned = versioned
 
434
        self.show_ids = show_ids
 
435
        self.short = short
 
436
        self.verbose = verbose
 
437
 
 
438
    def __eq__(self, other):
 
439
        return self.__dict__ == other.__dict__
 
440
 
 
441
    def __repr__(self):
 
442
        return "<%s(%s, %s, %s, %s, %s, %s, %s)>" % (self.__class__.__name__,
 
443
            self.old_tree, self.new_tree, self.to_file, self.versioned,
 
444
            self.show_ids, self.short, self.verbose)
 
445
 
 
446
 
 
447
def _show_shelve_summary(params):
 
448
    """post_status hook to display a summary of shelves.
 
449
 
 
450
    :param params: StatusHookParams.
 
451
    """
 
452
    get_shelf_manager = getattr(params.new_tree, 'get_shelf_manager', None)
 
453
    if get_shelf_manager is None:
 
454
        return
 
455
    manager = get_shelf_manager()
 
456
    shelves = manager.active_shelves()
 
457
    if shelves:
 
458
        singular = '%d shelf exists. '
 
459
        plural = '%d shelves exist. '
 
460
        if len(shelves) == 1:
 
461
            fmt = singular
 
462
        else:
 
463
            fmt = plural
 
464
        params.to_file.write(fmt % len(shelves))
 
465
        params.to_file.write('See "bzr shelve --list" for details.\n')
 
466
 
 
467
 
 
468
hooks = StatusHooks()
 
469
 
 
470
 
 
471
hooks.install_named_hook('post_status', _show_shelve_summary,
 
472
    'bzr status')
 
473