/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/conflicts.py

  • Committer: Gustav Hartvigsson
  • Date: 2021-01-09 21:36:27 UTC
  • Revision ID: gustav.hartvigsson@gmail.com-20210109213627-h1xwcutzy9m7a99b
Added 'Case Preserving Working Tree Use Cases' from Canonical Wiki

* Addod a page from the Canonical Bazaar wiki
  with information on the scmeatics of case
  perserving filesystems an a case insensitive
  filesystem works.
  
  * Needs re-work, but this will do as it is the
    same inforamoton as what was on the linked
    page in the currint documentation.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2005, 2006, 2007, 2009, 2010, 2011 Canonical Ltd
 
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 
16
 
 
17
# TODO: 'brz resolve' should accept a directory name and work from that
 
18
# point down
 
19
 
 
20
import errno
 
21
import os
 
22
import re
 
23
 
 
24
from .lazy_import import lazy_import
 
25
lazy_import(globals(), """
 
26
 
 
27
from breezy import (
 
28
    workingtree,
 
29
    )
 
30
from breezy.i18n import gettext, ngettext
 
31
""")
 
32
from . import (
 
33
    cache_utf8,
 
34
    errors,
 
35
    commands,
 
36
    option,
 
37
    osutils,
 
38
    registry,
 
39
    trace,
 
40
    )
 
41
 
 
42
 
 
43
class cmd_conflicts(commands.Command):
 
44
    __doc__ = """List files with conflicts.
 
45
 
 
46
    Merge will do its best to combine the changes in two branches, but there
 
47
    are some kinds of problems only a human can fix.  When it encounters those,
 
48
    it will mark a conflict.  A conflict means that you need to fix something,
 
49
    before you can commit.
 
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
 
 
55
    Use brz resolve when you have fixed a problem.
 
56
    """
 
57
    takes_options = [
 
58
        'directory',
 
59
        option.Option('text',
 
60
                      help='List paths of files with text conflicts.'),
 
61
        ]
 
62
    _see_also = ['resolve', 'conflict-types']
 
63
 
 
64
    def run(self, text=False, directory=u'.'):
 
65
        wt = workingtree.WorkingTree.open_containing(directory)[0]
 
66
        for conflict in wt.conflicts():
 
67
            if text:
 
68
                if conflict.typestring != 'text conflict':
 
69
                    continue
 
70
                self.outf.write(conflict.path + '\n')
 
71
            else:
 
72
                self.outf.write(str(conflict) + '\n')
 
73
 
 
74
 
 
75
resolve_action_registry = registry.Registry()
 
76
 
 
77
 
 
78
resolve_action_registry.register(
 
79
    'auto', 'auto', 'Detect whether conflict has been resolved by user.')
 
80
resolve_action_registry.register(
 
81
    'done', 'done', 'Marks the conflict as resolved.')
 
82
resolve_action_registry.register(
 
83
    'take-this', 'take_this',
 
84
    'Resolve the conflict preserving the version in the working tree.')
 
85
resolve_action_registry.register(
 
86
    'take-other', 'take_other',
 
87
    'Resolve the conflict taking the merged version into account.')
 
88
resolve_action_registry.default_key = 'done'
 
89
 
 
90
 
 
91
class ResolveActionOption(option.RegistryOption):
 
92
 
 
93
    def __init__(self):
 
94
        super(ResolveActionOption, self).__init__(
 
95
            'action', 'How to resolve the conflict.',
 
96
            value_switches=True,
 
97
            registry=resolve_action_registry)
 
98
 
 
99
 
 
100
class cmd_resolve(commands.Command):
 
101
    __doc__ = """Mark a conflict as resolved.
 
102
 
 
103
    Merge will do its best to combine the changes in two branches, but there
 
104
    are some kinds of problems only a human can fix.  When it encounters those,
 
105
    it will mark a conflict.  A conflict means that you need to fix something,
 
106
    before you can commit.
 
107
 
 
108
    Once you have fixed a problem, use "brz resolve" to automatically mark
 
109
    text conflicts as fixed, "brz resolve FILE" to mark a specific conflict as
 
110
    resolved, or "brz resolve --all" to mark all conflicts as resolved.
 
111
    """
 
112
    aliases = ['resolved']
 
113
    takes_args = ['file*']
 
