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