/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
3144.4.2 by Aaron Bentley
Handle non-directory parent conflicts (abentley, #177390)
1
# Copyright (C) 2005, 2007 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
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1185.14.3 by Aaron Bentley
Copied conflict lister in
16
1185.16.11 by Martin Pool
todo
17
# TODO: Move this into builtins
18
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
19
# TODO: 'bzr resolve' should accept a directory name and work from that
1185.16.11 by Martin Pool
todo
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,
4597.3.24 by Vincent Ladeuil
Fix imports in bzrlib/conflicts.py.
35
    workingtree,
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
36
    )
37
""")
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
38
from bzrlib.option import Option
1534.10.6 by Aaron Bentley
Conflict serialization working for WorkingTree3
39
40
41
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
42
1185.14.3 by Aaron Bentley
Copied conflict lister in
43
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
44
class cmd_conflicts(commands.Command):
1185.14.3 by Aaron Bentley
Copied conflict lister in
45
    """List files with conflicts.
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
46
47
    Merge will do its best to combine the changes in two branches, but there
48
    are some kinds of problems only a human can fix.  When it encounters those,
49
    it will mark a conflict.  A conflict means that you need to fix something,
50
    before you should commit.
51
1551.9.8 by Aaron Bentley
Add --text parameter to conflicts
52
    Conflicts normally are listed as short, human-readable messages.  If --text
53
    is supplied, the pathnames of files with text conflicts are listed,
54
    instead.  (This is useful for editing all files with text conflicts.)
55
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
56
    Use bzr resolve when you have fixed a problem.
57
58
    See also bzr resolve.
1185.14.3 by Aaron Bentley
Copied conflict lister in
59
    """
2598.1.1 by Martin Pool
Add test for and documentation of option style, fix up existing options to comply
60
    takes_options = [
61
            Option('text',
62
                   help='List paths of files with text conflicts.'),
63
        ]
1551.9.8 by Aaron Bentley
Add --text parameter to conflicts
64
65
    def run(self, text=False):
4597.3.24 by Vincent Ladeuil
Fix imports in bzrlib/conflicts.py.
66
        wt = workingtree.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.'),
4597.3.23 by Vincent Ladeuil
Option test pass.
94
            Option('interactive', help='Dialog-based resolution'),
2598.1.2 by Martin Pool
Also check that option help ends in a period, and fix those that don't
95
            ]
4597.3.23 by Vincent Ladeuil
Option test pass.
96
    def run(self, file_list=None, all=False, interactive=False):
4597.3.25 by Vincent Ladeuil
Define --interactive usage.
97
        if all and interactive:
98
            raise errors.BzrCommandError(
99
                '--all and --interactive are mutually exclusive')
1652.1.3 by Martin Pool
Improved bzr resolve command line handling
100
        if all:
101
            if file_list:
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
102
                raise errors.BzrCommandError("If --all is specified,"
103
                                             " no FILE may be provided")
4597.3.24 by Vincent Ladeuil
Fix imports in bzrlib/conflicts.py.
104
            tree = workingtree.WorkingTree.open_containing('.')[0]
1652.1.3 by Martin Pool
Improved bzr resolve command line handling
105
            resolve(tree)
4597.3.25 by Vincent Ladeuil
Define --interactive usage.
106
        elif interactive:
4597.3.26 by Vincent Ladeuil
Tests passing for a minimal --interactive implementation.
107
            tree, file_list = builtins.tree_files(file_list)
4597.3.25 by Vincent Ladeuil
Define --interactive usage.
108
            if file_list is None or len(file_list) != 1:
109
                raise errors.BzrCommandError(
110
                    '--interactive requires a single FILE parameter')
4597.3.28 by Vincent Ladeuil
Implement --interactive for ContentsConflict.
111
            _resolve_interactive(tree, file_list[0])
1185.14.3 by Aaron Bentley
Copied conflict lister in
112
        else:
2120.7.3 by Aaron Bentley
Update resolve command to automatically mark conflicts as resolved
113
            tree, file_list = builtins.tree_files(file_list)
1652.1.3 by Martin Pool
Improved bzr resolve command line handling
114
            if file_list is None:
2120.7.3 by Aaron Bentley
Update resolve command to automatically mark conflicts as resolved
115
                un_resolved, resolved = tree.auto_resolve()
116
                if len(un_resolved) > 0:
117
                    trace.note('%d conflict(s) auto-resolved.', len(resolved))
118
                    trace.note('Remaining conflicts:')
119
                    for conflict in un_resolved:
120
                        trace.note(conflict)
121
                    return 1
122
                else:
123
                    trace.note('All conflicts resolved.')
124
                    return 0
125
            else:
2120.7.4 by Aaron Bentley
Fix normal resolve
126
                resolve(tree, file_list)
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
127
128
3017.2.1 by Aaron Bentley
Revert now resolves conflicts recursively (#102739)
129
def resolve(tree, paths=None, ignore_misses=False, recursive=False):
3017.2.2 by Aaron Bentley
Add docstring to resolve
130
    """Resolve some or all of the conflicts in a working tree.
