/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
1185.14.3 by Aaron Bentley
Copied conflict lister in
1
# Copyright (C) 2005 by Aaron Bentley
2
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.
7
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.
12
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
1185.16.11 by Martin Pool
todo
17
# TODO: Move this into builtins
18
19
# TODO: 'bzr resolve' should accept a directory name and work from that 
20
# point down
21
1185.16.33 by Martin Pool
- move 'conflict' and 'resolved' from shipped plugin to regular builtins
22
import os
23
import errno
24
1534.10.21 by Aaron Bentley
Moved and renamed conflict functions
25
import bzrlib
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
26
from bzrlib.commands import register_command
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
27
from bzrlib.errors import BzrCommandError, NotConflicted, UnsupportedOperation
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
28
from bzrlib.option import Option
1534.10.4 by Aaron Bentley
Implemented conflict serialization
29
from bzrlib.osutils import rename
30
from bzrlib.rio import Stanza
1534.10.6 by Aaron Bentley
Conflict serialization working for WorkingTree3
31
32
33
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
34
1185.14.3 by Aaron Bentley
Copied conflict lister in
35
36
class cmd_conflicts(bzrlib.commands.Command):
37
    """List files with conflicts.
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
38
39
    Merge will do its best to combine the changes in two branches, but there
40
    are some kinds of problems only a human can fix.  When it encounters those,
41
    it will mark a conflict.  A conflict means that you need to fix something,
42
    before you should commit.
43
44
    Use bzr resolve when you have fixed a problem.
45
1185.14.3 by Aaron Bentley
Copied conflict lister in
46
    (conflicts are determined by the presence of .BASE .TREE, and .OTHER 
47
    files.)
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
48
49
    See also bzr resolve.
1185.14.3 by Aaron Bentley
Copied conflict lister in
50
    """
51
    def run(self):
1534.10.6 by Aaron Bentley
Conflict serialization working for WorkingTree3
52
        from bzrlib.workingtree import WorkingTree
1534.10.9 by Aaron Bentley
Switched display functions to conflict_lines
53
        wt = WorkingTree.open_containing(u'.')[0]
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
54
        for conflict in wt.conflicts():
1534.10.9 by Aaron Bentley
Switched display functions to conflict_lines
55
            print conflict
1185.14.3 by Aaron Bentley
Copied conflict lister in
56
57
class cmd_resolve(bzrlib.commands.Command):
58
    """Mark a conflict as resolved.
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
59
60
    Merge will do its best to combine the changes in two branches, but there
61
    are some kinds of problems only a human can fix.  When it encounters those,
62
    it will mark a conflict.  A conflict means that you need to fix something,
63
    before you should commit.
64
65
    Once you have fixed a problem, use "bzr resolve FILE.." to mark
66
    individual files as fixed, or "bzr resolve --all" to mark all conflicts as
67
    resolved.
68
69
    See also bzr conflicts.
1185.14.3 by Aaron Bentley
Copied conflict lister in
70
    """
1185.33.24 by Martin Pool
Add alias 'resolved'
71
    aliases = ['resolved']
1185.14.3 by Aaron Bentley
Copied conflict lister in
72
    takes_args = ['file*']
1551.2.18 by Aaron Bentley
Updated docs to clarify conflict handling
73
    takes_options = [Option('all', help='Resolve all conflicts in this tree')]
1185.14.3 by Aaron Bentley
Copied conflict lister in
74
    def run(self, file_list=None, all=False):
1534.10.6 by Aaron Bentley
Conflict serialization working for WorkingTree3
75
        from bzrlib.workingtree import WorkingTree
1185.14.3 by Aaron Bentley
Copied conflict lister in
76
        if file_list is None:
77
            if not all:
78
                raise BzrCommandError(
79
                    "command 'resolve' needs one or more FILE, or --all")
80
        else:
81
            if all:
82
                raise BzrCommandError(
83
                    "If --all is specified, no FILE may be provided")
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
84
        tree = WorkingTree.open_containing(u'.')[0]
85
        resolve(tree, file_list)
86
87
1534.10.15 by Aaron Bentley
Revert does resolve
88
def resolve(tree, paths=None, ignore_misses=False):
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
89
    tree.lock_write()
90
    try:
1534.10.23 by Aaron Bentley
Removed conflicts_to_stanzas and stanzas_to_conflicts
91
        tree_conflicts = tree.conflicts()
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
92
        if paths is None:
93
            new_conflicts = []
94
            selected_conflicts = tree_conflicts
95
        else:
96
            new_conflicts, selected_conflicts = \
1534.10.15 by Aaron Bentley
Revert does resolve
97
                select_conflicts(tree, paths, tree_conflicts, ignore_misses)
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
98
        try:
