~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/conflicts.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2006-04-09 16:49:22 UTC
  • mfrom: (1534.10.27 bzr.ttransform)
  • Revision ID: pqm@pqm.ubuntu.com-20060409164922-071803e906c8b96d
New conflict-handling system

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 Aaron Bentley, 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
 
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
class cmd_resolve(bzrlib.commands.Command):
77
58
    """Mark a conflict as resolved.
78
59
 
79
60
    Merge will do its best to combine the changes in two branches, but there
81
62
    it will mark a conflict.  A conflict means that you need to fix something,
82
63
    before you should commit.
83
64
 
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.
 
65
    Once you have fixed a problem, use "bzr resolve FILE.." to mark
 
66
    individual files as fixed, or "bzr resolve --all" to mark all conflicts as
 
67
    resolved.
87
68
 
88
69
    See also bzr conflicts.
89
70
    """
90
71
    aliases = ['resolved']
91
72
    takes_args = ['file*']
92
 
    takes_options = [
93
 
            Option('all', help='Resolve all conflicts in this tree.'),
94
 
            ]
 
73
    takes_options = [Option('all', help='Resolve all conflicts in this tree')]
95
74
    def run(self, file_list=None, all=False):
96
75
        from bzrlib.workingtree import WorkingTree
97
 
        if all:
98
 
            if file_list:
99
 
                raise errors.BzrCommandError("If --all is specified,"
100
 
                                             " no FILE may be provided")
101
 
            tree = WorkingTree.open_containing('.')[0]
102
 
            resolve(tree)
 
76
        if file_list is None:
 
77
            if not all:
 
78
                raise BzrCommandError(
 
79
                    "command 'resolve' needs one or more FILE, or --all")
103
80
        else:
104
 
            tree, file_list = builtins.tree_files(file_list)
105
 
            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)
 
81
            if all:
 
82
                raise BzrCommandError(
 
83
                    "If --all is specified, no FILE may be provided")
 
84
        tree = WorkingTree.open_containing(u'.')[0]
 
85
        resolve(tree, file_list)
118
86
 
119
87
 
120
88
def resolve(tree, paths=None, ignore_misses=False):
121
 
    tree.lock_tree_write()
 
89
    tree.lock_write()
122
90
    try:
123
91
        tree_conflicts = tree.conflicts()
124
92
        if paths is None:
129
97
                tree_conflicts.select_conflicts(tree, paths, ignore_misses)
130
98
        try:
131
99
            tree.set_conflicts(new_conflicts)
132
 
        except errors.UnsupportedOperation:
 
100
        except UnsupportedOperation:
133
101
            pass
134
102
        selected_conflicts.remove_files(tree)
135
103
    finally:
143
111
    """
144
112
    conflicted = False
145
113
    try:
146
 
        osutils.rename(filename + ".THIS", filename)
 
114
        rename(filename + ".THIS", filename)
147
115
        conflicted = True
148
116
    except OSError, e:
149
117
        if e.errno != errno.ENOENT:
161
129
        if e.errno != errno.ENOENT:
162
130
            raise
163
131
    if not conflicted:
164
 
        raise errors.NotConflicted(filename)
 
132
        raise NotConflicted(filename)
165
133
 
166
134
 
167
135
class ConflictList(object):
168
 
    """List of conflicts.
169
 
 
170
 
    Typically obtained from WorkingTree.conflicts()
171
 
 
 
136
    """List of conflicts
172
137
    Can be instantiated from stanzas or from Conflict subclasses.
173
138
    """
174
139
 
179
144
        else:
180
145
            self.__list = conflicts
181
146
 
182
 
    def is_empty(self):
183
 
        return len(self.__list) == 0
184
 
 
185
147
    def __len__(self):
186
148
        return len(self.__list)
187
149
 
228
190
                continue
229
191
            for suffix in CONFLICT_SUFFIXES:
230
192
                try:
231
 
                    osutils.delete_any(tree.abspath(conflict.path+suffix))
 
193
                    os.unlink(tree.abspath(conflict.path+suffix))
232
194
                except OSError, e:
233
195
                    if e.errno != errno.ENOENT:
234
196
                        raise
237
199
        """Select the conflicts associated with paths in a tree.
238
200
        
239
201
        File-ids are also used for this.
240
 
        :return: a pair of ConflictLists: (not_selected, selected)
241
202
        """
