~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

  • Committer: Michael Ellerman
  • Date: 2005-12-10 22:11:13 UTC
  • mto: This revision was merged to the branch mainline in revision 1528.
  • Revision ID: michael@ellerman.id.au-20051210221113-99ca561aaab4661e
Simplify handling of DivergedBranches in cmd_pull()

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2004, 2005, 2006 Canonical Ltd.
 
1
# -*- coding: UTF-8 -*-
2
2
 
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
 
import time
18
 
 
 
17
from bzrlib.trace import mutter
 
18
from bzrlib.errors import BzrError
19
19
from bzrlib.delta import compare_trees
20
 
from bzrlib.errors import BzrError
21
 
import bzrlib.errors as errors
22
 
from bzrlib.patiencediff import unified_diff
23
 
import bzrlib.patiencediff
24
 
from bzrlib.symbol_versioning import *
25
 
from bzrlib.textfile import check_text_lines
26
 
from bzrlib.trace import mutter
27
 
 
28
20
 
29
21
# TODO: Rather than building a changeset object, we should probably
30
22
# invoke callbacks on an object.  That object can either accumulate a
31
23
# list, write them out directly, etc etc.
32
24
 
33
 
def internal_diff(old_filename, oldlines, new_filename, newlines, to_file,
34
 
                  allow_binary=False, sequence_matcher=None,
35
 
                  path_encoding='utf8'):
 
25
def internal_diff(old_filename, oldlines, new_filename, newlines, to_file):
 
26
    import difflib
 
27
    
36
28
    # FIXME: difflib is wrong if there is no trailing newline.
37
29
    # The syntax used by patch seems to be "\ No newline at
38
30
    # end of file" following the last diff line from that
48
40
    # both sequences are empty.
49
41
    if not oldlines and not newlines:
50
42
        return
51
 
    
52
 
    if allow_binary is False:
53
 
        check_text_lines(oldlines)
54
 
        check_text_lines(newlines)
55
43
 
56
 
    if sequence_matcher is None:
57
 
        sequence_matcher = bzrlib.patiencediff.PatienceSequenceMatcher
58
 
    ud = unified_diff(oldlines, newlines,
59
 
                      fromfile=old_filename.encode(path_encoding),
60
 
                      tofile=new_filename.encode(path_encoding),
61
 
                      sequencematcher=sequence_matcher)
 
44
    ud = difflib.unified_diff(oldlines, newlines,
 
45
                              fromfile=old_filename+'\t', 
 
46
                              tofile=new_filename+'\t')
62
47
 
63
48
    ud = list(ud)
64
49
    # work-around for difflib being too smart for its own good
113
98
        if not diff_opts:
114
99
            diff_opts = []
115
100
        diffcmd = ['diff',
116
 
                   '--label', old_filename,
 
101
                   '--label', old_filename+'\t',
117
102
                   oldtmpf.name,
118
 
                   '--label', new_filename,
 
103
                   '--label', new_filename+'\t',
119
104
                   newtmpf.name]
120
105
 
121
106
        # diff only allows one style to be specified; they don't override.
156
141
        oldtmpf.close()                 # and delete
157
142
        newtmpf.close()
158
143
 
159
 
 
160
 
@deprecated_function(zero_eight)
161
144
def show_diff(b, from_spec, specific_files, external_diff_options=None,
162
145
              revision2=None, output=None, b2=None):
163
146
    """Shortcut for showing the diff to the working tree.
164
147
 
165
 
    Please use show_diff_trees instead.
166
 
 
167
148
    b
168
149
        Branch.
169
150
 
178
159
        output = sys.stdout
179
160
 
180
161
    if from_spec is None:
181
 
        old_tree = b.bzrdir.open_workingtree()
182
162
        if b2 is None:
183
 
            old_tree = old_tree = old_tree.basis_tree()
 
163
            old_tree = b.basis_tree()
 
164
        else:
 
165
            old_tree = b.working_tree()
184
166
    else:
185
 
        old_tree = b.repository.revision_tree(from_spec.in_history(b).rev_id)
 
167
        old_tree = b.revision_tree(from_spec.in_history(b).rev_id)
186
168
 
187
169
    if revision2 is None:
188
170
        if b2 is None:
189
 
            new_tree = b.bzrdir.open_workingtree()
 
171
            new_tree = b.working_tree()
190
172
        else:
191
 
            new_tree = b2.bzrdir.open_workingtree()
 
173
            new_tree = b2.working_tree()
192
174
    else:
193
 
        new_tree = b.repository.revision_tree(revision2.in_history(b).rev_id)
 
175
        new_tree = b.revision_tree(revision2.in_history(b).rev_id)
194
176
 
195
177
    return show_diff_trees(old_tree, new_tree, output, specific_files,
196
178
                           external_diff_options)
197
179
 
198
180
 
199
 
def diff_cmd_helper(tree, specific_files, external_diff_options, 
200
 
                    old_revision_spec=None, new_revision_spec=None,
201
 
                    old_label='a/', new_label='b/'):
202
 
    """Helper for cmd_diff.
203
 
 
204
 
   tree 
