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
24
# compatability - plugins import compare_trees from diff!!!
25
# deprecated as of 0.10
26
17
from bzrlib.delta import compare_trees
27
18
from bzrlib.errors import BzrError
28
19
import bzrlib.errors as errors
30
from bzrlib.patiencediff import unified_diff
31
import bzrlib.patiencediff
32
from bzrlib.symbol_versioning import (deprecated_function,
20
from bzrlib.symbol_versioning import *
34
21
from bzrlib.textfile import check_text_lines
35
from bzrlib.trace import mutter, warning
22
from bzrlib.trace import mutter
38
24
# TODO: Rather than building a changeset object, we should probably
39
25
# invoke callbacks on an object. That object can either accumulate a
40
26
# list, write them out directly, etc etc.
42
28
def internal_diff(old_filename, oldlines, new_filename, newlines, to_file,
43
allow_binary=False, sequence_matcher=None,
44
path_encoding='utf8'):
45
32
# FIXME: difflib is wrong if there is no trailing newline.
46
33
# The syntax used by patch seems to be "\ No newline at
47
34
# end of file" following the last diff line from that
318
257
def _show_diff_trees(old_tree, new_tree, to_file,
319
specific_files, external_diff_options,
320
old_label='a/', new_label='b/', extra_trees=None):
322
# GNU Patch uses the epoch date to detect files that are being added
323
# or removed in a diff.
324
EPOCH_DATE = '1970-01-01 00:00:00 +0000'
258
specific_files, external_diff_options):
260
# TODO: Options to control putting on a prefix or suffix, perhaps
261
# as a format string?
265
DEVNULL = '/dev/null'
266
# Windows users, don't panic about this filename -- it is a
267
# special signal to GNU patch that the file should be created or
268
# deleted respectively.
326
270
# TODO: Generation of pseudo-diffs for added/deleted files could
327
271
# be usefully made into a much faster special case.
273
_raise_if_doubly_unversioned(specific_files, old_tree, new_tree)
329
275
if external_diff_options:
330
276
assert isinstance(external_diff_options, basestring)
331
277
opts = external_diff_options.split()
335
281
diff_file = internal_diff
337
delta = new_tree.changes_from(old_tree,
338
specific_files=specific_files,
339
extra_trees=extra_trees, require_versioned=True)
283
delta = compare_trees(old_tree, new_tree, want_unchanged=False,
284
specific_files=specific_files)
342
287
for path, file_id, kind in delta.removed:
344
print >>to_file, '=== removed %s %r' % (kind, path.encode('utf8'))
345
old_name = '%s%s\t%s' % (old_label, path,
346
_patch_header_date(old_tree, file_id, path))
347
new_name = '%s%s\t%s' % (new_label, path, EPOCH_DATE)
348
old_tree.inventory[file_id].diff(diff_file, old_name, old_tree,
349
new_name, None, None, to_file)
289
print >>to_file, '=== removed %s %r' % (kind, old_label + path)
290
old_tree.inventory[file_id].diff(diff_file, old_label + path, old_tree,
291
DEVNULL, None, None, to_file)
350
292
for path, file_id, kind in delta.added:
352
print >>to_file, '=== added %s %r' % (kind, path.encode('utf8'))
353
old_name = '%s%s\t%s' % (old_label, path, EPOCH_DATE)
354
new_name = '%s%s\t%s' % (new_label, path,
355
_patch_header_date(new_tree, file_id, path))
356
new_tree.inventory[file_id].diff(diff_file, new_name, new_tree,
357
old_name, None, None, to_file,
294
print >>to_file, '=== added %s %r' % (kind, new_label + path)
295
new_tree.inventory[file_id].diff(diff_file, new_label + path, new_tree,
296
DEVNULL, None, None, to_file,
359
298
for (old_path, new_path, file_id, kind,
360
299
text_modified, meta_modified) in delta.renamed:
362
301
prop_str = get_prop_change(meta_modified)
363
302
print >>to_file, '=== renamed %s %r => %r%s' % (
364
kind, old_path.encode('utf8'),
365
new_path.encode('utf8'), prop_str)
366
old_name = '%s%s\t%s' % (old_label, old_path,
367
_patch_header_date(old_tree, file_id,
369
new_name = '%s%s\t%s' % (new_label, new_path,
370
_patch_header_date(new_tree, file_id,
372
_maybe_diff_file_or_symlink(old_name, old_tree, file_id,
303
kind, old_label + old_path, new_label + new_path, prop_str)
304
_maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
305
new_label, new_path, new_tree,
374
306
text_modified, kind, to_file, diff_file)
375
307
for path, file_id, kind, text_modified, meta_modified in delta.modified:
377
309
prop_str = get_prop_change(meta_modified)
378
print >>to_file, '=== modified %s %r%s' % (kind, path.encode('utf8'), prop_str)
379
old_name = '%s%s\t%s' % (old_label, path,
380
_patch_header_date(old_tree, file_id, path))
381
new_name = '%s%s\t%s' % (new_label, path,
382
_patch_header_date(new_tree, file_id, path))
310
print >>to_file, '=== modified %s %r%s' % (kind, old_label + path,
383
312
if text_modified:
384
_maybe_diff_file_or_symlink(old_name, old_tree, file_id,
313
_maybe_diff_file_or_symlink(old_label, path, old_tree, file_id,
314
new_label, path, new_tree,
386
315
True, kind, to_file, diff_file)
388
317
return has_changes
391
def _patch_header_date(tree, file_id, path):
392
"""Returns a timestamp suitable for use in a patch header."""
393
tm = time.gmtime(tree.get_file_mtime(file_id, path))
394
return time.strftime('%Y-%m-%d %H:%M:%S +0000', tm)
320
def _raise_if_doubly_unversioned(specific_files, old_tree, new_tree):
321
"""Complain if paths are not versioned in either tree."""
322
if not specific_files:
324
old_unversioned = old_tree.filter_unversioned_files(specific_files)
325
new_unversioned = new_tree.filter_unversioned_files(specific_files)
326
unversioned = old_unversioned.intersection(new_unversioned)
328
raise errors.PathsNotVersionedError(sorted(unversioned))
397
331
def _raise_if_nonexistent(paths, old_tree, new_tree):
398
332
"""Complain if paths are not in either inventory or tree.