/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-04-07 21:34:13 UTC
  • mto: This revision was merged to the branch mainline in revision 5161.
  • Revision ID: arnaud.jeansen@gmail.com-20100407213413-vj7jf30ajwnik6jb
Make new unit test easier to read wrt. sorting

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