~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

  • Committer: Martin Pool
  • Date: 2005-05-11 07:45:56 UTC
  • Revision ID: mbp@sourcefrog.net-20050511074556-88739f06c90aed43
- rewrite diff using compare_trees()
- include entry kind in TreeDelta

Show diffs side-by-side

added added

removed removed

Lines of Context:
130
130
 
131
131
 
132
132
 
 
133
def _diff_one(oldlines, newlines, to_file, **kw):
 
134
    import difflib
 
135
    
 
136
    # FIXME: difflib is wrong if there is no trailing newline.
 
137
    # The syntax used by patch seems to be "\ No newline at
 
138
    # end of file" following the last diff line from that
 
139
    # file.  This is not trivial to insert into the
 
140
    # unified_diff output and it might be better to just fix
 
141
    # or replace that function.
 
142
 
 
143
    # In the meantime we at least make sure the patch isn't
 
144
    # mangled.
 
145
 
 
146
 
 
147
    # Special workaround for Python2.3, where difflib fails if
 
148
    # both sequences are empty.
 
149
    if not oldlines and not newlines:
 
150
        return
 
151
 
 
152
    nonl = False
 
153
 
 
154
    if oldlines and (oldlines[-1][-1] != '\n'):
 
155
        oldlines[-1] += '\n'
 
156
        nonl = True
 
157
    if newlines and (newlines[-1][-1] != '\n'):
 
158
        newlines[-1] += '\n'
 
159
        nonl = True
 
160
 
 
161
    ud = difflib.unified_diff(oldlines, newlines, **kw)
 
162
 
 
163
    # work-around for difflib being too smart for its own good
 
164
    # if /dev/null is "1,0", patch won't recognize it as /dev/null
 
165
    if not oldlines:
 
166
        ud = list(ud)
 
167
        ud[2] = ud[2].replace('-1,0', '-0,0')
 
168
    elif not newlines:
 
169
        ud = list(ud)
 
170
        ud[2] = ud[2].replace('+1,0', '+0,0')
 
171
 
 
172
    to_file.writelines(ud)
 
173
    if nonl:
 
174
        print >>to_file, "\\ No newline at end of file"
 
175
    print >>to_file
 
176
 
 
177
 
133
178
def show_diff(b, revision, file_list):
134
 
    import difflib, sys, types
 
179
    import sys
 
180
 
 
181
    if file_list:
 
182
        raise NotImplementedError('diff on restricted files broken at the moment')
135
183
    
136
184
    if revision == None:
137
185
        old_tree = b.basis_tree()
152
200
    # TODO: Generation of pseudo-diffs for added/deleted files could
153
201
    # be usefully made into a much faster special case.
154
202
 
155
 
    # TODO: Better to return them in sorted order I think.
156
 
 
157
 
    if file_list:
158
 
        file_list = [b.relpath(f) for f in file_list]
159
 
 
160
 
    # FIXME: If given a file list, compare only those files rather
161
 
    # than comparing everything and then throwing stuff away.
162
 
    
163
 
    for file_state, fid, old_name, new_name, kind in diff_trees(old_tree, new_tree):
164
 
 
165
 
        if file_list and (new_name not in file_list):
166
 
            continue
167
 
        
168
 
        # Don't show this by default; maybe do it if an option is passed
169
 
        # idlabel = '      {%s}' % fid
170
 
        idlabel = ''
171
 
 
172
 
        def diffit(oldlines, newlines, **kw):
173
 
            
174
 
            # FIXME: difflib is wrong if there is no trailing newline.
175
 
            # The syntax used by patch seems to be "\ No newline at
176
 
            # end of file" following the last diff line from that
177
 
            # file.  This is not trivial to insert into the
178
 
            # unified_diff output and it might be better to just fix
179
 
            # or replace that function.
180
 
 
181
 
            # In the meantime we at least make sure the patch isn't
182
 
            # mangled.
183
 
            
184
 
 
185
 
            # Special workaround for Python2.3, where difflib fails if
186
 
            # both sequences are empty.
187
 
            if not oldlines and not newlines:
188
 
                return
189
 
 
190
 
            nonl = False
191
 
 
192
 
            if oldlines and (oldlines[-1][-1] != '\n'):
193
 
                oldlines[-1] += '\n'
194
 
                nonl = True
