/brz/remove-bazaar

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