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