~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/conflicts.py

Update test support, and remove deprecated functions pullable_revisions and get_intervening_revisions.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005, 2007 Canonical Ltd
2
 
#
 
1
# Copyright (C) 2005 by Aaron Bentley
 
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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
#
 
7
 
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
#
 
12
 
13
13
# You should have received a copy of the GNU General Public License
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
20
20
# point down
21
21
 
22
22
import os
23
 
 
24
 
from bzrlib.lazy_import import lazy_import
25
 
lazy_import(globals(), """
26
23
import errno
27
24
 
28
 
from bzrlib import (
29
 
    builtins,
30
 
    commands,
31
 
    errors,
32
 
    osutils,
33
 
    rio,
34
 
    trace,
35
 
    )
36
 
""")
 
25
import bzrlib
 
26
from bzrlib.commands import register_command
 
27
from bzrlib.errors import BzrCommandError, NotConflicted, UnsupportedOperation
37
28
from bzrlib.option import Option
 
29
from bzrlib.osutils import rename, delete_any
 
30
from bzrlib.rio import Stanza
38
31
 
39
32
 
40
33
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
41
34
 
42
35
 
43
 
class cmd_conflicts(commands.Command):
 
36
class cmd_conflicts(bzrlib.commands.Command):
44
37
    """List files with conflicts.
45
38
 
46
39
    Merge will do its best to combine the changes in two branches, but there
48
41
    it will mark a conflict.  A conflict means that you need to fix something,
49
42
    before you should commit.
50
43
 
51
 
    Conflicts normally are listed as short, human-readable messages.  If --text
52
 
    is supplied, the pathnames of files with text conflicts are listed,
53
 
    instead.  (This is useful for editing all files with text conflicts.)
54
 
 
55
44
    Use bzr resolve when you have fixed a problem.
56
45
 
 
46
    (conflicts are determined by the presence of .BASE .TREE, and .OTHER 
 
47
    files.)
 
48
 
57
49
    See also bzr resolve.
58
50
    """
59
 
    takes_options = [
60
 
            Option('text',
61
 
                   help='List paths of files with text conflicts.'),
62
 
        ]
63
 
 
64
 
    def run(self, text=False):
 
51
    def run(self):
65
52
        from bzrlib.workingtree import WorkingTree
66
53
        wt = WorkingTree.open_containing(u'.')[0]
67
54
        for conflict in wt.conflicts():
68
 
            if text:
69
 
                if conflict.typestring != 'text conflict':
70
 
                    continue
71
 
                self.outf.write(conflict.path + '\n')
72
 
            else:
73
 
                self.outf.write(str(conflict) + '\n')
74
 
 
75
 
 
76
 
class cmd_resolve(commands.Command):
 
55
            print conflict
 
56
 
 
57
 
 
58
class cmd_resolve(bzrlib.commands.Command):
77
59
    """Mark a conflict as resolved.
78
60
 
79
61
    Merge will do its best to combine the changes in two branches, but there
81
63
    it will mark a conflict.  A conflict means that you need to fix something,
82
64
    before you should commit.
83
65
 
84
 
    Once you have fixed a problem, use "bzr resolve" to automatically mark
85
 
    text conflicts as fixed, resolve FILE to mark a specific conflict as
86
 
    resolved, or "bzr resolve --all" to mark all conflicts as resolved.
 
66
    Once you have fixed a problem, use "bzr resolve FILE.." to mark
 
67
    individual files as fixed, or "bzr resolve --all" to mark all conflicts as
 
68
    resolved.
87
69
 
88
70
    See also bzr conflicts.
89
71
    """
90
72
    aliases = ['resolved']
91
73
    takes_args = ['file*']
92
 
    takes_options = [
93
 
            Option('all', help='Resolve all conflicts in this tree.'),
94
 
            ]
 
74
    takes_options = [Option('all', help='Resolve all conflicts in this tree')]
95
75
    def run(self, file_list=None, all=False):
96
76
        from bzrlib.workingtree import WorkingTree
97
77
        if all:
98
78
            if file_list:
99
 
                raise errors.BzrCommandError("If --all is specified,"
100
 
                                             " no FILE may be provided")
 
79
                raise BzrCommandError("If --all is specified, no FILE may be provided")
101
80
            tree = WorkingTree.open_containing('.')[0]
