~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

first cut at merge from integration.

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
 
import time
18
 
 
 
17
from bzrlib.trace import mutter
 
18
from bzrlib.errors import BzrError
19
19
from bzrlib.delta import compare_trees
20
 
from bzrlib.errors import BzrError
21
 
import bzrlib.errors as errors
22
 
from bzrlib.patiencediff import unified_diff
23
 
import bzrlib.patiencediff
24
 
from bzrlib.symbol_versioning import *
25
 
from bzrlib.textfile import check_text_lines
26
 
from bzrlib.trace import mutter
27
 
 
28
20
 
29
21
# TODO: Rather than building a changeset object, we should probably
30
22
# invoke callbacks on an object.  That object can either accumulate a
31
23
# list, write them out directly, etc etc.
32
24
 
33
 
def internal_diff(old_filename, oldlines, new_filename, newlines, to_file,
34
 
                  allow_binary=False, sequence_matcher=None,
35
 
                  path_encoding='utf8'):
 
25
def internal_diff(old_filename, oldlines, new_filename, newlines, to_file):
 
26
    import difflib
 
27
    
36
28
    # FIXME: difflib is wrong if there is no trailing newline.
37
29
    # The syntax used by patch seems to be "\ No newline at
38
30
    # end of file" following the last diff line from that
48
40
    # both sequences are empty.
49
41
    if not oldlines and not newlines:
50
42
        return
51
 
    
52
 
    if allow_binary is False:
53
 
        check_text_lines(oldlines)
54
 
        check_text_lines(newlines)
55
43
 
56
 
    if sequence_matcher is None:
57
 
        sequence_matcher = bzrlib.patiencediff.PatienceSequenceMatcher
58
 
    ud = unified_diff(oldlines, newlines,
59
 
                      fromfile=old_filename.encode(path_encoding),
60
 
                      tofile=new_filename.encode(path_encoding),
61
 
                      sequencematcher=sequence_matcher)
 
44
    ud = difflib.unified_diff(oldlines, newlines,
 
45
                              fromfile=old_filename+'\t', 
 
46
                              tofile=new_filename+'\t')
62
47
 
63
48
    ud = list(ud)
64
49
    # work-around for difflib being too smart for its own good
113
98
        if not diff_opts:
114
99
            diff_opts = []
115
100
        diffcmd = ['diff',
116
 
                   '--label', old_filename,
 
101
                   '--label', old_filename+'\t',
117
102
                   oldtmpf.name,
118
 
                   '--label', new_filename,
 
103
                   '--label', new_filename+'\t',
119
104
                   newtmpf.name]
120
105
 
121
106
        # diff only allows one style to be specified; they don't override.
156
141
        oldtmpf.close()                 # and delete
157
142
        newtmpf.close()
158
143
 
159
 
 
160
 
@deprecated_function(zero_eight)
161
144
def show_diff(b, from_spec, specific_files, external_diff_options=None,
162
145
              revision2=None, output=None, b2=None):
163
146
    """Shortcut for showing the diff to the working tree.
164
147
 
165
 
    Please use show_diff_trees instead.
166
 
 
167
148
    b
168
149
        Branch.
169
150
 
178
159
        output = sys.stdout
179
160
 
180
161
    if from_spec is None:
181
 
        old_tree = b.bzrdir.open_workingtree()
182
162
        if b2 is None:
183
 
            old_tree = old_tree = old_tree.basis_tree()
 
163
            old_tree = b.basis_tree()
 
164
        else:
 
165
            old_tree = b.working_tree()
184
166
    else:
185
167
        old_tree = b.repository.revision_tree(from_spec.in_history(b).rev_id)
186
168
 
187
169
    if revision2 is None:
188
170
        if b2 is None:
189
 
            new_tree = b.bzrdir.open_workingtree()
 
171
            new_tree = b.working_tree()
190
172
        else:
191
 
            new_tree = b2.bzrdir.open_workingtree()
 
173
            new_tree = b2.working_tree()
192
174
    else:
193
175
        new_tree = b.repository.revision_tree(revision2.in_history(b).rev_id)
194
176
 
196
178
                           external_diff_options)
197
179
 
198
180
 
199
 
def diff_cmd_helper(tree, specific_files, external_diff_options, 
200
 
                    old_revision_spec=None, new_revision_spec=None,
201
 
                    old_label='a/', new_label='b/'):
202
 
    """Helper for cmd_diff.
203
 
 
204
 
   tree 
205
 
        A WorkingTree
206
 
 
207
 
    specific_files
208
 
        The specific files to compare, or None
209
 
 
210
 
    external_diff_options
211
 
        If non-None, run an external diff, and pass it these options
212
 
 
213
 
    old_revision_spec
214
 
        If None, use basis tree as old revision, otherwise use the tree for
215
 
        the specified revision. 
