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