~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

  • Committer: Martin Pool
  • Date: 2006-06-20 07:55:43 UTC
  • mfrom: (1798 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1799.
  • Revision ID: mbp@sourcefrog.net-20060620075543-b10f6575d4a4fa32
[merge] bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2004, 2005, 2006 Canonical Ltd.
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
 
 
12
#
13
13
# You should have received a copy of the GNU General Public License
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 errno
 
18
import os
 
19
import subprocess
 
20
import sys
 
21
import tempfile
 
22
import time
 
23
 
17
24
from bzrlib.delta import compare_trees
18
25
from bzrlib.errors import BzrError
19
26
import bzrlib.errors as errors
 
27
import bzrlib.osutils
20
28
from bzrlib.patiencediff import unified_diff
21
29
import bzrlib.patiencediff
22
 
from bzrlib.symbol_versioning import *
 
30
from bzrlib.symbol_versioning import (deprecated_function,
 
31
        zero_eight)
23
32
from bzrlib.textfile import check_text_lines
24
 
from bzrlib.trace import mutter
 
33
from bzrlib.trace import mutter, warning
25
34
 
26
35
 
27
36
# TODO: Rather than building a changeset object, we should probably
54
63
    if sequence_matcher is None:
55
64
        sequence_matcher = bzrlib.patiencediff.PatienceSequenceMatcher
56
65
    ud = unified_diff(oldlines, newlines,
57
 
                      fromfile=old_filename.encode(path_encoding)+'\t', 
58
 
                      tofile=new_filename.encode(path_encoding)+'\t',
 
66
                      fromfile=old_filename.encode(path_encoding),
 
67
                      tofile=new_filename.encode(path_encoding),
59
68
                      sequencematcher=sequence_matcher)
60
69
 
61
70
    ud = list(ud)
79
88
def external_diff(old_filename, oldlines, new_filename, newlines, to_file,
80
89
                  diff_opts):
81
90
    """Display a diff by calling out to the external diff program."""
82
 
    import sys
 
91
    if hasattr(to_file, 'fileno'):
 
92
        out_file = to_file
 
93
        have_fileno = True
 
94
    else:
 
95
        out_file = subprocess.PIPE
 
96
        have_fileno = False
83
97
    
84
 
    if to_file != sys.stdout:
85
 
        raise NotImplementedError("sorry, can't send external diff other than to stdout yet",
86
 
                                  to_file)
87
 
 
88
98
    # make sure our own output is properly ordered before the diff
89
99
    to_file.flush()
90
100
 
91
 
    from tempfile import NamedTemporaryFile
92
 
    import os
93
 
 
94
 
    oldtmpf = NamedTemporaryFile()
95
 
    newtmpf = NamedTemporaryFile()
 
101
    oldtmp_fd, old_abspath = tempfile.mkstemp(prefix='bzr-diff-old-')
 
102
    newtmp_fd, new_abspath = tempfile.mkstemp(prefix='bzr-diff-new-')
 
103
    oldtmpf = os.fdopen(oldtmp_fd, 'wb')
 
104
    newtmpf = os.fdopen(newtmp_fd, 'wb')
96
105
 
97
106
    try:
98
107
        # TODO: perhaps a special case for comparing to or from the empty
105
114
        oldtmpf.writelines(oldlines)
106
115
        newtmpf.writelines(newlines)
107
116
 
108
 
        oldtmpf.flush()
109
 
        newtmpf.flush()
 
117
        oldtmpf.close()
 
118
        newtmpf.close()
110
119
 
111
120
        if not diff_opts:
112
121
            diff_opts = []
113
122
        diffcmd = ['diff',
114
 
                   '--label', old_filename+'\t',
115
 
                   oldtmpf.name,
116
 
                   '--label', new_filename+'\t',
117
 
                   newtmpf.name]
 
123
                   '--label', old_filename,
 
124
                   old_abspath,
 
125
                   '--label', new_filename,
 
126
                   new_abspath,
 
127
                   '--binary',
 
128
                  ]
118
129
 
119
130
        # diff only allows one style to be specified; they don't override.
120
131
        # note that some of these take optargs, and the optargs can be
140
151
        if diff_opts:
141
152
            diffcmd.extend(diff_opts)
142
153
 
143
 
        rc = os.spawnvp(os.P_WAIT, 'diff', diffcmd)
 
154
        try:
 
155
            pipe = subprocess.Popen(diffcmd,
 
156
                                    stdin=subprocess.PIPE,
 
157
                                    stdout=out_file)
 
158
        except OSError, e:
 
159
            if e.errno == errno.ENOENT:
 
160
                raise errors.NoDiff(str(e))
 
161
            raise
 
162
        pipe.stdin.close()
 
163
 
 
164
        if not have_fileno:
 
165
            bzrlib.osutils.pumpfile(pipe.stdout, to_file)
 
166
        rc = pipe.wait()
144
167
        
145
168
        if rc != 0 and rc != 1:
146
169
            # returns 1 if files differ; that's OK
153
176
    finally:
154
177
        oldtmpf.close()                 # and delete
155
178
        newtmpf.close()
 
179
        # Clean up. Warn in case the files couldn't be deleted
 
180
        # (in case windows still holds the file open, but not
 
181
        # if the files have already been deleted)
 
182
        try:
 
183
            os.remove(old_abspath)
 
184
        except OSError, e:
 
185
            if e.errno not in (errno.ENOENT,):
 
186
                warning('Failed to delete temporary file: %s %s',
 
187
                        old_abspath, e)
 
188
        try:
 
189
            os.remove(new_abspath)
 
190
        except OSError:
 
191
            if e.errno not in (errno.ENOENT,):
 