114
    takes_options = [
 
115
        'directory',
 
116
        option.Option('all', help='Resolve all conflicts in this tree.'),
 
117
        ResolveActionOption(),
 
118
        ]
 
119
    _see_also = ['conflicts']
 
120
 
 
121
    def run(self, file_list=None, all=False, action=None, directory=None):
 
122
        if all:
 
123
            if file_list:
 
124
                raise errors.CommandError(gettext("If --all is specified,"
 
125
                                                  " no FILE may be provided"))
 
126
            if directory is None:
 
127
                directory = u'.'
 
128
            tree = workingtree.WorkingTree.open_containing(directory)[0]
 
129
            if action is None:
 
130
                action = 'done'
 
131
        else:
 
132
            tree, file_list = workingtree.WorkingTree.open_containing_paths(
 
133
                file_list, directory)
 
134
            if action is None:
 
135
                if file_list is None:
 
136
                    action = 'auto'
 
137
                else:
 
138
                    action = 'done'
 
139
        before, after = resolve(tree, file_list, action=action)
 
140
        # GZ 2012-07-27: Should unify UI below now that auto is less magical.
 
141
        if action == 'auto' and file_list is None:
 
142
            if after > 0:
 
143
                trace.note(
 
144
                    ngettext('%d conflict auto-resolved.',
 
145
                             '%d conflicts auto-resolved.', before - after),
 
146
                    before - after)
 
147
                trace.note(gettext('Remaining conflicts:'))
 
148
                for conflict in tree.conflicts():
 
149
                    trace.note(str(conflict))
 
150
                return 1
 
151
            else:
 
152
                trace.note(gettext('All conflicts resolved.'))
 
153
                return 0
 
154
        else:
 
155
            trace.note(ngettext('{0} conflict resolved, {1} remaining',
 
156
                                '{0} conflicts resolved, {1} remaining',
 
157
                                before - after).format(before - after, after))
 
158
 
 
159
 
 
160
def resolve(tree, paths=None, ignore_misses=False, recursive=False,
 
161
            action='done'):
 
162
    """Resolve some or all of the conflicts in a working tree.
 
163
 
 
164
    :param paths: If None, resolve all conflicts.  Otherwise, select only
 
165
        specified conflicts.
 
166
    :param recursive: If True, then elements of paths which are directories
 
167
        have all their children resolved, etc.  When invoked as part of
 
168
        recursive commands like revert, this should be True.  For commands
 
169
        or applications wishing finer-grained control, like the resolve
 
170
        command, this should be False.
 
171
    :param ignore_misses: If False, warnings will be printed if the supplied
 
172
        paths do not have conflicts.
 
173
    :param action: How the conflict should be resolved,
 
174
    """
 
175
    nb_conflicts_after = None
 
176
    with tree.lock_tree_write():
 
177
        tree_conflicts = tree.conflicts()
 
178
        nb_conflicts_before = len(tree_conflicts)
 
179
        if paths is None:
 
180
            new_conflicts = []
 
181
            to_process = tree_conflicts
 
182
        else:
 
183
            new_conflicts, to_process = tree_conflicts.select_conflicts(
 
184
                tree, paths, ignore_misses, recursive)
 
185
        for conflict in to_process:
 
186
            try:
 
187
                conflict.do(action, tree)
 
188
                conflict.cleanup(tree)
 
189
            except NotImplementedError:
 
190
                new_conflicts.append(conflict)
 
191
        try:
 
192
            nb_conflicts_after = len(new_conflicts)
 
193
            tree.set_conflicts(new_conflicts)
 
194
        except errors.UnsupportedOperation:
 
195
            pass
 
196
    if nb_conflicts_after is None:
 
197
        nb_conflicts_after = nb_conflicts_before
 
198
    return nb_conflicts_before, nb_conflicts_after
 
199
 
 
200
 
 
201
def restore(filename):
 
202
    """Restore a conflicted file to the state it was in before merging.
 
203
 
 
204
    Only text restoration is supported at present.
 
205
    """
 
206
    conflicted = False
 
207
    try:
 
208
        osutils.rename(filename + ".THIS", filename)
 
209
        conflicted = True
 
210
    except OSError as e:
 
211
        if e.errno != errno.ENOENT:
 
212
            raise
 
213
    try:
 
214
        os.unlink(filename + ".BASE")
 
215
        conflicted = True
 
