/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

  • Committer: Martin Pool
  • Date: 2006-06-20 03:30:14 UTC
  • mfrom: (1793 +trunk)
  • mto: This revision was merged to the branch mainline in revision 1797.
  • Revision ID: mbp@sourcefrog.net-20060620033014-e19ce470e2ce6561
[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
30
from bzrlib.symbol_versioning import *
23
31
from bzrlib.textfile import check_text_lines
24
 
from bzrlib.trace import mutter
 
32
from bzrlib.trace import mutter, warning
25
33
 
26
34
 
27
35
# TODO: Rather than building a changeset object, we should probably
54
62
    if sequence_matcher is None:
55
63
        sequence_matcher = bzrlib.patiencediff.PatienceSequenceMatcher
56
64
    ud = unified_diff(oldlines, newlines,
57
 
                      fromfile=old_filename.encode(path_encoding)+'\t', 
58
 
                      tofile=new_filename.encode(path_encoding)+'\t',
 
65
                      fromfile=old_filename.encode(path_encoding),
 
66
                      tofile=new_filename.encode(path_encoding),
59
67
                      sequencematcher=sequence_matcher)
60
68
 
61
69
    ud = list(ud)
79
87
def external_diff(old_filename, oldlines, new_filename, newlines, to_file,
80
88
                  diff_opts):
81
89
    """Display a diff by calling out to the external diff program."""
82
 
    import sys
 
90
    if hasattr(to_file, 'fileno'):
 
91
        out_file = to_file
 
92
        have_fileno = True
 
93
    else:
 
94
        out_file = subprocess.PIPE
 
95
        have_fileno = False
83
96
    
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
97
    # make sure our own output is properly ordered before the diff
89
98
    to_file.flush()
90
99
 
91
 
    from tempfile import NamedTemporaryFile
92
 
    import os
93
 
 
94
 
    oldtmpf = NamedTemporaryFile()
95
 
    newtmpf = NamedTemporaryFile()
 
100
    oldtmp_fd, old_abspath = tempfile.mkstemp(prefix='bzr-diff-old-')
 
101
    newtmp_fd, new_abspath = tempfile.mkstemp(prefix='bzr-diff-new-')
 
102
    oldtmpf = os.fdopen(oldtmp_fd, 'wb')
 
103
    newtmpf = os.fdopen(newtmp_fd, 'wb')
96
104
 
97
105
    try:
98
106
        # TODO: perhaps a special case for comparing to or from the empty
105
113
        oldtmpf.writelines(oldlines)
106
114
        newtmpf.writelines(newlines)
107
115
 
108
 
        oldtmpf.flush()
109
 
        newtmpf.flush()
 
116
        oldtmpf.close()
 
117
        newtmpf.close()
110
118
 
111
119
        if not diff_opts:
112
120
            diff_opts = []
113
121
        diffcmd = ['diff',
114
 
                   '--label', old_filename+'\t',
115
 
                   oldtmpf.name,
116
 
                   '--label', new_filename+'\t',
117
 
                   newtmpf.name]
 
122
                   '--label', old_filename,
 
123
                   old_abspath,
 
124
                   '--label', new_filename,
 
125
                   new_abspath,
 
126
                   '--binary',
 
127
                  ]
118
128
 
119
129
        # diff only allows one style to be specified; they don't override.
120
130
        # note that some of these take optargs, and the optargs can be
140
150
        if diff_opts:
141
151
            diffcmd.extend(diff_opts)
142
152
 
143
 
        rc = os.spawnvp(os.P_WAIT, 'diff', diffcmd)
 
153
        try:
 
154
            pipe = subprocess.Popen(diffcmd,
 
155
                                    stdin=subprocess.PIPE,
 
156
                                    stdout=out_file)
 
157
        except OSError, e:
 
158
            if e.errno == errno.ENOENT:
 
159
                raise errors.NoDiff(str(e))
 
160
            raise
 
161
        pipe.stdin.close()
 
162
 
 
163
        if not have_fileno:
 
164
            bzrlib.osutils.pumpfile(pipe.stdout, to_file)
 
165
        rc = pipe.wait()
144
166
        
145
167
        if rc != 0 and rc != 1:
146
168
            # returns 1 if files differ; that's OK
153
175
    finally:
154
176
        oldtmpf.close()                 # and delete
155
177
        newtmpf.close()
 
178
        # Clean up. Warn in case the files couldn't be deleted
 
179
        # (in case windows still holds the file open, but not
 
180
        # if the files have already been deleted)
 
181
        try:
 
182
            os.remove(old_abspath)
 
183
        except OSError, e:
 
184
            if e.errno not in (errno.ENOENT,):
 
185
                warning('Failed to delete temporary file: %s %s',
 
186
                        old_abspath, e)
 
187
        try:
 
188
            os.remove(new_abspath)
 
189
        except OSError:
 
190
            if e.errno not in (errno.ENOENT,):
 
191
                warning('Failed to delete temporary file: %s %s',
 
192
                        new_abspath, e)
156
193
 
157
194
 