192
                warning('Failed to delete temporary file: %s %s',
 
193
                        new_abspath, e)
156
194
 
157
195
 
158
196
@deprecated_function(zero_eight)
172
210
    supplies any two trees.
173
211
    """
174
212
    if output is None:
175
 
        import sys
176
213
        output = sys.stdout
177
214
 
178
215
    if from_spec is None:
219
256
    The more general form is show_diff_trees(), where the caller
220
257
    supplies any two trees.
221
258
    """
222
 
    import sys
223
259
    output = sys.stdout
224
260
    def spec_tree(spec):
225
261
        revision_id = spec.in_store(tree.branch).rev_id
267
303
                     specific_files, external_diff_options, 
268
304
                     old_label='a/', new_label='b/' ):
269
305
 
270
 
    DEVNULL = '/dev/null'
271
 
    # Windows users, don't panic about this filename -- it is a
272
 
    # special signal to GNU patch that the file should be created or
273
 
    # deleted respectively.
 
306
    # GNU Patch uses the epoch date to detect files that are being added
 
307
    # or removed in a diff.
 
308
    EPOCH_DATE = '1970-01-01 00:00:00 +0000'
274
309
 
275
310
    # TODO: Generation of pseudo-diffs for added/deleted files could
276
311
    # be usefully made into a much faster special case.
292
327
    for path, file_id, kind in delta.removed:
293
328
        has_changes = 1
294
329
        print >>to_file, '=== removed %s %r' % (kind, path.encode('utf8'))
295
 
        old_tree.inventory[file_id].diff(diff_file, old_label + path, old_tree,
296
 
                                         DEVNULL, None, None, to_file)
 
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, EPOCH_DATE)
 
333
        old_tree.inventory[file_id].diff(diff_file, old_name, old_tree,
 
334
                                         new_name, None, None, to_file)
297
335
    for path, file_id, kind in delta.added:
298
336
        has_changes = 1
299
337
        print >>to_file, '=== added %s %r' % (kind, path.encode('utf8'))
300
 
        new_tree.inventory[file_id].diff(diff_file, new_label + path, new_tree,
301
 
                                         DEVNULL, None, None, to_file, 
 
338
        old_name = '%s%s\t%s' % (old_label, path, EPOCH_DATE)
 
339
        new_name = '%s%s\t%s' % (new_label, path,
 
340
                                 _patch_header_date(new_tree, file_id, path))
 
341
        new_tree.inventory[file_id].diff(diff_file, new_name, new_tree,
 
342
                                         old_name, None, None, to_file, 
302
343
                                         reverse=True)
303
344
    for (old_path, new_path, file_id, kind,
304
345
         text_modified, meta_modified) in delta.renamed:
307
348
        print >>to_file, '=== renamed %s %r => %r%s' % (
308
349
                    kind, old_path.encode('utf8'),
309
350
                    new_path.encode('utf8'), prop_str)
310
 
        _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
311
 
                                    new_label, new_path, new_tree,
 
351
        old_name = '%s%s\t%s' % (old_label, old_path,
 
352
                                 _patch_header_date(old_tree, file_id,
 
353
                                                    old_path))
 
354
        new_name = '%s%s\t%s' % (new_label, new_path,
 
355
                                 _patch_header_date(new_tree, file_id,
 
356
                                                    new_path))
 
357
        _maybe_diff_file_or_symlink(old_name, old_tree, file_id,
 
358
                                    new_name, new_tree,
312
359
                                    text_modified, kind, to_file, diff_file)
313
360
    for path, file_id, kind, text_modified, meta_modified in delta.modified:
314
361
        has_changes = 1
315
362
        prop_str = get_prop_change(meta_modified)
316
363
        print >>to_file, '=== modified %s %r%s' % (kind, path.encode('utf8'), prop_str)
 
364
        old_name = '%s%s\t%s' % (old_label, path,
 
365
                                 _patch_header_date(old_tree, file_id, path))
 
366
        new_name = '%s%s\t%s' % (new_label, path,
 
367
                                 _patch_header_date(new_tree, file_id, path))
317
368
        if text_modified:
318
 
            _maybe_diff_file_or_symlink(old_label, path, old_tree, file_id,
319
 
                                        new_label, path, new_tree,
 
369
            _maybe_diff_file_or_symlink(old_name, old_tree, file_id,
 
370
                                        new_name, new_tree,
320
371
                                        True, kind, to_file, diff_file)
321
372
 
322
373
    return has_changes
323
374
 
324
375
 
 
376
def _patch_header_date(tree, file_id, path):
 
377
    """Returns a timestamp suitable for use in a patch header."""
 
378
    tm = time.gmtime(tree.get_file_mtime(file_id, path))
 
379
    return time.strftime('%Y-%m-%d %H:%M:%S +0000', tm)
 
380
 
 
381
 
325
382
def _raise_if_doubly_unversioned(specific_files, old_tree, new_tree):
326
383
    """Complain if paths are not versioned in either tree."""
327
384
    if not specific_files:
359
416
        return  ""
360
417
 
361
418
 
362
 
def _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
363
 
                                new_label, new_path, new_tree, text_modified,
 
419
def _maybe_diff_file_or_symlink(old_path, old_tree, file_id,
 
420
                                new_path, new_tree, text_modified,
364
421
                                kind, to_file, diff_file):
365
422
    if text_modified:
366
423
        new_entry = new_tree.inventory[file_id]
367
424
        old_tree.inventory[file_id].diff(diff_file,
368
 
                                         old_label + old_path, old_tree,
369
 
                                         new_label + new_path, new_entry, 
 
425
                                         old_path, old_tree,
 
426
                                         new_path, new_entry, 
370
427
                                         new_tree, to_file)