131
132
    :param paths: If None, resolve all conflicts.  Otherwise, select only
133
        specified conflicts.
134
    :param recursive: If True, then elements of paths which are directories
135
        have all their children resolved, etc.  When invoked as part of
136
        recursive commands like revert, this should be True.  For commands
137
        or applications wishing finer-grained control, like the resolve
138
        command, this should be False.
139
    :ignore_misses: If False, warnings will be printed if the supplied paths
140
        do not have conflicts.
141
    """
1997.1.3 by Robert Collins
All WorkingTree methods which write to the tree, but not to the branch
142
    tree.lock_tree_write()
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
143
    try:
1534.10.23 by Aaron Bentley
Removed conflicts_to_stanzas and stanzas_to_conflicts
144
        tree_conflicts = tree.conflicts()
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
145
        if paths is None:
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
146
            new_conflicts = ConflictList()
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
147
            selected_conflicts = tree_conflicts
148
        else:
149
            new_conflicts, selected_conflicts = \
3017.2.1 by Aaron Bentley
Revert now resolves conflicts recursively (#102739)
150
                tree_conflicts.select_conflicts(tree, paths, ignore_misses,
151
                    recursive)
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
152
        try:
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
153
            tree.set_conflicts(new_conflicts)
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
154
        except errors.UnsupportedOperation:
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
155
            pass
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
156
        selected_conflicts.remove_files(tree)
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
157
    finally:
158
        tree.unlock()
159
160
4597.3.26 by Vincent Ladeuil
Tests passing for a minimal --interactive implementation.
161
def _resolve_interactive(tree, path):
162
    import sys # TEMPORARY
163
    tree.lock_tree_write()
164
    try:
165
        tree_conflicts = tree.conflicts()
166
        (remaining,
167
         selected) = tree_conflicts.select_conflicts(
168
            tree, [path], ignore_misses=True)
169
        if not selected:
170
            raise errors.NotConflicted(path)
171
        # FIXME: we should really do a loop below as some paths may be involved
172
        # in several conflicts but it's not yet clear how we will handle that.
173
        c = selected[0]
174
        action_name = sys.stdin.readline()
175
        action_name = action_name.rstrip('\n')
176
        # Crude exit
177
        if action_name == 'quit':
178
            return
179
        action = getattr(c, action_name, None)
180
        if action is None:
4597.3.30 by Vincent Ladeuil
Light changes learned while starting to understand multiple conflicts on
181
            raise NotImplementedError(c.__class__.__name__ + '.' + action_name)
4597.3.26 by Vincent Ladeuil
Tests passing for a minimal --interactive implementation.
182
        action(tree)
4597.3.28 by Vincent Ladeuil
Implement --interactive for ContentsConflict.
183
        # FIXME: We need an API to use that on a single conflict
184
        ConflictList([c]).remove_files(tree)
4597.3.26 by Vincent Ladeuil
Tests passing for a minimal --interactive implementation.
185
        tree.set_conflicts(remaining)
186
    finally:
187
        tree.unlock()
188
189
1185.35.1 by Aaron Bentley
Implemented conflicts.restore
190
def restore(filename):
4597.2.6 by Vincent Ladeuil
Cleanup doc string.
191
    """Restore a conflicted file to the state it was in before merging.
