1
1
# Copyright (C) 2004, 2005, 2006 Canonical Ltd.
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.
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.
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
17
24
from bzrlib.delta import compare_trees
18
25
from bzrlib.errors import BzrError
19
26
import bzrlib.errors as errors
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,
23
32
from bzrlib.textfile import check_text_lines
24
from bzrlib.trace import mutter
33
from bzrlib.trace import mutter, warning
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)
79
88
def external_diff(old_filename, oldlines, new_filename, newlines, to_file,
81
90
"""Display a diff by calling out to the external diff program."""
91
if hasattr(to_file, 'fileno'):
95
out_file = subprocess.PIPE
84
if to_file != sys.stdout:
85
raise NotImplementedError("sorry, can't send external diff other than to stdout yet",
88
98
# make sure our own output is properly ordered before the diff
91
from tempfile import NamedTemporaryFile
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')
98
107
# TODO: perhaps a special case for comparing to or from the empty
105
114
oldtmpf.writelines(oldlines)
106
115
newtmpf.writelines(newlines)
111
120
if not diff_opts:
113
122
diffcmd = ['diff',
114
'--label', old_filename+'\t',
116
'--label', new_filename+'\t',
123
'--label', old_filename,
125
'--label', new_filename,
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
141
152
diffcmd.extend(diff_opts)
143
rc = os.spawnvp(os.P_WAIT, 'diff', diffcmd)
155
pipe = subprocess.Popen(diffcmd,
156
stdin=subprocess.PIPE,
159
if e.errno == errno.ENOENT:
160
raise errors.NoDiff(str(e))
165
bzrlib.osutils.pumpfile(pipe.stdout, to_file)
145
168
if rc != 0 and rc != 1:
146
169
# returns 1 if files differ; that's OK
154
177
oldtmpf.close() # and delete
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)
183
os.remove(old_abspath)
185
if e.errno not in (errno.ENOENT,):
186
warning('Failed to delete temporary file: %s %s',
189
os.remove(new_abspath)
191
if e.errno not in (errno.ENOENT,):
192
warning('Failed to delete temporary file: %s %s',
158
196
@deprecated_function(zero_eight)
267
303
specific_files, external_diff_options,
268
304
old_label='a/', new_label='b/' ):
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'
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:
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:
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,
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,
354
new_name = '%s%s\t%s' % (new_label, new_path,
355
_patch_header_date(new_tree, file_id,
357
_maybe_diff_file_or_symlink(old_name, old_tree, file_id,
312
359
text_modified, kind, to_file, diff_file)
313
360
for path, file_id, kind, text_modified, meta_modified in delta.modified:
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,
320
371
True, kind, to_file, diff_file)
322
373
return has_changes
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)
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:
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,
370
427
new_tree, to_file)