~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/conflicts.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2009-03-24 05:12:24 UTC
  • mfrom: (4189.1.1 integration)
  • Revision ID: pqm@pqm.ubuntu.com-20090324051224-rneg6bkbzjyd85rl
(mbp) merge update to FSF address

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2005 by Aaron Bentley
2
 
 
 
1
# Copyright (C) 2005, 2007 Canonical Ltd
 
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
 
 
12
#
13
13
# You should have received a copy of the GNU General Public License
14
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
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
# TODO: Move this into builtins
18
18
 
19
 
# TODO: 'bzr resolve' should accept a directory name and work from that 
 
19
# TODO: 'bzr resolve' should accept a directory name and work from that
20
20
# point down
21
21
 
22
22
import os
 
23
 
 
24
from bzrlib.lazy_import import lazy_import
 
25
lazy_import(globals(), """
23
26
import errno
24
27
 
25
 
import bzrlib
26
 
from bzrlib.commands import register_command
27
 
from bzrlib.errors import BzrCommandError, NotConflicted, UnsupportedOperation
 
28
from bzrlib import (
 
29
    builtins,
 
30
    commands,
 
31
    errors,
 
32
    osutils,
 
33
    rio,
 
34
    trace,
 
35
    )
 
36
""")
28
37
from bzrlib.option import Option
29
 
from bzrlib.osutils import rename, delete_any
30
 
from bzrlib.rio import Stanza
31
38
 
32
39
 
33
40
CONFLICT_SUFFIXES = ('.THIS', '.BASE', '.OTHER')
34
41
 
35
42
 
36
 
class cmd_conflicts(bzrlib.commands.Command):
 
43
class cmd_conflicts(commands.Command):
37
44
    """List files with conflicts.
38
45
 
39
46
    Merge will do its best to combine the changes in two branches, but there
41
48
    it will mark a conflict.  A conflict means that you need to fix something,
42
49
    before you should commit.
43
50
 
 
51
    Conflicts normally are listed as short, human-readable messages.  If --text
 
52
    is supplied, the pathnames of files with text conflicts are listed,
 
53
    instead.  (This is useful for editing all files with text conflicts.)
 
54
 
44
55
    Use bzr resolve when you have fixed a problem.
45
56
 
46
 
    (conflicts are determined by the presence of .BASE .TREE, and .OTHER 
47
 
    files.)
48
 
 
49
57
    See also bzr resolve.
50
58
    """
51
 
    def run(self):
 
59
    takes_options = [
 
60
            Option('text',
 
61
                   help='List paths of files with text conflicts.'),
 
62
        ]
 
63
 
 
64
    def run(self, text=False):
52
65
        from bzrlib.workingtree import WorkingTree
53
66
        wt = WorkingTree.open_containing(u'.')[0]
54
67
        for conflict in wt.conflicts():
55
 
            print conflict
56
 
 
57
 
 
58
 
class cmd_resolve(bzrlib.commands.Command):
 
68
            if text:
 
69
                if conflict.typestring != 'text conflict':
 
70
                    continue
 
71
                self.outf.write(conflict.path + '\n')
 
72
            else:
 
73
                self.outf.write(str(conflict) + '\n')
 
74
 
 
75
 
 
76
class cmd_resolve(commands.Command):
59
77
    """Mark a conflict as resolved.
60
78
 
61
79
    Merge will do its best to combine the changes in two branches, but there
63
81
    it will mark a conflict.  A conflict means that you need to fix something,
64
82
    before you should commit.
65
83
 
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.
 
84
    Once you have fixed a problem, use "bzr resolve" to automatically mark
 
85
    text conflicts as fixed, resolve FILE to mark a specific conflict as
 
86
    resolved, or "bzr resolve --all" to mark all conflicts as resolved.
69
87
 
70
88
    See also bzr conflicts.
71
89
    """
72
90
    aliases = ['resolved']
73
91
    takes_args = ['file*']
74
 
    takes_options = [Option('all', help='Resolve all conflicts in this tree')]
 
92
    takes_options = [
 
93
            Option('all', help='Resolve all conflicts in this tree.'),
 
94
            ]
75
95
    def run(self, file_list=None, all=False):
76
96
        from bzrlib.workingtree import WorkingTree
77
97
        if all:
78
98
            if file_list:
79
 
                raise BzrCommandError("If --all is specified, no FILE may be provided")
 
99
                raise errors.BzrCommandError("If --all is specified,"
 
100
                                             " no FILE may be provided")
80
101
            tree = WorkingTree.open_containing('.')[0]
81
102
            resolve(tree)
82
103
        else:
 
104
            tree, file_list = builtins.tree_files(file_list)
83
105
            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)
88
 
 
89
 
 
90
 
def resolve(tree, paths=None, ignore_misses=False):
91
 
    tree.lock_write()
 
106
                un_resolved, resolved = tree.auto_resolve()
 
107
                if len(un_resolved) > 0:
 
108
                    trace.note('%d conflict(s) auto-resolved.', len(resolved))
 
109
                    trace.note('Remaining conflicts:')
 
110
                    for conflict in un_resolved:
 
111
                        trace.note(conflict)
 
112
                    return 1
 
113
                else:
 
114
                    trace.note('All conflicts resolved.')
 
115
                    return 0
 
116
            else:
 
117
                resolve(tree, file_list)
 
118
 
 
119
 
 
120
def resolve(tree, paths=None, ignore_misses=False, recursive=False):
 
121
    """Resolve some or all of the conflicts in a working tree.
 
122
 
 
123
    :param paths: If None, resolve all conflicts.  Otherwise, select only
 
124
        specified conflicts.
 
125
    :param recursive: If True, then elements of paths which are directories
 
126
        have all their children resolved, etc.  When invoked as part of
 
127
        recursive commands like revert, this should be True.  For commands
 
128
        or applications wishing finer-grained control, like the resolve
 
129
        command, this should be False.
 
130
    :ignore_misses: If False, warnings will be printed if the supplied paths
 
131
        do not have conflicts.
 
132
    """
 
133
    tree.lock_tree_write()
92
134
    try:
93
135
        tree_conflicts = tree.conflicts()
94
136
        if paths is None:
96
138
            selected_conflicts = tree_conflicts
97
139
        else:
98
140
            new_conflicts, selected_conflicts = \
99
 
                tree_conflicts.select_conflicts(tree, paths, ignore_misses)
 
141
                tree_conflicts.select_conflicts(tree, paths, ignore_misses,
 
142
                    recursive)
100
143
        try:
101
144
            tree.set_conflicts(new_conflicts)
102
 
        except UnsupportedOperation:
 
145
        except errors.UnsupportedOperation:
103
146
            pass
104
147
        selected_conflicts.remove_files(tree)
105
148
    finally:
113
156
    """
114
157
    conflicted = False
115
158
    try:
116
 
        rename(filename + ".THIS", filename)
 
159
        osutils.rename(filename + ".THIS", filename)
117
160
        conflicted = True
118
161
    except OSError, e:
119
162
        if e.errno != errno.ENOENT:
131
174
        if e.errno != errno.ENOENT:
132
175
            raise
133
176
    if not conflicted:
134
 
        raise NotConflicted(filename)
 
177
        raise errors.NotConflicted(filename)
135
178
 
136
179
 
137
180
class ConflictList(object):
185
228
        """Generator of stanzas"""
186
229
        for conflict in self:
187
230
            yield conflict.as_stanza()
188
 
            
 
231
 
189
232
    def to_strings(self):
190
233
        """Generate strings for the provided conflicts"""
191
234
        for conflict in self:
198
241
                continue
199
242
            for suffix in CONFLICT_SUFFIXES:
200
243
                try:
201
 
                    delete_any(tree.abspath(conflict.path+suffix))
 
244
                    osutils.delete_any(tree.abspath(conflict.path+suffix))
202
245
                except OSError, e:
203
246
                    if e.errno != errno.ENOENT:
204
247
                        raise
205
248
 
206
 
    def select_conflicts(self, tree, paths, ignore_misses=False):
 
249
    def select_conflicts(self, tree, paths, ignore_misses=False,
 
250
                         recurse=False):
207
251
        """Select the conflicts associated with paths in a tree.
208
 
        
 
252
 
209
253
        File-ids are also used for this.
 
254
        :return: a pair of ConflictLists: (not_selected, selected)
210
255
        """
211
256
        path_set = set(paths)
212
257
        ids = {}
227
272
                if cpath in path_set:
228
273
                    selected = True
229
274
                    selected_paths.add(cpath)
 
275
                if recurse:
 
276
                    if osutils.is_inside_any(path_set, cpath):
 
277
                        selected = True
 
278
                        selected_paths.add(cpath)
 
279
 
230
280
            for key in ('file_id', 'conflict_file_id'):
231
281
                cfile_id = getattr(conflict, key, None)
232
282
                if cfile_id is None:
249
299
                    print "%s is not conflicted" % path
250
300
        return new_conflicts, selected_conflicts
251
301
 
252
 
 
 
302
 
253
303
class Conflict(object):
254
304
    """Base class for all types of conflict"""
255
305
 
257
307
 
258
308
    def __init__(self, path, file_id=None):
259
309
        self.path = path
260
 
        self.file_id = file_id
 
310
        # warn turned off, because the factory blindly transfers the Stanza
 
311
        # values to __init__ and Stanza is purely a Unicode api.
 
312
        self.file_id = osutils.safe_file_id(file_id, warn=False)
261
313
 
262
314
    def as_stanza(self):
263
 
        s = Stanza(type=self.typestring, path=self.path)
 
315
        s = rio.Stanza(type=self.typestring, path=self.path)
264
316
        if self.file_id is not None:
265
 
            s.add('file_id', self.file_id)
 
317
            # Stanza requires Unicode apis
 
318
            s.add('file_id', self.file_id.decode('utf8'))
266
319
        return s
267
320
 
268
321
    def _cmp_list(self):
273
326
            return -1
274
327
        return cmp(self._cmp_list(), other._cmp_list())
275
328
 
 
329
    def __hash__(self):
 
330
        return hash((type(self), self.path, self.file_id))
 
331
 
276
332
    def __eq__(self, other):
277
333
        return self.__cmp__(other) == 0
278
334
 
347
403
    """
348
404
 
349
405
    rformat = "%(class)s(%(action)r, %(path)r, %(file_id)r)"
350
 
    
 
406
 
351
407
    def __init__(self, action, path, file_id=None):
352
408
        Conflict.__init__(self, path, file_id)
353
409
        self.action = action
372
428
    def __init__(self, action, path, conflict_path, file_id=None,
373
429
                 conflict_file_id=None):
374
430
        HandledConflict.__init__(self, action, path, file_id)
375
 
        self.conflict_path = conflict_path 
376
 
        self.conflict_file_id = conflict_file_id
377
 
        
 
431
        self.conflict_path = conflict_path
 
432
        # warn turned off, because the factory blindly transfers the Stanza
 
433
        # values to __init__.
 
434
        self.conflict_file_id = osutils.safe_file_id(conflict_file_id,
 
435
                                                     warn=False)
 
436
 
378
437
    def _cmp_list(self):
379
 
        return HandledConflict._cmp_list(self) + [self.conflict_path, 
 
438
        return HandledConflict._cmp_list(self) + [self.conflict_path,
380
439
                                                  self.conflict_file_id]
381
440
 
382
441
    def as_stanza(self):
383
442
        s = HandledConflict.as_stanza(self)
384
443
        s.add('conflict_path', self.conflict_path)
385
444
        if self.conflict_file_id is not None:
386
 
            s.add('conflict_file_id', self.conflict_file_id)
387
 
            
 
445
            s.add('conflict_file_id', self.conflict_file_id.decode('utf8'))
 
446
 
388
447
        return s
389
448
 
390
449
 
428
487
 
429
488
    typestring = 'unversioned parent'
430
489
 
431
 
    format = 'Conflict adding versioned files to %(path)s.  %(action)s.'
 
490
    format = 'Conflict because %(path)s is not versioned, but has versioned'\
 
491
             ' children.  %(action)s.'
432
492
 
433
493
 
434
494
class MissingParent(HandledConflict):
435
495
    """An attempt to add files to a directory that is not present.
436
 
    Typically, the result of a merge where one tree deleted the directory and
437
 
    the other added a file to it.
 
496
    Typically, the result of a merge where THIS deleted the directory and
 
497
    the OTHER added a file to it.
 
498
    See also: DeletingParent (same situation, reversed THIS and OTHER)
438
499
    """
439
500
 
440
501
    typestring = 'missing parent'
442
503
    format = 'Conflict adding files to %(path)s.  %(action)s.'
443
504
 
444
505
 
 
506
class DeletingParent(HandledConflict):
 
507
    """An attempt to add files to a directory that is not present.
 
508
    Typically, the result of a merge where one OTHER deleted the directory and
 
509
    the THIS added a file to it.
 
510
    """
 
511
 
 
512
    typestring = 'deleting parent'
 
513
 
 
514
    format = "Conflict: can't delete %(path)s because it is not empty.  "\
 
515
             "%(action)s."
 
516
 
 
517
 
 
518
class NonDirectoryParent(HandledConflict):
 
519
    """An attempt to add files to a directory that is not a director or
 
520
    an attempt to change the kind of a directory with files.
 
521
    """
 
522
 
 
523
    typestring = 'non-directory parent'
 
524
 
 
525
    format = "Conflict: %(path)s is not a directory, but has files in it."\
 
526
             "  %(action)s."
445
527
 
446
528
ctype = {}
447
529
 
454
536
 
455
537
 
456
538
register_types(ContentsConflict, TextConflict, PathConflict, DuplicateID,
457
 
               DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,)
 
539
               DuplicateEntry, ParentLoop, UnversionedParent, MissingParent,
 
540
               DeletingParent, NonDirectoryParent)