~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

Update news and readme

- better explanation of dependencies

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005, 2006 Canonical Ltd.
 
1
# -*- coding: UTF-8 -*-
2
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
 
17
from bzrlib.trace import mutter
 
18
from bzrlib.errors import BzrError
17
19
from bzrlib.delta import compare_trees
18
 
from bzrlib.errors import BzrError
19
 
import bzrlib.errors as errors
20
 
from bzrlib.patiencediff import SequenceMatcher, unified_diff
21
 
from bzrlib.symbol_versioning import *
22
 
from bzrlib.textfile import check_text_lines
23
 
from bzrlib.trace import mutter
24
20
 
25
21
# TODO: Rather than building a changeset object, we should probably
26
22
# invoke callbacks on an object.  That object can either accumulate a
27
23
# list, write them out directly, etc etc.
28
24
 
29
 
def internal_diff(old_filename, oldlines, new_filename, newlines, to_file,
30
 
                  allow_binary=False, sequence_matcher=None):
 
25
def internal_diff(old_label, oldlines, new_label, newlines, to_file):
 
26
    import difflib
 
27
    
31
28
    # FIXME: difflib is wrong if there is no trailing newline.
32
29
    # The syntax used by patch seems to be "\ No newline at
33
30
    # end of file" following the last diff line from that
43
40
    # both sequences are empty.
44
41
    if not oldlines and not newlines:
45
42
        return
46
 
    
47
 
    if allow_binary is False:
48
 
        check_text_lines(oldlines)
49
 
        check_text_lines(newlines)
50
43
 
51
 
    if sequence_matcher is None:
52
 
        sequence_matcher = SequenceMatcher
53
 
    ud = unified_diff(oldlines, newlines,
54
 
                      fromfile=old_filename+'\t', 
55
 
                      tofile=new_filename+'\t',
56
 
                      sequencematcher=sequence_matcher)
 
44
    ud = difflib.unified_diff(oldlines, newlines,
 
45
                              fromfile=old_label, tofile=new_label)
57
46
 
58
47
    ud = list(ud)
59
48
    # work-around for difflib being too smart for its own good
73
62
    print >>to_file
74
63
 
75
64
 
76
 
def external_diff(old_filename, oldlines, new_filename, newlines, to_file,
 
65
def external_diff(old_label, oldlines, new_label, newlines, to_file,
77
66
                  diff_opts):
78
67
    """Display a diff by calling out to the external diff program."""
79
68
    import sys
108
97
        if not diff_opts:
109
98
            diff_opts = []
110
99
        diffcmd = ['diff',
111
 
                   '--label', old_filename+'\t',
 
100
                   '--label', old_label,
112
101
                   oldtmpf.name,
113
 
                   '--label', new_filename+'\t',
 
102
                   '--label', new_label,
114
103
                   newtmpf.name]
115
104
 
116
105
        # diff only allows one style to be specified; they don't override.
151
140
        oldtmpf.close()                 # and delete
152
141
        newtmpf.close()
153
142
 
154
 
 
155
 
@deprecated_function(zero_eight)
156
143
def show_diff(b, from_spec, specific_files, external_diff_options=None,
157
 
              revision2=None, output=None, b2=None):
 
144
              revision2=None, output=None):
158
145
    """Shortcut for showing the diff to the working tree.
159
146
 
160
 
    Please use show_diff_trees instead.
161
 
 
162
147
    b
163
148
        Branch.
164
149
 
173
158
        output = sys.stdout
174
159
 
175
160
    if from_spec is None:
176
 
        old_tree = b.bzrdir.open_workingtree()
177
 
        if b2 is None:
178
 
            old_tree = old_tree = old_tree.basis_tree()
 
161
        old_tree = b.basis_tree()
179
162
    else:
180
 
        old_tree = b.repository.revision_tree(from_spec.in_history(b).rev_id)
 
163
        old_tree = b.revision_tree(from_spec.in_history(b).rev_id)
181
164
 
182
165
    if revision2 is None:
183
 
        if b2 is None:
184
 
            new_tree = b.bzrdir.open_workingtree()
185
 
        else:
186
 
            new_tree = b2.bzrdir.open_workingtree()
187
 
    else:
188
 
        new_tree = b.repository.revision_tree(revision2.in_history(b).rev_id)
189
 
 
190
 
    return show_diff_trees(old_tree, new_tree, output, specific_files,
191
 
                           external_diff_options)
192
 
 
193
 
 
194
 
def diff_cmd_helper(tree, specific_files, external_diff_options, 
195
 
                    old_revision_spec=None, new_revision_spec=None,
196
 
                    old_label='a/', new_label='b/'):
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
 
 
208
 
    old_revision_spec
209
 
        If None, use basis tree as old revision, otherwise use the tree for
210
 
        the specified revision. 
211
 
 
212
 
    new_revision_spec
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
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:
225
 
        old_tree = tree.basis_tree()
226
 
    else:
227
 
        old_tree = spec_tree(old_revision_spec)
228
 
 
229
 
    if new_revision_spec is None:
230
 
        new_tree = tree
231
 
    else:
232
 
        new_tree = spec_tree(new_revision_spec)
233
 
 
234
 
    return show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
235
 
                           external_diff_options,
236
 
                           old_label=old_label, new_label=new_label)
 
166
        new_tree = b.working_tree()
 
167
    else:
 
168
        new_tree = b.revision_tree(revision2.in_history(b).rev_id)
 
169
 
 
170
    show_diff_trees(old_tree, new_tree, output, specific_files,
 
171
                    external_diff_options)
 
172
 
237
173
 
238
174
 
239
175
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
240
 
                    external_diff_options=None,
241
 
                    old_label='a/', new_label='b/'):
 
176
                    external_diff_options=None):
242
177
    """Show in text form the changes from one tree to another.