1534.10.22 by Aaron Bentley
Got ConflictList implemented
99
            tree.set_conflicts(ConflictList(new_conflicts))
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
100
        except UnsupportedOperation:
101
            pass
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
102
        selected_conflicts.remove_files(tree)
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
103
    finally:
104
        tree.unlock()
105
106
1534.10.15 by Aaron Bentley
Revert does resolve
107
def select_conflicts(tree, paths, tree_conflicts, ignore_misses=False):
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
108
    path_set = set(paths)
109
    ids = {}
110
    selected_paths = set()
111
    new_conflicts = []
1534.10.23 by Aaron Bentley
Removed conflicts_to_stanzas and stanzas_to_conflicts
112
    selected_conflicts = ConflictList()
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
113
    for path in paths:
114
        file_id = tree.path2id(path)
115
        if file_id is not None:
116
            ids[file_id] = path
117
1534.10.23 by Aaron Bentley
Removed conflicts_to_stanzas and stanzas_to_conflicts
118
    for conflict, stanza in zip(tree_conflicts, tree_conflicts.to_stanzas()):
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
119
        selected = False
120
        for key in ('path', 'conflict_path'):
121
            try:
122
                cpath = stanza[key]
123
            except KeyError:
124
                continue
125
            if cpath in path_set:
126
                selected = True
127
                selected_paths.add(cpath)
128
        for key in ('file_id', 'conflict_file_id'):
129
            try:
130
                cfile_id = stanza[key]
131
            except KeyError:
132
                continue
133
            try:
134
                cpath = ids[cfile_id]
135
            except KeyError:
136
                continue
137
            selected = True
138
            selected_paths.add(cpath)
139
        if selected:
140
            selected_conflicts.append(conflict)
141
        else:
142
            new_conflicts.append(conflict)
1534.10.15 by Aaron Bentley
Revert does resolve
143
    if ignore_misses is not True:
144
        for path in [p for p in paths if p not in selected_paths]:
145
            if not os.path.exists(tree.abspath(path)):
146
                print "%s does not exist" % path
147
            else:
148
                print "%s is not conflicted" % path
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
149
    return ConflictList(new_conflicts), ConflictList(selected_conflicts)
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
150
151
    
1185.35.1 by Aaron Bentley
Implemented conflicts.restore
152
153
def restore(filename):
154
    """\
155
    Restore a conflicted file to the state it was in before merging.
156
    Only text restoration supported at present.
157
    """
158
    conflicted = False
159
    try:
1185.31.49 by John Arbash Meinel
Some corrections using the new osutils.rename. **ALL TESTS PASS**
160
        rename(filename + ".THIS", filename)
1185.35.1 by Aaron Bentley
Implemented conflicts.restore
161
        conflicted = True
162
    except OSError, e:
163
        if e.errno != errno.ENOENT:
164
            raise
165
    try:
166
        os.unlink(filename + ".BASE")
167
        conflicted = True
168
    except OSError, e:
169
        if e.errno != errno.ENOENT:
170
            raise
171
    try:
172
        os.unlink(filename + ".OTHER")
173
        conflicted = True
174
    except OSError, e:
175
        if e.errno != errno.ENOENT:
176
            raise
177
    if not conflicted:
178
        raise NotConflicted(filename)
1534.10.4 by Aaron Bentley
Implemented conflict serialization
179
180
1534.10.22 by Aaron Bentley
Got ConflictList implemented
181
class ConflictList(object):
182
    """List of conflicts
183
    Can be instantiated from stanzas or from Conflict subclasses.
184
    """
185
186
    def __init__(self, conflicts=None):
187
        object.__init__(self)
188
        if conflicts is None:
189
            self.__list = []
190
        else:
191
            self.__list = conflicts
192
193
    def __len__(self):
194
        return len(self.__list)
195
196
    def __iter__(self):
197
        return iter(self.__list)
198
199
    def __getitem__(self, key):
200
        return self.__list[key]
201
202
    def append(self, conflict):
203
        return self.__list.append(conflict)
204
205
    def __eq__(self, other_list):
206
        return list(self) == list(other_list)
207
208
    def __ne__(self, other_list):
209
        return not (self == other_list)
210
211
    def __repr__(self):
212
        return "ConflictList(%r)" % self.__list
213
214
    @staticmethod
215
    def from_stanzas(stanzas):
216
        """Produce a new ConflictList from an iterable of stanzas"""
217
        conflicts = ConflictList()
218
        for stanza in stanzas:
219
            conflicts.append(Conflict.factory(**stanza.as_dict()))
220
        return conflicts
221
222
    def to_stanzas(self):
223
        """Generator of stanzas"""
224
        for conflict in self:
225
            yield conflict.as_stanza()
226
            
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
227
    def to_strings(self):
