15
15
# along with this program; if not, write to the Free Software
16
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18
from bzrlib.trace import mutter
19
from bzrlib.errors import BzrError
20
from bzrlib.delta import compare_trees
18
from trace import mutter
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.
26
22
def internal_diff(old_label, oldlines, new_label, newlines, to_file):
101
'--label', old_label,
103
'--label', new_label,
106
# diff only allows one style to be specified; they don't override.
107
# note that some of these take optargs, and the optargs can be
108
# directly appended to the options.
109
# this is only an approximate parser; it doesn't properly understand
111
for s in ['-c', '-u', '-C', '-U',
116
'-y', '--side-by-side',
128
diffcmd.extend(diff_opts)
130
rc = os.spawnvp(os.P_WAIT, 'diff', diffcmd)
132
if rc != 0 and rc != 1:
133
# returns 1 if files differ; that's OK
135
msg = 'signal %d' % (-rc)
137
msg = 'exit code %d' % rc
139
raise BzrError('external diff failed with %s; command: %r' % (rc, diffcmd))
98
system('diff -u --label %s %s --label %s %s' % (old_label, oldtmpf.name, new_label, newtmpf.name))
141
100
oldtmpf.close() # and delete
146
def show_diff(b, revision, specific_files, external_diff_options=None):
147
"""Shortcut for showing the diff to the working tree.
153
None for each, or otherwise the old revision to compare against.
155
The more general form is show_diff_trees(), where the caller
156
supplies any two trees.
105
def diff_file(old_label, oldlines, new_label, newlines, to_file):
107
differ = external_diff
109
differ = internal_diff
111
differ(old_label, oldlines, new_label, newlines, to_file)
115
def show_diff(b, revision, specific_files):
160
118
if revision == None:
165
123
new_tree = b.working_tree()
167
show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
168
external_diff_options)
172
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
173
external_diff_options=None):
125
show_diff_trees(old_tree, new_tree, sys.stdout, specific_files)
129
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None):
174
130
"""Show in text form the changes from one tree to another.
177
133
If set, include only changes to these files.
179
external_diff_options
180
If set, use an external GNU diff and pass these options.
183
136
# TODO: Options to control putting on a prefix or suffix, perhaps as a format string
192
145
# TODO: Generation of pseudo-diffs for added/deleted files could
193
146
# be usefully made into a much faster special case.
195
if external_diff_options:
196
assert isinstance(external_diff_options, basestring)
197
opts = external_diff_options.split()
198
def diff_file(olab, olines, nlab, nlines, to_file):
199
external_diff(olab, olines, nlab, nlines, to_file, opts)
201
diff_file = internal_diff
204
148
delta = compare_trees(old_tree, new_tree, want_unchanged=False,
205
149
specific_files=specific_files)
207
151
for path, file_id, kind in delta.removed:
208
print >>to_file, '*** removed %s %r' % (kind, path)
152
print '*** removed %s %r' % (kind, path)
209
153
if kind == 'file':
210
154
diff_file(old_label + path,
211
155
old_tree.get_file(file_id).readlines(),
189
class TreeDelta(object):
190
"""Describes changes from one tree to another.
199
(oldpath, newpath, id, kind, text_modified)
205
Each id is listed only once.
207
Files that are both modified and renamed are listed only in
208
renamed, with the text_modified flag true.
210
The lists are normally sorted when the delta is created.
220
def touches_file_id(self, file_id):
221
"""Return True if file_id is modified by this delta."""
222
for l in self.added, self.removed, self.modified:
226
for v in self.renamed:
232
def show(self, to_file, show_ids=False, show_unchanged=False):
233
def show_list(files):
234
for path, fid, kind in files:
235
if kind == 'directory':
237
elif kind == 'symlink':
241
print >>to_file, ' %-30s %s' % (path, fid)
243
print >>to_file, ' ', path
246
print >>to_file, 'removed:'
247
show_list(self.removed)
250
print >>to_file, 'added:'
251
show_list(self.added)
254
print >>to_file, 'renamed:'
255
for oldpath, newpath, fid, kind, text_modified in self.renamed:
257
print >>to_file, ' %s => %s %s' % (oldpath, newpath, fid)
259
print >>to_file, ' %s => %s' % (oldpath, newpath)
262
print >>to_file, 'modified:'
263
show_list(self.modified)
265
if show_unchanged and self.unchanged:
266
print >>to_file, 'unchanged:'
267
show_list(self.unchanged)
271
def compare_trees(old_tree, new_tree, want_unchanged, specific_files=None):
272
"""Describe changes from one tree to another.
274
Returns a TreeDelta with details of added, modified, renamed, and
277
The root entry is specifically exempt.
279
This only considers versioned files.
282
If true, also list files unchanged from one version to
286
If true, only check for changes to specified names or
290
from osutils import is_inside_any
292
old_inv = old_tree.inventory
293
new_inv = new_tree.inventory
295
mutter('start compare_trees')
297
# TODO: match for specific files can be rather smarter by finding
298
# the IDs of those files up front and then considering only that.
300
for file_id in old_tree:
301
if file_id in new_tree:
302
kind = old_inv.get_file_kind(file_id)
303
assert kind == new_inv.get_file_kind(file_id)
305
assert kind in ('file', 'directory', 'symlink', 'root_directory'), \
306
'invalid file kind %r' % kind
308
if kind == 'root_directory':
311
old_path = old_inv.id2path(file_id)
312
new_path = new_inv.id2path(file_id)
315
if (not is_inside_any(specific_files, old_path)
316
and not is_inside_any(specific_files, new_path)):
320
old_sha1 = old_tree.get_file_sha1(file_id)
321
new_sha1 = new_tree.get_file_sha1(file_id)
322
text_modified = (old_sha1 != new_sha1)
324
## mutter("no text to check for %r %r" % (file_id, kind))
325
text_modified = False
327
# TODO: Can possibly avoid calculating path strings if the
328
# two files are unchanged and their names and parents are
329
# the same and the parents are unchanged all the way up.
330
# May not be worthwhile.
332
if old_path != new_path:
333
delta.renamed.append((old_path, new_path, file_id, kind,
336
delta.modified.append((new_path, file_id, kind))
338
delta.unchanged.append((new_path, file_id, kind))
340
kind = old_inv.get_file_kind(file_id)
341
old_path = old_inv.id2path(file_id)
343
if not is_inside_any(specific_files, old_path):
345
delta.removed.append((old_path, file_id, kind))
347
mutter('start looking for new files')
348
for file_id in new_inv:
349
if file_id in old_inv:
351
new_path = new_inv.id2path(file_id)
353
if not is_inside_any(specific_files, new_path):
355
kind = new_inv.get_file_kind(file_id)
356
delta.added.append((new_path, file_id, kind))
361
delta.modified.sort()
362
delta.unchanged.sort()