~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/status.py

  • Committer: Jelmer Vernooij
  • Date: 2009-01-28 18:42:55 UTC
  • mto: This revision was merged to the branch mainline in revision 3968.
  • Revision ID: jelmer@samba.org-20090128184255-bdmklkvm83ltk191
Update NEWS

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2006 Canonical Ltd
 
1
# Copyright (C) 2005, 2006, 2007 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
18
18
 
19
19
from bzrlib import (
20
20
    delta as _mod_delta,
 
21
    log,
 
22
    osutils,
21
23
    tree,
 
24
    tsort,
 
25
    revision as _mod_revision,
22
26
    )
23
 
from bzrlib.diff import _raise_if_nonexistent
24
27
import bzrlib.errors as errors
25
 
from bzrlib.log import line_log
26
28
from bzrlib.osutils import is_inside_any
27
29
from bzrlib.symbol_versioning import (deprecated_function,
28
 
        zero_eight,
29
30
        )
30
 
from bzrlib.trace import warning
 
31
from bzrlib.trace import mutter, warning
31
32
 
32
33
# TODO: when showing single-line logs, truncate to the width of the terminal
33
34
# if known, but only if really going to the terminal (not into a file)
34
35
 
35
36
 
36
 
@deprecated_function(zero_eight)
37
 
def show_status(branch, show_unchanged=None,
38
 
                specific_files=None,
39
 
                show_ids=False,
40
 
                to_file=None,
41
 
                show_pending=True,
42
 
                revision=None):
43
 
    """Display summary of changes.
44
 
 
45
 
    Please use show_tree_status instead.
46
 
 
47
 
    By default this compares the working tree to a previous revision. 
48
 
    If the revision argument is given, summarizes changes between the 
49
 
    working tree and another, or between two revisions.
50
 
 
51
 
    The result is written out as Unicode and to_file should be able 
52
 
    to encode that.
53
 
 
54
 
    show_unchanged
55
 
        If set, includes unchanged files.
56
 
 
57
 
    specific_files
58
 
        If set, only show the status of files in this list.
59
 
 
60
 
    show_ids
61
 
        If set, includes each file's id.
62
 
 
63
 
    to_file
64
 
        If set, write to this file (default stdout.)
65
 
 
66
 
    show_pending
67
 
        If set, write pending merges.
68
 
 
69
 
    revision
70
 
        If None the compare latest revision with working tree
71
 
        If one revision show compared it with working tree.
72
 
        If two revisions show status between first and second.
73
 
    """
74
 
    show_tree_status(branch.bzrdir.open_workingtree(), show_unchanged, 
75
 
                     specific_files, show_ids, to_file, show_pending, revision)
76
 
 
77
 
 
78
37
def show_tree_status(wt, show_unchanged=None,
79
38
                     specific_files=None,
80
39
                     show_ids=False,
81
40
                     to_file=None,
82
41
                     show_pending=True,
83
42
                     revision=None,
84
 
                     short=False):
 
43
                     short=False,
 
44
                     verbose=False,
 
45
                     versioned=False):
85
46
    """Display summary of changes.
86
47
 
87
48
    By default this compares the working tree to a previous revision. 
102
63
    :param show_ids: If set, includes each file's id.
103
64
    :param to_file: If set, write to this file (default stdout.)
104
65
    :param show_pending: If set, write pending merges.
105
 
    :param revision: If None the compare latest revision with working tree
106
 
        If not None it must be a RevisionSpec list.
107
 
        If one revision show compared it with working tree.
108
 
        If two revisions show status between first and second.
109
 
    :param short: If True, gives short SVN-style status lines
 
66
    :param revision: If None, compare latest revision with working tree
 
67
        If not None, it must be a RevisionSpec list.
 
68
        If one revision, compare with working tree.
 
69
        If two revisions, show status between first and second.
 
70
    :param short: If True, gives short SVN-style status lines.
 
71
    :param verbose: If True, show all merged revisions, not just
 
72
        the merge tips
 
73
    :param versioned: If True, only shows versioned files.
110
74
    """
111
75
    if show_unchanged is not None:
