~bzr-pqm/bzr/bzr.dev

2052.3.3 by John Arbash Meinel
Add (c) Canonical to files that Aaron has approved
1
# Copyright (C) 2005 Aaron Bentley, Canonical Ltd
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
2
#
1185.14.3 by Aaron Bentley
Copied conflict lister in
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
7
#
1185.14.3 by Aaron Bentley
Copied conflict lister in
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
1887.1.1 by Adeodato Simó
Do not separate paragraphs in the copyright statement with blank lines,
12
#
1185.14.3 by Aaron Bentley
Copied conflict lister in
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
1185.16.11 by Martin Pool
todo
17
# TODO: Move this into builtins
18
19
# TODO: 'bzr resolve' should accept a directory name and work from that 
20
# point down
21
1185.16.33 by Martin Pool
- move 'conflict' and 'resolved' from shipped plugin to regular builtins
22
import os
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
23
24
from bzrlib.lazy_import import lazy_import
25
lazy_import(globals(), """
1185.16.33 by Martin Pool
- move 'conflict' and 'resolved' from shipped plugin to regular builtins
26
import errno
27
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
28
from bzrlib import (
29
    commands,
30
    errors,
31
    osutils,
32
    rio,
33
    )
34
""")
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
35
from bzrlib.option import Option
1534.10.6 by Aaron Bentley
Conflict serialization working for WorkingTree3
36
37
38
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
39
1185.14.3 by Aaron Bentley
Copied conflict lister in
40
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
41
class cmd_conflicts(commands.Command):
1185.14.3 by Aaron Bentley
Copied conflict lister in
42
    """List files with conflicts.
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
43
44
    Merge will do its best to combine the changes in two branches, but there
45
    are some kinds of problems only a human can fix.  When it encounters those,
46
    it will mark a conflict.  A conflict means that you need to fix something,
47
    before you should commit.
48
1551.9.8 by Aaron Bentley
Add --text parameter to conflicts
49
    Conflicts normally are listed as short, human-readable messages.  If --text
50
    is supplied, the pathnames of files with text conflicts are listed,
51
    instead.  (This is useful for editing all files with text conflicts.)
52
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
53
    Use bzr resolve when you have fixed a problem.
54
55
    See also bzr resolve.
1185.14.3 by Aaron Bentley
Copied conflict lister in
56
    """
1551.9.8 by Aaron Bentley
Add --text parameter to conflicts
57
    takes_options = [Option('text', help='list text conflicts by pathname')]
58
59
    def run(self, text=False):
1534.10.6 by Aaron Bentley
Conflict serialization working for WorkingTree3
60
        from bzrlib.workingtree import WorkingTree
1534.10.9 by Aaron Bentley
Switched display functions to conflict_lines
61
        wt = WorkingTree.open_containing(u'.')[0]
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
62
        for conflict in wt.conflicts():
1551.9.8 by Aaron Bentley
Add --text parameter to conflicts
63
            if text:
64
                if conflict.typestring != 'text conflict':
65
                    continue
66
                self.outf.write(conflict.path + '\n')
67
            else:
68
                self.outf.write(str(conflict) + '\n')
1185.14.3 by Aaron Bentley
Copied conflict lister in
69
1652.1.1 by Martin Pool
Fix 'bzr resolve' run from subdirectory
70
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
71
class cmd_resolve(commands.Command):
1185.14.3 by Aaron Bentley
Copied conflict lister in
72
    """Mark a conflict as resolved.
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
73
74
    Merge will do its best to combine the changes in two branches, but there
75
    are some kinds of problems only a human can fix.  When it encounters those,
76
    it will mark a conflict.  A conflict means that you need to fix something,
77
    before you should commit.
78
79
    Once you have fixed a problem, use "bzr resolve FILE.." to mark
80
    individual files as fixed, or "bzr resolve --all" to mark all conflicts as
81
    resolved.
82
83
    See also bzr conflicts.
1185.14.3 by Aaron Bentley
Copied conflict lister in
84
    """
1185.33.24 by Martin Pool
Add alias 'resolved'
85
    aliases = ['resolved']
1185.14.3 by Aaron Bentley
Copied conflict lister in
86
    takes_args = ['file*']
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
87
    takes_options = [Option('all', help='Resolve all conflicts in this tree')]
1185.14.3 by Aaron Bentley
Copied conflict lister in
88
    def run(self, file_list=None, all=False):
1534.10.6 by Aaron Bentley
Conflict serialization working for WorkingTree3
89
        from bzrlib.workingtree import WorkingTree
