~bzr-pqm/bzr/bzr.dev

1658.1.9 by Martin Pool
Give an error for bzr diff on an nonexistent file (Malone #3619)
1
# Copyright (C) 2004, 2005, 2006 Canonical Ltd.
1 by mbp at sourcefrog
import from baz patch-364
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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
1534.4.35 by Robert Collins
Give branch its own basis tree and last_revision methods; deprecated branch.working_tree()
17
from bzrlib.delta import compare_trees
18
from bzrlib.errors import BzrError
1658.1.9 by Martin Pool
Give an error for bzr diff on an nonexistent file (Malone #3619)
19
import bzrlib.errors as errors
1551.2.13 by Aaron Bentley
Got diff working properly with checkouts
20
from bzrlib.symbol_versioning import *
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
21
from bzrlib.trace import mutter
1 by mbp at sourcefrog
import from baz patch-364
22
767 by Martin Pool
- files are only reported as modified if their name or parent has changed,
23
# TODO: Rather than building a changeset object, we should probably
24
# invoke callbacks on an object.  That object can either accumulate a
25
# list, write them out directly, etc etc.
26
1185.35.29 by Aaron Bentley
Support whitespace in diff filenames
27
def internal_diff(old_filename, oldlines, new_filename, newlines, to_file):
475 by Martin Pool
- rewrite diff using compare_trees()
28
    import difflib
29
    
30
    # FIXME: difflib is wrong if there is no trailing newline.
31
    # The syntax used by patch seems to be "\ No newline at
32
    # end of file" following the last diff line from that
33
    # file.  This is not trivial to insert into the
34
    # unified_diff output and it might be better to just fix
35
    # or replace that function.
36
37
    # In the meantime we at least make sure the patch isn't
38
    # mangled.
39
40
41
    # Special workaround for Python2.3, where difflib fails if
42
    # both sequences are empty.
43
    if not oldlines and not newlines:
44
        return
45
568 by Martin Pool
- start adding support for showing diffs by calling out to
46
    ud = difflib.unified_diff(oldlines, newlines,
1185.35.29 by Aaron Bentley
Support whitespace in diff filenames
47
                              fromfile=old_filename+'\t', 
48
                              tofile=new_filename+'\t')
475 by Martin Pool
- rewrite diff using compare_trees()
49
1092.1.50 by Robert Collins
make diff lsdiff/filterdiff friendly
50
    ud = list(ud)
475 by Martin Pool
- rewrite diff using compare_trees()
51
    # work-around for difflib being too smart for its own good
52
    # if /dev/null is "1,0", patch won't recognize it as /dev/null
53
    if not oldlines:
54
        ud[2] = ud[2].replace('-1,0', '-0,0')
55
    elif not newlines:
56
        ud[2] = ud[2].replace('+1,0', '+0,0')
1092.1.50 by Robert Collins
make diff lsdiff/filterdiff friendly
57
    # work around for difflib emitting random spaces after the label
58
    ud[0] = ud[0][:-2] + '\n'
59
    ud[1] = ud[1][:-2] + '\n'
475 by Martin Pool
- rewrite diff using compare_trees()
60
804 by Martin Pool
Patch from John:
61
    for line in ud:
62
        to_file.write(line)
974.1.5 by Aaron Bentley
Fixed handling of missing newlines in udiffs
63
        if not line.endswith('\n'):
64
            to_file.write("\n\\ No newline at end of file\n")
475 by Martin Pool
- rewrite diff using compare_trees()
65
    print >>to_file
66
67
1185.35.29 by Aaron Bentley
Support whitespace in diff filenames
68
def external_diff(old_filename, oldlines, new_filename, newlines, to_file,
571 by Martin Pool
- new --diff-options to pass options through to external
69
                  diff_opts):
568 by Martin Pool
- start adding support for showing diffs by calling out to
70
    """Display a diff by calling out to the external diff program."""
71
    import sys
72
    
73
    if to_file != sys.stdout:
74
        raise NotImplementedError("sorry, can't send external diff other than to stdout yet",
75
                                  to_file)
76
581 by Martin Pool
- make sure any bzr output is flushed before
77
    # make sure our own output is properly ordered before the diff
78
    to_file.flush()
79
568 by Martin Pool
- start adding support for showing diffs by calling out to
80
    from tempfile import NamedTemporaryFile
571 by Martin Pool
- new --diff-options to pass options through to external
81
    import os
568 by Martin Pool
- start adding support for showing diffs by calling out to
82
83
    oldtmpf = NamedTemporaryFile()
84
    newtmpf = NamedTemporaryFile()
85
86
    try:
87
        # TODO: perhaps a special case for comparing to or from the empty
88
        # sequence; can just use /dev/null on Unix
89
90
        # TODO: if either of the files being compared already exists as a
91
        # regular named file (e.g. in the working directory) then we can
92
        # compare directly to that, rather than copying it.
93
94
        oldtmpf.writelines(oldlines)
95
        newtmpf.writelines(newlines)
96
97
        oldtmpf.flush()
98
        newtmpf.flush()
99
571 by Martin Pool
- new --diff-options to pass options through to external
100
        if not diff_opts:
101
            diff_opts = []
102
        diffcmd = ['diff',
1185.35.29 by Aaron Bentley
Support whitespace in diff filenames
103
                   '--label', old_filename+'\t',
571 by Martin Pool
- new --diff-options to pass options through to external
104
                   oldtmpf.name,
1185.35.29 by Aaron Bentley
Support whitespace in diff filenames
105
                   '--label', new_filename+'\t',
571 by Martin Pool
- new --diff-options to pass options through to external
106
                   newtmpf.name]
107
108
        # diff only allows one style to be specified; they don't override.
109
        # note that some of these take optargs, and the optargs can be
110
        # directly appended to the options.
111
        # this is only an approximate parser; it doesn't properly understand
112
        # the grammar.
113
        for s in ['-c', '-u', '-C', '-U',
114
                  '-e', '--ed',
115
                  '-q', '--brief',
116
                  '--normal',
117
                  '-n', '--rcs',
118
                  '-y', '--side-by-side',
119
                  '-D', '--ifdef']:
120
            for j in diff_opts:
121
                if j.startswith(s):
122
                    break
123
            else:
124
                continue
125
            break
126
        else:
127
            diffcmd.append('-u')
128
                  
129
        if diff_opts:
130
            diffcmd.extend(diff_opts)
131
132
        rc = os.spawnvp(os.P_WAIT, 'diff', diffcmd)
133
        
134
        if rc != 0 and rc != 1:
135
            # returns 1 if files differ; that's OK
136
            if rc < 0:
137
                msg = 'signal %d' % (-rc)
138
            else:
139
                msg = 'exit code %d' % rc
140
                
141
            raise BzrError('external diff failed with %s; command: %r' % (rc, diffcmd))
568 by Martin Pool
- start adding support for showing diffs by calling out to
142
    finally:
143
        oldtmpf.close()                 # and delete
144
        newtmpf.close()
145
1551.2.13 by Aaron Bentley
Got diff working properly with checkouts
146
147
@deprecated_function(zero_eight)
1432 by Robert Collins
branch: namespace
148
def show_diff(b, from_spec, specific_files, external_diff_options=None,
1185.35.28 by Aaron Bentley
Support diff with two branches as input.
149
              revision2=None, output=None, b2=None):
619 by Martin Pool
doc
150
    """Shortcut for showing the diff to the working tree.
151
1551.2.13 by Aaron Bentley
Got diff working properly with checkouts
152
    Please use show_diff_trees instead.
153
619 by Martin Pool
doc
154
    b
155
        Branch.
156
157
    revision
1432 by Robert Collins
branch: namespace
158
        None for 'basis tree', or otherwise the old revision to compare against.
619 by Martin Pool
doc
159
    
160
    The more general form is show_diff_trees(), where the caller
161
    supplies any two trees.
162
    """
1092.1.47 by Robert Collins
make show_diff redirectable
163
    if output is None:
164
        import sys
165
        output = sys.stdout
475 by Martin Pool
- rewrite diff using compare_trees()
166
1432 by Robert Collins
branch: namespace
167
    if from_spec is None:
1508.1.19 by Robert Collins
Give format3 working trees their own last-revision marker.
168
        old_tree = b.bzrdir.open_workingtree()
1185.35.28 by Aaron Bentley
Support diff with two branches as input.
169
        if b2 is None:
1534.4.35 by Robert Collins
Give branch its own basis tree and last_revision methods; deprecated branch.working_tree()
170
            old_tree = old_tree = old_tree.basis_tree()
329 by Martin Pool
- refactor command functions into command classes
171
    else:
1185.67.2 by Aaron Bentley
Renamed Branch.storage to Branch.repository
172
        old_tree = b.repository.revision_tree(from_spec.in_history(b).rev_id)
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
173
1185.11.5 by John Arbash Meinel
Merged up-to-date against mainline, still broken.
174
    if revision2 is None:
1185.35.28 by Aaron Bentley
Support diff with two branches as input.
175
        if b2 is None:
1508.1.19 by Robert Collins
Give format3 working trees their own last-revision marker.
176
            new_tree = b.bzrdir.open_workingtree()
1185.35.28 by Aaron Bentley
Support diff with two branches as input.
177
        else:
1508.1.19 by Robert Collins
Give format3 working trees their own last-revision marker.
178
            new_tree = b2.bzrdir.open_workingtree()
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
179
    else:
1185.67.2 by Aaron Bentley
Renamed Branch.storage to Branch.repository
180
        new_tree = b.repository.revision_tree(revision2.in_history(b).rev_id)
329 by Martin Pool
- refactor command functions into command classes
181
1490 by Robert Collins
Implement a 'bzr push' command, with saved locations; update diff to return 1.
182
    return show_diff_trees(old_tree, new_tree, output, specific_files,
183
                           external_diff_options)
571 by Martin Pool
- new --diff-options to pass options through to external
184
185
1551.2.15 by Aaron Bentley
Rename cmd_show_diff to diff_cmd_helper
186
def diff_cmd_helper(tree, specific_files, external_diff_options, 
187
                    old_revision_spec=None, new_revision_spec=None):
1551.2.13 by Aaron Bentley
Got diff working properly with checkouts
188
    """Helper for cmd_diff.
189
190
   tree 
191
        A WorkingTree
192
193
    specific_files
194
        The specific files to compare, or None
195
196
    external_diff_options
197
        If non-None, run an external diff, and pass it these options
198
1551.2.14 by Aaron Bentley
Updated argument names, DRY fixes.
199
    old_revision_spec
1551.2.13 by Aaron Bentley
Got diff working properly with checkouts
200
        If None, use basis tree as old revision, otherwise use the tree for
201
        the specified revision. 
202
1551.2.14 by Aaron Bentley
Updated argument names, DRY fixes.
203
    new_revision_spec
1551.2.13 by Aaron Bentley
Got diff working properly with checkouts
204
        If None, use working tree as new revision, otherwise use the tree for
205
        the specified revision.
206
    
207
    The more general form is show_diff_trees(), where the caller
208
    supplies any two trees.
209
    """
210
    import sys
211
    output = sys.stdout
1551.2.14 by Aaron Bentley
Updated argument names, DRY fixes.
212
    def spec_tree(spec):
213
        revision_id = spec.in_store(tree.branch).rev_id
214
        return tree.branch.repository.revision_tree(revision_id)
215
    if old_revision_spec is None:
1551.2.13 by Aaron Bentley
Got diff working properly with checkouts
216
        old_tree = tree.basis_tree()
217
    else:
1551.2.14 by Aaron Bentley
Updated argument names, DRY fixes.
218
        old_tree = spec_tree(old_revision_spec)
1551.2.13 by Aaron Bentley
Got diff working properly with checkouts
219
1551.2.14 by Aaron Bentley
Updated argument names, DRY fixes.
220
    if new_revision_spec is None:
1551.2.13 by Aaron Bentley
Got diff working properly with checkouts
221
        new_tree = tree
222
    else:
1551.2.14 by Aaron Bentley
Updated argument names, DRY fixes.
223
        new_tree = spec_tree(new_revision_spec)
1551.2.13 by Aaron Bentley
Got diff working properly with checkouts
224
1551.2.14 by Aaron Bentley
Updated argument names, DRY fixes.
225
    return show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
1551.2.13 by Aaron Bentley
Got diff working properly with checkouts
226
                           external_diff_options)
227
571 by Martin Pool
- new --diff-options to pass options through to external
228
229
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
230
                    external_diff_options=None):