192
193
    Only text restoration is supported at present.
1185.35.1 by Aaron Bentley
Implemented conflicts.restore
194
    """
195
    conflicted = False
196
    try:
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
197
        osutils.rename(filename + ".THIS", filename)
1185.35.1 by Aaron Bentley
Implemented conflicts.restore
198
        conflicted = True
199
    except OSError, e:
200
        if e.errno != errno.ENOENT:
201
            raise
202
    try:
203
        os.unlink(filename + ".BASE")
204
        conflicted = True
205
    except OSError, e:
206
        if e.errno != errno.ENOENT:
207
            raise
208
    try:
209
        os.unlink(filename + ".OTHER")
210
        conflicted = True
211
    except OSError, e:
212
        if e.errno != errno.ENOENT:
213
            raise
214
    if not conflicted:
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
215
        raise errors.NotConflicted(filename)
1534.10.4 by Aaron Bentley
Implemented conflict serialization
216
217
1534.10.22 by Aaron Bentley
Got ConflictList implemented
218
class ConflictList(object):
1652.1.1 by Martin Pool
Fix 'bzr resolve' run from subdirectory
219
    """List of conflicts.
220
221
    Typically obtained from WorkingTree.conflicts()
222
1534.10.22 by Aaron Bentley
Got ConflictList implemented
223
    Can be instantiated from stanzas or from Conflict subclasses.
224
    """
225
226
    def __init__(self, conflicts=None):
227
        object.__init__(self)
228
        if conflicts is None:
229
            self.__list = []
230
        else:
231
            self.__list = conflicts
232
1652.1.1 by Martin Pool
Fix 'bzr resolve' run from subdirectory
233
    def is_empty(self):
234
        return len(self.__list) == 0
235
1534.10.22 by Aaron Bentley
Got ConflictList implemented
236
    def __len__(self):
237
        return len(self.__list)
238
239
    def __iter__(self):
240
        return iter(self.__list)
241
242
    def __getitem__(self, key):
243
        return self.__list[key]
244
245
    def append(self, conflict):
246
        return self.__list.append(conflict)
247
248
    def __eq__(self, other_list):
249
        return list(self) == list(other_list)
250
251
    def __ne__(self, other_list):
252
        return not (self == other_list)
253
254
    def __repr__(self):
255
        return "ConflictList(%r)" % self.__list
256
257
    @staticmethod
258
    def from_stanzas(stanzas):
259
        """Produce a new ConflictList from an iterable of stanzas"""
260
        conflicts = ConflictList()
261
        for stanza in stanzas:
262
            conflicts.append(Conflict.factory(**stanza.as_dict()))
263
        return conflicts
264
265
    def to_stanzas(self):
266
        """Generator of stanzas"""
267
        for conflict in self:
268
            yield conflict.as_stanza()
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
269
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
270
    def to_strings(self):
271
        """Generate strings for the provided conflicts"""
272
        for conflict in self:
273
            yield str(conflict)
274
275
    def remove_files(self, tree):
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
276
        """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
277
        for conflict in self:
278
            if not conflict.has_files:
279
                continue
280
            for suffix in CONFLICT_SUFFIXES:
281
                try:
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
282
                    osutils.delete_any(tree.abspath(conflict.path+suffix))
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
283
                except OSError, e:
284
                    if e.errno != errno.ENOENT:
285
                        raise