158
195
@deprecated_function(zero_eight)
172
209
    supplies any two trees.
173
210
    """
174
211
    if output is None:
175
 
        import sys
176
212
        output = sys.stdout
177
213
 
178
214
    if from_spec is None:
219
255
    The more general form is show_diff_trees(), where the caller
220
256
    supplies any two trees.
221
257
    """
222
 
    import sys
223
258
    output = sys.stdout
224
259
    def spec_tree(spec):
225
260
        revision_id = spec.in_store(tree.branch).rev_id
267
302
                     specific_files, external_diff_options, 
268
303
                     old_label='a/', new_label='b/' ):
269
304
 
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.
 
305
    # GNU Patch uses the epoch date to detect files that are being added
 
306
    # or removed in a diff.
 
307
    EPOCH_DATE = '1970-01-01 00:00:00 +0000'
274
308
 
275
309
    # TODO: Generation of pseudo-diffs for added/deleted files could
276
310
    # be usefully made into a much faster special case.
292
326
    for path, file_id, kind in delta.removed:
293
327
        has_changes = 1
294
328
        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)
 
329
        old_name = '%s%s\t%s' % (old_label, path,
 
330
                                 _patch_header_date(old_tree, file_id, path))
 
331
        new_name = '%s%s\t%s' % (new_label, path, EPOCH_DATE)
 
332
        old_tree.inventory[file_id].diff(diff_file, old_name, old_tree,
 
333
                                         new_name, None, None, to_file)
297
334
    for path, file_id, kind in delta.added:
298
335
        has_changes = 1
299
336
        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, 
 
337
        old_name = '%s%s\t%s' % (old_label, path, EPOCH_DATE)
 
338
        new_name = '%s%s\t%s' % (new_label, path,
 
339
                                 _patch_header_date(new_tree, file_id, path))
 
340
        new_tree.inventory[file_id].diff(diff_file, new_name, new_tree,
 
341
                                         old_name, None, None, to_file, 
302
342
                                         reverse=True)
303
343
    for (old_path, new_path, file_id, kind,
304
344
         text_modified, meta_modified) in delta.renamed:
307
347
        print >>to_file, '=== renamed %s %r => %r%s' % (
308
348
                    kind, old_path.encode('utf8'),
309
349
                    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,
 
350
        old_name = '%s%s\t%s' % (old_label, old_path,
 
351
                                 _patch_header_date(old_tree, file_id,
 
352
                                                    old_path))
 
353
        new_name = '%s%s\t%s' % (new_label, new_path,
 
354
                                 _patch_header_date(new_tree, file_id,
 
355
                                                    new_path))
 
356
        _maybe_diff_file_or_symlink(old_name, old_tree, file_id,
 
357
                                    new_name, new_tree,
312
358
                                    text_modified, kind, to_file, diff_file)
313
359
    for path, file_id, kind, text_modified, meta_modified in delta.modified:
314
360
        has_changes = 1
315
361
        prop_str = get_prop_change(meta_modified)
316
362
        print >>to_file, '=== modified %s %r%s' % (kind, path.encode('utf8'), prop_str)
 
363
        old_name = '%s%s\t%s' % (old_label, path,
 
364
                                 _patch_header_date(old_tree, file_id, path))
 
365
        new_name = '%s%s\t%s' % (new_label, path,
 
366
                                 _patch_header_date(new_tree, file_id, path))
317
367
        if text_modified:
318
 
            _maybe_diff_file_or_symlink(old_label, path, old_tree, file_id,
319
 
                                        new_label, path, new_tree,
 
368
            _maybe_diff_file_or_symlink(old_name, old_tree, file_id,
 
369
                                        new_name, new_tree,
320
370
                                        True, kind, to_file, diff_file)
321
371
 
322
372
    return has_changes
323
373
 
324
374
 
 
375
def _patch_header_date(tree, file_id, path):
 
376
    """Returns a timestamp suitable for use in a patch header."""
 
377
    tm = time.gmtime(tree.get_file_mtime(file_id, path))
 
378
    return time.strftime('%Y-%m-%d %H:%M:%S +0000', tm)
 
379
 
 
380
 
325
381
def _raise_if_doubly_unversioned(specific_files, old_tree, new_tree):
326
382
    """Complain if paths are not versioned in either tree."""
327
383
    if not specific_files:
359
415
        return  ""
360
416
 
361
417
 
362
 
def _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
363
 
                                new_label, new_path, new_tree, text_modified,
 
418
def _maybe_diff_file_or_symlink(old_path, old_tree, file_id,
 
419
                                new_path, new_tree, text_modified,
364
420
                                kind, to_file, diff_file):
365
421
    if text_modified:
366
422
        new_entry = new_tree.inventory[file_id]
367
423
        old_tree.inventory[file_id].diff(diff_file,
368
 
                                         old_label + old_path, old_tree,
369
 
                                         new_label + new_path, new_entry, 
 
424
                                         old_path, old_tree,
 
425
                                         new_path, new_entry, 
370
426
                                         new_tree, to_file)