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