550 by Martin Pool
- Refactor diff code into one that works purely on
231
    """Show in text form the changes from one tree to another.
232
233
    to_files
234
        If set, include only changes to these files.
571 by Martin Pool
- new --diff-options to pass options through to external
235
236
    external_diff_options
237
        If set, use an external GNU diff and pass these options.
550 by Martin Pool
- Refactor diff code into one that works purely on
238
    """
1543.1.1 by Denys Duchier
lock operations for trees - use them for diff
239
    old_tree.lock_read()
240
    try:
241
        new_tree.lock_read()
242
        try:
243
            return _show_diff_trees(old_tree, new_tree, to_file,
244
                                    specific_files, external_diff_options)
245
        finally:
246
            new_tree.unlock()
247
    finally:
248
        old_tree.unlock()
249
250
251
def _show_diff_trees(old_tree, new_tree, to_file,
252
                     specific_files, external_diff_options):
253
1583.1.1 by Michael Ellerman
Change to -p1 format diffs. Update existing tests to cope, and add some
254
    # TODO: Options to control putting on a prefix or suffix, perhaps
255
    # as a format string?
256
    old_label = 'a/'
257
    new_label = 'b/'
329 by Martin Pool
- refactor command functions into command classes
258
259
    DEVNULL = '/dev/null'
260
    # Windows users, don't panic about this filename -- it is a
261
    # special signal to GNU patch that the file should be created or
262
    # deleted respectively.
263
264
    # TODO: Generation of pseudo-diffs for added/deleted files could
265
    # be usefully made into a much faster special case.
266
1658.1.9 by Martin Pool
Give an error for bzr diff on an nonexistent file (Malone #3619)
267
    _raise_if_doubly_unversioned(specific_files, old_tree, new_tree)
268
571 by Martin Pool
- new --diff-options to pass options through to external
269
    if external_diff_options:
270
        assert isinstance(external_diff_options, basestring)
271
        opts = external_diff_options.split()
272
        def diff_file(olab, olines, nlab, nlines, to_file):
273
            external_diff(olab, olines, nlab, nlines, to_file, opts)
274
    else:
275
        diff_file = internal_diff
276
    
478 by Martin Pool
- put back support for running diff or status on
277
    delta = compare_trees(old_tree, new_tree, want_unchanged=False,
483 by Martin Pool
- change 'file_list' to more explanatory 'specific_files'
278
                          specific_files=specific_files)
475 by Martin Pool
- rewrite diff using compare_trees()
279
1490 by Robert Collins
Implement a 'bzr push' command, with saved locations; update diff to return 1.
280
    has_changes = 0
475 by Martin Pool
- rewrite diff using compare_trees()
281
    for path, file_id, kind in delta.removed:
1490 by Robert Collins
Implement a 'bzr push' command, with saved locations; update diff to return 1.
282
        has_changes = 1
1583.1.1 by Michael Ellerman
Change to -p1 format diffs. Update existing tests to cope, and add some
283
        print >>to_file, '=== removed %s %r' % (kind, old_label + path)
1399.1.4 by Robert Collins
move diff and symlink conditionals into inventory.py from diff.py
284
        old_tree.inventory[file_id].diff(diff_file, old_label + path, old_tree,
285
                                         DEVNULL, None, None, to_file)
475 by Martin Pool
- rewrite diff using compare_trees()
286
    for path, file_id, kind in delta.added:
1490 by Robert Collins
Implement a 'bzr push' command, with saved locations; update diff to return 1.
287
        has_changes = 1
1583.1.1 by Michael Ellerman
Change to -p1 format diffs. Update existing tests to cope, and add some
288
        print >>to_file, '=== added %s %r' % (kind, new_label + path)
1399.1.4 by Robert Collins
move diff and symlink conditionals into inventory.py from diff.py
289
        new_tree.inventory[file_id].diff(diff_file, new_label + path, new_tree,
290
                                         DEVNULL, None, None, to_file, 
291
                                         reverse=True)
1398 by Robert Collins
integrate in Gustavos x-bit patch
292
    for (old_path, new_path, file_id, kind,
293
         text_modified, meta_modified) in delta.renamed:
1490 by Robert Collins
Implement a 'bzr push' command, with saved locations; update diff to return 1.
294
        has_changes = 1
1398 by Robert Collins
integrate in Gustavos x-bit patch
295
        prop_str = get_prop_change(meta_modified)
296
        print >>to_file, '=== renamed %s %r => %r%s' % (
1583.1.1 by Michael Ellerman
Change to -p1 format diffs. Update existing tests to cope, and add some
297
                    kind, old_label + old_path, new_label + new_path, prop_str)
1092.2.6 by Robert Collins
symlink support updated to work
298
        _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
299
                                    new_label, new_path, new_tree,
1092.2.9 by Robert Collins
bugfix _maybe_diff, the test was not catching the error
300
                                    text_modified, kind, to_file, diff_file)
