143
204
diffcmd.extend(diff_opts)
145
rc = os.spawnvp(os.P_WAIT, 'diff', diffcmd)
206
pipe = _spawn_external_diff(diffcmd, capture_errors=True)
207
out,err = pipe.communicate()
147
if rc != 0 and rc != 1:
210
# internal_diff() adds a trailing newline, add one here for consistency
213
# 'diff' gives retcode == 2 for all sorts of errors
214
# one of those is 'Binary files differ'.
215
# Bad options could also be the problem.
216
# 'Binary files' is not a real error, so we suppress that error.
219
# Since we got here, we want to make sure to give an i18n error
220
pipe = _spawn_external_diff(diffcmd, capture_errors=False)
221
out, err = pipe.communicate()
223
# Write out the new i18n diff response
224
to_file.write(out+'\n')
225
if pipe.returncode != 2:
226
raise errors.BzrError(
227
'external diff failed with exit code 2'
228
' when run with LANG=C and LC_ALL=C,'
229
' but not when run natively: %r' % (diffcmd,))
231
first_line = lang_c_out.split('\n', 1)[0]
232
# Starting with diffutils 2.8.4 the word "binary" was dropped.
233
m = re.match('^(binary )?files.*differ$', first_line, re.I)
235
raise errors.BzrError('external diff failed with exit code 2;'
236
' command: %r' % (diffcmd,))
238
# Binary files differ, just return
241
# If we got to here, we haven't written out the output of diff
148
245
# returns 1 if files differ; that's OK
150
247
msg = 'signal %d' % (-rc)
152
249
msg = 'exit code %d' % rc
154
raise BzrError('external diff failed with %s; command: %r' % (rc, diffcmd))
251
raise errors.BzrError('external diff failed with %s; command: %r'
156
256
oldtmpf.close() # and delete
160
@deprecated_function(zero_eight)
161
def show_diff(b, from_spec, specific_files, external_diff_options=None,
162
revision2=None, output=None, b2=None):
163
"""Shortcut for showing the diff to the working tree.
165
Please use show_diff_trees instead.
171
None for 'basis tree', or otherwise the old revision to compare against.
173
The more general form is show_diff_trees(), where the caller
174
supplies any two trees.
258
# Clean up. Warn in case the files couldn't be deleted
259
# (in case windows still holds the file open, but not
260
# if the files have already been deleted)
262
os.remove(old_abspath)
264
if e.errno not in (errno.ENOENT,):
265
warning('Failed to delete temporary file: %s %s',
268
os.remove(new_abspath)
270
if e.errno not in (errno.ENOENT,):
271
warning('Failed to delete temporary file: %s %s',
275
def _get_trees_to_diff(path_list, revision_specs, old_url, new_url):
276
"""Get the trees and specific files to diff given a list of paths.
278
This method works out the trees to be diff'ed and the files of
279
interest within those trees.
282
the list of arguments passed to the diff command
283
:param revision_specs:
284
Zero, one or two RevisionSpecs from the diff command line,
285
saying what revisions to compare.
287
The url of the old branch or tree. If None, the tree to use is
288
taken from the first path, if any, or the current working tree.
290
The url of the new branch or tree. If None, the tree to use is
291
taken from the first path, if any, or the current working tree.
293
a tuple of (old_tree, new_tree, specific_files, extra_trees) where
294
extra_trees is a sequence of additional trees to search in for
297
# Get the old and new revision specs
298
old_revision_spec = None
299
new_revision_spec = None
300
if revision_specs is not None:
301
if len(revision_specs) > 0:
302
old_revision_spec = revision_specs[0]
304
old_url = old_revision_spec.get_branch()
305
if len(revision_specs) > 1:
306
new_revision_spec = revision_specs[1]
308
new_url = new_revision_spec.get_branch()
180
if from_spec is None:
181
old_tree = b.bzrdir.open_workingtree()
183
old_tree = old_tree = old_tree.basis_tree()
311
make_paths_wt_relative = True
312
consider_relpath = True
313
if path_list is None or len(path_list) == 0:
314
# If no path is given, the current working tree is used
315
default_location = u'.'
316
consider_relpath = False
317
elif old_url is not None and new_url is not None:
318
other_paths = path_list
319
make_paths_wt_relative = False
185
old_tree = b.repository.revision_tree(from_spec.in_history(b).rev_id)
187
if revision2 is None:
189
new_tree = b.bzrdir.open_workingtree()
321
default_location = path_list[0]
322
other_paths = path_list[1:]
324
# Get the old location
327
old_url = default_location
328
working_tree, branch, relpath = \
329
bzrdir.BzrDir.open_containing_tree_or_branch(old_url)
330
if consider_relpath and relpath != '':
331
specific_files.append(relpath)
332
old_tree = _get_tree_to_diff(old_revision_spec, working_tree, branch)
334
# Get the new location
336
new_url = default_location
337
if new_url != old_url:
338
working_tree, branch, relpath = \
339
bzrdir.BzrDir.open_containing_tree_or_branch(new_url)
340
if consider_relpath and relpath != '':
341
specific_files.append(relpath)
342
new_tree = _get_tree_to_diff(new_revision_spec, working_tree, branch,
343
basis_is_default=working_tree is None)
345
# Get the specific files (all files is None, no files is [])
346
if make_paths_wt_relative and working_tree is not None:
347
other_paths = _relative_paths_in_tree(working_tree, other_paths)
348
specific_files.extend(other_paths)
349
if len(specific_files) == 0:
350
specific_files = None
352
# Get extra trees that ought to be searched for file-ids
354
if working_tree is not None and working_tree not in (old_tree, new_tree):
355
extra_trees = (working_tree,)
356
return old_tree, new_tree, specific_files, extra_trees
359
def _get_tree_to_diff(spec, tree=None, branch=None, basis_is_default=True):
360
if branch is None and tree is not None:
362
if spec is None or spec.spec is None:
365
return tree.basis_tree()
367
return branch.basis_tree()
191
new_tree = b2.bzrdir.open_workingtree()
193
new_tree = b.repository.revision_tree(revision2.in_history(b).rev_id)
195
return show_diff_trees(old_tree, new_tree, output, specific_files,
196
external_diff_options)
199
def diff_cmd_helper(tree, specific_files, external_diff_options,
200
old_revision_spec=None, new_revision_spec=None,
201
old_label='a/', new_label='b/'):
202
"""Helper for cmd_diff.
208
The specific files to compare, or None
210
external_diff_options
211
If non-None, run an external diff, and pass it these options
214
If None, use basis tree as old revision, otherwise use the tree for
215
the specified revision.
218
If None, use working tree as new revision, otherwise use the tree for
219
the specified revision.
221
The more general form is show_diff_trees(), where the caller
222
supplies any two trees.
370
return spec.as_tree(branch)
373
def _relative_paths_in_tree(tree, paths):
374
"""Get the relative paths within a working tree.
376
Each path may be either an absolute path or a path relative to the
377
current working directory.
227
revision_id = spec.in_store(tree.branch).rev_id
228
return tree.branch.repository.revision_tree(revision_id)
229
if old_revision_spec is None:
230
old_tree = tree.basis_tree()
232
old_tree = spec_tree(old_revision_spec)
234
if new_revision_spec is None:
237
new_tree = spec_tree(new_revision_spec)
239
return show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
240
external_diff_options,
241
old_label=old_label, new_label=new_label)
380
for filename in paths:
382
result.append(tree.relpath(osutils.dereference_path(filename)))
383
except errors.PathNotChild:
384
raise errors.BzrCommandError("Files are in different branches")
244
388
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
245
389
external_diff_options=None,
246
old_label='a/', new_label='b/'):
390
old_label='a/', new_label='b/',
392
path_encoding='utf8',
247
394
"""Show in text form the changes from one tree to another.
250
If set, include only changes to these files.
400
Include only changes to these files - None for all changes.
252
402
external_diff_options
253
403
If set, use an external GNU diff and pass these options.
406
If set, more Trees to use for looking up file ids
409
If set, the path will be encoded as specified, otherwise is supposed
255
412
old_tree.lock_read()
414
if extra_trees is not None:
415
for tree in extra_trees:
257
417
new_tree.lock_read()
259
return _show_diff_trees(old_tree, new_tree, to_file,
260
specific_files, external_diff_options,
261
old_label=old_label, new_label=new_label)
419
differ = DiffTree.from_trees_options(old_tree, new_tree, to_file,
421
external_diff_options,
422
old_label, new_label, using)
423
return differ.show_diff(specific_files, extra_trees)
263
425
new_tree.unlock()
426
if extra_trees is not None:
427
for tree in extra_trees:
265
430
old_tree.unlock()
268
def _show_diff_trees(old_tree, new_tree, to_file,
269
specific_files, external_diff_options,
270
old_label='a/', new_label='b/' ):
272
# GNU Patch uses the epoch date to detect files that are being added
273
# or removed in a diff.
274
EPOCH_DATE = '1970-01-01 00:00:00 +0000'
276
# TODO: Generation of pseudo-diffs for added/deleted files could
277
# be usefully made into a much faster special case.
279
_raise_if_doubly_unversioned(specific_files, old_tree, new_tree)
281
if external_diff_options:
282
assert isinstance(external_diff_options, basestring)
283
opts = external_diff_options.split()
284
def diff_file(olab, olines, nlab, nlines, to_file):
285
external_diff(olab, olines, nlab, nlines, to_file, opts)
287
diff_file = internal_diff
289
delta = compare_trees(old_tree, new_tree, want_unchanged=False,
290
specific_files=specific_files)
293
for path, file_id, kind in delta.removed:
295
print >>to_file, '=== removed %s %r' % (kind, path.encode('utf8'))
296
old_name = '%s%s\t%s' % (old_label, path,
297
_patch_header_date(old_tree, file_id, path))
298
new_name = '%s%s\t%s' % (new_label, path, EPOCH_DATE)
299
old_tree.inventory[file_id].diff(diff_file, old_name, old_tree,
300
new_name, None, None, to_file)
301
for path, file_id, kind in delta.added:
303
print >>to_file, '=== added %s %r' % (kind, path.encode('utf8'))
304
old_name = '%s%s\t%s' % (old_label, path, EPOCH_DATE)
305
new_name = '%s%s\t%s' % (new_label, path,
306
_patch_header_date(new_tree, file_id, path))
307
new_tree.inventory[file_id].diff(diff_file, new_name, new_tree,
308
old_name, None, None, to_file,
310
for (old_path, new_path, file_id, kind,
311
text_modified, meta_modified) in delta.renamed:
313
prop_str = get_prop_change(meta_modified)
314
print >>to_file, '=== renamed %s %r => %r%s' % (
315
kind, old_path.encode('utf8'),
316
new_path.encode('utf8'), prop_str)
317
old_name = '%s%s\t%s' % (old_label, old_path,
318
_patch_header_date(old_tree, file_id,
320
new_name = '%s%s\t%s' % (new_label, new_path,
321
_patch_header_date(new_tree, file_id,
323
_maybe_diff_file_or_symlink(old_name, old_tree, file_id,
325
text_modified, kind, to_file, diff_file)
326
for path, file_id, kind, text_modified, meta_modified in delta.modified:
328
prop_str = get_prop_change(meta_modified)
329
print >>to_file, '=== modified %s %r%s' % (kind, path.encode('utf8'), prop_str)
330
old_name = '%s%s\t%s' % (old_label, path,
331
_patch_header_date(old_tree, file_id, path))
332
new_name = '%s%s\t%s' % (new_label, path,
333
_patch_header_date(new_tree, file_id, path))
335
_maybe_diff_file_or_symlink(old_name, old_tree, file_id,
337
True, kind, to_file, diff_file)
342
433
def _patch_header_date(tree, file_id, path):
343
434
"""Returns a timestamp suitable for use in a patch header."""
344
tm = time.gmtime(tree.get_file_mtime(file_id, path))
345
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
def _raise_if_nonexistent(paths, old_tree, new_tree):
360
"""Complain if paths are not in either inventory or tree.
362
It's OK with the files exist in either tree's inventory, or
363
if they exist in the tree but are not versioned.
365
This can be used by operations such as bzr status that can accept
366
unknown or ignored files.
368
mutter("check paths: %r", paths)
371
s = old_tree.filter_unversioned_files(paths)
372
s = new_tree.filter_unversioned_files(s)
373
s = [path for path in s if not new_tree.has_filename(path)]
375
raise errors.PathsDoNotExist(sorted(s))
435
mtime = tree.get_file_mtime(file_id, path)
436
return timestamp.format_patch_date(mtime)
439
@deprecated_function(one_three)
378
440
def get_prop_change(meta_modified):
379
441
if meta_modified:
380
442
return " (properties changed)"
385
def _maybe_diff_file_or_symlink(old_path, old_tree, file_id,
386
new_path, new_tree, text_modified,
387
kind, to_file, diff_file):
389
new_entry = new_tree.inventory[file_id]
390
old_tree.inventory[file_id].diff(diff_file,
446
def get_executable_change(old_is_x, new_is_x):
447
descr = { True:"+x", False:"-x", None:"??" }
448
if old_is_x != new_is_x:
449
return ["%s to %s" % (descr[old_is_x], descr[new_is_x],)]
454
class DiffPath(object):
455
"""Base type for command object that compare files"""
457
# The type or contents of the file were unsuitable for diffing
458
CANNOT_DIFF = 'CANNOT_DIFF'
459
# The file has changed in a semantic way
461
# The file content may have changed, but there is no semantic change
462
UNCHANGED = 'UNCHANGED'
464
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8'):
467
:param old_tree: The tree to show as the old tree in the comparison
468
:param new_tree: The tree to show as new in the comparison
469
:param to_file: The file to write comparison data to
470
:param path_encoding: The character encoding to write paths in
472
self.old_tree = old_tree
473
self.new_tree = new_tree
474
self.to_file = to_file
475
self.path_encoding = path_encoding
481
def from_diff_tree(klass, diff_tree):
482
return klass(diff_tree.old_tree, diff_tree.new_tree,
483
diff_tree.to_file, diff_tree.path_encoding)
486
def _diff_many(differs, file_id, old_path, new_path, old_kind, new_kind):
487
for file_differ in differs:
488
result = file_differ.diff(file_id, old_path, new_path, old_kind,
490
if result is not DiffPath.CANNOT_DIFF:
493
return DiffPath.CANNOT_DIFF
496
class DiffKindChange(object):
497
"""Special differ for file kind changes.
499
Represents kind change as deletion + creation. Uses the other differs
502
def __init__(self, differs):
503
self.differs = differs
509
def from_diff_tree(klass, diff_tree):
510
return klass(diff_tree.differs)
512
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
513
"""Perform comparison
515
:param file_id: The file_id of the file to compare
516
:param old_path: Path of the file in the old tree
517
:param new_path: Path of the file in the new tree
518
:param old_kind: Old file-kind of the file
519
:param new_kind: New file-kind of the file
521
if None in (old_kind, new_kind):
522
return DiffPath.CANNOT_DIFF
523
result = DiffPath._diff_many(self.differs, file_id, old_path,
524
new_path, old_kind, None)
525
if result is DiffPath.CANNOT_DIFF:
527
return DiffPath._diff_many(self.differs, file_id, old_path, new_path,
531
class DiffDirectory(DiffPath):
533
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
534
"""Perform comparison between two directories. (dummy)
537
if 'directory' not in (old_kind, new_kind):
538
return self.CANNOT_DIFF
539
if old_kind not in ('directory', None):
540
return self.CANNOT_DIFF
541
if new_kind not in ('directory', None):
542
return self.CANNOT_DIFF
546
class DiffSymlink(DiffPath):
548
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
549
"""Perform comparison between two symlinks
551
:param file_id: The file_id of the file to compare
552
:param old_path: Path of the file in the old tree
553
:param new_path: Path of the file in the new tree
554
:param old_kind: Old file-kind of the file
555
:param new_kind: New file-kind of the file
557
if 'symlink' not in (old_kind, new_kind):
558
return self.CANNOT_DIFF
559
if old_kind == 'symlink':
560
old_target = self.old_tree.get_symlink_target(file_id)
561
elif old_kind is None:
564
return self.CANNOT_DIFF
565
if new_kind == 'symlink':
566
new_target = self.new_tree.get_symlink_target(file_id)
567
elif new_kind is None:
570
return self.CANNOT_DIFF
571
return self.diff_symlink(old_target, new_target)
573
def diff_symlink(self, old_target, new_target):
574
if old_target is None:
575
self.to_file.write('=== target is %r\n' % new_target)
576
elif new_target is None:
577
self.to_file.write('=== target was %r\n' % old_target)
579
self.to_file.write('=== target changed %r => %r\n' %
580
(old_target, new_target))
584
class DiffText(DiffPath):
586
# GNU Patch uses the epoch date to detect files that are being added
587
# or removed in a diff.
588
EPOCH_DATE = '1970-01-01 00:00:00 +0000'
590
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
591
old_label='', new_label='', text_differ=internal_diff):
592
DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
593
self.text_differ = text_differ
594
self.old_label = old_label
595
self.new_label = new_label
596
self.path_encoding = path_encoding
598
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
599
"""Compare two files in unified diff format
601
:param file_id: The file_id of the file to compare
602
:param old_path: Path of the file in the old tree
603
:param new_path: Path of the file in the new tree
604
:param old_kind: Old file-kind of the file
605
:param new_kind: New file-kind of the file
607
if 'file' not in (old_kind, new_kind):
608
return self.CANNOT_DIFF
609
from_file_id = to_file_id = file_id
610
if old_kind == 'file':
611
old_date = _patch_header_date(self.old_tree, file_id, old_path)
612
elif old_kind is None:
613
old_date = self.EPOCH_DATE
616
return self.CANNOT_DIFF
617
if new_kind == 'file':
618
new_date = _patch_header_date(self.new_tree, file_id, new_path)
619
elif new_kind is None:
620
new_date = self.EPOCH_DATE
623
return self.CANNOT_DIFF
624
from_label = '%s%s\t%s' % (self.old_label, old_path, old_date)
625
to_label = '%s%s\t%s' % (self.new_label, new_path, new_date)
626
return self.diff_text(from_file_id, to_file_id, from_label, to_label)
628
def diff_text(self, from_file_id, to_file_id, from_label, to_label):
629
"""Diff the content of given files in two trees
631
:param from_file_id: The id of the file in the from tree. If None,
632
the file is not present in the from tree.
633
:param to_file_id: The id of the file in the to tree. This may refer
634
to a different file from from_file_id. If None,
635
the file is not present in the to tree.
637
def _get_text(tree, file_id):
638
if file_id is not None:
639
return tree.get_file(file_id).readlines()
643
from_text = _get_text(self.old_tree, from_file_id)
644
to_text = _get_text(self.new_tree, to_file_id)
645
self.text_differ(from_label, from_text, to_label, to_text,
647
except errors.BinaryFile:
649
("Binary files %s and %s differ\n" %
650
(from_label, to_label)).encode(self.path_encoding))
654
class DiffFromTool(DiffPath):
656
def __init__(self, command_template, old_tree, new_tree, to_file,
657
path_encoding='utf-8'):
658
DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
659
self.command_template = command_template
660
self._root = osutils.mkdtemp(prefix='bzr-diff-')
663
def from_string(klass, command_string, old_tree, new_tree, to_file,
664
path_encoding='utf-8'):
665
command_template = commands.shlex_split_unicode(command_string)
666
command_template.extend(['%(old_path)s', '%(new_path)s'])
667
return klass(command_template, old_tree, new_tree, to_file,
671
def make_from_diff_tree(klass, command_string):
672
def from_diff_tree(diff_tree):
673
return klass.from_string(command_string, diff_tree.old_tree,
674
diff_tree.new_tree, diff_tree.to_file)
675
return from_diff_tree
677
def _get_command(self, old_path, new_path):
678
my_map = {'old_path': old_path, 'new_path': new_path}
679
return [t % my_map for t in self.command_template]
681
def _execute(self, old_path, new_path):
682
command = self._get_command(old_path, new_path)
684
proc = subprocess.Popen(command, stdout=subprocess.PIPE,
687
if e.errno == errno.ENOENT:
688
raise errors.ExecutableMissing(command[0])
691
self.to_file.write(proc.stdout.read())
694
def _try_symlink_root(self, tree, prefix):
695
if (getattr(tree, 'abspath', None) is None
696
or not osutils.host_os_dereferences_symlinks()):
699
os.symlink(tree.abspath(''), osutils.pathjoin(self._root, prefix))
701
if e.errno != errno.EEXIST:
705
def _write_file(self, file_id, tree, prefix, relpath):
706
full_path = osutils.pathjoin(self._root, prefix, relpath)
707
if self._try_symlink_root(tree, prefix):
709
parent_dir = osutils.dirname(full_path)
711
os.makedirs(parent_dir)
713
if e.errno != errno.EEXIST:
715
source = tree.get_file(file_id, relpath)
717
target = open(full_path, 'wb')
719
osutils.pumpfile(source, target)
724
osutils.make_readonly(full_path)
725
mtime = tree.get_file_mtime(file_id)
726
os.utime(full_path, (mtime, mtime))
729
def _prepare_files(self, file_id, old_path, new_path):
730
old_disk_path = self._write_file(file_id, self.old_tree, 'old',
732
new_disk_path = self._write_file(file_id, self.new_tree, 'new',
734
return old_disk_path, new_disk_path
737
osutils.rmtree(self._root)
739
def diff(self, file_id, old_path, new_path, old_kind, new_kind):
740
if (old_kind, new_kind) != ('file', 'file'):
741
return DiffPath.CANNOT_DIFF
742
self._prepare_files(file_id, old_path, new_path)
743
self._execute(osutils.pathjoin('old', old_path),
744
osutils.pathjoin('new', new_path))
747
class DiffTree(object):
748
"""Provides textual representations of the difference between two trees.
750
A DiffTree examines two trees and where a file-id has altered
751
between them, generates a textual representation of the difference.
752
DiffTree uses a sequence of DiffPath objects which are each
753
given the opportunity to handle a given altered fileid. The list
754
of DiffPath objects can be extended globally by appending to
755
DiffTree.diff_factories, or for a specific diff operation by
756
supplying the extra_factories option to the appropriate method.
759
# list of factories that can provide instances of DiffPath objects
760
# may be extended by plugins.
761
diff_factories = [DiffSymlink.from_diff_tree,
762
DiffDirectory.from_diff_tree]
764
def __init__(self, old_tree, new_tree, to_file, path_encoding='utf-8',
765
diff_text=None, extra_factories=None):
768
:param old_tree: Tree to show as old in the comparison
769
:param new_tree: Tree to show as new in the comparison
770
:param to_file: File to write comparision to
771
:param path_encoding: Character encoding to write paths in
772
:param diff_text: DiffPath-type object to use as a last resort for
774
:param extra_factories: Factories of DiffPaths to try before any other
776
if diff_text is None:
777
diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
778
'', '', internal_diff)
779
self.old_tree = old_tree
780
self.new_tree = new_tree
781
self.to_file = to_file
782
self.path_encoding = path_encoding
784
if extra_factories is not None:
785
self.differs.extend(f(self) for f in extra_factories)
786
self.differs.extend(f(self) for f in self.diff_factories)
787
self.differs.extend([diff_text, DiffKindChange.from_diff_tree(self)])
790
def from_trees_options(klass, old_tree, new_tree, to_file,
791
path_encoding, external_diff_options, old_label,
793
"""Factory for producing a DiffTree.
795
Designed to accept options used by show_diff_trees.
796
:param old_tree: The tree to show as old in the comparison
797
:param new_tree: The tree to show as new in the comparison
798
:param to_file: File to write comparisons to
799
:param path_encoding: Character encoding to use for writing paths
800
:param external_diff_options: If supplied, use the installed diff
801
binary to perform file comparison, using supplied options.
802
:param old_label: Prefix to use for old file labels
803
:param new_label: Prefix to use for new file labels
804
:param using: Commandline to use to invoke an external diff tool
806
if using is not None:
807
extra_factories = [DiffFromTool.make_from_diff_tree(using)]
810
if external_diff_options:
811
opts = external_diff_options.split()
812
def diff_file(olab, olines, nlab, nlines, to_file):
813
external_diff(olab, olines, nlab, nlines, to_file, opts)
815
diff_file = internal_diff
816
diff_text = DiffText(old_tree, new_tree, to_file, path_encoding,
817
old_label, new_label, diff_file)
818
return klass(old_tree, new_tree, to_file, path_encoding, diff_text,
821
def show_diff(self, specific_files, extra_trees=None):
822
"""Write tree diff to self.to_file
824
:param sepecific_files: the specific files to compare (recursive)
825
:param extra_trees: extra trees to use for mapping paths to file_ids
828
return self._show_diff(specific_files, extra_trees)
830
for differ in self.differs:
833
def _show_diff(self, specific_files, extra_trees):
834
# TODO: Generation of pseudo-diffs for added/deleted files could
835
# be usefully made into a much faster special case.
836
iterator = self.new_tree.iter_changes(self.old_tree,
837
specific_files=specific_files,
838
extra_trees=extra_trees,
839
require_versioned=True)
841
def changes_key(change):
842
old_path, new_path = change[1]
847
def get_encoded_path(path):
849
return path.encode(self.path_encoding, "replace")
850
for (file_id, paths, changed_content, versioned, parent, name, kind,
851
executable) in sorted(iterator, key=changes_key):
852
# The root does not get diffed, and items with no known kind (that
853
# is, missing) in both trees are skipped as well.
854
if parent == (None, None) or kind == (None, None):
856
oldpath, newpath = paths
857
oldpath_encoded = get_encoded_path(paths[0])
858
newpath_encoded = get_encoded_path(paths[1])
859
old_present = (kind[0] is not None and versioned[0])
860
new_present = (kind[1] is not None and versioned[1])
861
renamed = (parent[0], name[0]) != (parent[1], name[1])
863
properties_changed = []
864
properties_changed.extend(get_executable_change(executable[0], executable[1]))
866
if properties_changed:
867
prop_str = " (properties changed: %s)" % (", ".join(properties_changed),)
871
if (old_present, new_present) == (True, False):
872
self.to_file.write("=== removed %s '%s'\n" %
873
(kind[0], oldpath_encoded))
875
elif (old_present, new_present) == (False, True):
876
self.to_file.write("=== added %s '%s'\n" %
877
(kind[1], newpath_encoded))
880
self.to_file.write("=== renamed %s '%s' => '%s'%s\n" %
881
(kind[0], oldpath_encoded, newpath_encoded, prop_str))
883
# if it was produced by iter_changes, it must be
884
# modified *somehow*, either content or execute bit.
885
self.to_file.write("=== modified %s '%s'%s\n" % (kind[0],
886
newpath_encoded, prop_str))
888
self.diff(file_id, oldpath, newpath)
894
def diff(self, file_id, old_path, new_path):
895
"""Perform a diff of a single file
897
:param file_id: file-id of the file
898
:param old_path: The path of the file in the old tree
899
:param new_path: The path of the file in the new tree
902
old_kind = self.old_tree.kind(file_id)
903
except (errors.NoSuchId, errors.NoSuchFile):
906
new_kind = self.new_tree.kind(file_id)
907
except (errors.NoSuchId, errors.NoSuchFile):
910
result = DiffPath._diff_many(self.differs, file_id, old_path,
911
new_path, old_kind, new_kind)
912
if result is DiffPath.CANNOT_DIFF:
913
error_path = new_path
914
if error_path is None:
915
error_path = old_path
916
raise errors.NoDiffFound(error_path)