228
        """Generate strings for the provided conflicts"""
229
        for conflict in self:
230
            yield str(conflict)
231
232
233
    def remove_files(self, tree):
234
        for conflict in self:
235
            if not conflict.has_files:
236
                continue
237
            for suffix in CONFLICT_SUFFIXES:
238
                try:
239
                    os.unlink(tree.abspath(conflict.path+suffix))
240
                except OSError, e:
241
                    if e.errno != errno.ENOENT:
242
                        raise
1534.10.21 by Aaron Bentley
Moved and renamed conflict functions
243
1534.10.18 by Aaron Bentley
Defined all new Conflict types
244
class Conflict(object):
245
    """Base class for all types of conflict"""
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
246
247
    has_files = False
248
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
249
    def __init__(self, path, file_id=None):
1534.10.18 by Aaron Bentley
Defined all new Conflict types
250
        self.path = path
251
        self.file_id = file_id
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
252
253
    def as_stanza(self):
254
        s = Stanza(type=self.typestring, path=self.path)
255
        if self.file_id is not None:
256
            s.add('file_id', self.file_id)
257
        return s
258
1534.10.22 by Aaron Bentley
Got ConflictList implemented
259
    def _cmp_list(self):
260
        return [type(self), self.path, self.file_id]
261
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
262
    def __cmp__(self, other):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
263
        if getattr(other, "_cmp_list", None) is None:
264
            return -1
265
        return cmp(self._cmp_list(), other._cmp_list())
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
266
267
    def __eq__(self, other):
268
        return self.__cmp__(other) == 0
269
270
    def __ne__(self, other):
271
        return not self.__eq__(other)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
272
1534.10.20 by Aaron Bentley
Got all tests passing
273
    def __str__(self):
274
        return self.format % self.__dict__
275
1534.10.22 by Aaron Bentley
Got ConflictList implemented
276
    def __repr__(self):
277
        rdict = dict(self.__dict__)
278
        rdict['class'] = self.__class__.__name__
279
        return self.rformat % rdict
280
1534.10.18 by Aaron Bentley
Defined all new Conflict types
281
    @staticmethod
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
282
    def factory(type, **kwargs):
1534.10.18 by Aaron Bentley
Defined all new Conflict types
283
        global ctype
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
284
        return ctype[type](**kwargs)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