1398 by Robert Collins
integrate in Gustavos x-bit patch
301
    for path, file_id, kind, text_modified, meta_modified in delta.modified:
1490 by Robert Collins
Implement a 'bzr push' command, with saved locations; update diff to return 1.
302
        has_changes = 1
1398 by Robert Collins
integrate in Gustavos x-bit patch
303
        prop_str = get_prop_change(meta_modified)
1583.1.1 by Michael Ellerman
Change to -p1 format diffs. Update existing tests to cope, and add some
304
        print >>to_file, '=== modified %s %r%s' % (kind, old_label + path,
305
                    prop_str)
1398 by Robert Collins
integrate in Gustavos x-bit patch
306
        if text_modified:
307
            _maybe_diff_file_or_symlink(old_label, path, old_tree, file_id,
308
                                        new_label, path, new_tree,
309
                                        True, kind, to_file, diff_file)
1658.1.9 by Martin Pool
Give an error for bzr diff on an nonexistent file (Malone #3619)
310
1490 by Robert Collins
Implement a 'bzr push' command, with saved locations; update diff to return 1.
311
    return has_changes
1658.1.9 by Martin Pool
Give an error for bzr diff on an nonexistent file (Malone #3619)
312
313
314
def _raise_if_doubly_unversioned(specific_files, old_tree, new_tree):
315
    """Complain if paths are not versioned in either tree."""
1658.1.10 by Martin Pool
diff on unversiond files should give an error (Malone #3619)
316
    if not specific_files:
317
        return
1658.1.9 by Martin Pool
Give an error for bzr diff on an nonexistent file (Malone #3619)
318
    old_unversioned = old_tree.filter_unversioned_files(specific_files)
319
    new_unversioned = new_tree.filter_unversioned_files(specific_files)
320
    unversioned = old_unversioned.intersection(new_unversioned)
321
    if unversioned:
322
        raise errors.PathsNotVersionedError(sorted(unversioned))
1092.3.4 by Robert Collins
update symlink branch to integration
323
    
1092.2.6 by Robert Collins
symlink support updated to work
324
1398 by Robert Collins
integrate in Gustavos x-bit patch
325
def get_prop_change(meta_modified):
326
    if meta_modified:
327
        return " (properties changed)"
328
    else:
329
        return  ""
330
331
1092.2.6 by Robert Collins
symlink support updated to work
332
def _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
333
                                new_label, new_path, new_tree, text_modified,
1092.2.9 by Robert Collins
bugfix _maybe_diff, the test was not catching the error
334
                                kind, to_file, diff_file):
1092.2.6 by Robert Collins
symlink support updated to work
335
    if text_modified:
1399.1.4 by Robert Collins
move diff and symlink conditionals into inventory.py from diff.py
336
        new_entry = new_tree.inventory[file_id]
337
        old_tree.inventory[file_id].diff(diff_file,
338
                                         old_label + old_path, old_tree,
339
                                         new_label + new_path, new_entry, 
340
                                         new_tree, to_file)