19
19
from errors import BzrError
22
# TODO: Rather than building a changeset object, we should probably
23
# invoke callbacks on an object. That object can either accumulate a
24
# list, write them out directly, etc etc.
23
def _diff_one(oldlines, newlines, to_file, **kw):
26
def internal_diff(old_label, oldlines, new_label, newlines, to_file):
26
29
# FIXME: difflib is wrong if there is no trailing newline.
60
64
ud[2] = ud[2].replace('+1,0', '+0,0')
62
to_file.writelines(ud)
64
69
print >>to_file, "\\ No newline at end of file"
69
def show_diff(b, revision, specific_files):
75
def external_diff(old_label, oldlines, new_label, newlines, to_file,
77
"""Display a diff by calling out to the external diff program."""
80
if to_file != sys.stdout:
81
raise NotImplementedError("sorry, can't send external diff other than to stdout yet",
84
# make sure our own output is properly ordered before the diff
87
from tempfile import NamedTemporaryFile
90
oldtmpf = NamedTemporaryFile()
91
newtmpf = NamedTemporaryFile()
94
# TODO: perhaps a special case for comparing to or from the empty
95
# sequence; can just use /dev/null on Unix
97
# TODO: if either of the files being compared already exists as a
98
# regular named file (e.g. in the working directory) then we can
99
# compare directly to that, rather than copying it.
101
oldtmpf.writelines(oldlines)
102
newtmpf.writelines(newlines)
110
'--label', old_label,
112
'--label', new_label,
115
# diff only allows one style to be specified; they don't override.
116
# note that some of these take optargs, and the optargs can be
117
# directly appended to the options.
118
# this is only an approximate parser; it doesn't properly understand
120
for s in ['-c', '-u', '-C', '-U',
125
'-y', '--side-by-side',
137
diffcmd.extend(diff_opts)
139
rc = os.spawnvp(os.P_WAIT, 'diff', diffcmd)
141
if rc != 0 and rc != 1:
142
# returns 1 if files differ; that's OK
144
msg = 'signal %d' % (-rc)
146
msg = 'exit code %d' % rc
148
raise BzrError('external diff failed with %s; command: %r' % (rc, diffcmd))
150
oldtmpf.close() # and delete
155
def show_diff(b, revision, specific_files, external_diff_options=None):
156
"""Shortcut for showing the diff to the working tree.
162
None for each, or otherwise the old revision to compare against.
164
The more general form is show_diff_trees(), where the caller
165
supplies any two trees.
72
169
if revision == None:
77
174
new_tree = b.working_tree()
79
show_diff_trees(old_tree, new_tree, sys.stdout, specific_files)
83
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None):
176
show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
177
external_diff_options)
181
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
182
external_diff_options=None):
84
183
"""Show in text form the changes from one tree to another.
87
186
If set, include only changes to these files.
188
external_diff_options
189
If set, use an external GNU diff and pass these options.
90
192
# TODO: Options to control putting on a prefix or suffix, perhaps as a format string
99
201
# TODO: Generation of pseudo-diffs for added/deleted files could
100
202
# be usefully made into a much faster special case.
204
if external_diff_options:
205
assert isinstance(external_diff_options, basestring)
206
opts = external_diff_options.split()
207
def diff_file(olab, olines, nlab, nlines, to_file):
208
external_diff(olab, olines, nlab, nlines, to_file, opts)
210
diff_file = internal_diff
102
213
delta = compare_trees(old_tree, new_tree, want_unchanged=False,
103
214
specific_files=specific_files)
105
216
for path, file_id, kind in delta.removed:
106
print '*** removed %s %r' % (kind, path)
217
print >>to_file, '*** removed %s %r' % (kind, path)
107
218
if kind == 'file':
108
_diff_one(old_tree.get_file(file_id).readlines(),
111
fromfile=old_label + path,
219
diff_file(old_label + path,
220
old_tree.get_file(file_id).readlines(),
114
225
for path, file_id, kind in delta.added:
115
print '*** added %s %r' % (kind, path)
226
print >>to_file, '*** added %s %r' % (kind, path)
116
227
if kind == 'file':
118
new_tree.get_file(file_id).readlines(),
121
tofile=new_label + path)
231
new_tree.get_file(file_id).readlines(),
123
234
for old_path, new_path, file_id, kind, text_modified in delta.renamed:
124
print '*** renamed %s %r => %r' % (kind, old_path, new_path)
235
print >>to_file, '*** renamed %s %r => %r' % (kind, old_path, new_path)
125
236
if text_modified:
126
_diff_one(old_tree.get_file(file_id).readlines(),
127
new_tree.get_file(file_id).readlines(),
129
fromfile=old_label + old_path,
130
tofile=new_label + new_path)
237
diff_file(old_label + old_path,
238
old_tree.get_file(file_id).readlines(),
239
new_label + new_path,
240
new_tree.get_file(file_id).readlines(),
132
243
for path, file_id, kind in delta.modified:
133
print '*** modified %s %r' % (kind, path)
244
print >>to_file, '*** modified %s %r' % (kind, path)
134
245
if kind == 'file':
135
_diff_one(old_tree.get_file(file_id).readlines(),
136
new_tree.get_file(file_id).readlines(),
138
fromfile=old_label + path,
139
tofile=new_label + path)
246
diff_file(old_label + path,
247
old_tree.get_file(file_id).readlines(),
249
new_tree.get_file(file_id).readlines(),
170
285
self.modified = []
171
286
self.unchanged = []
288
def __eq__(self, other):
289
if not isinstance(other, TreeDelta):
291
return self.added == other.added \
292
and self.removed == other.removed \
293
and self.renamed == other.renamed \
294
and self.modified == other.modified \
295
and self.unchanged == other.unchanged
297
def __ne__(self, other):
298
return not (self == other)
301
return "TreeDelta(added=%r, removed=%r, renamed=%r, modified=%r," \
302
" unchanged=%r)" % (self.added, self.removed, self.renamed,
303
self.modified, self.unchanged)
305
def has_changed(self):
306
changes = len(self.added) + len(self.removed) + len(self.renamed)
307
changes += len(self.modified)
308
return (changes != 0)
174
310
def touches_file_id(self, file_id):
175
311
"""Return True if file_id is modified by this delta."""