205
 
        A WorkingTree
206
 
 
207
 
    specific_files
208
 
        The specific files to compare, or None
209
 
 
210
 
    external_diff_options
211
 
        If non-None, run an external diff, and pass it these options
212
 
 
213
 
    old_revision_spec
214
 
        If None, use basis tree as old revision, otherwise use the tree for
215
 
        the specified revision. 
216
 
 
217
 
    new_revision_spec
218
 
        If None, use working tree as new revision, otherwise use the tree for
219
 
        the specified revision.
220
 
    
221
 
    The more general form is show_diff_trees(), where the caller
222
 
    supplies any two trees.
223
 
    """
224
 
    import sys
225
 
    output = sys.stdout
226
 
    def spec_tree(spec):
227
 
        revision_id = spec.in_store(tree.branch).rev_id
228
 
        return tree.branch.repository.revision_tree(revision_id)
229
 
    if old_revision_spec is None:
230
 
        old_tree = tree.basis_tree()
231
 
    else:
232
 
        old_tree = spec_tree(old_revision_spec)
233
 
 
234
 
    if new_revision_spec is None:
235
 
        new_tree = tree
236
 
    else:
237
 
        new_tree = spec_tree(new_revision_spec)
238
 
 
239
 
    return show_diff_trees(old_tree, new_tree, sys.stdout, specific_files,
240
 
                           external_diff_options,
241
 
                           old_label=old_label, new_label=new_label)
242
 
 
243
181
 
244
182
def show_diff_trees(old_tree, new_tree, to_file, specific_files=None,
245
 
                    external_diff_options=None,
246
 
                    old_label='a/', new_label='b/'):
 
183
                    external_diff_options=None):
247
184
    """Show in text form the changes from one tree to another.
248
185
 
249
186
    to_files
252
189
    external_diff_options
253
190
        If set, use an external GNU diff and pass these options.