1652.1.3 by Martin Pool
Improved bzr resolve command line handling
90
        if all:
91
            if file_list:
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
92
                raise errors.BzrCommandError("If --all is specified,"
93
                                             " no FILE may be provided")
1652.1.3 by Martin Pool
Improved bzr resolve command line handling
94
            tree = WorkingTree.open_containing('.')[0]
95
            resolve(tree)
1185.14.3 by Aaron Bentley
Copied conflict lister in
96
        else:
1652.1.3 by Martin Pool
Improved bzr resolve command line handling
97
            if file_list is None:
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
98
                raise errors.BzrCommandError("command 'resolve' needs one or"
99
                                             " more FILE, or --all")
1652.1.3 by Martin Pool
Improved bzr resolve command line handling
100
            tree = WorkingTree.open_containing(file_list[0])[0]
101
            to_resolve = [tree.relpath(p) for p in file_list]
102
            resolve(tree, to_resolve)
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
103
104
1534.10.15 by Aaron Bentley
Revert does resolve
105
def resolve(tree, paths=None, ignore_misses=False):
1997.1.3 by Robert Collins
All WorkingTree methods which write to the tree, but not to the branch
106
    tree.lock_tree_write()
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
107
    try:
1534.10.23 by Aaron Bentley
Removed conflicts_to_stanzas and stanzas_to_conflicts
108
        tree_conflicts = tree.conflicts()
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
109
        if paths is None:
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
110
            new_conflicts = ConflictList()
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
111
            selected_conflicts = tree_conflicts
112
        else:
113
            new_conflicts, selected_conflicts = \
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
114
                tree_conflicts.select_conflicts(tree, paths, ignore_misses)
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
115
        try:
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
116
            tree.set_conflicts(new_conflicts)
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
117
        except errors.UnsupportedOperation:
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
118
            pass
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
119
        selected_conflicts.remove_files(tree)
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
120
    finally:
121
        tree.unlock()
122
123
1185.35.1 by Aaron Bentley
Implemented conflicts.restore
124
def restore(filename):
125
    """\
126
    Restore a conflicted file to the state it was in before merging.
127
    Only text restoration supported at present.
128
    """
129
    conflicted = False
130
    try:
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
131
        osutils.rename(filename + ".THIS", filename)
1185.35.1 by Aaron Bentley
Implemented conflicts.restore
132
        conflicted = True
133
    except OSError, e:
134
        if e.errno != errno.ENOENT:
135
            raise
136
    try:
137
        os.unlink(filename + ".BASE")
138
        conflicted = True
139
    except OSError, e:
140
        if e.errno != errno.ENOENT:
141
            raise
142
    try:
143
        os.unlink(filename + ".OTHER")
144
        conflicted = True
145
    except OSError, e:
146
        if e.errno != errno.ENOENT:
147
            raise
148
    if not conflicted:
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
149
        raise errors.NotConflicted(filename)
1534.10.4 by Aaron Bentley
Implemented conflict serialization
150
151
1534.10.22 by Aaron Bentley
Got ConflictList implemented
152
class ConflictList(object):
1652.1.1 by Martin Pool
Fix 'bzr resolve' run from subdirectory
153
    """List of conflicts.
154
155
    Typically obtained from WorkingTree.conflicts()
156
1534.10.22 by Aaron Bentley
Got ConflictList implemented
157
    Can be instantiated from stanzas or from Conflict subclasses.
158
    """
159
160
    def __init__(self, conflicts=None):
161
        object.__init__(self)
162
        if conflicts is None:
163
            self.__list = []
164
        else:
165
            self.__list = conflicts
166
1652.1.1 by Martin Pool
Fix 'bzr resolve' run from subdirectory
167
    def is_empty(self):
168
        return len(self.__list) == 0
169
1534.10.22 by Aaron Bentley
Got ConflictList implemented
170
    def __len__(self):
171
        return len(self.__list)
172
173
    def __iter__(self):
174
        return iter(self.__list)
175
176
    def __getitem__(self, key):
177
        return self.__list[key]
178
179
    def append(self, conflict):
180
        return self.__list.append(conflict)
181
182
    def __eq__(self, other_list):
183
        return list(self) == list(other_list)
184
185
    def __ne__(self, other_list):
186
        return not (self == other_list)
187
188
    def __repr__(self):
189
        return "ConflictList(%r)" % self.__list
190
191
    @staticmethod
192
    def from_stanzas(stanzas):
193
        """Produce a new ConflictList from an iterable of stanzas"""