1534.10.21 by Aaron Bentley
Moved and renamed conflict functions
286
1551.15.58 by Aaron Bentley
Status honours selected paths for conflicts (#127606)
287
    def select_conflicts(self, tree, paths, ignore_misses=False,
288
                         recurse=False):
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
289
        """Select the conflicts associated with paths in a tree.
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
290
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
291
        File-ids are also used for this.
1551.7.10 by Aaron Bentley
Remerge doesn't clear unrelated conflicts
292
        :return: a pair of ConflictLists: (not_selected, selected)
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
293
        """
294
        path_set = set(paths)
295
        ids = {}
296
        selected_paths = set()
297
        new_conflicts = ConflictList()
298
        selected_conflicts = ConflictList()
299
        for path in paths:
300
            file_id = tree.path2id(path)
301
            if file_id is not None:
302
                ids[file_id] = path
303
304
        for conflict in self:
305
            selected = False
306
            for key in ('path', 'conflict_path'):
307
                cpath = getattr(conflict, key, None)
308
                if cpath is None:
309
                    continue
310
                if cpath in path_set:
311
                    selected = True
312
                    selected_paths.add(cpath)
1551.15.58 by Aaron Bentley
Status honours selected paths for conflicts (#127606)
313
                if recurse:
314
                    if osutils.is_inside_any(path_set, cpath):
315
                        selected = True
316
                        selected_paths.add(cpath)
317
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
318
            for key in ('file_id', 'conflict_file_id'):
319
                cfile_id = getattr(conflict, key, None)
320
                if cfile_id is None:
321
                    continue
322
                try:
323
                    cpath = ids[cfile_id]
324
                except KeyError:
325
                    continue
326
                selected = True
327
                selected_paths.add(cpath)
328
            if selected:
329
                selected_conflicts.append(conflict)
330
            else:
331
                new_conflicts.append(conflict)
332
        if ignore_misses is not True:
333
            for path in [p for p in paths if p not in selected_paths]:
334
                if not os.path.exists(tree.abspath(path)):
335
                    print "%s does not exist" % path
336
                else:
337
                    print "%s is not conflicted" % path
338
        return new_conflicts, selected_conflicts
339
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
340
1534.10.18 by Aaron Bentley
Defined all new Conflict types
341
class Conflict(object):
342
    """Base class for all types of conflict"""
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
343
344
    has_files = False
345
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
346
    def __init__(self, path, file_id=None):
1534.10.18 by Aaron Bentley
Defined all new Conflict types
347
        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.
348
        # warn turned off, because the factory blindly transfers the Stanza
349
        # values to __init__ and Stanza is purely a Unicode api.
350
        self.file_id = osutils.safe_file_id(file_id, warn=False)
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
351
352
    def as_stanza(self):
1996.3.33 by John Arbash Meinel
make bzrlib/conflicts.py lazy
353
        s = rio.Stanza(type=self.typestring, path=self.path)
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
354
        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.
355
            # Stanza requires Unicode apis
356
            s.add('file_id', self.file_id.decode('utf8'))
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
357
        return s
358
1534.10.22 by Aaron Bentley
Got ConflictList implemented
359
    def _cmp_list(self):
360
        return [type(self), self.path, self.file_id]
361
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
362
    def __cmp__(self, other):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
363
        if getattr(other, "_cmp_list", None) is None:
364
            return -1
365
        return cmp(self._cmp_list(), other._cmp_list())
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
366
1551.7.11 by Aaron Bentley
Add WorkingTree.add_conflicts
367
    def __hash__(self):
368
        return hash((type(self), self.path, self.file_id))
369
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
370
    def __eq__(self, other):
371
        return self.__cmp__(other) == 0
372
373
    def __ne__(self, other):
374
        return not self.__eq__(other)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
375
1534.10.20 by Aaron Bentley
Got all tests passing
376
    def __str__(self):
377
        return self.format % self.__dict__
378
1534.10.22 by Aaron Bentley
Got ConflictList implemented
379
    def __repr__(self):
380
        rdict = dict(self.__dict__)
381
        rdict['class'] = self.__class__.__name__
382
        return self.rformat % rdict
383
1534.10.18 by Aaron Bentley
Defined all new Conflict types
384
    @staticmethod
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
385
    def factory(type, **kwargs):
1534.10.18 by Aaron Bentley
Defined all new Conflict types
386
        global ctype
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
387
        return ctype[type](**kwargs)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
388
1666.1.4 by Robert Collins
* 'Metadir' is now the default disk format. This improves behaviour in
389
    @staticmethod
390
    def sort_key(conflict):
391
        if conflict.path is not None:
392
            return conflict.path, conflict.typestring
393
        elif getattr(conflict, "conflict_path", None) is not None:
394
            return conflict.conflict_path, conflict.typestring
395
        else:
396
            return None, conflict.typestring
397
1534.10.18 by Aaron Bentley
Defined all new Conflict types
398
399
class PathConflict(Conflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
400
    """A conflict was encountered merging file paths"""
401
1534.10.18 by Aaron Bentley
Defined all new Conflict types
402
    typestring = 'path conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
403
1534.10.20 by Aaron Bentley
Got all tests passing
404
    format = 'Path conflict: %(path)s / %(conflict_path)s'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
405
406
    rformat = '%(class)s(%(path)r, %(conflict_path)r, %(file_id)r)'
1534.10.20 by Aaron Bentley
Got all tests passing
407
    def __init__(self, path, conflict_path=None, file_id=None):
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
408
        Conflict.__init__(self, path, file_id)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
409
        self.conflict_path = conflict_path
410
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
411
    def as_stanza(self):
412
        s = Conflict.as_stanza(self)
1534.10.20 by Aaron Bentley
Got all tests passing
413
        if self.conflict_path is not None:
414
            s.add('conflict_path', self.conflict_path)
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
415
        return s
416
4597.3.33 by Vincent Ladeuil
Implement --interactive for PathConflict.
417
    def keep_mine(self, tree):
418
        tree.rename_one(self.conflict_path, self.path)
419
420
    def take_theirs(self, tree):
421
        # just acccept bzr proposal
422
        pass
423
1534.10.20 by Aaron Bentley
Got all tests passing
424
425
class ContentsConflict(PathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
426
    """The files are of different types, or not present"""
427
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
428
    has_files = True
429
1534.10.20 by Aaron Bentley
Got all tests passing
430
    typestring = 'contents conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
431
1534.10.20 by Aaron Bentley
Got all tests passing
432
    format = 'Contents conflict in %(path)s'
433
4597.3.28 by Vincent Ladeuil
Implement --interactive for ContentsConflict.
434
    def keep_mine(self, tree):
435
        tree.remove([self.path + '.OTHER'], force=True, keep_files=False)
436
437
    def take_theirs(self, tree):
438
        tree.remove([self.path], force=True, keep_files=False)
439
4597.3.30 by Vincent Ladeuil
Light changes learned while starting to understand multiple conflicts on
440
4597.3.29 by Vincent Ladeuil
Fix bogus tests.
441
# FIXME: TextConflict is about a single file-id, there never is a conflict_path
442
# attribute so we shouldn't inherit from PathConflict but simply from Conflict
1534.10.20 by Aaron Bentley
Got all tests passing
443
class TextConflict(PathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
444
    """The merge algorithm could not resolve all differences encountered."""
445
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
446
    has_files = True
447
1534.10.20 by Aaron Bentley
Got all tests passing
448
    typestring = 'text conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
449
1534.10.20 by Aaron Bentley
Got all tests passing
450
    format = 'Text conflict in %(path)s'
451
4597.3.33 by Vincent Ladeuil
Implement --interactive for PathConflict.
452
    def keep_mine(self, tree):
453
        raise NotImplementedError(self.keep_mine)
454
455
    def take_theirs(self, tree):
456
        raise NotImplementedError(self.take_theirs)
457
1534.10.20 by Aaron Bentley
Got all tests passing
458
1534.10.18 by Aaron Bentley
Defined all new Conflict types
459
class HandledConflict(Conflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
460
    """A path problem that has been provisionally resolved.
461
    This is intended to be a base class.
462
    """
463
464
    rformat = "%(class)s(%(action)r, %(path)r, %(file_id)r)"
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
465
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
466
    def __init__(self, action, path, file_id=None):
467
        Conflict.__init__(self, path, file_id)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
468
        self.action = action
469
1534.10.22 by Aaron Bentley
Got ConflictList implemented
470
    def _cmp_list(self):
471
        return Conflict._cmp_list(self) + [self.action]
472
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
473
    def as_stanza(self):
474
        s = Conflict.as_stanza(self)
475
        s.add('action', self.action)
476
        return s
477
1534.10.20 by Aaron Bentley
Got all tests passing
478
1534.10.18 by Aaron Bentley
Defined all new Conflict types
479
class HandledPathConflict(HandledConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
480
    """A provisionally-resolved path problem involving two paths.
481
    This is intended to be a base class.
482
    """
483
484
    rformat = "%(class)s(%(action)r, %(path)r, %(conflict_path)r,"\
485
        " %(file_id)r, %(conflict_file_id)r)"
486
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
487
    def __init__(self, action, path, conflict_path, file_id=None,
1534.10.18 by Aaron Bentley
Defined all new Conflict types
488
                 conflict_file_id=None):
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
489
        HandledConflict.__init__(self, action, path, file_id)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
490
        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.
491
        # warn turned off, because the factory blindly transfers the Stanza
492
        # values to __init__.
493
        self.conflict_file_id = osutils.safe_file_id(conflict_file_id,
494
                                                     warn=False)
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
495
1534.10.22 by Aaron Bentley
Got ConflictList implemented
496
    def _cmp_list(self):
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
497
        return HandledConflict._cmp_list(self) + [self.conflict_path,
1534.10.22 by Aaron Bentley
Got ConflictList implemented
498
                                                  self.conflict_file_id]
499
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
500
    def as_stanza(self):
501
        s = HandledConflict.as_stanza(self)
502
        s.add('conflict_path', self.conflict_path)
503
        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.
504
            s.add('conflict_file_id', self.conflict_file_id.decode('utf8'))
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
505
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
506
        return s
1534.10.20 by Aaron Bentley
Got all tests passing
507
508
1534.10.18 by Aaron Bentley
Defined all new Conflict types
509
class DuplicateID(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
510
    """Two files want the same file_id."""
511
1534.10.18 by Aaron Bentley
Defined all new Conflict types
512
    typestring = 'duplicate id'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
513
1534.10.20 by Aaron Bentley
Got all tests passing
514
    format = 'Conflict adding id to %(conflict_path)s.  %(action)s %(path)s.'
515
1534.10.18 by Aaron Bentley
Defined all new Conflict types
516
517
class DuplicateEntry(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
518
    """Two directory entries want to have the same name."""
519
1534.10.18 by Aaron Bentley
Defined all new Conflict types
520
    typestring = 'duplicate'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
521
1534.10.20 by Aaron Bentley
Got all tests passing
522
    format = 'Conflict adding file %(conflict_path)s.  %(action)s %(path)s.'
523
4597.3.27 by Vincent Ladeuil
(keep_mine, take_theirs) sounds clearer than (keep_this, keep_other).
524
    def keep_mine(self, tree):
4597.3.26 by Vincent Ladeuil
Tests passing for a minimal --interactive implementation.
525
        tree.remove([self.conflict_path], force=True, keep_files=False)
526
        tree.rename_one(self.path, self.conflict_path)
527
4597.3.27 by Vincent Ladeuil
(keep_mine, take_theirs) sounds clearer than (keep_this, keep_other).
528
    def take_theirs(self, tree):
4597.3.26 by Vincent Ladeuil
Tests passing for a minimal --interactive implementation.
529
        tree.remove([self.path], force=True, keep_files=False)
530
1534.10.18 by Aaron Bentley
Defined all new Conflict types
531
532
class ParentLoop(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
533
    """An attempt to create an infinitely-looping directory structure.
534
    This is rare, but can be produced like so:
535
536
    tree A:
537
      mv foo/bar
538
    tree B:
539
      mv bar/foo
540
    merge A and B
541
    """
542
1534.10.18 by Aaron Bentley
Defined all new Conflict types
543
    typestring = 'parent loop'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
544
1534.10.20 by Aaron Bentley
Got all tests passing
545
    format = 'Conflict moving %(conflict_path)s into %(path)s.  %(action)s.'
546
4597.3.34 by Vincent Ladeuil
Implement --interactive for ParentLoop.
547
    def keep_mine(self, tree):
548
        # just acccept bzr proposal
549
        pass
550
551
    def take_theirs(self, tree):
552
        # FIXME: We should have to manipulate so many paths here (and there is
553
        # probably a bug or two...)
554
        conflict_base_path = osutils.basename(self.conflict_path)
555
        base_path = osutils.basename(self.path)
556
        tree.rename_one(self.conflict_path, conflict_base_path)
557
        tree.rename_one(self.path,
558
                        osutils.joinpath([conflict_base_path, base_path]))
559
1534.10.18 by Aaron Bentley
Defined all new Conflict types
560
561
class UnversionedParent(HandledConflict):
4597.2.1 by Vincent Ladeuil
Fix some typos.
562
    """An attempt to version a file whose parent directory is not versioned.
1534.10.22 by Aaron Bentley
Got ConflictList implemented
563
    Typically, the result of a merge where one tree unversioned the directory
564
    and the other added a versioned file to it.
565
    """
566
1534.10.18 by Aaron Bentley
Defined all new Conflict types
567
    typestring = 'unversioned parent'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
568
1551.8.22 by Aaron Bentley
Improve message when OTHER deletes an in-use tree
569
    format = 'Conflict because %(path)s is not versioned, but has versioned'\
570
             ' children.  %(action)s.'
1534.10.20 by Aaron Bentley
Got all tests passing
571
1534.10.18 by Aaron Bentley
Defined all new Conflict types
572
573
class MissingParent(HandledConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
574
    """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
575
    Typically, the result of a merge where THIS deleted the directory and
576
    the OTHER added a file to it.
577
    See also: DeletingParent (same situation, reversed THIS and OTHER)
1534.10.22 by Aaron Bentley
Got ConflictList implemented
578
    """
579
1534.10.18 by Aaron Bentley
Defined all new Conflict types
580
    typestring = 'missing parent'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
581
1534.10.20 by Aaron Bentley
Got all tests passing
582
    format = 'Conflict adding files to %(path)s.  %(action)s.'
583
4597.3.31 by Vincent Ladeuil
Implement --interactive for MissingParent.
584
    def keep_mine(self, tree):
585
        tree.remove([self.path], force=True, keep_files=False)
586
587
    def take_theirs(self, tree):
588
        # just acccept bzr proposal
589
        pass
590
1534.10.20 by Aaron Bentley
Got all tests passing
591
1551.8.22 by Aaron Bentley
Improve message when OTHER deletes an in-use tree
592
class DeletingParent(HandledConflict):
593
    """An attempt to add files to a directory that is not present.
594
    Typically, the result of a merge where one OTHER deleted the directory and
595
    the THIS added a file to it.
596
    """
597
598
    typestring = 'deleting parent'
599
1551.8.23 by Aaron Bentley
Tweaked conflict message to be more understandable
600
    format = "Conflict: can't delete %(path)s because it is not empty.  "\
601
             "%(action)s."
1551.8.22 by Aaron Bentley
Improve message when OTHER deletes an in-use tree
602
4597.3.32 by Vincent Ladeuil
Implement --interactive for DeletingParent noting the inconsistency.
603
    # FIXME: It's a bit strange that the default action is not coherent with
604
    # MissingParent from the *user* pov.
605
606
    def keep_mine(self, tree):
607
        # just acccept bzr proposal
608
        pass
609
610
    def take_theirs(self, tree):
611
        tree.remove([self.path], force=True, keep_files=False)
612
1534.10.18 by Aaron Bentley
Defined all new Conflict types
613
3144.4.2 by Aaron Bentley
Handle non-directory parent conflicts (abentley, #177390)
614
class NonDirectoryParent(HandledConflict):
615
    """An attempt to add files to a directory that is not a director or
616
    an attempt to change the kind of a directory with files.
617
    """
618
619
    typestring = 'non-directory parent'
620
621
    format = "Conflict: %(path)s is not a directory, but has files in it."\
622
             "  %(action)s."
623
1534.10.18 by Aaron Bentley
Defined all new Conflict types
624
ctype = {}
1534.10.20 by Aaron Bentley
Got all tests passing
625
626
1534.10.18 by Aaron Bentley
Defined all new Conflict types
627
def register_types(*conflict_types):
628
    """Register a Conflict subclass for serialization purposes"""
629
    global ctype
630
    for conflict_type in conflict_types:
631
        ctype[conflict_type.typestring] = conflict_type
632
1534.10.20 by Aaron Bentley
Got all tests passing
633
1534.10.18 by Aaron Bentley
Defined all new Conflict types
634
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
1551.8.22 by Aaron Bentley
Improve message when OTHER deletes an in-use tree
635
               DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,
3144.4.2 by Aaron Bentley
Handle non-directory parent conflicts (abentley, #177390)
636
               DeletingParent, NonDirectoryParent)