2
# Copyright (C) 2005, 2006, 2007 Canonical Ltd
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18
from __future__ import absolute_import
20
from bzrlib.lazy_import import lazy_import
21
lazy_import(globals(), """
29
__all__ = ['PatienceSequenceMatcher', 'unified_diff', 'unified_diff_files']
32
# This is a version of unified_diff which only adds a factory parameter
33
# so that you can override the default SequenceMatcher
34
# this has been submitted as a patch to python
35
def unified_diff(a, b, fromfile='', tofile='', fromfiledate='',
36
tofiledate='', n=3, lineterm='\n',
37
sequencematcher=None):
39
Compare two sequences of lines; generate the delta as a unified diff.
41
Unified diffs are a compact way of showing line changes and a few
42
lines of context. The number of context lines is set by 'n' which
45
By default, the diff control lines (those with ---, +++, or @@) are
46
created with a trailing newline. This is helpful so that inputs
47
created from file.readlines() result in diffs that are suitable for
48
file.writelines() since both the inputs and outputs have trailing
51
For inputs that do not have trailing newlines, set the lineterm
52
argument to "" so that the output will be uniformly newline free.
54
The unidiff format normally has a header for filenames and modification
55
times. Any or all of these may be specified using strings for
56
'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'. The modification
57
times are normally expressed in the format returned by time.ctime().
61
>>> for line in unified_diff('one two three four'.split(),
62
... 'zero one tree four'.split(), 'Original', 'Current',
63
... 'Sat Jan 26 23:30:50 1991', 'Fri Jun 06 10:20:52 2003',
66
--- Original Sat Jan 26 23:30:50 1991
67
+++ Current Fri Jun 06 10:20:52 2003
76
if sequencematcher is None:
77
sequencematcher = difflib.SequenceMatcher
80
fromfiledate = '\t' + str(fromfiledate)
82
tofiledate = '\t' + str(tofiledate)
85
for group in sequencematcher(None,a,b).get_grouped_opcodes(n):
87
yield '--- %s%s%s' % (fromfile, fromfiledate, lineterm)
88
yield '+++ %s%s%s' % (tofile, tofiledate, lineterm)
90
i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4]
91
yield "@@ -%d,%d +%d,%d @@%s" % (i1+1, i2-i1, j1+1, j2-j1, lineterm)
92
for tag, i1, i2, j1, j2 in group:
97
if tag == 'replace' or tag == 'delete':
100
if tag == 'replace' or tag == 'insert':
101
for line in b[j1:j2]:
105
def unified_diff_files(a, b, sequencematcher=None):
106
"""Generate the diff for two files.
108
# Should this actually be an error?
115
file_a = open(a, 'rb')
116
time_a = os.stat(a).st_mtime
122
file_b = open(b, 'rb')
123
time_b = os.stat(b).st_mtime
125
# TODO: Include fromfiledate and tofiledate
126
return unified_diff(file_a.readlines(), file_b.readlines(),
127
fromfile=a, tofile=b,
128
sequencematcher=sequencematcher)
132
from bzrlib._patiencediff_c import (
133
unique_lcs_c as unique_lcs,
134
recurse_matches_c as recurse_matches,
135
PatienceSequenceMatcher_c as PatienceSequenceMatcher
138
from bzrlib._patiencediff_py import (
139
unique_lcs_py as unique_lcs,
140
recurse_matches_py as recurse_matches,
141
PatienceSequenceMatcher_py as PatienceSequenceMatcher
147
p = optparse.OptionParser(usage='%prog [options] file_a file_b'
148
'\nFiles can be "-" to read from stdin')
149
p.add_option('--patience', dest='matcher', action='store_const', const='patience',
150
default='patience', help='Use the patience difference algorithm')
151
p.add_option('--difflib', dest='matcher', action='store_const', const='difflib',
152
default='patience', help='Use python\'s difflib algorithm')
154
algorithms = {'patience':PatienceSequenceMatcher, 'difflib':difflib.SequenceMatcher}
156
(opts, args) = p.parse_args(args)
157
matcher = algorithms[opts.matcher]
160
print 'You must supply 2 filenames to diff'
163
for line in unified_diff_files(args[0], args[1], sequencematcher=matcher):
164
sys.stdout.write(line)
167
if __name__ == '__main__':
168
sys.exit(main(sys.argv[1:]))