/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 bzrlib/shelf_ui.py

  • Committer: John Arbash Meinel
  • Date: 2010-01-16 20:46:03 UTC
  • mto: (4634.119.9 2.0)
  • mto: This revision was merged to the branch mainline in revision 4968.
  • Revision ID: john@arbash-meinel.com-20100116204603-556rlu1v6blzf2vk
(jam) Fix bug #507566, concurrent autopacking correctness.

There was a bug where if two processes started autopacking synchronously,
when one was told to reload, it would then consider its 'new' pack to be
old, and wouldn't add it to the disk index. This would then look like a
commit / push succeeded, but the repository would not be referencing the
new data, leading to NoSuchRevision errors.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2008 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
 
 
18
from cStringIO import StringIO
 
19
import shutil
 
20
import sys
 
21
import tempfile
 
22
 
 
23
from bzrlib import (
 
24
    builtins,
 
25
    delta,
 
26
    diff,
 
27
    errors,
 
28
    osutils,
 
29
    patches,
 
30
    shelf,
 
31
    textfile,
 
32
    trace,
 
33
    ui,
 
34
    workingtree,
 
35
)
 
36
 
 
37
 
 
38
class ShelfReporter(object):
 
39
 
 
40
    vocab = {'add file': 'Shelve adding file "%(path)s"?',
 
41
             'binary': 'Shelve binary changes?',
 
42
             'change kind': 'Shelve changing "%s" from %(other)s'
 
43
             ' to %(this)s?',
 
44
             'delete file': 'Shelve removing file "%(path)s"?',
 
45
             'final': 'Shelve %d change(s)?',
 
46
             'hunk': 'Shelve?',
 
47
             'modify target': 'Shelve changing target of'
 
48
             ' "%(path)s" from "%(other)s" to "%(this)s"?',
 
49
             'rename': 'Shelve renaming "%(other)s" =>'
 
50
                        ' "%(this)s"?'
 
51
             }
 
52
 
 
53
    invert_diff = False
 
54
 
 
55
    def __init__(self):
 
56
        self.delta_reporter = delta._ChangeReporter()
 
57
 
 
58
    def no_changes(self):
 
59
        """Report that no changes were selected to apply."""
 
60
        trace.warning('No changes to shelve.')
 
61
 
 
62
    def shelved_id(self, shelf_id):
 
63
        """Report the id changes were shelved to."""
 
64
        trace.note('Changes shelved with id "%d".' % shelf_id)
 
65
 
 
66
    def changes_destroyed(self):
 
67
        """Report that changes were made without shelving."""
 
68
        trace.note('Selected changes destroyed.')
 
69
 
 
70
    def selected_changes(self, transform):
 
71
        """Report the changes that were selected."""
 
72
        trace.note("Selected changes:")
 
73
        changes = transform.iter_changes()
 
74
        delta.report_changes(changes, self.delta_reporter)
 
75
 
 
76
    def prompt_change(self, change):
 
77
        """Determine the prompt for a change to apply."""
 
78
        if change[0] == 'rename':
 
79
            vals = {'this': change[3], 'other': change[2]}
 
80
        elif change[0] == 'change kind':
 
81
            vals = {'path': change[4], 'other': change[2], 'this': change[3]}
 
82
        elif change[0] == 'modify target':
 
83
            vals = {'path': change[2], 'other': change[3], 'this': change[4]}
 
84
        else:
 
85
            vals = {'path': change[3]}
 
86
        prompt = self.vocab[change[0]] % vals
 
87
        return prompt
 
88
 
 
89
 
 
90
class ApplyReporter(ShelfReporter):
 
91
 
 
92
    vocab = {'add file': 'Delete file "%(path)s"?',
 
93
             'binary': 'Apply binary changes?',
 
94
             'change kind': 'Change "%(path)s" from %(this)s'
 
95
             ' to %(other)s?',
 
96
             'delete file': 'Add file "%(path)s"?',
 
97
             'final': 'Apply %d change(s)?',
 
98
             'hunk': 'Apply change?',
 
99
             'modify target': 'Change target of'
 
100
             ' "%(path)s" from "%(this)s" to "%(other)s"?',
 
101
             'rename': 'Rename "%(this)s" => "%(other)s"?',
 
102
             }
 
103
 
 
104
    invert_diff = True
 
105
 
 
106
    def changes_destroyed(self):
 
107
        pass
 
108
 
 
109
 
 
110
class Shelver(object):
 
111
    """Interactively shelve the changes in a working tree."""
 
112
 
 
113
    def __init__(self, work_tree, target_tree, diff_writer=None, auto=False,
 
114
                 auto_apply=False, file_list=None, message=None,
 
115
                 destroy=False, manager=None, reporter=None):
 
116
        """Constructor.
 
117
 
 
118
        :param work_tree: The working tree to shelve changes from.
 
119
        :param target_tree: The "unchanged" / old tree to compare the
 
120
            work_tree to.
 
121
        :param auto: If True, shelve each possible change.
 
122
        :param auto_apply: If True, shelve changes with no final prompt.
 
123
        :param file_list: If supplied, only files in this list may be shelved.
 
124
        :param message: The message to associate with the shelved changes.
 
125
        :param destroy: Change the working tree without storing the shelved
 
126
            changes.
 
127
        :param manager: The shelf manager to use.
 
128
        :param reporter: Object for reporting changes to user.
 
129
        """
 
130
        self.work_tree = work_tree
 
131
        self.target_tree = target_tree
 
132
        self.diff_writer = diff_writer
 
133
        if self.diff_writer is None:
 
134
            self.diff_writer = sys.stdout
 
135
        if manager is None:
 
136
            manager = work_tree.get_shelf_manager()
 
137
        self.manager = manager
 
138
        self.auto = auto
 
139
        self.auto_apply = auto_apply
 
140
        self.file_list = file_list
 
141
        self.message = message
 
142
        self.destroy = destroy
 
143
        if reporter is None:
 
144
            reporter = ShelfReporter()
 
145
        self.reporter = reporter
 
146
 
 
147
    @classmethod
 
148
    def from_args(klass, diff_writer, revision=None, all=False, file_list=None,
 
149
                  message=None, directory='.', destroy=False):
 
150
        """Create a shelver from commandline arguments.
 
151
 
 
152
        The returned shelver wil have a work_tree that is locked and should
 
153
        be unlocked.
 
154
 
 
155
        :param revision: RevisionSpec of the revision to compare to.
 
156
        :param all: If True, shelve all changes without prompting.
 
157
        :param file_list: If supplied, only files in this list may be  shelved.
 
158
        :param message: The message to associate with the shelved changes.
 
159
        :param directory: The directory containing the working tree.
 
160
        :param destroy: Change the working tree without storing the shelved
 
161
            changes.
 
162
        """
 
163
        tree, path = workingtree.WorkingTree.open_containing(directory)
 
164
        # Ensure that tree is locked for the lifetime of target_tree, as
 
165
        # target tree may be reading from the same dirstate.
 
166
        tree.lock_tree_write()
 
167
        try:
 
168
            target_tree = builtins._get_one_revision_tree('shelf2', revision,
 
169
                tree.branch, tree)
 
170
            files = builtins.safe_relpath_files(tree, file_list)
 
171
        except:
 
172
            tree.unlock()
 
173
            raise
 
174
        return klass(tree, target_tree, diff_writer, all, all, files, message,
 
175
                     destroy)
 
176
 
 
177
    def run(self):
 
178
        """Interactively shelve the changes."""
 
179
        creator = shelf.ShelfCreator(self.work_tree, self.target_tree,
 
180
                                     self.file_list)
 
181
        self.tempdir = tempfile.mkdtemp()
 
182
        changes_shelved = 0
 
183
        try:
 
184
            for change in creator.iter_shelvable():
 
185
                if change[0] == 'modify text':
 
186
                    try:
 
187
                        changes_shelved += self.handle_modify_text(creator,
 
188
                                                                   change[1])
 
189
                    except errors.BinaryFile:
 
190
                        if self.prompt_bool(self.reporter.vocab['binary']):
 
191
                            changes_shelved += 1
 
192
                            creator.shelve_content_change(change[1])
 
193
                else:
 
194
                    if self.prompt_bool(self.reporter.prompt_change(change)):
 
195
                        creator.shelve_change(change)
 
196
                        changes_shelved += 1
 
197
            if changes_shelved > 0:
 
198
                self.reporter.selected_changes(creator.work_transform)
 
199
                if (self.auto_apply or self.prompt_bool(
 
200
                    self.reporter.vocab['final'] % changes_shelved)):
 
201
                    if self.destroy:
 
202
                        creator.transform()
 
203
                        self.reporter.changes_destroyed()
 
204
                    else:
 
205
                        shelf_id = self.manager.shelve_changes(creator,
 
206
                                                               self.message)
 
207
                        self.reporter.shelved_id(shelf_id)
 
208
            else:
 
209
                self.reporter.no_changes()
 
210
        finally:
 
211
            shutil.rmtree(self.tempdir)
 
212
            creator.finalize()
 
213
 
 
214
    def get_parsed_patch(self, file_id, invert=False):
 
215
        """Return a parsed version of a file's patch.
 
216
 
 
217
        :param file_id: The id of the file to generate a patch for.
 
218
        :param invert: If True, provide an inverted patch (insertions displayed
 
219
            as removals, removals displayed as insertions).
 
220
        :return: A patches.Patch.
 
221
        """
 
222
        diff_file = StringIO()
 
223
        if invert:
 
224
            old_tree = self.work_tree
 
225
            new_tree = self.target_tree
 
226
        else:
 
227
            old_tree = self.target_tree
 
228
            new_tree = self.work_tree
 
229
        old_path = old_tree.id2path(file_id)
 
230
        new_path = new_tree.id2path(file_id)
 
231
        text_differ = diff.DiffText(old_tree, new_tree, diff_file)
 
232
        patch = text_differ.diff(file_id, old_path, new_path, 'file', 'file')
 
233
        diff_file.seek(0)
 
234
        return patches.parse_patch(diff_file)
 
235
 
 
236
    def prompt(self, message):
 
237
        """Prompt the user for a character.
 
238
 
 
239
        :param message: The message to prompt a user with.
 
240
        :return: A character.
 
241
        """
 
242
        sys.stdout.write(message)
 
243
        char = osutils.getchar()
 
244
        sys.stdout.write("\r" + ' ' * len(message) + '\r')
 
245
        sys.stdout.flush()
 
246
        return char
 
247
 
 
248
    def prompt_bool(self, question, long=False):
 
249
        """Prompt the user with a yes/no question.
 
250
 
 
251
        This may be overridden by self.auto.  It may also *set* self.auto.  It
 
252
        may also raise UserAbort.
 
253
        :param question: The question to ask the user.
 
254
        :return: True or False
 
255
        """
 
256
        if self.auto:
 
257
            return True
 
258
        if long:
 
259
            prompt = ' [(y)es, (N)o, (f)inish, or (q)uit]'
 
260
        else:
 
261
            prompt = ' [yNfq?]'
 
262
        char = self.prompt(question + prompt)
 
263
        if char == 'y':
 
264
            return True
 
265
        elif char == 'f':
 
266
            self.auto = True
 
267
            return True
 
268
        elif char == '?':
 
269
            return self.prompt_bool(question, long=True)
 
270
        if char == 'q':
 
271
            raise errors.UserAbort()
 
272
        else:
 
273
            return False
 
274
 
 
275
    def handle_modify_text(self, creator, file_id):
 
276
        """Provide diff hunk selection for modified text.
 
277
 
 
278
        If self.reporter.invert_diff is True, the diff is inverted so that
 
279
        insertions are displayed as removals and vice versa.
 
280
 
 
281
        :param creator: a ShelfCreator
 
282
        :param file_id: The id of the file to shelve.
 
283
        :return: number of shelved hunks.
 
284
        """
 
285
        if self.reporter.invert_diff:
 
286
            target_lines = self.work_tree.get_file_lines(file_id)
 
287
        else:
 
288
            target_lines = self.target_tree.get_file_lines(file_id)
 
289
        textfile.check_text_lines(self.work_tree.get_file_lines(file_id))
 
290
        textfile.check_text_lines(target_lines)
 
291
        parsed = self.get_parsed_patch(file_id, self.reporter.invert_diff)
 
292
        final_hunks = []
 
293
        if not self.auto:
 
294
            offset = 0
 
295
            self.diff_writer.write(parsed.get_header())
 
296
            for hunk in parsed.hunks:
 
297
                self.diff_writer.write(str(hunk))
 
298
                selected = self.prompt_bool(self.reporter.vocab['hunk'])
 
299
                if not self.reporter.invert_diff:
 
300
                    selected = (not selected)
 
301
                if selected:
 
302
                    hunk.mod_pos += offset
 
303
                    final_hunks.append(hunk)
 
304
                else:
 
305
                    offset -= (hunk.mod_range - hunk.orig_range)
 
306
        sys.stdout.flush()
 
307
        if not self.reporter.invert_diff and (
 
308
            len(parsed.hunks) == len(final_hunks)):
 
309
            return 0
 
310
        if self.reporter.invert_diff and len(final_hunks) == 0:
 
311
            return 0
 
312
        patched = patches.iter_patched_from_hunks(target_lines, final_hunks)
 
313
        creator.shelve_lines(file_id, list(patched))
 
314
        if self.reporter.invert_diff:
 
315
            return len(final_hunks)
 
316
        return len(parsed.hunks) - len(final_hunks)
 
317
 
 
318
 
 
319
class Unshelver(object):
 
320
    """Unshelve changes into a working tree."""
 
321
 
 
322
    @classmethod
 
323
    def from_args(klass, shelf_id=None, action='apply', directory='.'):
 
324
        """Create an unshelver from commandline arguments.
 
325
 
 
326
        The returned shelver wil have a tree that is locked and should
 
327
        be unlocked.
 
328
 
 
329
        :param shelf_id: Integer id of the shelf, as a string.
 
330
        :param action: action to perform.  May be 'apply', 'dry-run',
 
331
            'delete'.
 
332
        :param directory: The directory to unshelve changes into.
 
333
        """
 
334
        tree, path = workingtree.WorkingTree.open_containing(directory)
 
335
        tree.lock_tree_write()
 
336
        try:
 
337
            manager = tree.get_shelf_manager()
 
338
            if shelf_id is not None:
 
339
                try:
 
340
                    shelf_id = int(shelf_id)
 
341
                except ValueError:
 
342
                    raise errors.InvalidShelfId(shelf_id)
 
343
            else:
 
344
                shelf_id = manager.last_shelf()
 
345
                if shelf_id is None:
 
346
                    raise errors.BzrCommandError('No changes are shelved.')
 
347
                trace.note('Unshelving changes with id "%d".' % shelf_id)
 
348
            apply_changes = True
 
349
            delete_shelf = True
 
350
            read_shelf = True
 
351
            if action == 'dry-run':
 
352
                apply_changes = False
 
353
                delete_shelf = False
 
354
            if action == 'delete-only':
 
355
                apply_changes = False
 
356
                read_shelf = False
 
357
        except:
 
358
            tree.unlock()
 
359
            raise
 
360
        return klass(tree, manager, shelf_id, apply_changes, delete_shelf,
 
361
                     read_shelf)
 
362
 
 
363
    def __init__(self, tree, manager, shelf_id, apply_changes=True,
 
364
                 delete_shelf=True, read_shelf=True):
 
365
        """Constructor.
 
366
 
 
367
        :param tree: The working tree to unshelve into.
 
368
        :param manager: The ShelveManager containing the shelved changes.
 
369
        :param shelf_id:
 
370
        :param apply_changes: If True, apply the shelved changes to the
 
371
            working tree.
 
372
        :param delete_shelf: If True, delete the changes from the shelf.
 
373
        :param read_shelf: If True, read the changes from the shelf.
 
374
        """
 
375
        self.tree = tree
 
376
        manager = tree.get_shelf_manager()
 
377
        self.manager = manager
 
378
        self.shelf_id = shelf_id
 
379
        self.apply_changes = apply_changes
 
380
        self.delete_shelf = delete_shelf
 
381
        self.read_shelf = read_shelf
 
382
 
 
383
    def run(self):
 
384
        """Perform the unshelving operation."""
 
385
        self.tree.lock_tree_write()
 
386
        cleanups = [self.tree.unlock]
 
387
        try:
 
388
            if self.read_shelf:
 
389
                unshelver = self.manager.get_unshelver(self.shelf_id)
 
390
                cleanups.append(unshelver.finalize)
 
391
                if unshelver.message is not None:
 
392
                    trace.note('Message: %s' % unshelver.message)
 
393
                change_reporter = delta._ChangeReporter()
 
394
                task = ui.ui_factory.nested_progress_bar()
 
395
                try:
 
396
                    merger = unshelver.make_merger(task)
 
397
                    merger.change_reporter = change_reporter
 
398
                    if self.apply_changes:
 
399
                        merger.do_merge()
 
400
                    else:
 
401
                        self.show_changes(merger)
 
402
                finally:
 
403
                    task.finished()
 
404
            if self.delete_shelf:
 
405
                self.manager.delete_shelf(self.shelf_id)
 
406
        finally:
 
407
            for cleanup in reversed(cleanups):
 
408
                cleanup()
 
409
 
 
410
    def show_changes(self, merger):
 
411
        """Show the changes that this operation specifies."""
 
412
        tree_merger = merger.make_merger()
 
413
        # This implicitly shows the changes via the reporter, so we're done...
 
414
        tt = tree_merger.make_preview_transform()
 
415
        tt.finalize()