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(), """
37
24
# compatability - plugins import compare_trees from diff!!!
38
25
# deprecated as of 0.10
39
26
from bzrlib.delta import compare_trees
40
from bzrlib.symbol_versioning import (
27
from bzrlib.errors import BzrError
28
import bzrlib.errors as errors
30
from bzrlib.patiencediff import unified_diff
31
import bzrlib.patiencediff
32
from bzrlib.symbol_versioning import (deprecated_function,
34
from bzrlib.textfile import check_text_lines
44
35
from bzrlib.trace import mutter, warning
70
61
if allow_binary is False:
71
textfile.check_text_lines(oldlines)
72
textfile.check_text_lines(newlines)
62
check_text_lines(oldlines)
63
check_text_lines(newlines)
74
65
if sequence_matcher is None:
75
sequence_matcher = patiencediff.PatienceSequenceMatcher
76
ud = patiencediff.unified_diff(oldlines, newlines,
66
sequence_matcher = bzrlib.patiencediff.PatienceSequenceMatcher
67
ud = unified_diff(oldlines, newlines,
77
68
fromfile=old_filename.encode(path_encoding),
78
69
tofile=new_filename.encode(path_encoding),
79
70
sequencematcher=sequence_matcher)
99
def _spawn_external_diff(diffcmd, capture_errors=True):
100
"""Spawn the externall diff process, and return the child handle.
102
:param diffcmd: The command list to spawn
103
:param capture_errors: Capture stderr as well as setting LANG=C
104
and LC_ALL=C. This lets us read and understand the output of diff,
105
and respond to any errors.
106
:return: A Popen object.
109
# construct minimal environment
111
path = os.environ.get('PATH')
114
env['LANGUAGE'] = 'C' # on win32 only LANGUAGE has effect
117
stderr = subprocess.PIPE
123
pipe = subprocess.Popen(diffcmd,
124
stdin=subprocess.PIPE,
125
stdout=subprocess.PIPE,
129
if e.errno == errno.ENOENT:
130
raise errors.NoDiff(str(e))
136
90
def external_diff(old_filename, oldlines, new_filename, newlines, to_file,
138
92
"""Display a diff by calling out to the external diff program."""
93
if hasattr(to_file, 'fileno'):
97
out_file = subprocess.PIPE
139
100
# make sure our own output is properly ordered before the diff
193
154
diffcmd.extend(diff_opts)
195
pipe = _spawn_external_diff(diffcmd, capture_errors=True)
196
out,err = pipe.communicate()
157
pipe = subprocess.Popen(diffcmd,
158
stdin=subprocess.PIPE,
161
if e.errno == errno.ENOENT:
162
raise errors.NoDiff(str(e))
167
bzrlib.osutils.pumpfile(pipe.stdout, to_file)
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 errors.BzrError(
216
'external diff failed with exit code 2'
217
' when run with LANG=C and LC_ALL=C,'
218
' but not when run natively: %r' % (diffcmd,))
220
first_line = lang_c_out.split('\n', 1)[0]
221
# Starting with diffutils 2.8.4 the word "binary" was dropped.
222
m = re.match('^(binary )?files.*differ$', first_line, re.I)
224
raise errors.BzrError('external diff failed with exit code 2;'
225
' command: %r' % (diffcmd,))
227
# Binary files differ, just return
230
# If we got to here, we haven't written out the output of diff
170
if rc != 0 and rc != 1:
234
171
# returns 1 if files differ; that's OK
236
173
msg = 'signal %d' % (-rc)
238
175
msg = 'exit code %d' % rc
240
raise errors.BzrError('external diff failed with %s; command: %r'
177
raise BzrError('external diff failed with %s; command: %r' % (rc, diffcmd))
245
179
oldtmpf.close() # and delete
302
236
def diff_cmd_helper(tree, specific_files, external_diff_options,
303
237
old_revision_spec=None, new_revision_spec=None,
305
238
old_label='a/', new_label='b/'):
306
239
"""Helper for cmd_diff.
311
:param specific_files:
312
245
The specific files to compare, or None
314
:param external_diff_options:
247
external_diff_options
315
248
If non-None, run an external diff, and pass it these options
317
:param old_revision_spec:
318
251
If None, use basis tree as old revision, otherwise use the tree for
319
252
the specified revision.
321
:param new_revision_spec:
322
255
If None, use working tree as new revision, otherwise use the tree for
323
256
the specified revision.
325
:param revision_specs:
326
Zero, one or two RevisionSpecs from the command line, saying what revisions
327
to compare. This can be passed as an alternative to the old_revision_spec
328
and new_revision_spec parameters.
330
258
The more general form is show_diff_trees(), where the caller
331
259
supplies any two trees.
334
# TODO: perhaps remove the old parameters old_revision_spec and
335
# new_revision_spec, since this is only really for use from cmd_diff and
336
# it now always passes through a sequence of revision_specs -- mbp
339
261
def spec_tree(spec):
341
263
revision = spec.in_store(tree.branch)
344
266
revision_id = revision.rev_id
345
267
branch = revision.branch
346
268
return branch.repository.revision_tree(revision_id)
348
if revision_specs is not None:
349
assert (old_revision_spec is None
350
and new_revision_spec is None)
351
if len(revision_specs) > 0:
352
old_revision_spec = revision_specs[0]
353
if len(revision_specs) > 1:
354
new_revision_spec = revision_specs[1]
356
269
if old_revision_spec is None:
357
270
old_tree = tree.basis_tree()
359
272
old_tree = spec_tree(old_revision_spec)
361
if (new_revision_spec is None
362
or new_revision_spec.spec is None):
274
if new_revision_spec is None:
365
277
new_tree = spec_tree(new_revision_spec)
367
278
if new_tree is not tree:
368
279
extra_trees = (tree,)
472
377
prop_str = get_prop_change(meta_modified)
473
378
print >>to_file, '=== modified %s %r%s' % (kind, path.encode('utf8'), prop_str)
474
# The file may be in a different location in the old tree (because
475
# the containing dir was renamed, but the file itself was not)
476
old_path = old_tree.id2path(file_id)
477
old_name = '%s%s\t%s' % (old_label, old_path,
478
_patch_header_date(old_tree, file_id, old_path))
379
old_name = '%s%s\t%s' % (old_label, path,
380
_patch_header_date(old_tree, file_id, path))
479
381
new_name = '%s%s\t%s' % (new_label, path,
480
382
_patch_header_date(new_tree, file_id, path))
481
383
if text_modified:
489
391
def _patch_header_date(tree, file_id, path):
490
392
"""Returns a timestamp suitable for use in a patch header."""
491
mtime = tree.get_file_mtime(file_id, path)
492
assert mtime is not None, \
493
"got an mtime of None for file-id %s, path %s in tree %s" % (
495
return timestamp.format_patch_date(mtime)
393
tm = time.gmtime(tree.get_file_mtime(file_id, path))
394
return time.strftime('%Y-%m-%d %H:%M:%S +0000', tm)
498
397
def _raise_if_nonexistent(paths, old_tree, new_tree):