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
21
from bzrlib.lazy_import import lazy_import
22
lazy_import(globals(), """
36
# compatability - plugins import compare_trees from diff!!!
37
# deprecated as of 0.10
24
38
from bzrlib.delta import compare_trees
25
from bzrlib.errors import BzrError
26
import bzrlib.errors as errors
28
from bzrlib.patiencediff import unified_diff
29
import bzrlib.patiencediff
30
from bzrlib.symbol_versioning import (deprecated_function,
32
from bzrlib.textfile import check_text_lines
39
from bzrlib.symbol_versioning import (
33
43
from bzrlib.trace import mutter, warning
59
69
if allow_binary is False:
60
check_text_lines(oldlines)
61
check_text_lines(newlines)
70
textfile.check_text_lines(oldlines)
71
textfile.check_text_lines(newlines)
63
73
if sequence_matcher is None:
64
sequence_matcher = bzrlib.patiencediff.PatienceSequenceMatcher
65
ud = unified_diff(oldlines, newlines,
74
sequence_matcher = patiencediff.PatienceSequenceMatcher
75
ud = patiencediff.unified_diff(oldlines, newlines,
66
76
fromfile=old_filename.encode(path_encoding),
67
77
tofile=new_filename.encode(path_encoding),
68
78
sequencematcher=sequence_matcher)
99
"""Set the env vars LANG=C and LC_ALL=C."""
100
osutils.set_or_unset_env('LANG', 'C')
101
osutils.set_or_unset_env('LC_ALL', 'C')
102
osutils.set_or_unset_env('LC_CTYPE', None)
103
osutils.set_or_unset_env('LANGUAGE', None)
106
def _spawn_external_diff(diffcmd, capture_errors=True):
107
"""Spawn the externall diff process, and return the child handle.
109
:param diffcmd: The command list to spawn
110
:param capture_errors: Capture stderr as well as setting LANG=C
111
and LC_ALL=C. This lets us read and understand the output of diff,
112
and respond to any errors.
113
:return: A Popen object.
116
if sys.platform == 'win32':
117
# Win32 doesn't support preexec_fn, but that is
118
# okay, because it doesn't support LANG and LC_ALL either.
121
preexec_fn = _set_lang_C
122
stderr = subprocess.PIPE
128
pipe = subprocess.Popen(diffcmd,
129
stdin=subprocess.PIPE,
130
stdout=subprocess.PIPE,
132
preexec_fn=preexec_fn)
134
if e.errno == errno.ENOENT:
135
raise errors.NoDiff(str(e))
88
141
def external_diff(old_filename, oldlines, new_filename, newlines, to_file,
90
143
"""Display a diff by calling out to the external diff program."""
91
if hasattr(to_file, 'fileno'):
95
out_file = subprocess.PIPE
98
144
# make sure our own output is properly ordered before the diff
152
198
diffcmd.extend(diff_opts)
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)
200
pipe = _spawn_external_diff(diffcmd, capture_errors=True)
201
out,err = pipe.communicate()
168
if rc != 0 and rc != 1:
204
# internal_diff() adds a trailing newline, add one here for consistency
207
# 'diff' gives retcode == 2 for all sorts of errors
208
# one of those is 'Binary files differ'.
209
# Bad options could also be the problem.
210
# 'Binary files' is not a real error, so we suppress that error.
213
# Since we got here, we want to make sure to give an i18n error
214
pipe = _spawn_external_diff(diffcmd, capture_errors=False)
215
out, err = pipe.communicate()
217
# Write out the new i18n diff response
218
to_file.write(out+'\n')
219
if pipe.returncode != 2:
220
raise errors.BzrError(
221
'external diff failed with exit code 2'
222
' when run with LANG=C and LC_ALL=C,'
223
' but not when run natively: %r' % (diffcmd,))
225
first_line = lang_c_out.split('\n', 1)[0]
226
# Starting with diffutils 2.8.4 the word "binary" was dropped.
227
m = re.match('^(binary )?files.*differ$', first_line, re.I)
229
raise errors.BzrError('external diff failed with exit code 2;'
230
' command: %r' % (diffcmd,))
232
# Binary files differ, just return
235
# If we got to here, we haven't written out the output of diff
169
239
# returns 1 if files differ; that's OK
171
241
msg = 'signal %d' % (-rc)
173
243
msg = 'exit code %d' % rc
175
raise BzrError('external diff failed with %s; command: %r' % (rc, diffcmd))
245
raise errors.BzrError('external diff failed with %s; command: %r'
177
250
oldtmpf.close() # and delete
234
307
def diff_cmd_helper(tree, specific_files, external_diff_options,
235
308
old_revision_spec=None, new_revision_spec=None,
236
310
old_label='a/', new_label='b/'):
237
311
"""Helper for cmd_diff.
316
:param specific_files:
243
317
The specific files to compare, or None
245
external_diff_options
319
:param external_diff_options:
246
320
If non-None, run an external diff, and pass it these options
322
:param old_revision_spec:
249
323
If None, use basis tree as old revision, otherwise use the tree for
250
324
the specified revision.
326
:param new_revision_spec:
253
327
If None, use working tree as new revision, otherwise use the tree for
254
328
the specified revision.
330
:param revision_specs:
331
Zero, one or two RevisionSpecs from the command line, saying what revisions
332
to compare. This can be passed as an alternative to the old_revision_spec
333
and new_revision_spec parameters.
256
335
The more general form is show_diff_trees(), where the caller
257
336
supplies any two trees.
339
# TODO: perhaps remove the old parameters old_revision_spec and
340
# new_revision_spec, since this is only really for use from cmd_diff and
341
# it now always passes through a sequence of revision_specs -- mbp
259
344
def spec_tree(spec):
261
346
revision = spec.in_store(tree.branch)
264
349
revision_id = revision.rev_id
265
350
branch = revision.branch
266
351
return branch.repository.revision_tree(revision_id)
353
if revision_specs is not None:
354
assert (old_revision_spec is None
355
and new_revision_spec is None)
356
if len(revision_specs) > 0:
357
old_revision_spec = revision_specs[0]
358
if len(revision_specs) > 1:
359
new_revision_spec = revision_specs[1]
267
361
if old_revision_spec is None:
268
362
old_tree = tree.basis_tree()
270
364
old_tree = spec_tree(old_revision_spec)
272
if new_revision_spec is None:
366
if (new_revision_spec is None
367
or new_revision_spec.spec is None):
275
370
new_tree = spec_tree(new_revision_spec)
276
372
if new_tree is not tree:
277
373
extra_trees = (tree,)
333
429
diff_file = internal_diff
335
delta = compare_trees(old_tree, new_tree, want_unchanged=False,
336
specific_files=specific_files,
337
extra_trees=extra_trees, require_versioned=True)
431
delta = new_tree.changes_from(old_tree,
432
specific_files=specific_files,
433
extra_trees=extra_trees, require_versioned=True)
340
436
for path, file_id, kind in delta.removed: