2
# -*- coding: UTF-8 -*-
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
from trace import mutter
21
from errors import BzrError
25
def _diff_one(oldlines, newlines, to_file, **kw):
28
# FIXME: difflib is wrong if there is no trailing newline.
29
# The syntax used by patch seems to be "\ No newline at
30
# end of file" following the last diff line from that
31
# file. This is not trivial to insert into the
32
# unified_diff output and it might be better to just fix
33
# or replace that function.
35
# In the meantime we at least make sure the patch isn't
39
# Special workaround for Python2.3, where difflib fails if
40
# both sequences are empty.
41
if not oldlines and not newlines:
46
if oldlines and (oldlines[-1][-1] != '\n'):
49
if newlines and (newlines[-1][-1] != '\n'):
53
ud = difflib.unified_diff(oldlines, newlines, **kw)
55
# work-around for difflib being too smart for its own good
56
# if /dev/null is "1,0", patch won't recognize it as /dev/null
59
ud[2] = ud[2].replace('-1,0', '-0,0')
62
ud[2] = ud[2].replace('+1,0', '+0,0')
64
to_file.writelines(ud)
66
print >>to_file, "\\ No newline at end of file"
70
def show_diff(b, revision, file_list):
74
raise NotImplementedError('diff on restricted files broken at the moment')
77
old_tree = b.basis_tree()
79
old_tree = b.revision_tree(b.lookup_revision(revision))
81
new_tree = b.working_tree()
83
# TODO: Options to control putting on a prefix or suffix, perhaps as a format string
88
# Windows users, don't panic about this filename -- it is a
89
# special signal to GNU patch that the file should be created or
90
# deleted respectively.
92
# TODO: Generation of pseudo-diffs for added/deleted files could
93
# be usefully made into a much faster special case.
95
delta = compare_trees(old_tree, new_tree, want_unchanged=False)
97
for path, file_id, kind in delta.removed:
98
print '*** removed %s %r' % (kind, path)
100
_diff_one(old_tree.get_file(file_id).readlines(),
103
fromfile=old_label + path,
106
for path, file_id, kind in delta.added:
107
print '*** added %s %r' % (kind, path)
110
new_tree.get_file(file_id).readlines(),
113
tofile=new_label + path)
115
for old_path, new_path, file_id, kind, text_modified in delta.renamed:
116
print '*** renamed %s %r => %r' % (kind, old_path, new_path)
118
_diff_one(old_tree.get_file(file_id).readlines(),
119
new_tree.get_file(file_id).readlines(),
121
fromfile=old_label + old_path,
122
tofile=new_label + new_path)
124
for path, file_id, kind in delta.modified:
125
print '*** modified %s %r' % (kind, path)
127
_diff_one(old_tree.get_file(file_id).readlines(),
128
new_tree.get_file(file_id).readlines(),
130
fromfile=old_label + path,
131
tofile=new_label + path)
136
"""Describes changes from one tree to another.
145
(oldpath, newpath, id, kind, text_modified)
151
Each id is listed only once.
153
Files that are both modified and renamed are listed only in
154
renamed, with the text_modified flag true.
156
The lists are normally sorted when the delta is created.
165
def show(self, to_file, show_ids=False, show_unchanged=False):
166
def show_list(files):
167
for path, fid, kind in files:
168
if kind == 'directory':
170
elif kind == 'symlink':
174
print >>to_file, ' %-30s %s' % (path, fid)
176
print >>to_file, ' ', path
179
print >>to_file, 'removed:'
180
show_list(self.removed)
183
print >>to_file, 'added:'
184
show_list(self.added)
187
print >>to_file, 'renamed:'
188
for oldpath, newpath, fid, kind, text_modified in self.renamed:
190
print >>to_file, ' %s => %s %s' % (oldpath, newpath, fid)
192
print >>to_file, ' %s => %s' % (oldpath, newpath)
195
print >>to_file, 'modified:'
196
show_list(self.modified)
198
if show_unchanged and self.unchanged:
199
print >>to_file, 'unchanged:'
200
show_list(self.unchanged)
204
def compare_trees(old_tree, new_tree, want_unchanged):
205
old_inv = old_tree.inventory
206
new_inv = new_tree.inventory
208
mutter('start compare_trees')
209
for file_id in old_tree:
210
if file_id in new_tree:
211
kind = old_inv.get_file_kind(file_id)
212
assert kind == new_inv.get_file_kind(file_id)
214
assert kind in ('file', 'directory', 'symlink', 'root_directory'), \
215
'invalid file kind %r' % kind
217
if kind == 'root_directory':
220
old_path = old_inv.id2path(file_id)
221
new_path = new_inv.id2path(file_id)
224
old_sha1 = old_tree.get_file_sha1(file_id)
225
new_sha1 = new_tree.get_file_sha1(file_id)
226
text_modified = (old_sha1 != new_sha1)
228
## mutter("no text to check for %r %r" % (file_id, kind))
229
text_modified = False
231
# TODO: Can possibly avoid calculating path strings if the
232
# two files are unchanged and their names and parents are
233
# the same and the parents are unchanged all the way up.
234
# May not be worthwhile.
236
if old_path != new_path:
237
delta.renamed.append((old_path, new_path, file_id, kind,
240
delta.modified.append((new_path, file_id, kind))
242
delta.unchanged.append((new_path, file_id, kind))
244
delta.removed.append((old_inv.id2path(file_id), file_id, kind))
246
mutter('start looking for new files')
247
for file_id in new_inv:
248
if file_id in old_inv:
250
kind = new_inv.get_file_kind(file_id)
251
delta.added.append((new_inv.id2path(file_id), file_id, kind))
256
delta.modified.sort()
257
delta.unchanged.sort()