/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 bzrlib/status.py

  • Committer: Arnaud Jeansen
  • Date: 2010-03-19 23:58:06 UTC
  • mto: This revision was merged to the branch mainline in revision 5126.
  • Revision ID: arnaud.jeansen@gmail.com-20100319235806-n0owdq874qsrb12u
Go back to unified report_delta method (i.e. former TreeDelta.show())

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
import sys
 
18
 
 
19
from bzrlib import (
 
20
    delta as _mod_delta,
 
21
    log,
 
22
    osutils,
 
23
    tree,
 
24
    tsort,
 
25
    revision as _mod_revision,
 
26
    )
 
27
import bzrlib.errors as errors
 
28
from bzrlib.osutils import is_inside_any
 
29
from bzrlib.symbol_versioning import (deprecated_function,
 
30
        )
 
31
from bzrlib.trace import mutter, warning
 
32
 
 
33
# TODO: when showing single-line logs, truncate to the width of the terminal
 
34
# if known, but only if really going to the terminal (not into a file)
 
35
 
 
36
 
 
37
def report_changes(to_file, old, new, specific_files, 
 
38
                   show_short_reporter, show_long_callback, 
 
39
                   short=False, want_unchanged=False, 
 
40
                   want_unversioned=False, show_ids=False):
 
41
    #TODO document sig
 
42
 
 
43
    if short:
 
44
        changes = new.iter_changes(old, want_unchanged, specific_files,
 
45
            require_versioned=False, want_unversioned=want_unversioned)
 
46
        _mod_delta.report_changes(changes, show_short_reporter)
 
47
        
 
48
    else:
 
49
        delta = new.changes_from(old, want_unchanged=want_unchanged,
 
50
                              specific_files=specific_files,
 
51
                              want_unversioned=want_unversioned)
 
52
        # filter out unknown files. We may want a tree method for
 
53
        # this
 
54
        delta.unversioned = [unversioned for unversioned in
 
55
            delta.unversioned if not new.is_ignored(unversioned[0])]
 
56
        show_long_callback(to_file, delta, 
 
57
                           show_ids=show_ids,
 
58
                           show_unchanged=want_unchanged)
 
59
 
 
60
 
 
61
def show_tree_status(wt, show_unchanged=None,
 
62
                     specific_files=None,
 
63
                     show_ids=False,
 
64
                     to_file=None,
 
65
                     show_pending=True,
 
66
                     revision=None,
 
67
                     short=False,
 
68
                     verbose=False,
 
69
                     versioned=False,
 
70
                     show_long_callback=_mod_delta.report_delta):
 
71
    """Display summary of changes.
 
72
 
 
73
    By default this compares the working tree to a previous revision.
 
74
    If the revision argument is given, summarizes changes between the
 
75
    working tree and another, or between two revisions.
 
76
 
 
77
    The result is written out as Unicode and to_file should be able
 
78
    to encode that.
 
79
 
 
80
    If showing the status of a working tree, extra information is included
 
81
    about unknown files, conflicts, and pending merges.
 
82
 
 
83
    :param show_unchanged: Deprecated parameter. If set, includes unchanged
 
84
        files.
 
85
    :param specific_files: If set, a list of filenames whose status should be
 
86
        shown.  It is an error to give a filename that is not in the working
 
87
        tree, or in the working inventory or in the basis inventory.
 
88
    :param show_ids: If set, includes each file's id.
 
89
    :param to_file: If set, write to this file (default stdout.)
 
90
    :param show_pending: If set, write pending merges.
 
91
    :param revision: If None, compare latest revision with working tree
 
92
        If not None, it must be a RevisionSpec list.
 
93
        If one revision, compare with working tree.
 
94
        If two revisions, show status between first and second.
 
95
    :param short: If True, gives short SVN-style status lines.
 
96
    :param verbose: If True, show all merged revisions, not just
 
97
        the merge tips
 
98
    :param versioned: If True, only shows versioned files.
 
99
    :param show_long_callback: A callback: message = show_long_callback(to_file, delta, 
 
100
        show_ids, show_unchanged, indent, filter), only used with the long output
 
101
    """
 
102
    if show_unchanged is not None:
 
103
        warn("show_tree_status with show_unchanged has been deprecated "
 
104
             "since bzrlib 0.9", DeprecationWarning, stacklevel=2)
 
105
 
 
106
    if to_file is None:
 
107
        to_file = sys.stdout
 
108
 
 
109
    wt.lock_read()
 
110
    try:
 
111
        new_is_working_tree = True
 
112
        if revision is None:
 
113
            if wt.last_revision() != wt.branch.last_revision():
 
114
                warning("working tree is out of date, run 'bzr update'")
 
115
            new = wt
 
116
            old = new.basis_tree()
 
117
        elif len(revision) > 0:
 
118
            try:
 
