~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 (
2120.7.3 by Aaron Bentley
Update resolve command to automatically mark conflicts as resolved
29
    builtins,
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
30
    commands,
31
    errors,
32
    osutils,
33
    rio,
2120.7.3 by Aaron Bentley
Update resolve command to automatically mark conflicts as resolved
34
    trace,
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
35
    )
36
""")
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
37
from bzrlib.option import Option
1534.10.6 by Aaron Bentley
Conflict serialization working for WorkingTree3
38
39
40
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
41
1185.14.3 by Aaron Bentley
Copied conflict lister in
42
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
43
class cmd_conflicts(commands.Command):
1185.14.3 by Aaron Bentley
Copied conflict lister in
44
    """List files with conflicts.
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
45
46
    Merge will do its best to combine the changes in two branches, but there
47
    are some kinds of problems only a human can fix.  When it encounters those,
48
    it will mark a conflict.  A conflict means that you need to fix something,
49
    before you should commit.
50
1551.9.8 by Aaron Bentley
Add --text parameter to conflicts
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
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
55
    Use bzr resolve when you have fixed a problem.
56
57
    See also bzr resolve.
1185.14.3 by Aaron Bentley
Copied conflict lister in
58
    """
2598.1.1 by Martin Pool
Add test for and documentation of option style, fix up existing options to comply
59
    takes_options = [
60
            Option('text',
61
                   help='List paths of files with text conflicts.'),
62
        ]
1551.9.8 by Aaron Bentley
Add --text parameter to conflicts
63
64
    def run(self, text=False):
1534.10.6 by Aaron Bentley
Conflict serialization working for WorkingTree3
65
        from bzrlib.workingtree import WorkingTree
1534.10.9 by Aaron Bentley
Switched display functions to conflict_lines
66
        wt = WorkingTree.open_containing(u'.')[0]
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
67
        for conflict in wt.conflicts():
1551.9.8 by Aaron Bentley
Add --text parameter to 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')
1185.14.3 by Aaron Bentley
Copied conflict lister in
74
1652.1.1 by Martin Pool
Fix 'bzr resolve' run from subdirectory
75
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
76
class cmd_resolve(commands.Command):
1185.14.3 by Aaron Bentley
Copied conflict lister in
77
    """Mark a conflict as resolved.
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
78
79
    Merge will do its best to combine the changes in two branches, but there
80
    are some kinds of problems only a human can fix.  When it encounters those,
81
    it will mark a conflict.  A conflict means that you need to fix something,
82
    before you should commit.
83
2120.7.3 by Aaron Bentley
Update resolve command to automatically mark conflicts as resolved
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.
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
87
88
    See also bzr conflicts.
1185.14.3 by Aaron Bentley
Copied conflict lister in
89
    """
1185.33.24 by Martin Pool
Add alias 'resolved'
90
    aliases = ['resolved']
1185.14.3 by Aaron Bentley
Copied conflict lister in
91
    takes_args = ['file*']
2598.1.2 by Martin Pool
Also check that option help ends in a period, and fix those that don't
92
    takes_options = [
93
            Option('all', help='Resolve all conflicts in this tree.'),
94
            ]
1185.14.3 by Aaron Bentley
Copied conflict lister in
95
    def run(self, file_list=None, all=False):
1534.10.6 by Aaron Bentley
Conflict serialization working for WorkingTree3
96
        from bzrlib.workingtree import WorkingTree
1652.1.3 by Martin Pool
Improved bzr resolve command line handling
97
        if all:
98
            if file_list:
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
99
                raise errors.BzrCommandError("If --all is specified,"
100
                                             " no FILE may be provided")
1652.1.3 by Martin Pool
Improved bzr resolve command line handling
101
            tree = WorkingTree.open_containing('.')[0]
102
            resolve(tree)
1185.14.3 by Aaron Bentley
Copied conflict lister in
103
        else:
2120.7.3 by Aaron Bentley
Update resolve command to automatically mark conflicts as resolved
104
            tree, file_list = builtins.tree_files(file_list)
1652.1.3 by Martin Pool
Improved bzr resolve command line handling
105
            if file_list is None:
2120.7.3 by Aaron Bentley
Update resolve command to automatically mark conflicts as resolved
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:
2120.7.4 by Aaron Bentley
Fix normal resolve
117
                resolve(tree, file_list)
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
118
119
1534.10.15 by Aaron Bentley
Revert does resolve
120
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
121
    tree.lock_tree_write()
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
122
    try:
1534.10.23 by Aaron Bentley
Removed conflicts_to_stanzas and stanzas_to_conflicts
123
        tree_conflicts = tree.conflicts()
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
124
        if paths is None:
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
125
            new_conflicts = ConflictList()
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
126
            selected_conflicts = tree_conflicts
127
        else:
128
            new_conflicts, selected_conflicts = \
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
129
                tree_conflicts.select_conflicts(tree, paths, ignore_misses)
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
130
        try:
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
131
            tree.set_conflicts(new_conflicts)
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
132
        except errors.UnsupportedOperation:
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
133
            pass
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
134
        selected_conflicts.remove_files(tree)
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
135
    finally:
136
        tree.unlock()
137
138
1185.35.1 by Aaron Bentley
Implemented conflicts.restore
139
def restore(filename):
140
    """\