285
286
287
class PathConflict(Conflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
288
    """A conflict was encountered merging file paths"""
289
1534.10.18 by Aaron Bentley
Defined all new Conflict types
290
    typestring = 'path conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
291
1534.10.20 by Aaron Bentley
Got all tests passing
292
    format = 'Path conflict: %(path)s / %(conflict_path)s'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
293
294
    rformat = '%(class)s(%(path)r, %(conflict_path)r, %(file_id)r)'
1534.10.20 by Aaron Bentley
Got all tests passing
295
    def __init__(self, path, conflict_path=None, file_id=None):
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
296
        Conflict.__init__(self, path, file_id)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
297
        self.conflict_path = conflict_path
298
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
299
    def as_stanza(self):
300
        s = Conflict.as_stanza(self)
1534.10.20 by Aaron Bentley
Got all tests passing
301
        if self.conflict_path is not None:
302
            s.add('conflict_path', self.conflict_path)
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
303
        return s
304
1534.10.20 by Aaron Bentley
Got all tests passing
305
306
class ContentsConflict(PathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
307
    """The files are of different types, or not present"""
308
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
309
    has_files = True
310
1534.10.20 by Aaron Bentley
Got all tests passing
311
    typestring = 'contents conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
312
1534.10.20 by Aaron Bentley
Got all tests passing
313
    format = 'Contents conflict in %(path)s'
314
315
316
class TextConflict(PathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
317
    """The merge algorithm could not resolve all differences encountered."""
318
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
319
    has_files = True
320
1534.10.20 by Aaron Bentley
Got all tests passing
321
    typestring = 'text conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
322
1534.10.20 by Aaron Bentley
Got all tests passing
323
    format = 'Text conflict in %(path)s'
324
325
1534.10.18 by Aaron Bentley
Defined all new Conflict types
326
class HandledConflict(Conflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
327
    """A path problem that has been provisionally resolved.
328
    This is intended to be a base class.
329
    """
330
331
    rformat = "%(class)s(%(action)r, %(path)r, %(file_id)r)"
332
    
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
333
    def __init__(self, action, path, file_id=None):
334
        Conflict.__init__(self, path, file_id)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
335
        self.action = action
336
1534.10.22 by Aaron Bentley
Got ConflictList implemented
337
    def _cmp_list(self):
338
        return Conflict._cmp_list(self) + [self.action]
339
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
340
    def as_stanza(self):
341
        s = Conflict.as_stanza(self)
342
        s.add('action', self.action)
343
        return s
344
1534.10.20 by Aaron Bentley
Got all tests passing
345
1534.10.18 by Aaron Bentley
Defined all new Conflict types
346
class HandledPathConflict(HandledConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
347
    """A provisionally-resolved path problem involving two paths.
348
    This is intended to be a base class.
349
    """
350
351
    rformat = "%(class)s(%(action)r, %(path)r, %(conflict_path)r,"\
352
        " %(file_id)r, %(conflict_file_id)r)"
353
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
354
    def __init__(self, action, path, conflict_path, file_id=None,
1534.10.18 by Aaron Bentley
Defined all new Conflict types
355
                 conflict_file_id=None):
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
356
        HandledConflict.__init__(self, action, path, file_id)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
357
        self.conflict_path = conflict_path 
358
        self.conflict_file_id = conflict_file_id
359
        
1534.10.22 by Aaron Bentley
Got ConflictList implemented
360
    def _cmp_list(self):
361
        return HandledConflict._cmp_list(self) + [self.conflict_path, 
362
                                                  self.conflict_file_id]
363
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
364
    def as_stanza(self):
365
        s = HandledConflict.as_stanza(self)
366
        s.add('conflict_path', self.conflict_path)
367
        if self.conflict_file_id is not None:
368
            s.add('conflict_file_id', self.conflict_file_id)
369
            
370
        return s
1534.10.20 by Aaron Bentley
Got all tests passing
371
372
1534.10.18 by Aaron Bentley
Defined all new Conflict types
373
class DuplicateID(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
374
    """Two files want the same file_id."""
375
1534.10.18 by Aaron Bentley
Defined all new Conflict types
376
    typestring = 'duplicate id'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
377
1534.10.20 by Aaron Bentley
Got all tests passing
378
    format = 'Conflict adding id to %(conflict_path)s.  %(action)s %(path)s.'
379
1534.10.18 by Aaron Bentley
Defined all new Conflict types
380
381
class DuplicateEntry(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
382
    """Two directory entries want to have the same name."""
383
1534.10.18 by Aaron Bentley
Defined all new Conflict types
384
    typestring = 'duplicate'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
385
1534.10.20 by Aaron Bentley
Got all tests passing
386
    format = 'Conflict adding file %(conflict_path)s.  %(action)s %(path)s.'
387
1534.10.18 by Aaron Bentley
Defined all new Conflict types
388
389
class ParentLoop(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
390
    """An attempt to create an infinitely-looping directory structure.
391
    This is rare, but can be produced like so:
392
393
    tree A:
394
      mv foo/bar
395
    tree B:
396
      mv bar/foo
397
    merge A and B
398
    """
399
1534.10.18 by Aaron Bentley
Defined all new Conflict types
400
    typestring = 'parent loop'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
401
1534.10.20 by Aaron Bentley
Got all tests passing
402
    format = 'Conflict moving %(conflict_path)s into %(path)s.  %(action)s.'
403
1534.10.18 by Aaron Bentley
Defined all new Conflict types
404
405
class UnversionedParent(HandledConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
406
    """An attempt to version an file whose parent directory is not versioned.
407
    Typically, the result of a merge where one tree unversioned the directory
408
    and the other added a versioned file to it.
409
    """
410
1534.10.18 by Aaron Bentley
Defined all new Conflict types
411
    typestring = 'unversioned parent'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
412
1534.10.20 by Aaron Bentley
Got all tests passing
413
    format = 'Conflict adding versioned files to %(path)s.  %(action)s.'
414
1534.10.18 by Aaron Bentley
Defined all new Conflict types
415
416
class MissingParent(HandledConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
417
    """An attempt to add files to a directory that is not present.
418
    Typically, the result of a merge where one tree deleted the directory and
419
    the other added a file to it.
420
    """
421
1534.10.18 by Aaron Bentley
Defined all new Conflict types
422
    typestring = 'missing parent'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
423
1534.10.20 by Aaron Bentley
Got all tests passing
424
    format = 'Conflict adding files to %(path)s.  %(action)s.'
425
426
1534.10.18 by Aaron Bentley
Defined all new Conflict types
427
428
ctype = {}
1534.10.20 by Aaron Bentley
Got all tests passing
429
430
1534.10.18 by Aaron Bentley
Defined all new Conflict types
431
def register_types(*conflict_types):
432
    """Register a Conflict subclass for serialization purposes"""
433
    global ctype
434
    for conflict_type in conflict_types:
435
        ctype[conflict_type.typestring] = conflict_type
436
1534.10.20 by Aaron Bentley
Got all tests passing
437
1534.10.18 by Aaron Bentley
Defined all new Conflict types
438
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
1534.10.20 by Aaron Bentley
Got all tests passing
439
               DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,)