119
                old = revision[0].as_tree(wt.branch)
 
120
            except errors.NoSuchRevision, e:
 
121
                raise errors.BzrCommandError(str(e))
 
122
            if (len(revision) > 1) and (revision[1].spec is not None):
 
123
                try:
 
124
                    new = revision[1].as_tree(wt.branch)
 
125
                    new_is_working_tree = False
 
126
                except errors.NoSuchRevision, e:
 
127
                    raise errors.BzrCommandError(str(e))
 
128
            else:
 
129
                new = wt
 
130
        old.lock_read()
 
131
        new.lock_read()
 
132
        try:
 
133
            specific_files, nonexistents \
 
134
                = _filter_nonexistent(specific_files, old, new)
 
135
            want_unversioned = not versioned
 
136
 
 
137
            # Reporter used for short outputs
 
138
            reporter = _mod_delta._ChangeReporter(output_file=to_file,
 
139
                unversioned_filter=new.is_ignored)
 
140
            report_changes(to_file, old, new, specific_files, 
 
141
                           reporter, show_long_callback, 
 
142
                           short=short, want_unchanged=show_unchanged, 
 
143
                           want_unversioned=want_unversioned, show_ids=show_ids)
 
144
 
 
145
            # show the new conflicts only for now. XXX: get them from the
 
146
            # delta.
 
147
            conflicts = new.conflicts()
 
148
            if specific_files is not None:
 
149
                conflicts = conflicts.select_conflicts(new, specific_files,
 
150
                    ignore_misses=True, recurse=True)[1]
 
151
            if len(conflicts) > 0 and not short:
 
152
                to_file.write("conflicts:\n")
 
153
            for conflict in conflicts:
 
154
                if short:
 
155
                    prefix = 'C  '
 
156
                else:
 
157
                    prefix = ' '
 
158
                to_file.write("%s %s\n" % (prefix, conflict))
 
159
            # Show files that were requested but don't exist (and are
 
160
            # not versioned).  We don't involve delta in this; these
 
161
            # paths are really the province of just the status
 
162
            # command, since they have more to do with how it was
 
163
            # invoked than with the tree it's operating on.
 
164
            if nonexistents and not short:
 
165
                to_file.write("nonexistent:\n")
 
166
            for nonexistent in nonexistents:
 
167
                # We could calculate prefix outside the loop but, given
 
168
                # how rarely this ought to happen, it's OK and arguably
 
169
                # slightly faster to do it here (ala conflicts above)
 
170
                if short:
 
171
                    prefix = 'X  '
 
172
                else:
 
173
                    prefix = ' '
 
174
                to_file.write("%s %s\n" % (prefix, nonexistent))
 
175
            if (new_is_working_tree and show_pending):
 
176
                show_pending_merges(new, to_file, short, verbose=verbose)
 
177
            if nonexistents:
 
178
                raise errors.PathsDoNotExist(nonexistents)
 
179
        finally:
 
180
            old.unlock()
 
181
            new.unlock()
 
182
    finally:
 
183
        wt.unlock()
 
184
 
 
185
 
 
186
def _get_sorted_revisions(tip_revision, revision_ids, parent_map):
 
187
    """Get an iterator which will return the revisions in merge sorted order.
 
188
 
 
189
    This will build up a list of all nodes, such that only nodes in the list
 
190
    are referenced. It then uses MergeSorter to return them in 'merge-sorted'
 
191
    order.
 
192
 
 
193
    :param revision_ids: A set of revision_ids
 
194
    :param parent_map: The parent information for each node. Revisions which
 
195
        are considered ghosts should not be present in the map.
 
196
    :return: iterator from MergeSorter.iter_topo_order()
 
197
    """
 
198
    # MergeSorter requires that all nodes be present in the graph, so get rid
 
199
    # of any references pointing outside of this graph.
 
200
    parent_graph = {}
 
201
    for revision_id in revision_ids:
 
202
        if revision_id not in parent_map: # ghost
 
203
            parent_graph[revision_id] = []
 
204
        else:
 
205
            # Only include parents which are in this sub-graph
 
206
            parent_graph[revision_id] = [p for p in parent_map[revision_id]
 
207
                                            if p in revision_ids]
 
208
    sorter = tsort.MergeSorter(parent_graph, tip_revision)
 
209
    return sorter.iter_topo_order()
 
210
 
 
211
 
 
212
def show_pending_merges(new, to_file, short=False, verbose=False):
 
213
    """Write out a display of pending merges in a working tree."""
 
214
    parents = new.get_parent_ids()
 
215
    if len(parents) < 2:
 
216
        return
 
217
 
 
218
    term_width = osutils.terminal_width()
 
219
    if term_width is not None:
 
220
        # we need one extra space for terminals that wrap on last char
 
221
        term_width = term_width - 1
 