195
 
            if newlines and (newlines[-1][-1] != '\n'):
196
 
                newlines[-1] += '\n'
197
 
                nonl = True
198
 
 
199
 
            ud = difflib.unified_diff(oldlines, newlines, **kw)
200
 
 
201
 
            # work-around for difflib being too smart for its own good
202
 
            # if /dev/null is "1,0", patch won't recognize it as /dev/null
203
 
            if not oldlines:
204
 
                ud = list(ud)
205
 
                ud[2] = ud[2].replace('-1,0', '-0,0')
206
 
            elif not newlines:
207
 
                ud = list(ud)
208
 
                ud[2] = ud[2].replace('+1,0', '+0,0')
209
 
            
210
 
            sys.stdout.writelines(ud)
211
 
            if nonl:
212
 
                print "\\ No newline at end of file"
213
 
            sys.stdout.write('\n')
214
 
        
215
 
        if file_state in ['.', '?', 'I']:
216
 
            continue
217
 
        elif file_state == 'A':
218
 
            print '*** added %s %r' % (kind, new_name)
219
 
            if kind == 'file':
220
 
                diffit([],
221
 
                       new_tree.get_file(fid).readlines(),
222
 
                       fromfile=DEVNULL,
223
 
                       tofile=new_label + new_name + idlabel)
224
 
        elif file_state == 'D':
225
 
            assert isinstance(old_name, types.StringTypes)
226
 
            print '*** deleted %s %r' % (kind, old_name)
227
 
            if kind == 'file':
228
 
                diffit(old_tree.get_file(fid).readlines(), [],
229
 
                       fromfile=old_label + old_name + idlabel,
230
 
                       tofile=DEVNULL)
231
 
        elif file_state in ['M', 'R']:
232
 
            if file_state == 'M':
233
 
                assert kind == 'file'
234
 
                assert old_name == new_name
235
 
                print '*** modified %s %r' % (kind, new_name)
236
 
            elif file_state == 'R':
237
 
                print '*** renamed %s %r => %r' % (kind, old_name, new_name)
238
 
 
239
 
            if kind == 'file':
240
 
                diffit(old_tree.get_file(fid).readlines(),
241
 
                       new_tree.get_file(fid).readlines(),
242
 
                       fromfile=old_label + old_name + idlabel,
243
 
                       tofile=new_label + new_name)
244
 
        else:
245
 
            raise BzrError("can't represent state %s {%s}" % (file_state, fid))
 
203
    delta = compare_trees(old_tree, new_tree, want_unchanged=False)
 
204
 
 
205
    for path, file_id, kind in delta.removed:
 
206
        print '*** removed %s %r' % (kind, path)
 
207
        if kind == 'file':
 
208
            _diff_one(old_tree.get_file(file_id).readlines(),
 
209
                   [],
 
210
                   sys.stdout,
 
211
                   fromfile=old_label + path,
 
212
                   tofile=DEVNULL)
 
213
 
 
214
    for path, file_id, kind in delta.added:
 
215
        print '*** added %s %r' % (kind, path)
 
216
        if kind == 'file':
 
217
            _diff_one([],
 
218
                   new_tree.get_file(file_id).readlines(),
 
219
                   sys.stdout,
 
220
                   fromfile=DEVNULL,
 
221
                   tofile=new_label + path)
 
222
 
 
223
    for old_path, new_path, file_id, kind, text_modified in delta.renamed:
 
224
        print '*** renamed %s %r => %r' % (kind, old_path, new_path)
 
225
        if text_modified:
 
226
            _diff_one(old_tree.get_file(file_id).readlines(),
 
227
                   new_tree.get_file(file_id).readlines(),
 
228
                   sys.stdout,
 
229
                   fromfile=old_label + old_path,
 
230
                   tofile=new_label + new_path)
 
231
 
 
232
    for path, file_id, kind in delta.modified:
 
233
        print '*** modified %s %r' % (kind, path)
 
234
        if kind == 'file':
 
235
            _diff_one(old_tree.get_file(file_id).readlines(),
 
236
                   new_tree.get_file(file_id).readlines(),
 
237
                   sys.stdout,
 
238
                   fromfile=old_label + path,
 
239
                   tofile=new_label + path)
246
240
 
247
241
 
248
242
 
252
246
    Contains four lists:
253
247
 
254
248
    added
255
 
        (path, id)
 
