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
from bzrlib.symbol_versioning import *
21
from bzrlib.trace import mutter
28
from bzrlib.patiencediff import unified_diff
29
import bzrlib.patiencediff
30
from bzrlib.symbol_versioning import (deprecated_function,
32
from bzrlib.textfile import check_text_lines
33
from bzrlib.trace import mutter, warning
23
36
# TODO: Rather than building a changeset object, we should probably
24
37
# invoke callbacks on an object. That object can either accumulate a
25
38
# list, write them out directly, etc etc.
27
def internal_diff(old_filename, oldlines, new_filename, newlines, to_file):
40
def internal_diff(old_filename, oldlines, new_filename, newlines, to_file,
41
allow_binary=False, sequence_matcher=None,
42
path_encoding='utf8'):
30
43
# FIXME: difflib is wrong if there is no trailing newline.
31
44
# The syntax used by patch seems to be "\ No newline at
32
45
# end of file" following the last diff line from that
42
55
# both sequences are empty.
43
56
if not oldlines and not newlines:
59
if allow_binary is False:
60
check_text_lines(oldlines)
61
check_text_lines(newlines)
46
ud = difflib.unified_diff(oldlines, newlines,
47
fromfile=old_filename+'\t',
48
tofile=new_filename+'\t')
63
if sequence_matcher is None:
64
sequence_matcher = bzrlib.patiencediff.PatienceSequenceMatcher
65
ud = unified_diff(oldlines, newlines,
66
fromfile=old_filename.encode(path_encoding),
67
tofile=new_filename.encode(path_encoding),
68
sequencematcher=sequence_matcher)
51
71
# work-around for difflib being too smart for its own good
68
88
def external_diff(old_filename, oldlines, new_filename, newlines, to_file,
70
90
"""Display a diff by calling out to the external diff program."""
91
if hasattr(to_file, 'fileno'):
95
out_file = subprocess.PIPE
73
if to_file != sys.stdout:
74
raise NotImplementedError("sorry, can't send external diff other than to stdout yet",
77
98
# make sure our own output is properly ordered before the diff
80
from tempfile import NamedTemporaryFile
83
oldtmpf = NamedTemporaryFile()
84
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')
87
107
# TODO: perhaps a special case for comparing to or from the empty
281
327
for path, file_id, kind in delta.removed:
283
print >>to_file, '=== removed %s %r' % (kind, old_label + path)
284
old_tree.inventory[file_id].diff(diff_file, old_label + path, old_tree,
285
DEVNULL, None, None, to_file)
329
print >>to_file, '=== removed %s %r' % (kind, path.encode('utf8'))
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)
286
335
for path, file_id, kind in delta.added:
288
print >>to_file, '=== added %s %r' % (kind, new_label + path)
289
new_tree.inventory[file_id].diff(diff_file, new_label + path, new_tree,
290
DEVNULL, None, None, to_file,
337
print >>to_file, '=== added %s %r' % (kind, path.encode('utf8'))
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,
292
344
for (old_path, new_path, file_id, kind,
293
345
text_modified, meta_modified) in delta.renamed:
295
347
prop_str = get_prop_change(meta_modified)
296
348
print >>to_file, '=== renamed %s %r => %r%s' % (
297
kind, old_label + old_path, new_label + new_path, prop_str)
298
_maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
299
new_label, new_path, new_tree,
349
kind, old_path.encode('utf8'),
350
new_path.encode('utf8'), prop_str)
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,
300
359
text_modified, kind, to_file, diff_file)
301
360
for path, file_id, kind, text_modified, meta_modified in delta.modified:
303
362
prop_str = get_prop_change(meta_modified)
304
print >>to_file, '=== modified %s %r%s' % (kind, old_label + path,
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))
306
368
if text_modified:
307
_maybe_diff_file_or_symlink(old_label, path, old_tree, file_id,
308
new_label, path, new_tree,
369
_maybe_diff_file_or_symlink(old_name, old_tree, file_id,
309
371
True, kind, to_file, diff_file)
311
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)
314
382
def _raise_if_doubly_unversioned(specific_files, old_tree, new_tree):
315
383
"""Complain if paths are not versioned in either tree."""
316
384
if not specific_files:
332
def _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
333
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,
334
421
kind, to_file, diff_file):
335
422
if text_modified:
336
423
new_entry = new_tree.inventory[file_id]
337
424
old_tree.inventory[file_id].diff(diff_file,
338
old_label + old_path, old_tree,
339
new_label + new_path, new_entry,
340
427
new_tree, to_file)