254
191
    """
255
 
    old_tree.lock_read()
256
 
    try:
257
 
        new_tree.lock_read()
258
 
        try:
259
 
            return _show_diff_trees(old_tree, new_tree, to_file,
260
 
                                    specific_files, external_diff_options,
261
 
                                    old_label=old_label, new_label=new_label)
262
 
        finally:
263
 
            new_tree.unlock()
264
 
    finally:
265
 
        old_tree.unlock()
266
 
 
267
 
 
268
 
def _show_diff_trees(old_tree, new_tree, to_file,
269
 
                     specific_files, external_diff_options, 
270
 
                     old_label='a/', new_label='b/' ):
271
 
 
272
 
    # GNU Patch uses the epoch date to detect files that are being added
273
 
    # or removed in a diff.
274
 
    EPOCH_DATE = '1970-01-01 00:00:00 +0000'
 
192
 
 
193
    # TODO: Options to control putting on a prefix or suffix, perhaps as a format string
 
194
    old_label = ''
 
195
    new_label = ''
 
196
 
 
197
    DEVNULL = '/dev/null'
 
198
    # Windows users, don't panic about this filename -- it is a
 
199
    # special signal to GNU patch that the file should be created or
 
200
    # deleted respectively.
275
201
 
276
202
    # TODO: Generation of pseudo-diffs for added/deleted files could
277
203
    # be usefully made into a much faster special case.
278
204
 
279
 
    _raise_if_doubly_unversioned(specific_files, old_tree, new_tree)
280
 
 
281
205
    if external_diff_options:
282
206
        assert isinstance(external_diff_options, basestring)
283
207
        opts = external_diff_options.split()
286
210
    else:
287
211
        diff_file = internal_diff
288
212
    
 
213
 
289
214
    delta = compare_trees(old_tree, new_tree, want_unchanged=False,
290
215
                          specific_files=specific_files)
291
216
 
292
217
    has_changes = 0
293
218
    for path, file_id, kind in delta.removed:
294
219
        has_changes = 1
295
 
        print >>to_file, '=== removed %s %r' % (kind, path.encode('utf8'))
296
 
        old_name = '%s%s\t%s' % (old_label, path,
297
 
                                 _patch_header_date(old_tree, file_id, path))
298
 
        new_name = '%s%s\t%s' % (new_label, path, EPOCH_DATE)
299
 
        old_tree.inventory[file_id].diff(diff_file, old_name, old_tree,
300
 
                                         new_name, None, None, to_file)
 
220
        print >>to_file, '=== removed %s %r' % (kind, path)
 
221
        old_tree.inventory[file_id].diff(diff_file, old_label + path, old_tree,
 
222
                                         DEVNULL, None, None, to_file)
301
223
    for path, file_id, kind in delta.added:
302
224
        has_changes = 1
303
 
        print >>to_file, '=== added %s %r' % (kind, path.encode('utf8'))
304
 
        old_name = '%s%s\t%s' % (old_label, path, EPOCH_DATE)
305
 
        new_name = '%s%s\t%s' % (new_label, path,
306
 
                                 _patch_header_date(new_tree, file_id, path))
307
 
        new_tree.inventory[file_id].diff(diff_file, new_name, new_tree,
308
 
                                         old_name, None, None, to_file, 
 
225
        print >>to_file, '=== added %s %r' % (kind, path)
 
226
        new_tree.inventory[file_id].diff(diff_file, new_label + path, new_tree,
 
227
                                         DEVNULL, None, None, to_file, 
309
228
                                         reverse=True)
310
229
    for (old_path, new_path, file_id, kind,
311
230
         text_modified, meta_modified) in delta.renamed:
312
231
        has_changes = 1
313
232
        prop_str = get_prop_change(meta_modified)
314
233
        print >>to_file, '=== renamed %s %r => %r%s' % (
315
 
                    kind, old_path.encode('utf8'),
316
 
                    new_path.encode('utf8'), prop_str)
317
 
        old_name = '%s%s\t%s' % (old_label, old_path,
318
 
                                 _patch_header_date(old_tree, file_id,
319
 
                                                    old_path))
320
 
        new_name = '%s%s\t%s' % (new_label, new_path,
321
 
                                 _patch_header_date(new_tree, file_id,
322
 
                                                    new_path))
323
 
        _maybe_diff_file_or_symlink(old_name, old_tree, file_id,
324
 
                                    new_name, new_tree,
 
234
                          kind, old_path, new_path, prop_str)
 
235
        _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
 
236
                                    new_label, new_path, new_tree,
325
237
                                    text_modified, kind, to_file, diff_file)
326
238
    for path, file_id, kind, text_modified, meta_modified in delta.modified:
327
239
        has_changes = 1
328
240
        prop_str = get_prop_change(meta_modified)
329
 
        print >>to_file, '=== modified %s %r%s' % (kind, path.encode('utf8'), prop_str)
330
 
        old_name = '%s%s\t%s' % (old_label, path,
331
 
                                 _patch_header_date(old_tree, file_id, path))
332
 
        new_name = '%s%s\t%s' % (new_label, path,
333
 
                                 _patch_header_date(new_tree, file_id, path))
 
241
        print >>to_file, '=== modified %s %r%s' % (kind, path, prop_str)
334
242
        if text_modified:
335
 
            _maybe_diff_file_or_symlink(old_name, old_tree, file_id,
336
 
                                        new_name, new_tree,
 
243
            _maybe_diff_file_or_symlink(old_label, path, old_tree, file_id,
 
244
                                        new_label, path, new_tree,
337
245
                                        True, kind, to_file, diff_file)
338
 
 
339
246
    return has_changes
340
 
 
341
 
 
342
 
def _patch_header_date(tree, file_id, path):
343
 
    """Returns a timestamp suitable for use in a patch header."""
344
 
    tm = time.gmtime(tree.get_file_mtime(file_id, path))
345
 
    return time.strftime('%Y-%m-%d %H:%M:%S +0000', tm)
346
 
 
347
 
 
348
 
def _raise_if_doubly_unversioned(specific_files, old_tree, new_tree):
349
 
    """Complain if paths are not versioned in either tree."""
350
 
    if not specific_files:
351
 
        return
352
 
    old_unversioned = old_tree.filter_unversioned_files(specific_files)
353
 
    new_unversioned = new_tree.filter_unversioned_files(specific_files)
354
 
    unversioned = old_unversioned.intersection(new_unversioned)
355
 
    if unversioned:
356
 
        raise errors.PathsNotVersionedError(sorted(unversioned))
357
 
    
358
 
 
359
 
def _raise_if_nonexistent(paths, old_tree, new_tree):
360
 
    """Complain if paths are not in either inventory or tree.
361
 
 
362
 
    It's OK with the files exist in either tree's inventory, or 
363
 
    if they exist in the tree but are not versioned.
364
 
    
365
 
    This can be used by operations such as bzr status that can accept
366
 
    unknown or ignored files.
367
 
    """
368
 
    mutter("check paths: %r", paths)
369
 
    if not paths:
370
 
        return
371
 
    s = old_tree.filter_unversioned_files(paths)
372
 
    s = new_tree.filter_unversioned_files(s)
373
 
    s = [path for path in s if not new_tree.has_filename(path)]
374
 
    if s:
375
 
        raise errors.PathsDoNotExist(sorted(s))
376
 
 
 
247
    
377
248
 
378
249
def get_prop_change(meta_modified):
379
250
    if meta_modified:
382
253
        return  ""
383
254
 
384
255
 
385
 
def _maybe_diff_file_or_symlink(old_path, old_tree, file_id,
386
 
                                new_path, new_tree, text_modified,
 
256
def _maybe_diff_file_or_symlink(old_label, old_path, old_tree, file_id,
 
257
                                new_label, new_path, new_tree, text_modified,
387
258
                                kind, to_file, diff_file):
388
259
    if text_modified:
389
260
        new_entry = new_tree.inventory[file_id]
390
261
        old_tree.inventory[file_id].diff(diff_file,
391
 
                                         old_path, old_tree,
392
 
                                         new_path, new_entry, 
 
262
                                         old_label + old_path, old_tree,
 
263
                                         new_label + new_path, new_entry, 
393
264
                                         new_tree, to_file)