249
        (path, id, kind)
256
250
    removed
257
 
        (path, id)
 
251
        (path, id, kind)
258
252
    renamed
259
 
        (oldpath, newpath, id, text_modified)
 
253
        (oldpath, newpath, id, kind, text_modified)
260
254
    modified
261
 
        (path, id)
 
255
        (path, id, kind)
262
256
    unchanged
263
 
        (path, id)
 
257
        (path, id, kind)
264
258
 
265
259
    Each id is listed only once.
266
260
 
278
272
 
279
273
    def show(self, to_file, show_ids=False, show_unchanged=False):
280
274
        def show_list(files):
281
 
            for path, fid in files:
 
275
            for path, fid, kind in files:
 
276
                if kind == 'directory':
 
277
                    path += '/'
 
278
                elif kind == 'symlink':
 
279
                    path += '@'
 
280
                    
282
281
                if show_ids:
283
282
                    print >>to_file, '  %-30s %s' % (path, fid)
284
283
                else:
285
284
                    print >>to_file, ' ', path
286
285
            
287
286
        if self.removed:
288
 
            print >>to_file, 'removed files:'
 
287
            print >>to_file, 'removed:'
289
288
            show_list(self.removed)
290
289
                
291
290
        if self.added:
292
 
            print >>to_file, 'added files:'
 
291
            print >>to_file, 'added:'
293
292
            show_list(self.added)
294
293
 
295
294
        if self.renamed:
296
 
            print >>to_file, 'renamed files:'
297
 
            for oldpath, newpath, fid, text_modified in self.renamed:
 
295
            print >>to_file, 'renamed:'
 
296
            for oldpath, newpath, fid, kind, text_modified in self.renamed:
298
297
                if show_ids:
299
298
                    print >>to_file, '  %s => %s %s' % (oldpath, newpath, fid)
300
299
                else:
301
300
                    print >>to_file, '  %s => %s' % (oldpath, newpath)
302
301
                    
303
302
        if self.modified:
304
 
            print >>to_file, 'modified files:'
 
303
            print >>to_file, 'modified:'
305
304
            show_list(self.modified)
306
305
            
307
306
        if show_unchanged and self.unchanged:
308
 
            print >>to_file, 'unchanged files:'
 
307
            print >>to_file, 'unchanged:'
309
308
            show_list(self.unchanged)
310
309
 
311
310
 
314
313
    old_inv = old_tree.inventory
315
314
    new_inv = new_tree.inventory
316
315
    delta = TreeDelta()
 
316
    mutter('start compare_trees')
317
317
    for file_id in old_tree:
318
318
        if file_id in new_tree:
319
319
            old_path = old_inv.id2path(file_id)
320
320
            new_path = new_inv.id2path(file_id)
321
321
 
322
322
            kind = old_inv.get_file_kind(file_id)
 
323
            assert kind == new_inv.get_file_kind(file_id)
 
324
            
323
325
            assert kind in ('file', 'directory', 'symlink', 'root_directory'), \
324
326
                   'invalid file kind %r' % kind
325
327
            if kind == 'file':
336
338
            # May not be worthwhile.
337
339
            
338
340
            if old_path != new_path:
339
 
                delta.renamed.append((old_path, new_path, file_id, text_modified))
 
341
                delta.renamed.append((old_path, new_path, file_id, kind,
 
342
                                      text_modified))
340
343
            elif text_modified:
341
 
                delta.modified.append((new_path, file_id))
 
344
                delta.modified.append((new_path, file_id, kind))
342
345
            elif want_unchanged:
343
 
                delta.unchanged.append((new_path, file_id))
 
346
                delta.unchanged.append((new_path, file_id, kind))
344
347
        else:
345
 
            delta.removed.append((old_inv.id2path(file_id), file_id))
 
348
            delta.removed.append((old_inv.id2path(file_id), file_id, kind))
 
349
 
 
350
    mutter('start looking for new files')
346
351
    for file_id in new_inv:
347
352
        if file_id in old_inv:
348
353
            continue
349
 
        delta.added.append((new_inv.id2path(file_id), file_id))
 
354
        kind = new_inv.get_file_kind(file_id)
 
355
        delta.added.append((new_inv.id2path(file_id), file_id, kind))
350
356
            
351
357
    delta.removed.sort()
352
358
    delta.added.sort()