242
203
        path_set = set(paths)
243
204
        ids = {}
288
249
 
289
250
    def __init__(self, path, file_id=None):
290
251
        self.path = path
291
 
        # warn turned off, because the factory blindly transfers the Stanza
292
 
        # values to __init__ and Stanza is purely a Unicode api.
293
 
        self.file_id = osutils.safe_file_id(file_id, warn=False)
 
252
        self.file_id = file_id
294
253
 
295
254
    def as_stanza(self):
296
 
        s = rio.Stanza(type=self.typestring, path=self.path)
 
255
        s = Stanza(type=self.typestring, path=self.path)
297
256
        if self.file_id is not None:
298
 
            # Stanza requires Unicode apis
299
 
            s.add('file_id', self.file_id.decode('utf8'))
 
257
            s.add('file_id', self.file_id)
300
258
        return s
301
259
 
302
260
    def _cmp_list(self):
307
265
            return -1
308
266
        return cmp(self._cmp_list(), other._cmp_list())
309
267
 
310
 
    def __hash__(self):
311
 
        return hash((type(self), self.path, self.file_id))
312
 
 
313
268
    def __eq__(self, other):
314
269
        return self.__cmp__(other) == 0
315
270
 
329
284
        global ctype
330
285
        return ctype[type](**kwargs)
331
286
 
332
 
    @staticmethod
333
 
    def sort_key(conflict):
334
 
        if conflict.path is not None:
335
 
            return conflict.path, conflict.typestring
336
 
        elif getattr(conflict, "conflict_path", None) is not None:
337
 
            return conflict.conflict_path, conflict.typestring
338
 
        else:
339
 
            return None, conflict.typestring
340
 
 
341
287
 
342
288
class PathConflict(Conflict):
343
289
    """A conflict was encountered merging file paths"""
410
356
                 conflict_file_id=None):
411
357
        HandledConflict.__init__(self, action, path, file_id)
412
358
        self.conflict_path = conflict_path 
413
 
        # warn turned off, because the factory blindly transfers the Stanza
414
 
        # values to __init__.
415
 
        self.conflict_file_id = osutils.safe_file_id(conflict_file_id,
416
 
                                                     warn=False)
 
359
        self.conflict_file_id = conflict_file_id
417
360
        
418
361
    def _cmp_list(self):
419
362
        return HandledConflict._cmp_list(self) + [self.conflict_path, 
423
366
        s = HandledConflict.as_stanza(self)
424
367
        s.add('conflict_path', self.conflict_path)
425
368
        if self.conflict_file_id is not None:
426
 
            s.add('conflict_file_id', self.conflict_file_id.decode('utf8'))
 
369
            s.add('conflict_file_id', self.conflict_file_id)
427
370
            
428
371
        return s
429
372
 
468
411
 
469
412
    typestring = 'unversioned parent'
470
413
 
471
 
    format = 'Conflict because %(path)s is not versioned, but has versioned'\
472
 
             ' children.  %(action)s.'
 
414
    format = 'Conflict adding versioned files to %(path)s.  %(action)s.'
473
415
 
474
416
 
475
417
class MissingParent(HandledConflict):
476
418
    """An attempt to add files to a directory that is not present.
477
 
    Typically, the result of a merge where THIS deleted the directory and
478
 
    the OTHER added a file to it.
479
 
    See also: DeletingParent (same situation, reversed THIS and OTHER)
 
419
    Typically, the result of a merge where one tree deleted the directory and
 
420
    the other added a file to it.
480
421
    """
481
422
 
482
423
    typestring = 'missing parent'
484
425
    format = 'Conflict adding files to %(path)s.  %(action)s.'
485
426
 
486
427
 
487
 
class DeletingParent(HandledConflict):
488
 
    """An attempt to add files to a directory that is not present.
489
 
    Typically, the result of a merge where one OTHER deleted the directory and
490
 
    the THIS added a file to it.
491
 
    """
492
 
 
493
 
    typestring = 'deleting parent'
494
 
 
495
 
    format = "Conflict: can't delete %(path)s because it is not empty.  "\
496
 
             "%(action)s."
497
 
 
498
428
 
499
429
ctype = {}
500
430
 
507
437
 
508
438
 
509
439
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
510
 
               DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,
511
 
               DeletingParent,)
 
440
               DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,)