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
25
# compatability - plugins import compare_trees from diff!!!
26
# deprecated as of 0.10
17
27
from bzrlib.delta import compare_trees
18
28
from bzrlib.errors import BzrError
19
29
import bzrlib.errors as errors
20
from bzrlib.symbol_versioning import *
21
from bzrlib.trace import mutter
31
from bzrlib.patiencediff import unified_diff
32
import bzrlib.patiencediff
33
from bzrlib.symbol_versioning import (deprecated_function,
35
from bzrlib.textfile import check_text_lines
36
from bzrlib.trace import mutter, warning
23
39
# TODO: Rather than building a changeset object, we should probably
24
40
# invoke callbacks on an object. That object can either accumulate a
25
41
# list, write them out directly, etc etc.
27
def internal_diff(old_filename, oldlines, new_filename, newlines, to_file):
43
def internal_diff(old_filename, oldlines, new_filename, newlines, to_file,
44
allow_binary=False, sequence_matcher=None,
45
path_encoding='utf8'):
30
46
# FIXME: difflib is wrong if there is no trailing newline.
31
47
# The syntax used by patch seems to be "\ No newline at
32
48
# end of file" following the last diff line from that
130
148
diffcmd.extend(diff_opts)
132
rc = os.spawnvp(os.P_WAIT, 'diff', diffcmd)
151
pipe = subprocess.Popen(diffcmd,
152
stdin=subprocess.PIPE,
153
stdout=subprocess.PIPE)
155
if e.errno == errno.ENOENT:
156
raise errors.NoDiff(str(e))
160
first_line = pipe.stdout.readline()
161
to_file.write(first_line)
162
bzrlib.osutils.pumpfile(pipe.stdout, to_file)
134
if rc != 0 and rc != 1:
166
# 'diff' gives retcode == 2 for all sorts of errors
167
# one of those is 'Binary files differ'.
168
# Bad options could also be the problem.
169
# 'Binary files' is not a real error, so we suppress that error
170
m = re.match('^binary files.*differ$', first_line, re.I)
172
raise BzrError('external diff failed with exit code 2;'
173
' command: %r' % (diffcmd,))
174
elif rc not in (0, 1):
135
175
# returns 1 if files differ; that's OK
137
177
msg = 'signal %d' % (-rc)
139
179
msg = 'exit code %d' % rc
141
raise BzrError('external diff failed with %s; command: %r' % (rc, diffcmd))
181
raise BzrError('external diff failed with %s; command: %r'
184
# internal_diff() adds a trailing newline, add one here for consistency
143
188
oldtmpf.close() # and delete
190
# Clean up. Warn in case the files couldn't be deleted
191
# (in case windows still holds the file open, but not
192
# if the files have already been deleted)
194
os.remove(old_abspath)
196
if e.errno not in (errno.ENOENT,):
197
warning('Failed to delete temporary file: %s %s',
200
os.remove(new_abspath)
202
if e.errno not in (errno.ENOENT,):
203
warning('Failed to delete temporary file: %s %s',
147
207
@deprecated_function(zero_eight)
275
344
diff_file = internal_diff
277
delta = compare_trees(old_tree, new_tree, want_unchanged=False,
278
specific_files=specific_files)
346
delta = new_tree.changes_from(old_tree,
347
specific_files=specific_files,
348
extra_trees=extra_trees, require_versioned=True)
281
351
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)
353
print >>to_file, '=== removed %s %r' % (kind, path.encode('utf8'))
354
old_name = '%s%s\t%s' % (old_label, path,
355
_patch_header_date(old_tree, file_id, path))
356
new_name = '%s%s\t%s' % (new_label, path, EPOCH_DATE)
357
old_tree.inventory[file_id].diff(diff_file, old_name, old_tree,
358
new_name, None, None, to_file)
286
359
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,
361
print >>to_file, '=== added %s %r' % (kind, path.encode('utf8'))
362
old_name = '%s%s\t%s' % (old_label, path, EPOCH_DATE)
363
new_name = '%s%s\t%s' % (new_label, path,
364
_patch_header_date(new_tree, file_id, path))
365
new_tree.inventory[file_id].diff(diff_file, new_name, new_tree,
366
old_name, None, None, to_file,
292
368
for (old_path, new_path, file_id, kind,
293
369
text_modified, meta_modified) in delta.renamed:
295
371
prop_str = get_prop_change(meta_modified)
296
372
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,
373
kind, old_path.encode('utf8'),
374
new_path.encode('utf8'), prop_str)
375
old_name = '%s%s\t%s' % (old_label, old_path,
376
_patch_header_date(old_tree, file_id,
378
new_name = '%s%s\t%s' % (new_label, new_path,
379
_patch_header_date(new_tree, file_id,
381
_maybe_diff_file_or_symlink(old_name, old_tree, file_id,
300
383
text_modified, kind, to_file, diff_file)
301
384
for path, file_id, kind, text_modified, meta_modified in delta.modified:
303
386
prop_str = get_prop_change(meta_modified)
304
print >>to_file, '=== modified %s %r%s' % (kind, old_label + path,
387
print >>to_file, '=== modified %s %r%s' % (kind, path.encode('utf8'), prop_str)
388
old_name = '%s%s\t%s' % (old_label, path,
389
_patch_header_date(old_tree, file_id, path))
390
new_name = '%s%s\t%s' % (new_label, path,
391
_patch_header_date(new_tree, file_id, path))
306
392
if text_modified:
307
_maybe_diff_file_or_symlink(old_label, path, old_tree, file_id,
308
new_label, path, new_tree,
393
_maybe_diff_file_or_symlink(old_name, old_tree, file_id,
309
395
True, kind, to_file, diff_file)
311
397
return has_changes
314
def _raise_if_doubly_unversioned(specific_files, old_tree, new_tree):
315
"""Complain if paths are not versioned in either tree."""
316
if not specific_files:
400
def _patch_header_date(tree, file_id, path):
401
"""Returns a timestamp suitable for use in a patch header."""
402
tm = time.gmtime(tree.get_file_mtime(file_id, path))
403
return time.strftime('%Y-%m-%d %H:%M:%S +0000', tm)
406
def _raise_if_nonexistent(paths, old_tree, new_tree):
407
"""Complain if paths are not in either inventory or tree.
409
It's OK with the files exist in either tree's inventory, or
410
if they exist in the tree but are not versioned.
412
This can be used by operations such as bzr status that can accept
413
unknown or ignored files.
415
mutter("check paths: %r", paths)
318
old_unversioned = old_tree.filter_unversioned_files(specific_files)
319
new_unversioned = new_tree.filter_unversioned_files(specific_files)
320
unversioned = old_unversioned.intersection(new_unversioned)
322
raise errors.PathsNotVersionedError(sorted(unversioned))
418
s = old_tree.filter_unversioned_files(paths)
419
s = new_tree.filter_unversioned_files(s)
420
s = [path for path in s if not new_tree.has_filename(path)]
422
raise errors.PathsDoNotExist(sorted(s))
325
425
def get_prop_change(meta_modified):
326
426
if meta_modified: