/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:
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
93
            new_conflicts = ConflictList()
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
94
            selected_conflicts = tree_conflicts
95
        else:
96
            new_conflicts, selected_conflicts = \
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
97
                tree_conflicts.select_conflicts(tree, paths, ignore_misses)
1534.10.10 by Aaron Bentley
Resolve uses the new stuff.
98
        try:
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
99
            tree.set_conflicts(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
1185.35.1 by Aaron Bentley
Implemented conflicts.restore
107
def restore(filename):
108
    """\
109
    Restore a conflicted file to the state it was in before merging.
110
    Only text restoration supported at present.
111
    """
112
    conflicted = False
113
    try:
1185.31.49 by John Arbash Meinel
Some corrections using the new osutils.rename. **ALL TESTS PASS**
114
        rename(filename + ".THIS", filename)
1185.35.1 by Aaron Bentley
Implemented conflicts.restore
115
        conflicted = True
116
    except OSError, e:
117
        if e.errno != errno.ENOENT:
118
            raise
119
    try:
120
        os.unlink(filename + ".BASE")
121
        conflicted = True
122
    except OSError, e:
123
        if e.errno != errno.ENOENT:
124
            raise
125
    try:
126
        os.unlink(filename + ".OTHER")
127
        conflicted = True
128
    except OSError, e:
129
        if e.errno != errno.ENOENT:
130
            raise
131
    if not conflicted:
132
        raise NotConflicted(filename)
1534.10.4 by Aaron Bentley
Implemented conflict serialization
133
134
1534.10.22 by Aaron Bentley
Got ConflictList implemented
135
class ConflictList(object):
136
    """List of conflicts
137
    Can be instantiated from stanzas or from Conflict subclasses.
138
    """
139
140
    def __init__(self, conflicts=None):
141
        object.__init__(self)
142
        if conflicts is None:
143
            self.__list = []
144
        else:
145
            self.__list = conflicts
146
147
    def __len__(self):
148
        return len(self.__list)
149
150
    def __iter__(self):
151
        return iter(self.__list)
152
153
    def __getitem__(self, key):
154
        return self.__list[key]
155
156
    def append(self, conflict):
157
        return self.__list.append(conflict)
158
159
    def __eq__(self, other_list):
160
        return list(self) == list(other_list)
161
162
    def __ne__(self, other_list):
163
        return not (self == other_list)
164
165
    def __repr__(self):
166
        return "ConflictList(%r)" % self.__list
167
168
    @staticmethod
169
    def from_stanzas(stanzas):
170
        """Produce a new ConflictList from an iterable of stanzas"""
171
        conflicts = ConflictList()
172
        for stanza in stanzas:
173
            conflicts.append(Conflict.factory(**stanza.as_dict()))
174
        return conflicts
175
176
    def to_stanzas(self):
177
        """Generator of stanzas"""
178
        for conflict in self:
179
            yield conflict.as_stanza()
180
            
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
181
    def to_strings(self):
182
        """Generate strings for the provided conflicts"""
183
        for conflict in self:
184
            yield str(conflict)
185
186
    def remove_files(self, tree):
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
187
        """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
188
        for conflict in self:
189
            if not conflict.has_files:
190
                continue
191
            for suffix in CONFLICT_SUFFIXES:
192
                try:
193
                    os.unlink(tree.abspath(conflict.path+suffix))
194
                except OSError, e:
195
                    if e.errno != errno.ENOENT:
196
                        raise
1534.10.21 by Aaron Bentley
Moved and renamed conflict functions
197
1534.10.25 by Aaron Bentley
Move select_conflicts into ConflictList
198
    def select_conflicts(self, tree, paths, ignore_misses=False):
199
        """Select the conflicts associated with paths in a tree.
200
        
201
        File-ids are also used for this.
202
        """
203
        path_set = set(paths)
204
        ids = {}
205
        selected_paths = set()
206
        new_conflicts = ConflictList()
207
        selected_conflicts = ConflictList()
208
        for path in paths:
209
            file_id = tree.path2id(path)
210
            if file_id is not None:
211
                ids[file_id] = path
212
213
        for conflict in self:
214
            selected = False
215
            for key in ('path', 'conflict_path'):
216
                cpath = getattr(conflict, key, None)
217
                if cpath is None:
218
                    continue
219
                if cpath in path_set:
220
                    selected = True
221
                    selected_paths.add(cpath)
222
            for key in ('file_id', 'conflict_file_id'):
223
                cfile_id = getattr(conflict, key, None)
224
                if cfile_id is None:
225
                    continue
226
                try:
227
                    cpath = ids[cfile_id]
228
                except KeyError:
229
                    continue
230
                selected = True
231
                selected_paths.add(cpath)
232
            if selected:
233
                selected_conflicts.append(conflict)
234
            else:
235
                new_conflicts.append(conflict)
236
        if ignore_misses is not True:
237
            for path in [p for p in paths if p not in selected_paths]:
238
                if not os.path.exists(tree.abspath(path)):
239
                    print "%s does not exist" % path
240
                else:
241
                    print "%s is not conflicted" % path
242
        return new_conflicts, selected_conflicts
243
244
 
1534.10.18 by Aaron Bentley
Defined all new Conflict types
245
class Conflict(object):
246
    """Base class for all types of conflict"""
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
247
248
    has_files = False
249
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
250
    def __init__(self, path, file_id=None):
1534.10.18 by Aaron Bentley
Defined all new Conflict types
251
        self.path = path
252
        self.file_id = file_id
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
253
254
    def as_stanza(self):
255
        s = Stanza(type=self.typestring, path=self.path)
256
        if self.file_id is not None:
257
            s.add('file_id', self.file_id)
258
        return s
259
1534.10.22 by Aaron Bentley
Got ConflictList implemented
260
    def _cmp_list(self):
261
        return [type(self), self.path, self.file_id]
262
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
263
    def __cmp__(self, other):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
264
        if getattr(other, "_cmp_list", None) is None:
265
            return -1
266
        return cmp(self._cmp_list(), other._cmp_list())
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
267
268
    def __eq__(self, other):
269
        return self.__cmp__(other) == 0
270
271
    def __ne__(self, other):
272
        return not self.__eq__(other)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
273
1534.10.20 by Aaron Bentley
Got all tests passing
274
    def __str__(self):
275
        return self.format % self.__dict__
276
1534.10.22 by Aaron Bentley
Got ConflictList implemented
277
    def __repr__(self):
278
        rdict = dict(self.__dict__)
279
        rdict['class'] = self.__class__.__name__
280
        return self.rformat % rdict
281
1534.10.18 by Aaron Bentley
Defined all new Conflict types
282
    @staticmethod
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
283
    def factory(type, **kwargs):
1534.10.18 by Aaron Bentley
Defined all new Conflict types
284
        global ctype
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
285
        return ctype[type](**kwargs)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
286
287
288
class PathConflict(Conflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
289
    """A conflict was encountered merging file paths"""
290
1534.10.18 by Aaron Bentley
Defined all new Conflict types
291
    typestring = 'path conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
292
1534.10.20 by Aaron Bentley
Got all tests passing
293
    format = 'Path conflict: %(path)s / %(conflict_path)s'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
294
295
    rformat = '%(class)s(%(path)r, %(conflict_path)r, %(file_id)r)'
1534.10.20 by Aaron Bentley
Got all tests passing
296
    def __init__(self, path, conflict_path=None, file_id=None):
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
297
        Conflict.__init__(self, path, file_id)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
298
        self.conflict_path = conflict_path
299
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
300
    def as_stanza(self):
301
        s = Conflict.as_stanza(self)
1534.10.20 by Aaron Bentley
Got all tests passing
302
        if self.conflict_path is not None:
303
            s.add('conflict_path', self.conflict_path)
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
304
        return s
305
1534.10.20 by Aaron Bentley
Got all tests passing
306
307
class ContentsConflict(PathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
308
    """The files are of different types, or not present"""
309
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
310
    has_files = True
311
1534.10.20 by Aaron Bentley
Got all tests passing
312
    typestring = 'contents conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
313
1534.10.20 by Aaron Bentley
Got all tests passing
314
    format = 'Contents conflict in %(path)s'
315
316
317
class TextConflict(PathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
318
    """The merge algorithm could not resolve all differences encountered."""
319
1534.10.24 by Aaron Bentley
Eliminated conflicts_to_strings, made remove_files a ConflictList member
320
    has_files = True
321
1534.10.20 by Aaron Bentley
Got all tests passing
322
    typestring = 'text conflict'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
323
1534.10.20 by Aaron Bentley
Got all tests passing
324
    format = 'Text conflict in %(path)s'
325
326
1534.10.18 by Aaron Bentley
Defined all new Conflict types
327
class HandledConflict(Conflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
328
    """A path problem that has been provisionally resolved.
329
    This is intended to be a base class.
330
    """
331
332
    rformat = "%(class)s(%(action)r, %(path)r, %(file_id)r)"
333
    
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
334
    def __init__(self, action, path, file_id=None):
335
        Conflict.__init__(self, path, file_id)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
336
        self.action = action
337
1534.10.22 by Aaron Bentley
Got ConflictList implemented
338
    def _cmp_list(self):
339
        return Conflict._cmp_list(self) + [self.action]
340
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
341
    def as_stanza(self):
342
        s = Conflict.as_stanza(self)
343
        s.add('action', self.action)
344
        return s
345
1534.10.20 by Aaron Bentley
Got all tests passing
346
1534.10.18 by Aaron Bentley
Defined all new Conflict types
347
class HandledPathConflict(HandledConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
348
    """A provisionally-resolved path problem involving two paths.
349
    This is intended to be a base class.
350
    """
351
352
    rformat = "%(class)s(%(action)r, %(path)r, %(conflict_path)r,"\
353
        " %(file_id)r, %(conflict_file_id)r)"
354
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
355
    def __init__(self, action, path, conflict_path, file_id=None,
1534.10.18 by Aaron Bentley
Defined all new Conflict types
356
                 conflict_file_id=None):
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
357
        HandledConflict.__init__(self, action, path, file_id)
1534.10.18 by Aaron Bentley
Defined all new Conflict types
358
        self.conflict_path = conflict_path 
359
        self.conflict_file_id = conflict_file_id
360
        
1534.10.22 by Aaron Bentley
Got ConflictList implemented
361
    def _cmp_list(self):
362
        return HandledConflict._cmp_list(self) + [self.conflict_path, 
363
                                                  self.conflict_file_id]
364
1534.10.19 by Aaron Bentley
Stanza conversion, cooking
365
    def as_stanza(self):
366
        s = HandledConflict.as_stanza(self)
367
        s.add('conflict_path', self.conflict_path)
368
        if self.conflict_file_id is not None:
369
            s.add('conflict_file_id', self.conflict_file_id)
370
            
371
        return s
1534.10.20 by Aaron Bentley
Got all tests passing
372
373
1534.10.18 by Aaron Bentley
Defined all new Conflict types
374
class DuplicateID(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
375
    """Two files want the same file_id."""
376
1534.10.18 by Aaron Bentley
Defined all new Conflict types
377
    typestring = 'duplicate id'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
378
1534.10.20 by Aaron Bentley
Got all tests passing
379
    format = 'Conflict adding id to %(conflict_path)s.  %(action)s %(path)s.'
380
1534.10.18 by Aaron Bentley
Defined all new Conflict types
381
382
class DuplicateEntry(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
383
    """Two directory entries want to have the same name."""
384
1534.10.18 by Aaron Bentley
Defined all new Conflict types
385
    typestring = 'duplicate'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
386
1534.10.20 by Aaron Bentley
Got all tests passing
387
    format = 'Conflict adding file %(conflict_path)s.  %(action)s %(path)s.'
388
1534.10.18 by Aaron Bentley
Defined all new Conflict types
389
390
class ParentLoop(HandledPathConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
391
    """An attempt to create an infinitely-looping directory structure.
392
    This is rare, but can be produced like so:
393
394
    tree A:
395
      mv foo/bar
396
    tree B:
397
      mv bar/foo
398
    merge A and B
399
    """
400
1534.10.18 by Aaron Bentley
Defined all new Conflict types
401
    typestring = 'parent loop'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
402
1534.10.20 by Aaron Bentley
Got all tests passing
403
    format = 'Conflict moving %(conflict_path)s into %(path)s.  %(action)s.'
404
1534.10.18 by Aaron Bentley
Defined all new Conflict types
405
406
class UnversionedParent(HandledConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
407
    """An attempt to version an file whose parent directory is not versioned.
408
    Typically, the result of a merge where one tree unversioned the directory
409
    and the other added a versioned file to it.
410
    """
411
1534.10.18 by Aaron Bentley
Defined all new Conflict types
412
    typestring = 'unversioned parent'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
413
1534.10.20 by Aaron Bentley
Got all tests passing
414
    format = 'Conflict adding versioned files to %(path)s.  %(action)s.'
415
1534.10.18 by Aaron Bentley
Defined all new Conflict types
416
417
class MissingParent(HandledConflict):
1534.10.22 by Aaron Bentley
Got ConflictList implemented
418
    """An attempt to add files to a directory that is not present.
419
    Typically, the result of a merge where one tree deleted the directory and
420
    the other added a file to it.
421
    """
422
1534.10.18 by Aaron Bentley
Defined all new Conflict types
423
    typestring = 'missing parent'
1534.10.22 by Aaron Bentley
Got ConflictList implemented
424
1534.10.20 by Aaron Bentley
Got all tests passing
425
    format = 'Conflict adding files to %(path)s.  %(action)s.'
426
427
1534.10.18 by Aaron Bentley
Defined all new Conflict types
428
429
ctype = {}
1534.10.20 by Aaron Bentley
Got all tests passing
430
431
1534.10.18 by Aaron Bentley
Defined all new Conflict types
432
def register_types(*conflict_types):
433
    """Register a Conflict subclass for serialization purposes"""
434
    global ctype
435
    for conflict_type in conflict_types:
436
        ctype[conflict_type.typestring] = conflict_type
437
1534.10.20 by Aaron Bentley
Got all tests passing
438
1534.10.18 by Aaron Bentley
Defined all new Conflict types
439
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
1534.10.20 by Aaron Bentley
Got all tests passing
440
               DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,)