/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/diff.py

  • Committer: Martin Pool
  • Date: 2006-06-15 05:36:34 UTC
  • mto: This revision was merged to the branch mainline in revision 1797.
  • Revision ID: mbp@sourcefrog.net-20060615053634-4fd52ba691855659
Clean up many exception classes.

Errors indicating a user error are now shown with is_user_error on the
exception; use this rather than hardcoding a list of exceptions that should be
handled this way.

Exceptions now inherit from BzrNewException where possible to use consistent
formatting method.

Remove rather obsolete docstring test on Branch.missing_revisions.

Remove dead code from find_merge_base.


Show diffs side-by-side

added added

removed removed

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