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
29
# compatability - plugins import compare_trees from diff!!!
30
# deprecated as of 0.10
19
31
from bzrlib.delta import compare_trees
20
32
from bzrlib.errors import BzrError
21
import bzrlib.errors as errors
22
33
from bzrlib.patiencediff import unified_diff
23
34
import bzrlib.patiencediff
24
from bzrlib.symbol_versioning import *
35
from bzrlib.symbol_versioning import (deprecated_function,
25
37
from bzrlib.textfile import check_text_lines
26
from bzrlib.trace import mutter
38
from bzrlib.trace import mutter, warning
29
41
# TODO: Rather than building a changeset object, we should probably
94
"""Set the env var LANG=C"""
95
osutils.set_or_unset_env('LANG', 'C')
96
osutils.set_or_unset_env('LC_ALL', None)
97
osutils.set_or_unset_env('LC_CTYPE', None)
98
osutils.set_or_unset_env('LANGUAGE', None)
101
def _spawn_external_diff(diffcmd, capture_errors=True):
102
"""Spawn the externall diff process, and return the child handle.
104
:param diffcmd: The command list to spawn
105
:param capture_errors: Capture stderr as well as setting LANG=C.
106
This lets us read and understand the output of diff, and respond
108
:return: A Popen object.
111
if sys.platform == 'win32':
112
# Win32 doesn't support preexec_fn, but that is
113
# okay, because it doesn't support LANG either.
116
preexec_fn = _set_lang_C
117
stderr = subprocess.PIPE
123
pipe = subprocess.Popen(diffcmd,
124
stdin=subprocess.PIPE,
125
stdout=subprocess.PIPE,
127
preexec_fn=preexec_fn)
129
if e.errno == errno.ENOENT:
130
raise errors.NoDiff(str(e))
81
136
def external_diff(old_filename, oldlines, new_filename, newlines, to_file,
83
138
"""Display a diff by calling out to the external diff program."""
86
if to_file != sys.stdout:
87
raise NotImplementedError("sorry, can't send external diff other than to stdout yet",
90
139
# make sure our own output is properly ordered before the diff
93
from tempfile import NamedTemporaryFile
96
oldtmpf = NamedTemporaryFile()
97
newtmpf = NamedTemporaryFile()
142
oldtmp_fd, old_abspath = tempfile.mkstemp(prefix='bzr-diff-old-')
143
newtmp_fd, new_abspath = tempfile.mkstemp(prefix='bzr-diff-new-')
144
oldtmpf = os.fdopen(oldtmp_fd, 'wb')
145
newtmpf = os.fdopen(newtmp_fd, 'wb')
100
148
# TODO: perhaps a special case for comparing to or from the empty
143
193
diffcmd.extend(diff_opts)
145
rc = os.spawnvp(os.P_WAIT, 'diff', diffcmd)
195
pipe = _spawn_external_diff(diffcmd, capture_errors=True)
196
out,err = pipe.communicate()
147
if rc != 0 and rc != 1:
199
# internal_diff() adds a trailing newline, add one here for consistency
202
# 'diff' gives retcode == 2 for all sorts of errors
203
# one of those is 'Binary files differ'.
204
# Bad options could also be the problem.
205
# 'Binary files' is not a real error, so we suppress that error.
208
# Since we got here, we want to make sure to give an i18n error
209
pipe = _spawn_external_diff(diffcmd, capture_errors=False)
210
out, err = pipe.communicate()
212
# Write out the new i18n diff response
213
to_file.write(out+'\n')
214
if pipe.returncode != 2:
215
raise BzrError('external diff failed with exit code 2'
216
' when run with LANG=C, but not when run'
217
' natively: %r' % (diffcmd,))
219
first_line = lang_c_out.split('\n', 1)[0]
220
# Starting with diffutils 2.8.4 the word "binary" was dropped.
221
m = re.match('^(binary )?files.*differ$', first_line, re.I)
223
raise BzrError('external diff failed with exit code 2;'
224
' command: %r' % (diffcmd,))
226
# Binary files differ, just return
229
# If we got to here, we haven't written out the output of diff
148
233
# returns 1 if files differ; that's OK
150
235
msg = 'signal %d' % (-rc)
152
237
msg = 'exit code %d' % rc
154
raise BzrError('external diff failed with %s; command: %r' % (rc, diffcmd))
239
raise BzrError('external diff failed with %s; command: %r'
156
244
oldtmpf.close() # and delete
246
# Clean up. Warn in case the files couldn't be deleted
247
# (in case windows still holds the file open, but not
248
# if the files have already been deleted)
250
os.remove(old_abspath)
252
if e.errno not in (errno.ENOENT,):
253
warning('Failed to delete temporary file: %s %s',
256
os.remove(new_abspath)
258
if e.errno not in (errno.ENOENT,):
259
warning('Failed to delete temporary file: %s %s',
160
263
@deprecated_function(zero_eight)
221
323
The more general form is show_diff_trees(), where the caller
222
324
supplies any two trees.
226
326
def spec_tree(spec):
227
revision_id = spec.in_store(tree.branch).rev_id
228
return tree.branch.repository.revision_tree(revision_id)
328
revision = spec.in_store(tree.branch)
330
revision = spec.in_store(None)
331
revision_id = revision.rev_id
332
branch = revision.branch
333
return branch.repository.revision_tree(revision_id)
229
334
if old_revision_spec is None:
230
335
old_tree = tree.basis_tree()
237
342
new_tree = spec_tree(new_revision_spec)
343
if new_tree is not tree:
344
extra_trees = (tree,)
239
348
return show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
240
349
external_diff_options,
241
old_label=old_label, new_label=new_label)
350
old_label=old_label, new_label=new_label,
351
extra_trees=extra_trees)
244
354
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
245
355
external_diff_options=None,
246
old_label='a/', new_label='b/'):
356
old_label='a/', new_label='b/',
247
358
"""Show in text form the changes from one tree to another.
259
373
return _show_diff_trees(old_tree, new_tree, to_file,
260
374
specific_files, external_diff_options,
261
old_label=old_label, new_label=new_label)
375
old_label=old_label, new_label=new_label,
376
extra_trees=extra_trees)
263
378
new_tree.unlock()
268
383
def _show_diff_trees(old_tree, new_tree, to_file,
269
384
specific_files, external_diff_options,
270
old_label='a/', new_label='b/' ):
385
old_label='a/', new_label='b/', extra_trees=None):
272
387
# GNU Patch uses the epoch date to detect files that are being added
273
388
# or removed in a diff.
276
391
# TODO: Generation of pseudo-diffs for added/deleted files could
277
392
# be usefully made into a much faster special case.
279
_raise_if_doubly_unversioned(specific_files, old_tree, new_tree)
281
394
if external_diff_options:
282
395
assert isinstance(external_diff_options, basestring)
283
396
opts = external_diff_options.split()
287
400
diff_file = internal_diff
289
delta = compare_trees(old_tree, new_tree, want_unchanged=False,
290
specific_files=specific_files)
402
delta = new_tree.changes_from(old_tree,
403
specific_files=specific_files,
404
extra_trees=extra_trees, require_versioned=True)
293
407
for path, file_id, kind in delta.removed:
345
459
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
462
def _raise_if_nonexistent(paths, old_tree, new_tree):
360
463
"""Complain if paths are not in either inventory or tree.