243
178
 
244
179
    to_files
247
182
    external_diff_options
248
183
        If set, use an external GNU diff and pass these options.
249
184
    """
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,
255
 
                                    specific_files, external_diff_options,
256
 
                                    old_label=old_label, new_label=new_label)
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,
264
 
                     specific_files, external_diff_options, 
265
 
                     old_label='a/', new_label='b/' ):
 
185
 
 
186
    # TODO: Options to control putting on a prefix or suffix, perhaps as a format string
 
187
    old_label = ''
 
188
    new_label = ''
266
189
 
267
190
    DEVNULL = '/dev/null'
268
191
    # Windows users, don't panic about this filename -- it is a
272
195
    # TODO: Generation of pseudo-diffs for added/deleted files could
273
196
    # be usefully made into a much faster special case.
274
197
 
275
 
    _raise_if_doubly_unversioned(specific_files, old_tree, new_tree)
276
 
 
277
198
    if external_diff_options:
278
199
        assert isinstance(external_diff_options, basestring)
279
200
        opts = external_diff_options.split()
282
203
    else:
283
204
        diff_file = internal_diff
284
205
    
 
206
 
285
207
    delta = compare_trees(old_tree, new_tree, want_unchanged=False,
286
208
                          specific_files=specific_files)
287
209
 
288
 
    has_changes = 0
289
210
    for path, file_id, kind in delta.removed:
290
 
        has_changes = 1
291
211
        print >>to_file, '=== removed %s %r' % (kind, path)
292
212
        old_tree.inventory[file_id].diff(diff_file, old_label + path, old_tree,
293
213
                                         DEVNULL, None, None, to_file)
294
214
    for path, file_id, kind in delta.added:
295
 
        has_changes = 1
296
215
        print >>to_file, '=== added %s %r' % (kind, path)
297
216
        new_tree.inventory[file_id].diff(diff_file, new_label + path, new_tree,
298
217
                                         DEVNULL, None, None, to_file, 
299
218
                                         reverse=True)
300
219
    for (old_path, new_path, file_id, kind,
301
220
         text_modified, meta_modified) in delta.renamed:
302
 
        has_changes = 1
303
221
        prop_str = get_prop_change(meta_modified)
304
222
        print >>to_file, '=== renamed %s %r => %r%s' % (
305
 
                    kind, old_path, new_path, prop_str)
 
223
                          kind, old_path, new_path, prop_str)
306
224
        _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
307
225
                                    new_label, new_path, new_tree,
308
226
                                    text_modified, kind, to_file, diff_file)
309
227
    for path, file_id, kind, text_modified, meta_modified in delta.modified:
310
 
        has_changes = 1
311
228
        prop_str = get_prop_change(meta_modified)
312
229
        print >>to_file, '=== modified %s %r%s' % (kind, path, prop_str)
313
230
        if text_modified:
314
231
            _maybe_diff_file_or_symlink(old_label, path, old_tree, file_id,
315
232
                                        new_label, path, new_tree,
316
233
                                        True, kind, to_file, diff_file)
317
 
 
318
 
    return has_changes
319
 
 
320
 
 
321
 
def _raise_if_doubly_unversioned(specific_files, old_tree, new_tree):
322
 
    """Complain if paths are not versioned in either tree."""
323
 
    if not specific_files:
324
 
        return
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))
330
 
    
331
 
 
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
 
 
 
234
    
350
235
 
351
236
def get_prop_change(meta_modified):
352
237
    if meta_modified: