~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
329 by Martin Pool
- refactor command functions into command classes
254
    # TODO: Options to control putting on a prefix or suffix, perhaps as a format string
255
    old_label = ''
256
    new_label = ''
257
258
    DEVNULL = '/dev/null'
259
    # Windows users, don't panic about this filename -- it is a
260
    # special signal to GNU patch that the file should be created or
261
    # deleted respectively.
262
263
    # TODO: Generation of pseudo-diffs for added/deleted files could
264
    # be usefully made into a much faster special case.
265
571 by Martin Pool
- new --diff-options to pass options through to external
266
    if external_diff_options:
267
        assert isinstance(external_diff_options, basestring)
268
        opts = external_diff_options.split()
269
        def diff_file(olab, olines, nlab, nlines, to_file):
270
            external_diff(olab, olines, nlab, nlines, to_file, opts)
271
    else:
272
        diff_file = internal_diff
273
    
274
478 by Martin Pool
- put back support for running diff or status on
275
    delta = compare_trees(old_tree, new_tree, want_unchanged=False,
483 by Martin Pool
- change 'file_list' to more explanatory 'specific_files'
276
                          specific_files=specific_files)
475 by Martin Pool
- rewrite diff using compare_trees()
277
1490 by Robert Collins
Implement a 'bzr push' command, with saved locations; update diff to return 1.
278
    has_changes = 0
475 by Martin Pool
- rewrite diff using compare_trees()
279
    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.
280
        has_changes = 1
1092.1.50 by Robert Collins
make diff lsdiff/filterdiff friendly
281
        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
282
        old_tree.inventory[file_id].diff(diff_file, old_label + path, old_tree,
283
                                         DEVNULL, None, None, to_file)
475 by Martin Pool
- rewrite diff using compare_trees()
284
    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.
285
        has_changes = 1
1092.1.50 by Robert Collins
make diff lsdiff/filterdiff friendly
286
        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
287
        new_tree.inventory[file_id].diff(diff_file, new_label + path, new_tree,
288
                                         DEVNULL, None, None, to_file, 
289
                                         reverse=True)
1398 by Robert Collins
integrate in Gustavos x-bit patch
290
    for (old_path, new_path, file_id, kind,
291
         text_modified, meta_modified) in delta.renamed:
1490 by Robert Collins
Implement a 'bzr push' command, with saved locations; update diff to return 1.
292
        has_changes = 1
1398 by Robert Collins
integrate in Gustavos x-bit patch
293
        prop_str = get_prop_change(meta_modified)
294
        print >>to_file, '=== renamed %s %r => %r%s' % (
295
                          kind, old_path, new_path, prop_str)
1092.2.6 by Robert Collins
symlink support updated to work
296
        _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
297
                                    new_label, new_path, new_tree,
1092.2.9 by Robert Collins
bugfix _maybe_diff, the test was not catching the error
298
                                    text_modified, kind, to_file, diff_file)
1398 by Robert Collins
integrate in Gustavos x-bit patch
299
    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.
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, '=== modified %s %r%s' % (kind, path, prop_str)
303
        if text_modified:
304
            _maybe_diff_file_or_symlink(old_label, path, old_tree, file_id,
305
                                        new_label, path, new_tree,
306
                                        True, kind, to_file, diff_file)
1490 by Robert Collins
Implement a 'bzr push' command, with saved locations; update diff to return 1.
307
    return has_changes
1092.3.4 by Robert Collins
update symlink branch to integration
308
    
1092.2.6 by Robert Collins
symlink support updated to work
309
1398 by Robert Collins
integrate in Gustavos x-bit patch
310
def get_prop_change(meta_modified):
311
    if meta_modified:
312
        return " (properties changed)"
313
    else:
314
        return  ""
315
316
1092.2.6 by Robert Collins
symlink support updated to work
317
def _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
318
                                new_label, new_path, new_tree, text_modified,
1092.2.9 by Robert Collins
bugfix _maybe_diff, the test was not catching the error
319
                                kind, to_file, diff_file):
1092.2.6 by Robert Collins
symlink support updated to work
320
    if text_modified:
1399.1.4 by Robert Collins
move diff and symlink conditionals into inventory.py from diff.py
321
        new_entry = new_tree.inventory[file_id]
322
        old_tree.inventory[file_id].diff(diff_file,
323
                                         old_label + old_path, old_tree,
324
                                         new_label + new_path, new_entry, 
325
                                         new_tree, to_file)