216
 
 
217
 
    new_revision_spec
218
 
        If None, use working tree as new revision, otherwise use the tree for
219
 
        the specified revision.
220
 
    
221
 
    The more general form is show_diff_trees(), where the caller
222
 
    supplies any two trees.
223
 
    """
224
 
    import sys
225
 
    output = sys.stdout
226
 
    def spec_tree(spec):
227
 
        revision_id = spec.in_store(tree.branch).rev_id
228
 
        return tree.branch.repository.revision_tree(revision_id)
229
 
    if old_revision_spec is None:
230
 
        old_tree = tree.basis_tree()
231
 
    else:
232
 
        old_tree = spec_tree(old_revision_spec)
233
 
 
234
 
    if new_revision_spec is None:
235
 
        new_tree = tree
236
 
    else:
237
 
        new_tree = spec_tree(new_revision_spec)
238
 
 
239
 
    return show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
240
 
                           external_diff_options,
241
 
                           old_label=old_label, new_label=new_label)
242
 
 
243
181
 
244
182
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
245
 
                    external_diff_options=None,
246
 
                    old_label='a/', new_label='b/'):
 
183
                    external_diff_options=None):
247
184
    """Show in text form the changes from one tree to another.
248
185
 
249
186
    to_files
252
189
    external_diff_options
253
190
        If set, use an external GNU diff and pass these options.
254
191
    """
 
192
 
255
193
    old_tree.lock_read()
256
194
    try:
257
195
        new_tree.lock_read()
258
196
        try:
259
197
            return _show_diff_trees(old_tree, new_tree, to_file,
260
 
                                    specific_files, external_diff_options,
261
 
                                    old_label=old_label, new_label=new_label)
 
198
                                    specific_files, external_diff_options)
262
199
        finally:
263
200
            new_tree.unlock()
264
201
    finally:
266
203
 
267
204
 
268
205
def _show_diff_trees(old_tree, new_tree, to_file,
269
 
                     specific_files, external_diff_options, 
270
 
                     old_label='a/', new_label='b/' ):
271
 
 
272
 
    # GNU Patch uses the epoch date to detect files that are being added
273
 
    # or removed in a diff.
274
 
    EPOCH_DATE = '1970-01-01 00:00:00 +0000'
 
206
                     specific_files, external_diff_options):
 
207
 
 
208
    # TODO: Options to control putting on a prefix or suffix, perhaps as a format string
 
209
    old_label = ''
 
210
    new_label = ''
 
211
 
 
212
    DEVNULL = '/dev/null'
 
213
    # Windows users, don't panic about this filename -- it is a
 
214
    # special signal to GNU patch that the file should be created or
 
215
    # deleted respectively.
275
216
 
276
217
    # TODO: Generation of pseudo-diffs for added/deleted files could
277
218
    # be usefully made into a much faster special case.
278
219
 
279
 
    _raise_if_doubly_unversioned(specific_files, old_tree, new_tree)
280
 
 
281
220
    if external_diff_options:
282
221
        assert isinstance(external_diff_options, basestring)
283
222
        opts = external_diff_options.split()
286
225
    else:
287
226
        diff_file = internal_diff
288
227
    
 
228
 
289
229
    delta = compare_trees(old_tree, new_tree, want_unchanged=False,
290
230
                          specific_files=specific_files)
291
231
 
292
232
    has_changes = 0
293
233
    for path, file_id, kind in delta.removed:
294
234
        has_changes = 1
295
 
        print >>to_file, '=== removed %s %r' % (kind, path.encode('utf8'))
296
 
        old_name = '%s%s\t%s' % (old_label, path,
297
 
                                 _patch_header_date(old_tree, file_id, path))
298
 
        new_name = '%s%s\t%s' % (new_label, path, EPOCH_DATE)
299
 
        old_tree.inventory[file_id].diff(diff_file, old_name, old_tree,
300
 
                                         new_name, None, None, to_file)
 
235
        print >>to_file, '=== removed %s %r' % (kind, path)
 
236
        old_tree.inventory[file_id].diff(diff_file, old_label + path, old_tree,
 
237
                                         DEVNULL, None, None, to_file)
301
238
    for path, file_id, kind in delta.added:
302
239
        has_changes = 1
303
 
        print >>to_file, '=== added %s %r' % (kind, path.encode('utf8'))
304
 
        old_name = '%s%s\t%s' % (old_label, path, EPOCH_DATE)
305
 
        new_name = '%s%s\t%s' % (new_label, path,
306
 
                                 _patch_header_date(new_tree, file_id, path))
307
 
        new_tree.inventory[file_id].diff(diff_file, new_name, new_tree,
308
 
                                         old_name, None, None, to_file, 
 
240
        print >>to_file, '=== added %s %r' % (kind, path)
 
241
        new_tree.inventory[file_id].diff(diff_file, new_label + path, new_tree,
 
242
                                         DEVNULL, None, None, to_file, 
309
243
                                         reverse=True)
310
244
    for (old_path, new_path, file_id, kind,
311
245
         text_modified, meta_modified) in delta.renamed:
312
246
        has_changes = 1
313
247
        prop_str = get_prop_change(meta_modified)
314
248
        print >>to_file, '=== renamed %s %r => %r%s' % (
315
 
                    kind, old_path.encode('utf8'),
316
 
                    new_path.encode('utf8'), prop_str)
317
 
        old_name = '%s%s\t%s' % (old_label, old_path,
318
 
                                 _patch_header_date(old_tree, file_id,
319
 
                                                    old_path))
320
 
        new_name = '%s%s\t%s' % (new_label, new_path,
321
 
                                 _patch_header_date(new_tree, file_id,
322
 
                                                    new_path))
323
 
        _maybe_diff_file_or_symlink(old_name, old_tree, file_id,
324
 
                                    new_name, new_tree,
 
249
                          kind, old_path, new_path, prop_str)
 
250
        _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
 
251
                                    new_label, new_path, new_tree,
325
252
                                    text_modified, kind, to_file, diff_file)
326
253
    for path, file_id, kind, text_modified, meta_modified in delta.modified:
327
254
        has_changes = 1
328
255
        prop_str = get_prop_change(meta_modified)
329
 
        print >>to_file, '=== modified %s %r%s' % (kind, path.encode('utf8'), prop_str)
330
 
        old_name = '%s%s\t%s' % (old_label, path,
331
 
                                 _patch_header_date(old_tree, file_id, path))
332
 
        new_name = '%s%s\t%s' % (new_label, path,
333
 
                                 _patch_header_date(new_tree, file_id, path))
 
256
        print >>to_file, '=== modified %s %r%s' % (kind, path, prop_str)
334
257
        if text_modified:
335
 
            _maybe_diff_file_or_symlink(old_name, old_tree, file_id,
336
 
                                        new_name, new_tree,
 
258
            _maybe_diff_file_or_symlink(old_label, path, old_tree, file_id,
 
259
                                        new_label, path, new_tree,
337
260
                                        True, kind, to_file, diff_file)
338
 
 
339
261
    return has_changes
340
 
 
341
 
 
342
 
def _patch_header_date(tree, file_id, path):
343
 
    """Returns a timestamp suitable for use in a patch header."""
344
 
    tm = time.gmtime(tree.get_file_mtime(file_id, path))
345
 
    return time.strftime('%Y-%m-%d %H:%M:%S +0000', tm)
346
 
 
347
 
 
348
 
def _raise_if_doubly_unversioned(specific_files, old_tree, new_tree):
349
 
    """Complain if paths are not versioned in either tree."""
350
 
    if not specific_files:
351
 
        return
352
 
    old_unversioned = old_tree.filter_unversioned_files(specific_files)
353
 
    new_unversioned = new_tree.filter_unversioned_files(specific_files)
354
 
    unversioned = old_unversioned.intersection(new_unversioned)
355
 
    if unversioned:
356
 
        raise errors.PathsNotVersionedError(sorted(unversioned))
357
 
    
358
 
 
359
 
def _raise_if_nonexistent(paths, old_tree, new_tree):
360
 
    """Complain if paths are not in either inventory or tree.