102
81
            resolve(tree)
103
82
        else:
104
 
            tree, file_list = builtins.tree_files(file_list)
105
83
            if file_list is None:
106
 
                un_resolved, resolved = tree.auto_resolve()
107
 
                if len(un_resolved) > 0:
108
 
                    trace.note('%d conflict(s) auto-resolved.', len(resolved))
109
 
                    trace.note('Remaining conflicts:')
110
 
                    for conflict in un_resolved:
111
 
                        trace.note(conflict)
112
 
                    return 1
113
 
                else:
114
 
                    trace.note('All conflicts resolved.')
115
 
                    return 0
116
 
            else:
117
 
                resolve(tree, file_list)
118
 
 
119
 
 
120
 
def resolve(tree, paths=None, ignore_misses=False, recursive=False):
121
 
    """Resolve some or all of the conflicts in a working tree.
122
 
 
123
 
    :param paths: If None, resolve all conflicts.  Otherwise, select only
124
 
        specified conflicts.
125
 
    :param recursive: If True, then elements of paths which are directories
126
 
        have all their children resolved, etc.  When invoked as part of
127
 
        recursive commands like revert, this should be True.  For commands
128
 
        or applications wishing finer-grained control, like the resolve
129
 
        command, this should be False.
130
 
    :ignore_misses: If False, warnings will be printed if the supplied paths
131
 
        do not have conflicts.
132
 
    """
133
 
    tree.lock_tree_write()
 
84
                raise BzrCommandError("command 'resolve' needs one or more FILE, or --all")
 
85
            tree = WorkingTree.open_containing(file_list[0])[0]
 
86
            to_resolve = [tree.relpath(p) for p in file_list]
 
87
            resolve(tree, to_resolve)
 
88
 
 
89
 
 
90
def resolve(tree, paths=None, ignore_misses=False):
 
91
    tree.lock_write()
134
92
    try:
135
93
        tree_conflicts = tree.conflicts()
136
94
        if paths is None:
138
96
            selected_conflicts = tree_conflicts
139
97
        else:
140
98
            new_conflicts, selected_conflicts = \
141
 
                tree_conflicts.select_conflicts(tree, paths, ignore_misses,
142
 
                    recursive)
 
99
                tree_conflicts.select_conflicts(tree, paths, ignore_misses)
143
100
        try:
144
101
            tree.set_conflicts(new_conflicts)
145
 
        except errors.UnsupportedOperation:
 
102
        except UnsupportedOperation:
146
103
            pass
147
104
        selected_conflicts.remove_files(tree)
148
105
    finally:
156
113
    """
157
114
    conflicted = False
158
115
    try:
159
 
        osutils.rename(filename + ".THIS", filename)
 
116
        rename(filename + ".THIS", filename)
160
117
        conflicted = True
161
118
    except OSError, e:
162
119
        if e.errno != errno.ENOENT:
174
131
        if e.errno != errno.ENOENT:
175
132
            raise
176
133
    if not conflicted:
177
 
        raise errors.NotConflicted(filename)
 
134
        raise NotConflicted(filename)
178
135
 
179
136
 
180
137
class ConflictList(object):
241
198
                continue
242
199
            for suffix in CONFLICT_SUFFIXES:
243
200
                try:
244
 
                    osutils.delete_any(tree.abspath(conflict.path+suffix))
 
201
                    delete_any(tree.abspath(conflict.path+suffix))
245
202
                except OSError, e:
246
203
                    if e.errno != errno.ENOENT:
247
204
                        raise
248
205
 
249
 
    def select_conflicts(self, tree, paths, ignore_misses=False,
250
 
                         recurse=False):
 
206
    def select_conflicts(self, tree, paths, ignore_misses=False):
251
207
        """Select the conflicts associated with paths in a tree.
252
208
        
253
209
        File-ids are also used for this.
254
 
        :return: a pair of ConflictLists: (not_selected, selected)