194
        conflicts = ConflictList()
195
        for stanza in stanzas:
196
            conflicts.append(Conflict.factory(**stanza.as_dict()))
197
        return conflicts
198
199
    def to_stanzas(self):
200
        """Generator of stanzas"""
201
        for conflict in self:
202
            yield conflict.as_stanza()
203
            
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
204
    def to_strings(self):
205
        """Generate strings for the provided conflicts"""
206
        for conflict in self:
207
            yield str(conflict)
208
209
    def remove_files(self, tree):
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
210
        """Remove the THIS, BASE and OTHER files for listed conflicts"""
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
211
        for conflict in self:
212
            if not conflict.has_files:
213
                continue
214
            for suffix in CONFLICT_SUFFIXES:
215
                try:
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
216
                    osutils.delete_any(tree.abspath(conflict.path+suffix))
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
217
                except OSError, e:
218
                    if e.errno != errno.ENOENT:
219
                        raise
1534.10.21 by Aaron Bentley
Moved and renamed conflict functions
220
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
221
    def select_conflicts(self, tree, paths, ignore_misses=False):
222
        """Select the conflicts associated with paths in a tree.
223
        
224
        File-ids are also used for this.
1551.7.10 by Aaron Bentley
Remerge doesn't clear unrelated conflicts
225
        :return: a pair of ConflictLists: (not_selected, selected)
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
226
        """
227
        path_set = set(paths)
228
        ids = {}
229
        selected_paths = set()
230
        new_conflicts = ConflictList()
231
        selected_conflicts = ConflictList()
232
        for path in paths:
233
            file_id = tree.path2id(path)
234
            if file_id is not None:
235
                ids[file_id] = path
236
237
        for conflict in self:
238
            selected = False
239
            for key in ('path', 'conflict_path'):
240
                cpath = getattr(conflict, key, None)
241
                if cpath is None:
242
                    continue
243
                if cpath in path_set:
244
                    selected = True
245
                    selected_paths.add(cpath)
246
            for key in ('file_id', 'conflict_file_id'):
247
                cfile_id = getattr(conflict, key, None)
248
                if cfile_id is None:
249
                    continue
250
                try:
251
                    cpath = ids[cfile_id]
252
                except KeyError:
253
                    continue
254
                selected = True
255
                selected_paths.add(cpath)
256
            if selected:
257
                selected_conflicts.append(conflict)
258
            else:
259
                new_conflicts.append(conflict)
260
        if ignore_misses is not True:
261
            for path in [p for p in paths if p not in selected_paths]:
262
                if not os.path.exists(tree.abspath(path)):
263
                    print "%s does not exist" % path
264
                else:
265
                    print "%s is not conflicted" % path
266
        return new_conflicts, selected_conflicts
267
268
 
1534.10.18 by Aaron Bentley
Defined all new Conflict types
269
class Conflict(object):
270
    """Base class for all types of conflict"""
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
271
272
    has_files = False
273
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
274
    def __init__(self, path, file_id=None):
1534.10.18 by Aaron Bentley
Defined all new Conflict types
275
        self.path = path
276
        self.file_id = file_id
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
277
278
    def as_stanza(self):
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
279
        s = rio.Stanza(type=self.typestring, path=self.path)
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
280
        if self.file_id is not None:
281
            s.add('file_id', self.file_id)
282
        return s
283
1534.10.22 by Aaron Bentley
Got ConflictList implemented
284
    def _cmp_list(self):
285
        return [type(self), self.path, self.file_id]
286
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
287
    def __cmp__(self, other):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
288
        if getattr(other, "_cmp_list", None) is None:
289
            return -1
290
        return cmp(self._cmp_list(), other._cmp_list())
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
291
1551.7.11 by Aaron Bentley
Add WorkingTree.add_conflicts
292
    def __hash__(self):
293
        return hash((type(self), self.path, self.file_id))
294
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
295
    def __eq__(self, other):
296
        return self.__cmp__(other) == 0
297
298
    def __ne__(self, other):
299
        return not self.__eq__(other)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
300
1534.10.20 by Aaron Bentley
Got all tests passing
301
    def __str__(self):
302
        return self.format % self.__dict__
303
1534.10.22 by Aaron Bentley
Got ConflictList implemented
304
    def __repr__(self):
305
        rdict = dict(self.__dict__)
306
        rdict['class'] = self.__class__.__name__
307
        return self.rformat % rdict
