~bzr-pqm/bzr/bzr.dev

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