255
210
        """
256
211
        path_set = set(paths)
257
212
        ids = {}
272
227
                if cpath in path_set:
273
228
                    selected = True
274
229
                    selected_paths.add(cpath)
275
 
                if recurse:
276
 
                    if osutils.is_inside_any(path_set, cpath):
277
 
                        selected = True
278
 
                        selected_paths.add(cpath)
279
 
 
280
230
            for key in ('file_id', 'conflict_file_id'):
281
231
                cfile_id = getattr(conflict, key, None)
282
232
                if cfile_id is None:
307
257
 
308
258
    def __init__(self, path, file_id=None):
309
259
        self.path = path
310
 
        # warn turned off, because the factory blindly transfers the Stanza
311
 
        # values to __init__ and Stanza is purely a Unicode api.
312
 
        self.file_id = osutils.safe_file_id(file_id, warn=False)
 
260
        self.file_id = file_id
313
261
 
314
262
    def as_stanza(self):
315
 
        s = rio.Stanza(type=self.typestring, path=self.path)
 
263
        s = Stanza(type=self.typestring, path=self.path)
316
264
        if self.file_id is not None:
317
 
            # Stanza requires Unicode apis
318
 
            s.add('file_id', self.file_id.decode('utf8'))
 
265
            s.add('file_id', self.file_id)
319
266
        return s
320
267
 
321
268
    def _cmp_list(self):
326
273
            return -1
327
274
        return cmp(self._cmp_list(), other._cmp_list())
328
275
 
329
 
    def __hash__(self):
330
 
        return hash((type(self), self.path, self.file_id))
331
 
 
332
276
    def __eq__(self, other):
333
277
        return self.__cmp__(other) == 0
334
278
 
429
373
                 conflict_file_id=None):
430
374
        HandledConflict.__init__(self, action, path, file_id)
431
375
        self.conflict_path = conflict_path 
432
 
        # warn turned off, because the factory blindly transfers the Stanza
433
 
        # values to __init__.
434
 
        self.conflict_file_id = osutils.safe_file_id(conflict_file_id,
435
 
                                                     warn=False)
 
376
        self.conflict_file_id = conflict_file_id
436
377
        
437
378
    def _cmp_list(self):
438
379
        return HandledConflict._cmp_list(self) + [self.conflict_path, 
442
383
        s = HandledConflict.as_stanza(self)
443
384
        s.add('conflict_path', self.conflict_path)
444
385
        if self.conflict_file_id is not None:
445
 
            s.add('conflict_file_id', self.conflict_file_id.decode('utf8'))
 
386
            s.add('conflict_file_id', self.conflict_file_id)
446
387
            
447
388
        return s
448
389
 
487
428
 
488
429
    typestring = 'unversioned parent'
489
430
 
490
 
    format = 'Conflict because %(path)s is not versioned, but has versioned'\
491
 
             ' children.  %(action)s.'
 
431
    format = 'Conflict adding versioned files to %(path)s.  %(action)s.'
492
432
 
493
433
 
494
434
class MissingParent(HandledConflict):
495
435
    """An attempt to add files to a directory that is not present.
496
 
    Typically, the result of a merge where THIS deleted the directory and
497
 
    the OTHER added a file to it.
498
 
    See also: DeletingParent (same situation, reversed THIS and OTHER)
 
436
    Typically, the result of a merge where one tree deleted the directory and
 
437
    the other added a file to it.
499
438
    """
500
439
 
501
440
    typestring = 'missing parent'
503
442
    format = 'Conflict adding files to %(path)s.  %(action)s.'
504
443
 
505
444
 
506
 
class DeletingParent(HandledConflict):
507
 
    """An attempt to add files to a directory that is not present.
508
 
    Typically, the result of a merge where one OTHER deleted the directory and
509
 
    the THIS added a file to it.
510
 
    """
511
 
 
512
 
    typestring = 'deleting parent'
513
 
 
514
 
    format = "Conflict: can't delete %(path)s because it is not empty.  "\
515
 
             "%(action)s."
516
 
 
517
 
 
518
 
class NonDirectoryParent(HandledConflict):
519
 
    """An attempt to add files to a directory that is not a director or
520
 
    an attempt to change the kind of a directory with files.
521
 
    """
522
 
 
523
 
    typestring = 'non-directory parent'
524
 
 
525
 
    format = "Conflict: %(path)s is not a directory, but has files in it."\
526
 
             "  %(action)s."
527
445
 
528
446
ctype = {}
529
447
 
536
454
 
537
455
 
538
456
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
539
 
               DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,
540
 
               DeletingParent, NonDirectoryParent)
 
457
               DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,)