141
    Restore a conflicted file to the state it was in before merging.
142
    Only text restoration supported at present.
143
    """
144
    conflicted = False
145
    try:
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
146
        osutils.rename(filename + ".THIS", filename)
1185.35.1 by Aaron Bentley
Implemented conflicts.restore
147
        conflicted = True
148
    except OSError, e:
149
        if e.errno != errno.ENOENT:
150
            raise
151
    try:
152
        os.unlink(filename + ".BASE")
153
        conflicted = True
154
    except OSError, e:
155
        if e.errno != errno.ENOENT:
156
            raise
157
    try:
158
        os.unlink(filename + ".OTHER")
159
        conflicted = True
160
    except OSError, e:
161
        if e.errno != errno.ENOENT:
162
            raise
163
    if not conflicted:
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
164
        raise errors.NotConflicted(filename)
1534.10.4 by Aaron Bentley
Implemented conflict serialization
165
166
1534.10.22 by Aaron Bentley
Got ConflictList implemented
167
class ConflictList(object):
1652.1.1 by Martin Pool
Fix 'bzr resolve' run from subdirectory
168
    """List of conflicts.
169
170
    Typically obtained from WorkingTree.conflicts()
171
1534.10.22 by Aaron Bentley
Got ConflictList implemented
172
    Can be instantiated from stanzas or from Conflict subclasses.
173
    """
174
175
    def __init__(self, conflicts=None):
176
        object.__init__(self)
177
        if conflicts is None:
178
            self.__list = []
179
        else:
180
            self.__list = conflicts
181
1652.1.1 by Martin Pool
Fix 'bzr resolve' run from subdirectory
182
    def is_empty(self):
183
        return len(self.__list) == 0
184
1534.10.22 by Aaron Bentley
Got ConflictList implemented
185
    def __len__(self):
186
        return len(self.__list)
187
188
    def __iter__(self):
189
        return iter(self.__list)
190
191
    def __getitem__(self, key):
192
        return self.__list[key]
193
194
    def append(self, conflict):
195
        return self.__list.append(conflict)
196
197
    def __eq__(self, other_list):
198
        return list(self) == list(other_list)
199
200
    def __ne__(self, other_list):
201
        return not (self == other_list)
202
203
    def __repr__(self):
204
        return "ConflictList(%r)" % self.__list
205
206
    @staticmethod
207
    def from_stanzas(stanzas):
208
        """Produce a new ConflictList from an iterable of stanzas"""
209
        conflicts = ConflictList()
210
        for stanza in stanzas:
211
            conflicts.append(Conflict.factory(**stanza.as_dict()))
212
        return conflicts
213
214
    def to_stanzas(self):
215
        """Generator of stanzas"""
216
        for conflict in self:
217
            yield conflict.as_stanza()
218
            
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
219
    def to_strings(self):
220
        """Generate strings for the provided conflicts"""
221
        for conflict in self:
222
            yield str(conflict)
223
224
    def remove_files(self, tree):
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
225
        """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
226
        for conflict in self:
227
            if not conflict.has_files:
228
                continue
229
            for suffix in CONFLICT_SUFFIXES:
230
                try:
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
231
                    osutils.delete_any(tree.abspath(conflict.path+suffix))
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
232
                except OSError, e:
233
                    if e.errno != errno.ENOENT:
234
                        raise
1534.10.21 by Aaron Bentley
Moved and renamed conflict functions
235
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
236
    def select_conflicts(self, tree, paths, ignore_misses=False):
237
        """Select the conflicts associated with paths in a tree.
238
        
239
        File-ids are also used for this.
1551.7.10 by Aaron Bentley
Remerge doesn't clear unrelated conflicts
240
        :return: a pair of ConflictLists: (not_selected, selected)
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
241
        """
242
        path_set = set(paths)
243
        ids = {}
244
        selected_paths = set()
245
        new_conflicts = ConflictList()
246
        selected_conflicts = ConflictList()
247
        for path in paths:
248
            file_id = tree.path2id(path)
249
            if file_id is not None:
250
                ids[file_id] = path
251
252
        for conflict in self:
253
            selected = False
254
            for key in ('path', 'conflict_path'):
255
                cpath = getattr(conflict, key, None)
256
                if cpath is None:
257
                    continue
258
                if cpath in path_set:
259
                    selected = True
260
                    selected_paths.add(cpath)
261
            for key in ('file_id', 'conflict_file_id'):
262
                cfile_id = getattr(conflict, key, None)
263
                if cfile_id is None:
264
                    continue
265
                try:
266
                    cpath = ids[cfile_id]
267
                except KeyError:
268
                    continue
269
                selected = True
270
                selected_paths.add(cpath)
271
            if selected:
272
                selected_conflicts.append(conflict)
273
            else:
274
                new_conflicts.append(conflict)
275
        if ignore_misses is not True:
276
            for path in [p for p in paths if p not in selected_paths]:
277
                if not os.path.exists(tree.abspath(path)):
278
                    print "%s does not exist" % path
279
                else:
280
                    print "%s is not conflicted" % path
281
        return new_conflicts, selected_conflicts
282
283
 
1534.10.18 by Aaron Bentley
Defined all new Conflict types
284
class Conflict(object):
285
    """Base class for all types of conflict"""
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
286
287
    has_files = False
288
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
289
    def __init__(self, path, file_id=None):
1534.10.18 by Aaron Bentley
Defined all new Conflict types
290
        self.path = path
2309.4.13 by John Arbash Meinel
Conflicts go through Stanza so the need to be aware of utf8 versus unicode file ids.
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)
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
294
295
    def as_stanza(self):
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
296
        s = rio.Stanza(type=self.typestring, path=self.path)
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
297
        if self.file_id is not None:
2309.4.13 by John Arbash Meinel
Conflicts go through Stanza so the need to be aware of utf8 versus unicode file ids.
298
            # Stanza requires Unicode apis
299
            s.add('file_id', self.file_id.decode('utf8'))
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
300
        return s
301
1534.10.22 by Aaron Bentley
Got ConflictList implemented
302
    def _cmp_list(self):
303
        return [type(self), self.path, self.file_id]
304
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
305
    def __cmp__(self, other):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
306
        if getattr(other, "_cmp_list", None) is None:
307
            return -1
308
        return cmp(self._cmp_list(), other._cmp_list())
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
309
1551.7.11 by Aaron Bentley
Add WorkingTree.add_conflicts
310
    def __hash__(self):
311
        return hash((type(self), self.path, self.file_id))
312
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
313
    def __eq__(self, other):
314
        return self.__cmp__(other) == 0
315
316
    def __ne__(self, other):
317
        return not self.__eq__(other)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
318
1534.10.20 by Aaron Bentley
Got all tests passing
319
    def __str__(self):
320
        return self.format % self.__dict__
321
1534.10.22 by Aaron Bentley
Got ConflictList implemented
322
    def __repr__(self):
323
        rdict = dict(self.__dict__)
324
        rdict['class'] = self.__class__.__name__
325
        return self.rformat % rdict
326
1534.10.18 by Aaron Bentley
Defined all new Conflict types
327
    @staticmethod
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
328
    def factory(type, **kwargs):
1534.10.18 by Aaron Bentley
Defined all new Conflict types
329
        global ctype
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
330
        return ctype[type](**kwargs)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
331
1666.1.4 by Robert Collins
* 'Metadir' is now the default disk format. This improves behaviour in
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
1534.10.18 by Aaron Bentley
Defined all new Conflict types
341
342
class PathConflict(Conflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
343
    """A conflict was encountered merging file paths"""
344
1534.10.18 by Aaron Bentley
Defined all new Conflict types
345
    typestring = 'path conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
346
1534.10.20 by Aaron Bentley
Got all tests passing
347
    format = 'Path conflict: %(path)s / %(conflict_path)s'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
348
349
    rformat = '%(class)s(%(path)r, %(conflict_path)r, %(file_id)r)'
1534.10.20 by Aaron Bentley
Got all tests passing
350
    def __init__(self, path, conflict_path=None, file_id=None):
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
351
        Conflict.__init__(self, path, file_id)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
352
        self.conflict_path = conflict_path
353
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
354
    def as_stanza(self):
355
        s = Conflict.as_stanza(self)
1534.10.20 by Aaron Bentley
Got all tests passing
356
        if self.conflict_path is not None:
357
            s.add('conflict_path', self.conflict_path)
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
358
        return s
359
1534.10.20 by Aaron Bentley
Got all tests passing
360
361
class ContentsConflict(PathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
362
    """The files are of different types, or not present"""
363
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
364
    has_files = True
365
1534.10.20 by Aaron Bentley
Got all tests passing
366
    typestring = 'contents conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
367
1534.10.20 by Aaron Bentley
Got all tests passing
368
    format = 'Contents conflict in %(path)s'
369
370
371
class TextConflict(PathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
372
    """The merge algorithm could not resolve all differences encountered."""
373
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
374
    has_files = True
375
1534.10.20 by Aaron Bentley
Got all tests passing
376
    typestring = 'text conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
377
1534.10.20 by Aaron Bentley
Got all tests passing
378
    format = 'Text conflict in %(path)s'
379
380
1534.10.18 by Aaron Bentley
Defined all new Conflict types
381
class HandledConflict(Conflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
382
    """A path problem that has been provisionally resolved.
383
    This is intended to be a base class.
384
    """
385
386
    rformat = "%(class)s(%(action)r, %(path)r, %(file_id)r)"
387
    
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
388
    def __init__(self, action, path, file_id=None):
389
        Conflict.__init__(self, path, file_id)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
390
        self.action = action
391
1534.10.22 by Aaron Bentley
Got ConflictList implemented
392
    def _cmp_list(self):
393
        return Conflict._cmp_list(self) + [self.action]
394
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
395
    def as_stanza(self):
396
        s = Conflict.as_stanza(self)
397
        s.add('action', self.action)
398
        return s
399
1534.10.20 by Aaron Bentley
Got all tests passing
400
1534.10.18 by Aaron Bentley
Defined all new Conflict types
401
class HandledPathConflict(HandledConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
402
    """A provisionally-resolved path problem involving two paths.
403
    This is intended to be a base class.
404
    """
405
406
    rformat = "%(class)s(%(action)r, %(path)r, %(conflict_path)r,"\
407
        " %(file_id)r, %(conflict_file_id)r)"
408
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
409
    def __init__(self, action, path, conflict_path, file_id=None,
1534.10.18 by Aaron Bentley
Defined all new Conflict types
410
                 conflict_file_id=None):
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
411
        HandledConflict.__init__(self, action, path, file_id)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
412
        self.conflict_path = conflict_path 
2309.4.13 by John Arbash Meinel
Conflicts go through Stanza so the need to be aware of utf8 versus unicode file ids.
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)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
417
        
1534.10.22 by Aaron Bentley
Got ConflictList implemented
418
    def _cmp_list(self):
419
        return HandledConflict._cmp_list(self) + [self.conflict_path, 
420
                                                  self.conflict_file_id]
421
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
422
    def as_stanza(self):
423
        s = HandledConflict.as_stanza(self)
424
        s.add('conflict_path', self.conflict_path)
425
        if self.conflict_file_id is not None:
2309.4.13 by John Arbash Meinel
Conflicts go through Stanza so the need to be aware of utf8 versus unicode file ids.
426
            s.add('conflict_file_id', self.conflict_file_id.decode('utf8'))
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
427
            
428
        return s
1534.10.20 by Aaron Bentley
Got all tests passing
429
430
1534.10.18 by Aaron Bentley
Defined all new Conflict types
431
class DuplicateID(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
432
    """Two files want the same file_id."""
433
1534.10.18 by Aaron Bentley
Defined all new Conflict types
434
    typestring = 'duplicate id'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
435
1534.10.20 by Aaron Bentley
Got all tests passing
436
    format = 'Conflict adding id to %(conflict_path)s.  %(action)s %(path)s.'
437
1534.10.18 by Aaron Bentley
Defined all new Conflict types
438
439
class DuplicateEntry(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
440
    """Two directory entries want to have the same name."""
441
1534.10.18 by Aaron Bentley
Defined all new Conflict types
442
    typestring = 'duplicate'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
443
1534.10.20 by Aaron Bentley
Got all tests passing
444
    format = 'Conflict adding file %(conflict_path)s.  %(action)s %(path)s.'
445
1534.10.18 by Aaron Bentley
Defined all new Conflict types
446
447
class ParentLoop(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
448
    """An attempt to create an infinitely-looping directory structure.
449
    This is rare, but can be produced like so:
450
451
    tree A:
452
      mv foo/bar
453
    tree B:
454
      mv bar/foo
455
    merge A and B
456
    """
457
1534.10.18 by Aaron Bentley
Defined all new Conflict types
458
    typestring = 'parent loop'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
459
1534.10.20 by Aaron Bentley
Got all tests passing
460
    format = 'Conflict moving %(conflict_path)s into %(path)s.  %(action)s.'
461
1534.10.18 by Aaron Bentley
Defined all new Conflict types
462
463
class UnversionedParent(HandledConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
464
    """An attempt to version an file whose parent directory is not versioned.
465
    Typically, the result of a merge where one tree unversioned the directory
466
    and the other added a versioned file to it.
467
    """
468
1534.10.18 by Aaron Bentley
Defined all new Conflict types
469
    typestring = 'unversioned parent'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
470
1551.8.22 by Aaron Bentley
Improve message when OTHER deletes an in-use tree
471
    format = 'Conflict because %(path)s is not versioned, but has versioned'\
472
             ' children.  %(action)s.'
1534.10.20 by Aaron Bentley
Got all tests passing
473
1534.10.18 by Aaron Bentley
Defined all new Conflict types
474
475
class MissingParent(HandledConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
476
    """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
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)
1534.10.22 by Aaron Bentley
Got ConflictList implemented
480
    """
481
1534.10.18 by Aaron Bentley
Defined all new Conflict types
482
    typestring = 'missing parent'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
483
1534.10.20 by Aaron Bentley
Got all tests passing
484
    format = 'Conflict adding files to %(path)s.  %(action)s.'
485
486
1551.8.22 by Aaron Bentley
Improve message when OTHER deletes an in-use tree
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
1551.8.23 by Aaron Bentley
Tweaked conflict message to be more understandable
495
    format = "Conflict: can't delete %(path)s because it is not empty.  "\
496
             "%(action)s."
1551.8.22 by Aaron Bentley
Improve message when OTHER deletes an in-use tree
497
1534.10.18 by Aaron Bentley
Defined all new Conflict types
498
499
ctype = {}
1534.10.20 by Aaron Bentley
Got all tests passing
500
501
1534.10.18 by Aaron Bentley
Defined all new Conflict types
502
def register_types(*conflict_types):
503
    """Register a Conflict subclass for serialization purposes"""
504
    global ctype
505
    for conflict_type in conflict_types:
506
        ctype[conflict_type.typestring] = conflict_type
507
1534.10.20 by Aaron Bentley
Got all tests passing
508
1534.10.18 by Aaron Bentley
Defined all new Conflict types
509
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
1551.8.22 by Aaron Bentley
Improve message when OTHER deletes an in-use tree
510
               DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,
511
               DeletingParent,)