222
    if short:
 
223
        first_prefix = 'P   '
 
224
        sub_prefix = 'P.   '
 
225
    else:
 
226
        first_prefix = '  '
 
227
        sub_prefix = '    '
 
228
 
 
229
    def show_log_message(rev, prefix):
 
230
        if term_width is None:
 
231
            width = term_width
 
232
        else:
 
233
            width = term_width - len(prefix)
 
234
        log_message = log_formatter.log_string(None, rev, width, prefix=prefix)
 
235
        to_file.write(log_message + '\n')
 
236
 
 
237
    pending = parents[1:]
 
238
    branch = new.branch
 
239
    last_revision = parents[0]
 
240
    if not short:
 
241
        if verbose:
 
242
            to_file.write('pending merges:\n')
 
243
        else:
 
244
            to_file.write('pending merge tips:'
 
245
                          ' (use -v to see all merge revisions)\n')
 
246
    graph = branch.repository.get_graph()
 
247
    other_revisions = [last_revision]
 
248
    log_formatter = log.LineLogFormatter(to_file)
 
249
    for merge in pending:
 
250
        try:
 
251
            rev = branch.repository.get_revisions([merge])[0]
 
252
        except errors.NoSuchRevision:
 
253
            # If we are missing a revision, just print out the revision id
 
254
            to_file.write(first_prefix + '(ghost) ' + merge + '\n')
 
255
            other_revisions.append(merge)
 
256
            continue
 
257
 
 
258
        # Log the merge, as it gets a slightly different formatting
 
259
        show_log_message(rev, first_prefix)
 
260
        if not verbose:
 
261
            continue
 
262
 
 
263
        # Find all of the revisions in the merge source, which are not in the
 
264
        # last committed revision.
 
265
        merge_extra = graph.find_unique_ancestors(merge, other_revisions)
 
266
        other_revisions.append(merge)
 
267
        merge_extra.discard(_mod_revision.NULL_REVISION)
 
268
 
 
269
        # Get a handle to all of the revisions we will need
 
270
        try:
 
271
            revisions = dict((rev.revision_id, rev) for rev in
 
272
                             branch.repository.get_revisions(merge_extra))
 
273
        except errors.NoSuchRevision:
 
274
            # One of the sub nodes is a ghost, check each one
 
275
            revisions = {}
 
276
            for revision_id in merge_extra:
 
277
                try:
 
278
                    rev = branch.repository.get_revisions([revision_id])[0]
 
279
                except errors.NoSuchRevision:
 
280
                    revisions[revision_id] = None
 
281
                else:
 
282
                    revisions[revision_id] = rev
 
283
 
 
284
        # Display the revisions brought in by this merge.
 
285
        rev_id_iterator = _get_sorted_revisions(merge, merge_extra,
 
286
                            branch.repository.get_parent_map(merge_extra))
 
287
        # Skip the first node
 
288
        num, first, depth, eom = rev_id_iterator.next()
 
289
        if first != merge:
 
290
            raise AssertionError('Somehow we misunderstood how'
 
291
                ' iter_topo_order works %s != %s' % (first, merge))
 
292
        for num, sub_merge, depth, eom in rev_id_iterator:
 
293
            rev = revisions[sub_merge]
 
294
            if rev is None:
 
295
                to_file.write(sub_prefix + '(ghost) ' + sub_merge + '\n')
 
296
                continue
 
297
            show_log_message(revisions[sub_merge], sub_prefix)
 
298
 
 
299
 
 
300
def _filter_nonexistent(orig_paths, old_tree, new_tree):
 
301
    """Convert orig_paths to two sorted lists and return them.
 
302
 
 
303
    The first is orig_paths paths minus the items in the second list,
 
304
    and the second list is paths that are not in either inventory or
 
305
    tree (they don't qualify if they exist in the tree's inventory, or
 
306
    if they exist in the tree but are not versioned.)
 
307
 
 
308
    If either of the two lists is empty, return it as an empty list.
 
309
 
 
310
    This can be used by operations such as bzr status that can accept
 
311
    unknown or ignored files.
 
312
    """
 
313
    mutter("check paths: %r", orig_paths)
 
314
    if not orig_paths:
 
315
        return orig_paths, []
 
316
    s = old_tree.filter_unversioned_files(orig_paths)
 
317
    s = new_tree.filter_unversioned_files(s)
 
318
    nonexistent = [path for path in s if not new_tree.has_filename(path)]
 
319
    remaining   = [path for path in orig_paths if not path in nonexistent]
 
320
    # Sorting the 'remaining' list doesn't have much effect in
 
321
    # practice, since the various status output sections will sort
 
322
    # their groups individually.  But for consistency of this
 
323
    # function's API, it's better to sort both than just 'nonexistent'.
 
324
    return sorted(remaining), sorted(nonexistent)