216
    except OSError as e:
 
217
        if e.errno != errno.ENOENT:
 
218
            raise
 
219
    try:
 
220
        os.unlink(filename + ".OTHER")
 
221
        conflicted = True
 
222
    except OSError as e:
 
223
        if e.errno != errno.ENOENT:
 
224
            raise
 
225
    if not conflicted:
 
226
        raise errors.NotConflicted(filename)
 
227
 
 
228
 
 
229
class ConflictList(object):
 
230
    """List of conflicts.
 
231
 
 
232
    Typically obtained from WorkingTree.conflicts()
 
233
    """
 
234
 
 
235
    def __init__(self, conflicts=None):
 
236
        object.__init__(self)
 
237
        if conflicts is None:
 
238
            self.__list = []
 
239
        else:
 
240
            self.__list = conflicts
 
241
 
 
242
    def is_empty(self):
 
243
        return len(self.__list) == 0
 
244
 
 
245
    def __len__(self):
 
246
        return len(self.__list)
 
247
 
 
248
    def __iter__(self):
 
249
        return iter(self.__list)
 
250
 
 
251
    def __getitem__(self, key):
 
252
        return self.__list[key]
 
253
 
 
254
    def append(self, conflict):
 
255
        return self.__list.append(conflict)
 
256
 
 
257
    def __eq__(self, other_list):
 
258
        return list(self) == list(other_list)
 
259
 
 
260
    def __ne__(self, other_list):
 
261
        return not (self == other_list)
 
262
 
 
263
    def __repr__(self):
 
264
        return "ConflictList(%r)" % self.__list
 
265
 
 
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):
 
272
        """Remove the THIS, BASE and OTHER files for listed conflicts"""
 
273
        for conflict in self:
 
274
            if not conflict.has_files:
 
275
                continue
 
276
            conflict.cleanup(tree)
 
277
 
 
278
    def select_conflicts(self, tree, paths, ignore_misses=False,
 
279
                         recurse=False):
 
280
        """Select the conflicts associated with paths in a tree.
 
281
 
 
282
        :return: a pair of ConflictLists: (not_selected, selected)
 
283
        """
 
284
        path_set = set(paths)
 
285
        selected_paths = set()
 
286
        new_conflicts = ConflictList()
 
287
        selected_conflicts = ConflictList()
 
288
 
 
289
        for conflict in self:
 
290
            selected = False
 
291
            if conflict.path in path_set:
 
292
                selected = True
 
293
                selected_paths.add(conflict.path)
 
294
            if recurse:
 
295
                if osutils.is_inside_any(path_set, conflict.path):
 
296
                    selected = True
 
297
                    selected_paths.add(conflict.path)
 
298
 
 
299
            if selected:
 
300
                selected_conflicts.append(conflict)
 
301
            else:
 
302
                new_conflicts.append(conflict)
 
303
        if ignore_misses is not True:
 
304
            for path in [p for p in paths if p not in selected_paths]:
 
305
                if not os.path.exists(tree.abspath(path)):
 
306
                    print("%s does not exist" % path)
 
307
                else:
 
308
                    print("%s is not conflicted" % path)
 
309
        return new_conflicts, selected_conflicts
 
310
 
 
311
 
 
312
class Conflict(object):
 
313
    """Base class for conflicts."""
 
314
 
 
315
    typestring = None
 
316
 
 
317
    def __init__(self, path):
 
318
        self.path = path
 
319
 
 
320
    def associated_filenames(self):
 
321
        """The names of the files generated to help resolve the conflict."""
 
322
        raise NotImplementedError(self.associated_filenames)
 
323
 
 
324
    def cleanup(self, tree):
 
325
        for fname in self.associated_filenames():
 
326
            try:
 
327
                osutils.delete_any(tree.abspath(fname))
 
328
            except OSError as e:
 
329
                if e.errno != errno.ENOENT:
 
330
                    raise
 
331
 
 
332
    def do(self, action, tree):
 
333
        """Apply the specified action to the conflict.
 
334
 
 
335
        :param action: The method name to call.
 
336
 
 
337
        :param tree: The tree passed as a parameter to the method.
 
338
        """
 
339
        raise NotImplementedError(self.do)
 
340
 
 
341
    def describe(self):
 
342
        """Return a string description of this conflict."""
 
343
        raise NotImplementedError(self.describe)