~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

  • Committer: Aaron Bentley
  • Date: 2005-09-21 15:33:23 UTC
  • mto: (1185.1.37)
  • mto: This revision was merged to the branch mainline in revision 1390.
  • Revision ID: abentley@panoramicfeedback.com-20050921153323-5db674d572d7649d
Fixed bug in distance-from-root graph operation

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_label, oldlines, new_label, 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_label, tofile=new_label)
62
46
 
63
47
    ud = list(ud)
64
48
    # work-around for difflib being too smart for its own good
78
62
    print >>to_file
79
63
 
80
64
 
81
 
def external_diff(old_filename, oldlines, new_filename, newlines, to_file,
 
65
 
 
66
 
 
67
def external_diff(old_label, oldlines, new_label, newlines, to_file,
82
68
                  diff_opts):
83
69
    """Display a diff by calling out to the external diff program."""
84
70
    import sys
113
99
        if not diff_opts:
114
100
            diff_opts = []
115
101
        diffcmd = ['diff',
116
 
                   '--label', old_filename,
 
102
                   '--label', old_label,
117
103
                   oldtmpf.name,
118
 
                   '--label', new_filename,
 
104
                   '--label', new_label,
119
105
                   newtmpf.name]
120
106
 
121
107
        # diff only allows one style to be specified; they don't override.
155
141
    finally:
156
142
        oldtmpf.close()                 # and delete
157
143
        newtmpf.close()
158
 
 
159
 
 
160
 
@deprecated_function(zero_eight)
161
 
def show_diff(b, from_spec, specific_files, external_diff_options=None,
162
 
              revision2=None, output=None, b2=None):
 
144
    
 
145
 
 
146
 
 
147
def show_diff(b, revision, specific_files, external_diff_options=None,
 
148
              revision2=None, output=None):
163
149
    """Shortcut for showing the diff to the working tree.
164
150
 
165
 
    Please use show_diff_trees instead.
166
 
 
167
151
    b
168
152
        Branch.
169
153
 
170
154
    revision
171
 
        None for 'basis tree', or otherwise the old revision to compare against.
 
155
        None for each, or otherwise the old revision to compare against.
172
156
    
173
157
    The more general form is show_diff_trees(), where the caller
174
158
    supplies any two trees.
177
161
        import sys
178
162
        output = sys.stdout
179
163
 
180
 
    if from_spec is None:
181
 
        old_tree = b.bzrdir.open_workingtree()
182
 
        if b2 is None:
183
 
            old_tree = old_tree = old_tree.basis_tree()
 
164
    if revision is None:
 
165
        old_tree = b.basis_tree()
184
166
    else:
185
 
        old_tree = b.repository.revision_tree(from_spec.in_history(b).rev_id)
 
167
        old_tree = b.revision_tree(revision.in_history(b).rev_id)
186
168
 
187
169
    if revision2 is None:
188
 
        if b2 is None:
189
 
            new_tree = b.bzrdir.open_workingtree()
190
 
        else:
191
 
            new_tree = b2.bzrdir.open_workingtree()
192
 
    else:
193
 
        new_tree = b.repository.revision_tree(revision2.in_history(b).rev_id)
194
 
 
195
 
    return show_diff_trees(old_tree, new_tree, output, specific_files,
196
 
                           external_diff_options)
197
 
 
198
 
 
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)
 
170
        new_tree = b.working_tree()
 
171
    else:
 
172
        new_tree = b.revision_tree(revision2.in_history(b).rev_id)
 
173
 
 
174
    show_diff_trees(old_tree, new_tree, output, specific_files,
 
175
                    external_diff_options)
 
176
 
242
177
 
243
178
 
244
179
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/'):
 
180
                    external_diff_options=None):
247
181
    """Show in text form the changes from one tree to another.
248
182
 
249
183
    to_files
252
186
    external_diff_options
253
187
        If set, use an external GNU diff and pass these options.
254
188
    """
255
 
    old_tree.lock_read()
256
 
    try:
257
 
        new_tree.lock_read()
258
 
        try:
259
 
            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)
262
 
        finally:
263
 
            new_tree.unlock()
264
 
    finally:
265
 
        old_tree.unlock()
266
 
 
267
 
 
268
 
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'
 
189
 
 
190
    # TODO: Options to control putting on a prefix or suffix, perhaps as a format string
 
191
    old_label = ''
 
192
    new_label = ''
 
193
 
 
194
    DEVNULL = '/dev/null'
 
195
    # Windows users, don't panic about this filename -- it is a
 
196
    # special signal to GNU patch that the file should be created or
 
197
    # deleted respectively.
275
198
 
276
199
    # TODO: Generation of pseudo-diffs for added/deleted files could
277
200
    # be usefully made into a much faster special case.
278
201
 
279
 
    _raise_if_doubly_unversioned(specific_files, old_tree, new_tree)
280
 
 
281
202
    if external_diff_options:
282
203
        assert isinstance(external_diff_options, basestring)
283
204
        opts = external_diff_options.split()
286
207
    else:
287
208
        diff_file = internal_diff
288
209
    
 
210
 
289
211
    delta = compare_trees(old_tree, new_tree, want_unchanged=False,
290
212
                          specific_files=specific_files)
291
213
 
292
 
    has_changes = 0
293
214
    for path, file_id, kind in delta.removed:
294
 
        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)
 
215
        print >>to_file, '=== removed %s %r' % (kind, path)
 
216
        if kind == 'file':
 
217
            diff_file(old_label + path,
 
218
                      old_tree.get_file(file_id).readlines(),
 
219
                      DEVNULL, 
 
220
                      [],
 
221
                      to_file)
 
222
 
301
223
    for path, file_id, kind in delta.added:
302
 
        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, 
309
 
                                         reverse=True)
310
 
    for (old_path, new_path, file_id, kind,
311
 
         text_modified, meta_modified) in delta.renamed:
312
 
        has_changes = 1
313
 
        prop_str = get_prop_change(meta_modified)
314
 
        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,
325
 
                                    text_modified, kind, to_file, diff_file)
326
 
    for path, file_id, kind, text_modified, meta_modified in delta.modified:
327
 
        has_changes = 1
328
 
        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))
 
224
        print >>to_file, '=== added %s %r' % (kind, path)
 
225
        if kind == 'file':
 
226
            diff_file(DEVNULL,
 
227
                      [],
 
228
                      new_label + path,
 
229
                      new_tree.get_file(file_id).readlines(),
 
230
                      to_file)
 
231
 
 
232
    for old_path, new_path, file_id, kind, text_modified in delta.renamed:
 
233
        print >>to_file, '=== renamed %s %r => %r' % (kind, old_path, new_path)
334
234
        if text_modified:
335
 
            _maybe_diff_file_or_symlink(old_name, old_tree, file_id,
336
 
                                        new_name, new_tree,
337
 
                                        True, kind, to_file, diff_file)
338
 
 
339
 
    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
 
 
377
 
 
378
 
def get_prop_change(meta_modified):
379
 
    if meta_modified:
380
 
        return " (properties changed)"
381
 
    else:
382
 
        return  ""
383
 
 
384
 
 
385
 
def _maybe_diff_file_or_symlink(old_path, old_tree, file_id,
386
 
                                new_path, new_tree, text_modified,
387
 
                                kind, to_file, diff_file):
388
 
    if text_modified:
389
 
        new_entry = new_tree.inventory[file_id]
390
 
        old_tree.inventory[file_id].diff(diff_file,
391
 
                                         old_path, old_tree,
392
 
                                         new_path, new_entry, 
393
 
                                         new_tree, to_file)
 
235
            diff_file(old_label + old_path,
 
236
                      old_tree.get_file(file_id).readlines(),
 
237
                      new_label + new_path,
 
238
                      new_tree.get_file(file_id).readlines(),
 
239
                      to_file)
 
240
 
 
241
    for path, file_id, kind in delta.modified:
 
242
        print >>to_file, '=== modified %s %r' % (kind, path)
 
243
        if kind == 'file':
 
244
            diff_file(old_label + path,
 
245
                      old_tree.get_file(file_id).readlines(),
 
246
                      new_label + path,
 
247
                      new_tree.get_file(file_id).readlines(),
 
248
                      to_file)
 
249
 
 
250
 
 
251
 
 
252
 
 
253