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
19
24
from bzrlib.delta import compare_trees
20
25
from bzrlib.errors import BzrError
21
26
import bzrlib.errors as errors
22
28
from bzrlib.patiencediff import unified_diff
23
29
import bzrlib.patiencediff
24
from bzrlib.symbol_versioning import *
30
from bzrlib.symbol_versioning import (deprecated_function,
25
32
from bzrlib.textfile import check_text_lines
26
from bzrlib.trace import mutter
33
from bzrlib.trace import mutter, warning
29
36
# TODO: Rather than building a changeset object, we should probably
81
88
def external_diff(old_filename, oldlines, new_filename, newlines, to_file,
83
90
"""Display a diff by calling out to the external diff program."""
91
if hasattr(to_file, 'fileno'):
95
out_file = subprocess.PIPE
86
if to_file != sys.stdout:
87
raise NotImplementedError("sorry, can't send external diff other than to stdout yet",
90
98
# make sure our own output is properly ordered before the diff
93
from tempfile import NamedTemporaryFile
96
oldtmpf = NamedTemporaryFile()
97
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')
100
107
# TODO: perhaps a special case for comparing to or from the empty
107
114
oldtmpf.writelines(oldlines)
108
115
newtmpf.writelines(newlines)
113
120
if not diff_opts:
115
122
diffcmd = ['diff',
116
123
'--label', old_filename,
118
125
'--label', new_filename,
121
130
# diff only allows one style to be specified; they don't override.
122
131
# note that some of these take optargs, and the optargs can be
143
152
diffcmd.extend(diff_opts)
145
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)
147
168
if rc != 0 and rc != 1:
148
169
# returns 1 if files differ; that's OK
156
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',
160
196
@deprecated_function(zero_eight)
237
271
new_tree = spec_tree(new_revision_spec)
272
if new_tree is not tree:
273
extra_trees = (tree,)
239
277
return show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
240
278
external_diff_options,
241
old_label=old_label, new_label=new_label)
279
old_label=old_label, new_label=new_label,
280
extra_trees=extra_trees)
244
283
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
245
284
external_diff_options=None,
246
old_label='a/', new_label='b/'):
285
old_label='a/', new_label='b/',
247
287
"""Show in text form the changes from one tree to another.
259
302
return _show_diff_trees(old_tree, new_tree, to_file,
260
303
specific_files, external_diff_options,
261
old_label=old_label, new_label=new_label)
304
old_label=old_label, new_label=new_label,
305
extra_trees=extra_trees)
263
307
new_tree.unlock()
268
312
def _show_diff_trees(old_tree, new_tree, to_file,
269
313
specific_files, external_diff_options,
270
old_label='a/', new_label='b/' ):
314
old_label='a/', new_label='b/', extra_trees=None):
272
316
# GNU Patch uses the epoch date to detect files that are being added
273
317
# or removed in a diff.
276
320
# TODO: Generation of pseudo-diffs for added/deleted files could
277
321
# be usefully made into a much faster special case.
279
_raise_if_doubly_unversioned(specific_files, old_tree, new_tree)
281
323
if external_diff_options:
282
324
assert isinstance(external_diff_options, basestring)
283
325
opts = external_diff_options.split()
287
329
diff_file = internal_diff
289
331
delta = compare_trees(old_tree, new_tree, want_unchanged=False,
290
specific_files=specific_files)
332
specific_files=specific_files,
333
extra_trees=extra_trees, require_versioned=True)
293
336
for path, file_id, kind in delta.removed:
345
388
return time.strftime('%Y-%m-%d %H:%M:%S +0000', tm)
348
def _raise_if_doubly_unversioned(specific_files, old_tree, new_tree):
349
"""Complain if paths are not versioned in either tree."""
350
if not specific_files:
352
old_unversioned = old_tree.filter_unversioned_files(specific_files)
353
new_unversioned = new_tree.filter_unversioned_files(specific_files)
354
unversioned = old_unversioned.intersection(new_unversioned)
356
raise errors.PathsNotVersionedError(sorted(unversioned))
359
391
def _raise_if_nonexistent(paths, old_tree, new_tree):
360
392
"""Complain if paths are not in either inventory or tree.