112
 
        warn("show_status_trees with show_unchanged has been deprecated "
 
76
        warn("show_tree_status with show_unchanged has been deprecated "
113
77
             "since bzrlib 0.9", DeprecationWarning, stacklevel=2)
114
78
 
115
79
    if to_file is None:
125
89
            old = new.basis_tree()
126
90
        elif len(revision) > 0:
127
91
            try:
128
 
                rev_id = revision[0].in_history(wt.branch).rev_id
129
 
                old = wt.branch.repository.revision_tree(rev_id)
 
92
                old = revision[0].as_tree(wt.branch)
130
93
            except errors.NoSuchRevision, e:
131
94
                raise errors.BzrCommandError(str(e))
132
95
            if (len(revision) > 1) and (revision[1].spec is not None):
133
96
                try:
134
 
                    rev_id = revision[1].in_history(wt.branch).rev_id
135
 
                    new = wt.branch.repository.revision_tree(rev_id)
 
97
                    new = revision[1].as_tree(wt.branch)
136
98
                    new_is_working_tree = False
137
99
                except errors.NoSuchRevision, e:
138
100
                    raise errors.BzrCommandError(str(e))
142
104
        new.lock_read()
143
105
        try:
144
106
            _raise_if_nonexistent(specific_files, old, new)
 
107
            want_unversioned = not versioned
145
108
            if short:
146
 
                changes = new._iter_changes(old, show_unchanged, specific_files,
147
 
                    require_versioned=False)
148
 
                reporter = _mod_delta.ChangeReporter(old.inventory,
149
 
                    output_file=to_file)
 
109
                changes = new.iter_changes(old, show_unchanged, specific_files,
 
110
                    require_versioned=False, want_unversioned=want_unversioned)
 
111
                reporter = _mod_delta._ChangeReporter(output_file=to_file,
 
112
                    unversioned_filter=new.is_ignored)
150
113
                _mod_delta.report_changes(changes, reporter)
151
114
            else:
152
115
                delta = new.changes_from(old, want_unchanged=show_unchanged,
153
 
                                      specific_files=specific_files)
 
116
                                      specific_files=specific_files,
 
117
                                      want_unversioned=want_unversioned)
 
118
                # filter out unknown files. We may want a tree method for
 
119
                # this
 
120
                delta.unversioned = [unversioned for unversioned in
 
121
                    delta.unversioned if not new.is_ignored(unversioned[0])]
154
122
                delta.show(to_file,
155
123
                           show_ids=show_ids,
156
124
                           show_unchanged=show_unchanged,
157
 
                           short_status=short)
158
 
            short_status_letter = '? '
159
 
            if not short:
160
 
                short_status_letter = ''
161
 
            list_paths('unknown', new.unknowns(), specific_files, to_file,
162
 
                       short_status_letter)
163
 
            conflict_title = False
164
 
            # show the new conflicts only for now. XXX: get them from the delta.
165
 
            for conflict in new.conflicts():
166
 
                if not short and conflict_title is False:
167
 
                    print >> to_file, "conflicts:"
168
 
                    conflict_title = True
 
125
                           short_status=False)
 
126
            # show the new conflicts only for now. XXX: get them from the
 
127
            # delta.
 
128
            conflicts = new.conflicts()
 
129
            if specific_files is not None:
 
130
                conflicts = conflicts.select_conflicts(new, specific_files,
 
131
                    ignore_misses=True, recurse=True)[1]
 
132
            if len(conflicts) > 0 and not short:
 
133
                to_file.write("conflicts:\n")
 
134
            for conflict in conflicts:
169
135
                if short:
170
136
                    prefix = 'C  '
171
137
                else:
172
138
                    prefix = ' '
173
 
                print >> to_file, "%s %s" % (prefix, conflict)
174
 
            if new_is_working_tree and show_pending:
175
 
                show_pending_merges(new, to_file, short)
 
139
                to_file.write("%s %s\n" % (prefix, conflict))
 
140
            if (new_is_working_tree and show_pending):
 
141
                show_pending_merges(new, to_file, short, verbose=verbose)
176
142
        finally:
177
143
            old.unlock()
178
144
            new.unlock()
179
145
    finally:
180
146
        wt.unlock()
181
147
 
182
 
def show_pending_merges(new, to_file, short=False):
 
148
 
 
149
def _get_sorted_revisions(tip_revision, revision_ids, parent_map):
 
150
    """Get an iterator which will return the revisions in merge sorted order.
 
151
 
 
152
    This will build up a list of all nodes, such that only nodes in the list
 
153
    are referenced. It then uses MergeSorter to return them in 'merge-sorted'
 
154
    order.
 
155
 
 
156
    :param revision_ids: A set of revision_ids
 
157
    :param parent_map: The parent information for each node. Revisions which
 
158
        are considered ghosts should not be present in the map.
 
159
    :return: iterator from MergeSorter.iter_topo_order()
 
160
    """
 
161
    # MergeSorter requires that all nodes be present in the graph, so get rid
 
162
    # of any references pointing outside of this graph.
 
163
    parent_graph = {}
 
164
    for revision_id in revision_ids:
 
165
        if revision_id not in parent_map: # ghost
 
166
            parent_graph[revision_id] = []
 
167
        else:
 
168
            # Only include parents which are in this sub-graph
 
169
            parent_graph[revision_id] = [p for p in parent_map[revision_id]
 
170
                                            if p in revision_ids]
 
171
    sorter = tsort.MergeSorter(parent_graph, tip_revision)
 
172
    return sorter.iter_topo_order()
 
173
 
 
174
 
 
175
def show_pending_merges(new, to_file, short=False, verbose=False):
183
176
    """Write out a display of pending merges in a working tree."""
184
177
    parents = new.get_parent_ids()
185
178
    if len(parents) < 2:
186
179
        return
 
180
 
 
181
    # we need one extra space for terminals that wrap on last char
 
182
    term_width = osutils.terminal_width() - 1
 
183
    if short:
 
184
        first_prefix = 'P   '
 
185
        sub_prefix = 'P.   '
 
186
    else:
 
187
        first_prefix = '  '
 
188
        sub_prefix = '    '
 
189
 
187
190
    pending = parents[1:]
188
191
    branch = new.branch
189
192
    last_revision = parents[0]
190
193
    if not short:
191
 
        print >>to_file, 'pending merges:'
192
 
    if last_revision is not None:
193
 
        try:
194
 
            ignore = set(branch.repository.get_ancestry(last_revision))
195
 
        except errors.NoSuchRevision:
196
 
            # the last revision is a ghost : assume everything is new 
197
 
            # except for it
198
 
            ignore = set([None, last_revision])
199
 
    else:
200
 
        ignore = set([None])
201
 
    # TODO: this could be improved using merge_sorted - we'd get the same 
202
 
    # output rather than one level of indent.
 
194
        if verbose:
 
195
            to_file.write('pending merges:\n')
 
196
        else:
 
197
            to_file.write('pending merge tips: (use -v to see all merge revisions)\n')
 
198
    graph = branch.repository.get_graph()
 
199
    other_revisions = [last_revision]
 
200
    log_formatter = log.LineLogFormatter(to_file)
203
201
    for merge in pending:
204
 
        ignore.add(merge)
205
 
        try:
206
 
            from bzrlib.osutils import terminal_width
207
 
            width = terminal_width()
208
 
            m_revision = branch.repository.get_revision(merge)
209
 
            if short:
210
 
                prefix = 'P  '
211
 
            else:
212
 
                prefix = ' '
213
 
            print >> to_file, prefix, line_log(m_revision, width - 4)
214
 
            inner_merges = branch.repository.get_ancestry(merge)
215
 
            assert inner_merges[0] is None
216
 
            inner_merges.pop(0)
217
 
            inner_merges.reverse()
218
 
            for mmerge in inner_merges:
219
 
                if mmerge in ignore:
220
 
                    continue
221
 
                mm_revision = branch.repository.get_revision(mmerge)
222
 
                if short:
223
 
                    prefix = 'P.  '
 
202
        try:
 
203
            rev = branch.repository.get_revisions([merge])[0]
 
204
        except errors.NoSuchRevision:
 
