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