308
1534.10.18 by Aaron Bentley
Defined all new Conflict types
309
    @staticmethod
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
310
    def factory(type, **kwargs):
1534.10.18 by Aaron Bentley
Defined all new Conflict types
311
        global ctype
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
312
        return ctype[type](**kwargs)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
313
1666.1.4 by Robert Collins
* 'Metadir' is now the default disk format. This improves behaviour in
314
    @staticmethod
315
    def sort_key(conflict):
316
        if conflict.path is not None:
317
            return conflict.path, conflict.typestring
318
        elif getattr(conflict, "conflict_path", None) is not None:
319
            return conflict.conflict_path, conflict.typestring
320
        else:
321
            return None, conflict.typestring
322
1534.10.18 by Aaron Bentley
Defined all new Conflict types
323
324
class PathConflict(Conflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
325
    """A conflict was encountered merging file paths"""
326
1534.10.18 by Aaron Bentley
Defined all new Conflict types
327
    typestring = 'path conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
328
1534.10.20 by Aaron Bentley
Got all tests passing
329
    format = 'Path conflict: %(path)s / %(conflict_path)s'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
330
331
    rformat = '%(class)s(%(path)r, %(conflict_path)r, %(file_id)r)'
1534.10.20 by Aaron Bentley
Got all tests passing
332
    def __init__(self, path, conflict_path=None, file_id=None):
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
333
        Conflict.__init__(self, path, file_id)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
334
        self.conflict_path = conflict_path
335
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
336
    def as_stanza(self):
337
        s = Conflict.as_stanza(self)
1534.10.20 by Aaron Bentley
Got all tests passing
338
        if self.conflict_path is not None:
339
            s.add('conflict_path', self.conflict_path)
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
340
        return s
341
1534.10.20 by Aaron Bentley
Got all tests passing
342
343
class ContentsConflict(PathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
344
    """The files are of different types, or not present"""
345
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
346
    has_files = True
347
1534.10.20 by Aaron Bentley
Got all tests passing
348
    typestring = 'contents conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
349
1534.10.20 by Aaron Bentley
Got all tests passing
350
    format = 'Contents conflict in %(path)s'
351
352
353
class TextConflict(PathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
354
    """The merge algorithm could not resolve all differences encountered."""
355
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
356
    has_files = True
357
1534.10.20 by Aaron Bentley
Got all tests passing
358
    typestring = 'text conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
359
1534.10.20 by Aaron Bentley
Got all tests passing
360
    format = 'Text conflict in %(path)s'
361
362
1534.10.18 by Aaron Bentley
Defined all new Conflict types
363
class HandledConflict(Conflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
364
    """A path problem that has been provisionally resolved.
365
    This is intended to be a base class.
366
    """
367
368
    rformat = "%(class)s(%(action)r, %(path)r, %(file_id)r)"
369
    
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
370
    def __init__(self, action, path, file_id=None):
371
        Conflict.__init__(self, path, file_id)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
372
        self.action = action
373
1534.10.22 by Aaron Bentley
Got ConflictList implemented
374
    def _cmp_list(self):
375
        return Conflict._cmp_list(self) + [self.action]
376
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
377
    def as_stanza(self):
378
        s = Conflict.as_stanza(self)
379
        s.add('action', self.action)
380
        return s
381
1534.10.20 by Aaron Bentley
Got all tests passing
382
1534.10.18 by Aaron Bentley
Defined all new Conflict types
383
class HandledPathConflict(HandledConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
384
    """A provisionally-resolved path problem involving two paths.
385
    This is intended to be a base class.
386
    """
387
388
    rformat = "%(class)s(%(action)r, %(path)r, %(conflict_path)r,"\
389
        " %(file_id)r, %(conflict_file_id)r)"
390
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
391
    def __init__(self, action, path, conflict_path, file_id=None,
1534.10.18 by Aaron Bentley
Defined all new Conflict types
392
                 conflict_file_id=None):
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
393
        HandledConflict.__init__(self, action, path, file_id)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
394
        self.conflict_path = conflict_path 
395
        self.conflict_file_id = conflict_file_id
396
        
1534.10.22 by Aaron Bentley
Got ConflictList implemented
397
    def _cmp_list(self):
398
        return HandledConflict._cmp_list(self) + [self.conflict_path, 
399
                                                  self.conflict_file_id]
400
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
401
    def as_stanza(self):
402
        s = HandledConflict.as_stanza(self)
403
        s.add('conflict_path', self.conflict_path)
404
        if self.conflict_file_id is not None:
405
            s.add('conflict_file_id', self.conflict_file_id)
406
            
407
        return s
1534.10.20 by Aaron Bentley
Got all tests passing
408
409
1534.10.18 by Aaron Bentley
Defined all new Conflict types
410
class DuplicateID(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
411
    """Two files want the same file_id."""
412
1534.10.18 by Aaron Bentley
Defined all new Conflict types
413
    typestring = 'duplicate id'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
414
1534.10.20 by Aaron Bentley
Got all tests passing
415
    format = 'Conflict adding id to %(conflict_path)s.  %(action)s %(path)s.'
416
1534.10.18 by Aaron Bentley
Defined all new Conflict types
417
418
class DuplicateEntry(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
419
    """Two directory entries want to have the same name."""
420
1534.10.18 by Aaron Bentley
Defined all new Conflict types
421
    typestring = 'duplicate'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
422
1534.10.20 by Aaron Bentley
Got all tests passing
423
    format = 'Conflict adding file %(conflict_path)s.  %(action)s %(path)s.'
424
1534.10.18 by Aaron Bentley
Defined all new Conflict types
425
426
class ParentLoop(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
427
    """An attempt to create an infinitely-looping directory structure.
428
    This is rare, but can be produced like so:
429
430
    tree A:
431
      mv foo/bar
432
    tree B:
433
      mv bar/foo
434
    merge A and B
435
    """
436
1534.10.18 by Aaron Bentley
Defined all new Conflict types
437
    typestring = 'parent loop'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
438
1534.10.20 by Aaron Bentley
Got all tests passing
439
    format = 'Conflict moving %(conflict_path)s into %(path)s.  %(action)s.'
440
1534.10.18 by Aaron Bentley
Defined all new Conflict types
441
442
class UnversionedParent(HandledConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
443
    """An attempt to version an file whose parent directory is not versioned.
444
    Typically, the result of a merge where one tree unversioned the directory
445
    and the other added a versioned file to it.
446
    """
447
1534.10.18 by Aaron Bentley
Defined all new Conflict types
448
    typestring = 'unversioned parent'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
449
1551.8.22 by Aaron Bentley
Improve message when OTHER deletes an in-use tree
450
    format = 'Conflict because %(path)s is not versioned, but has versioned'\
451
             ' children.  %(action)s.'
1534.10.20 by Aaron Bentley
Got all tests passing
452
1534.10.18 by Aaron Bentley
Defined all new Conflict types
453
454
class MissingParent(HandledConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
455
    """An attempt to add files to a directory that is not present.
1551.8.22 by Aaron Bentley
Improve message when OTHER deletes an in-use tree
456
    Typically, the result of a merge where THIS deleted the directory and
457
    the OTHER added a file to it.
458
    See also: DeletingParent (same situation, reversed THIS and OTHER)
1534.10.22 by Aaron Bentley
Got ConflictList implemented
459
    """
460
1534.10.18 by Aaron Bentley
Defined all new Conflict types
461
    typestring = 'missing parent'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
462
1534.10.20 by Aaron Bentley
Got all tests passing
463
    format = 'Conflict adding files to %(path)s.  %(action)s.'
464
465
1551.8.22 by Aaron Bentley
Improve message when OTHER deletes an in-use tree
466
class DeletingParent(HandledConflict):
467
    """An attempt to add files to a directory that is not present.
468
    Typically, the result of a merge where one OTHER deleted the directory and
469
    the THIS added a file to it.
470
    """
471
472
    typestring = 'deleting parent'
473
1551.8.23 by Aaron Bentley
Tweaked conflict message to be more understandable
474
    format = "Conflict: can't delete %(path)s because it is not empty.  "\
475
             "%(action)s."
1551.8.22 by Aaron Bentley
Improve message when OTHER deletes an in-use tree
476
1534.10.18 by Aaron Bentley
Defined all new Conflict types
477
478
ctype = {}
1534.10.20 by Aaron Bentley
Got all tests passing
479
480
1534.10.18 by Aaron Bentley
Defined all new Conflict types
481
def register_types(*conflict_types):
482
    """Register a Conflict subclass for serialization purposes"""
483
    global ctype
484
    for conflict_type in conflict_types:
485
        ctype[conflict_type.typestring] = conflict_type
486
1534.10.20 by Aaron Bentley
Got all tests passing
487
1534.10.18 by Aaron Bentley
Defined all new Conflict types
488
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
1551.8.22 by Aaron Bentley
Improve message when OTHER deletes an in-use tree
489
               DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,
490
               DeletingParent,)