205
            # If we are missing a revision, just print out the revision id
 
206
            to_file.write(first_prefix + '(ghost) ' + merge + '\n')
 
207
            other_revisions.append(merge)
 
208
            continue
 
209
 
 
210
        # Log the merge, as it gets a slightly different formatting
 
211
        log_message = log_formatter.log_string(None, rev,
 
212
                        term_width - len(first_prefix))
 
213
        to_file.write(first_prefix + log_message + '\n')
 
214
        if not verbose:
 
215
            continue
 
216
 
 
217
        # Find all of the revisions in the merge source, which are not in the
 
218
        # last committed revision.
 
219
        merge_extra = graph.find_unique_ancestors(merge, other_revisions)
 
220
        other_revisions.append(merge)
 
221
        merge_extra.discard(_mod_revision.NULL_REVISION)
 
222
 
 
223
        # Get a handle to all of the revisions we will need
 
224
        try:
 
225
            revisions = dict((rev.revision_id, rev) for rev in
 
226
                             branch.repository.get_revisions(merge_extra))
 
227
        except errors.NoSuchRevision:
 
228
            # One of the sub nodes is a ghost, check each one
 
229
            revisions = {}
 
230
            for revision_id in merge_extra:
 
231
                try:
 
232
                    rev = branch.repository.get_revisions([revision_id])[0]
 
233
                except errors.NoSuchRevision:
 
234
                    revisions[revision_id] = None
224
235
                else:
225
 
                    prefix = '   '
226
 
                print >> to_file, prefix, line_log(mm_revision, width - 5)
227
 
                ignore.add(mmerge)
228
 
        except errors.NoSuchRevision:
229
 
            if short:
230
 
                prefix = 'P  '
231
 
            else:
232
 
                prefix = ' '
233
 
            print >> to_file, prefix, merge
234
 
        
235
 
def list_paths(header, paths, specific_files, to_file, short_status_letter=''):
236
 
    done_header = False
237
 
    for path in paths:
238
 
        if specific_files and not is_inside_any(specific_files, path):
239
 
            continue
240
 
        if not short_status_letter and not done_header:
241
 
            print >>to_file, '%s:' % header
242
 
            done_header = True
243
 
        print >>to_file, '%s  %s' % (short_status_letter, path)
 
236
                    revisions[revision_id] = rev
 
237
 
 
238
        # Display the revisions brought in by this merge.
 
239
        rev_id_iterator = _get_sorted_revisions(merge, merge_extra,
 
240
                            branch.repository.get_parent_map(merge_extra))
 
241
        # Skip the first node
 
242
        num, first, depth, eom = rev_id_iterator.next()
 
243
        if first != merge:
 
244
            raise AssertionError('Somehow we misunderstood how'
 
245
                ' iter_topo_order works %s != %s' % (first, merge))
 
246
        for num, sub_merge, depth, eom in rev_id_iterator:
 
247
            rev = revisions[sub_merge]
 
248
            if rev is None:
 
249
                to_file.write(sub_prefix + '(ghost) ' + sub_merge + '\n')
 
250
                continue
 
251
            log_message = log_formatter.log_string(None,
 
252
                            revisions[sub_merge],
 
253
                            term_width - len(sub_prefix))
 
254
            to_file.write(sub_prefix + log_message + '\n')
 
255
 
 
256
 
 
257
def _raise_if_nonexistent(paths, old_tree, new_tree):
 
258
    """Complain if paths are not in either inventory or tree.
 
259
 
 
260
    It's OK with the files exist in either tree's inventory, or 
 
261
    if they exist in the tree but are not versioned.
 
262
    
 
263
    This can be used by operations such as bzr status that can accept
 
264
    unknown or ignored files.
 
265
    """
 
266
    mutter("check paths: %r", paths)
 
267
    if not paths:
 
268
        return
 
269
    s = old_tree.filter_unversioned_files(paths)
 
270
    s = new_tree.filter_unversioned_files(s)
 
271
    s = [path for path in s if not new_tree.has_filename(path)]
 
272
    if s:
 
273
        raise errors.PathsDoNotExist(sorted(s))
 
274
 
 
275