361
 
 
362
 
    It's OK with the files exist in either tree's inventory, or 
363
 
    if they exist in the tree but are not versioned.
364
 
    
365
 
    This can be used by operations such as bzr status that can accept
366
 
    unknown or ignored files.
367
 
    """
368
 
    mutter("check paths: %r", paths)
369
 
    if not paths:
370
 
        return
371
 
    s = old_tree.filter_unversioned_files(paths)
372
 
    s = new_tree.filter_unversioned_files(s)
373
 
    s = [path for path in s if not new_tree.has_filename(path)]
374
 
    if s:
375
 
        raise errors.PathsDoNotExist(sorted(s))
376
 
 
 
262
    
377
263
 
378
264
def get_prop_change(meta_modified):
379
265
    if meta_modified:
382
268
        return  ""
383
269
 
384
270
 
385
 
def _maybe_diff_file_or_symlink(old_path, old_tree, file_id,
386
 
                                new_path, new_tree, text_modified,
 
271
def _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
 
272
                                new_label, new_path, new_tree, text_modified,
387
273
                                kind, to_file, diff_file):
388
274
    if text_modified:
389
275
        new_entry = new_tree.inventory[file_id]
390
276
        old_tree.inventory[file_id].diff(diff_file,
391
 
                                         old_path, old_tree,
392
 
                                         new_path, new_entry, 
 
277
                                         old_label + old_path, old_tree,
 
278
                                         new_label + new_path, new_entry, 
393
279
                                         new_tree, to_file)