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.patiencediff import SequenceMatcher, unified_diff
21
from bzrlib.symbol_versioning import *
28
from bzrlib.patiencediff import unified_diff
29
import bzrlib.patiencediff
30
from bzrlib.symbol_versioning import (deprecated_function,
22
32
from bzrlib.textfile import check_text_lines
23
from bzrlib.trace import mutter
33
from bzrlib.trace import mutter, warning
25
36
# TODO: Rather than building a changeset object, we should probably
26
37
# invoke callbacks on an object. That object can either accumulate a
27
38
# list, write them out directly, etc etc.
29
40
def internal_diff(old_filename, oldlines, new_filename, newlines, to_file,
30
allow_binary=False, sequence_matcher=None):
41
allow_binary=False, sequence_matcher=None,
42
path_encoding='utf8'):
31
43
# FIXME: difflib is wrong if there is no trailing newline.
32
44
# The syntax used by patch seems to be "\ No newline at
33
45
# end of file" following the last diff line from that
76
88
def external_diff(old_filename, oldlines, new_filename, newlines, to_file,
78
90
"""Display a diff by calling out to the external diff program."""
91
if hasattr(to_file, 'fileno'):
95
out_file = subprocess.PIPE
81
if to_file != sys.stdout:
82
raise NotImplementedError("sorry, can't send external diff other than to stdout yet",
85
98
# make sure our own output is properly ordered before the diff
88
from tempfile import NamedTemporaryFile
91
oldtmpf = NamedTemporaryFile()
92
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')
95
107
# TODO: perhaps a special case for comparing to or from the empty
288
334
diff_file = internal_diff
290
336
delta = compare_trees(old_tree, new_tree, want_unchanged=False,
291
specific_files=specific_files)
337
specific_files=specific_files,
338
extra_trees=extra_trees, require_versioned=True)
294
341
for path, file_id, kind in delta.removed:
296
print >>to_file, '=== removed %s %r' % (kind, path)
297
old_tree.inventory[file_id].diff(diff_file, old_label + path, old_tree,
298
DEVNULL, None, None, to_file)
343
print >>to_file, '=== removed %s %r' % (kind, path.encode('utf8'))
344
old_name = '%s%s\t%s' % (old_label, path,
345
_patch_header_date(old_tree, file_id, path))
346
new_name = '%s%s\t%s' % (new_label, path, EPOCH_DATE)
347
old_tree.inventory[file_id].diff(diff_file, old_name, old_tree,
348
new_name, None, None, to_file)
299
349
for path, file_id, kind in delta.added:
301
print >>to_file, '=== added %s %r' % (kind, path)
302
new_tree.inventory[file_id].diff(diff_file, new_label + path, new_tree,
303
DEVNULL, None, None, to_file,
351
print >>to_file, '=== added %s %r' % (kind, path.encode('utf8'))
352
old_name = '%s%s\t%s' % (old_label, path, EPOCH_DATE)
353
new_name = '%s%s\t%s' % (new_label, path,
354
_patch_header_date(new_tree, file_id, path))
355
new_tree.inventory[file_id].diff(diff_file, new_name, new_tree,
356
old_name, None, None, to_file,
305
358
for (old_path, new_path, file_id, kind,
306
359
text_modified, meta_modified) in delta.renamed:
308
361
prop_str = get_prop_change(meta_modified)
309
362
print >>to_file, '=== renamed %s %r => %r%s' % (
310
kind, old_path, new_path, prop_str)
311
_maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
312
new_label, new_path, new_tree,
363
kind, old_path.encode('utf8'),
364
new_path.encode('utf8'), prop_str)
365
old_name = '%s%s\t%s' % (old_label, old_path,
366
_patch_header_date(old_tree, file_id,
368
new_name = '%s%s\t%s' % (new_label, new_path,
369
_patch_header_date(new_tree, file_id,
371
_maybe_diff_file_or_symlink(old_name, old_tree, file_id,
313
373
text_modified, kind, to_file, diff_file)
314
374
for path, file_id, kind, text_modified, meta_modified in delta.modified:
316
376
prop_str = get_prop_change(meta_modified)
317
print >>to_file, '=== modified %s %r%s' % (kind, path, prop_str)
377
print >>to_file, '=== modified %s %r%s' % (kind, path.encode('utf8'), prop_str)
378
old_name = '%s%s\t%s' % (old_label, path,
379
_patch_header_date(old_tree, file_id, path))
380
new_name = '%s%s\t%s' % (new_label, path,
381
_patch_header_date(new_tree, file_id, path))
318
382
if text_modified:
319
_maybe_diff_file_or_symlink(old_label, path, old_tree, file_id,
320
new_label, path, new_tree,
383
_maybe_diff_file_or_symlink(old_name, old_tree, file_id,
321
385
True, kind, to_file, diff_file)
323
387
return has_changes
326
def _raise_if_doubly_unversioned(specific_files, old_tree, new_tree):
327
"""Complain if paths are not versioned in either tree."""
328
if not specific_files:
330
old_unversioned = old_tree.filter_unversioned_files(specific_files)
331
new_unversioned = new_tree.filter_unversioned_files(specific_files)
332
unversioned = old_unversioned.intersection(new_unversioned)
334
raise errors.PathsNotVersionedError(sorted(unversioned))
390
def _patch_header_date(tree, file_id, path):
391
"""Returns a timestamp suitable for use in a patch header."""
392
tm = time.gmtime(tree.get_file_mtime(file_id, path))
393
return time.strftime('%Y-%m-%d %H:%M:%S +0000', tm)
337
396
def _raise_if_nonexistent(paths, old_tree, new_tree):
338
397
"""Complain if paths are not in either inventory or tree.