/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: 2008-11-25 18:51:48 UTC
  • mto: This revision was merged to the branch mainline in revision 3854.
  • Revision ID: john@arbash-meinel.com-20081125185148-jsfkqnzfjjqsleds
It seems we have some direct tests that don't use strings and expect a value error as well.

They would be sanitized later on by Revision. We could use that code, but this test
depends on the serializer, which Revision wouldn't know about.

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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  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 Shelver(object):
 
39
    """Interactively shelve the changes in a working tree."""
 
40
 
 
41
    def __init__(self, work_tree, target_tree, diff_writer=None, auto=False,
 
42
                 auto_apply=False, file_list=None, message=None):
 
43
        """Constructor.
 
44
 
 
45
        :param work_tree: The working tree to shelve changes from.
 
46
        :param target_tree: The "unchanged" / old tree to compare the
 
47
            work_tree to.
 
48
        :param auto: If True, shelve each possible change.
 
49
        :param auto_apply: If True, shelve changes with no final prompt.
 
50
        :param file_list: If supplied, only files in this list may be shelved.
 
51
        :param message: The message to associate with the shelved changes.
 
52
        """
 
53
        self.work_tree = work_tree
 
54
        self.target_tree = target_tree
 
55
        self.diff_writer = diff_writer
 
56
        if self.diff_writer is None:
 
57
            self.diff_writer = sys.stdout
 
58
        self.manager = work_tree.get_shelf_manager()
 
59
        self.auto = auto
 
60
        self.auto_apply = auto_apply
 
61
        self.file_list = file_list
 
62
        self.message = message
 
63
 
 
64
    @classmethod
 
65
    def from_args(klass, diff_writer, revision=None, all=False, file_list=None,
 
66
                  message=None, directory='.'):
 
67
        """Create a shelver from commandline arguments.
 
68
 
 
69
        :param revision: RevisionSpec of the revision to compare to.
 
70
        :param all: If True, shelve all changes without prompting.
 
71
        :param file_list: If supplied, only files in this list may be  shelved.
 
72
        :param message: The message to associate with the shelved changes.
 
73
        :param directory: The directory containing the working tree.
 
74
        """
 
75
        tree, path = workingtree.WorkingTree.open_containing(directory)
 
76
        target_tree = builtins._get_one_revision_tree('shelf2', revision,
 
77
            tree.branch, tree)
 
78
        return klass(tree, target_tree, diff_writer, all, all, file_list,
 
79
                     message)
 
80
 
 
81
    def run(self):
 
82
        """Interactively shelve the changes."""
 
83
        creator = shelf.ShelfCreator(self.work_tree, self.target_tree,
 
84
                                     self.file_list)
 
85
        self.tempdir = tempfile.mkdtemp()
 
86
        changes_shelved = 0
 
87
        try:
 
88
            for change in creator.iter_shelvable():
 
89
                if change[0] == 'modify text':
 
90
                    try:
 
91
                        changes_shelved += self.handle_modify_text(creator,
 
92
                                                                   change[1])
 
93
                    except errors.BinaryFile:
 
94
                        if self.prompt_bool('Shelve binary changes?'):
 
95
                            changes_shelved += 1
 
96
                            creator.shelve_content_change(change[1])
 
97
                if change[0] == 'add file':
 
98
                    if self.prompt_bool('Shelve adding file "%s"?'
 
99
                                        % change[3]):
 
100
                        creator.shelve_creation(change[1])
 
101
                        changes_shelved += 1
 
102
                if change[0] == 'delete file':
 
103
                    if self.prompt_bool('Shelve removing file "%s"?'
 
104
                                        % change[3]):
 
105
                        creator.shelve_deletion(change[1])
 
106
                        changes_shelved += 1
 
107
                if change[0] == 'change kind':
 
108
                    if self.prompt_bool('Shelve changing "%s" from %s to %s? '
 
109
                                        % (change[4], change[2], change[3])):
 
110
                        creator.shelve_content_change(change[1])
 
111
                        changes_shelved += 1
 
112
                if change[0] == 'rename':
 
113
                    if self.prompt_bool('Shelve renaming "%s" => "%s"?' %
 
114
                                   change[2:]):
 
115
                        creator.shelve_rename(change[1])
 
116
                        changes_shelved += 1
 
117
            if changes_shelved > 0:
 
118
                trace.note("Selected changes:")
 
119
                changes = creator.work_transform.iter_changes()
 
120
                reporter = delta._ChangeReporter()
 
121
                delta.report_changes(changes, reporter)
 
122
                if (self.auto_apply or self.prompt_bool(
 
123
                    'Shelve %d change(s)?' % changes_shelved)):
 
124
                    shelf_id = self.manager.shelve_changes(creator,
 
125
                                                           self.message)
 
126
                    trace.note('Changes shelved with id "%d".' % shelf_id)
 
127
            else:
 
128
                trace.warning('No changes to shelve.')
 
129
        finally:
 
130
            shutil.rmtree(self.tempdir)
 
131
            creator.finalize()
 
132
 
 
133
    def get_parsed_patch(self, file_id):
 
134
        """Return a parsed version of a file's patch.
 
135
 
 
136
        :param file_id: The id of the file to generate a patch for.
 
137
        :return: A patches.Patch.
 
138
        """
 
139
        old_path = self.target_tree.id2path(file_id)
 
140
        new_path = self.work_tree.id2path(file_id)
 
141
        diff_file = StringIO()
 
142
        text_differ = diff.DiffText(self.target_tree, self.work_tree,
 
143
                                    diff_file)
 
144
        patch = text_differ.diff(file_id, old_path, new_path, 'file', 'file')
 
145
        diff_file.seek(0)
 
146
        return patches.parse_patch(diff_file)
 
147
 
 
148
    def prompt(self, message):
 
149
        """Prompt the user for a character.
 
150
 
 
151
        :param message: The message to prompt a user with.
 
152
        :return: A character.
 
153
        """
 
154
        sys.stdout.write(message)
 
155
        char = osutils.getchar()
 
156
        sys.stdout.write("\r" + ' ' * len(message) + '\r')
 
157
        sys.stdout.flush()
 
158
        return char
 
159
 
 
160
    def prompt_bool(self, question):
 
161
        """Prompt the user with a yes/no question.
 
162
 
 
163
        This may be overridden by self.auto.  It may also *set* self.auto.  It
 
164
        may also raise UserAbort.
 
165
        :param question: The question to ask the user.
 
166
        :return: True or False
 
167
        """
 
168
        if self.auto:
 
169
            return True
 
170
        char = self.prompt(question + ' [yNfq]')
 
171
        if char == 'y':
 
172
            return True
 
173
        elif char == 'f':
 
174
            self.auto = True
 
175
            return True
 
176
        if char == 'q':
 
177
            raise errors.UserAbort()
 
178
        else:
 
179
            return False
 
180
 
 
181
    def handle_modify_text(self, creator, file_id):
 
182
        """Provide diff hunk selection for modified text.
 
183
 
 
184
        :param creator: a ShelfCreator
 
185
        :param file_id: The id of the file to shelve.
 
186
        :return: number of shelved hunks.
 
187
        """
 
188
        target_lines = self.target_tree.get_file_lines(file_id)
 
189
        textfile.check_text_lines(self.work_tree.get_file_lines(file_id))
 
190
        textfile.check_text_lines(target_lines)
 
191
        parsed = self.get_parsed_patch(file_id)
 
192
        final_hunks = []
 
193
        if not self.auto:
 
194
            offset = 0
 
195
            self.diff_writer.write(parsed.get_header())
 
196
            for hunk in parsed.hunks:
 
197
                self.diff_writer.write(str(hunk))
 
198
                if not self.prompt_bool('Shelve?'):
 
199
                    hunk.mod_pos += offset
 
200
                    final_hunks.append(hunk)
 
201
                else:
 
202
                    offset -= (hunk.mod_range - hunk.orig_range)
 
203
        sys.stdout.flush()
 
204
        if len(parsed.hunks) == len(final_hunks):
 
205
            return 0
 
206
        patched = patches.iter_patched_from_hunks(target_lines, final_hunks)
 
207
        creator.shelve_lines(file_id, list(patched))
 
208
        return len(parsed.hunks) - len(final_hunks)
 
209
 
 
210
 
 
211
class Unshelver(object):
 
212
    """Unshelve changes into a working tree."""
 
213
 
 
214
    @classmethod
 
215
    def from_args(klass, shelf_id=None, action='apply', directory='.'):
 
216
        """Create an unshelver from commandline arguments.
 
217
 
 
218
        :param shelf_id: Integer id of the shelf, as a string.
 
219
        :param action: action to perform.  May be 'apply', 'dry-run',
 
220
            'delete'.
 
221
        :param directory: The directory to unshelve changes into.
 
222
        """
 
223
        tree, path = workingtree.WorkingTree.open_containing(directory)
 
224
        manager = tree.get_shelf_manager()
 
225
        if shelf_id is not None:
 
226
            shelf_id = int(shelf_id)
 
227
        else:
 
228
            shelf_id = manager.last_shelf()
 
229
            if shelf_id is None:
 
230
                raise errors.BzrCommandError('No changes are shelved.')
 
231
            trace.note('Unshelving changes with id "%d".' % shelf_id)
 
232
        apply_changes = True
 
233
        delete_shelf = True
 
234
        read_shelf = True
 
235
        if action == 'dry-run':
 
236
            apply_changes = False
 
237
            delete_shelf = False
 
238
        if action == 'delete-only':
 
239
            apply_changes = False
 
240
            read_shelf = False
 
241
        return klass(tree, manager, shelf_id, apply_changes, delete_shelf,
 
242
                     read_shelf)
 
243
 
 
244
    def __init__(self, tree, manager, shelf_id, apply_changes=True,
 
245
                 delete_shelf=True, read_shelf=True):
 
246
        """Constructor.
 
247
 
 
248
        :param tree: The working tree to unshelve into.
 
249
        :param manager: The ShelveManager containing the shelved changes.
 
250
        :param shelf_id:
 
251
        :param apply_changes: If True, apply the shelved changes to the
 
252
            working tree.
 
253
        :param delete_shelf: If True, delete the changes from the shelf.
 
254
        :param read_shelf: If True, read the changes from the shelf.
 
255
        """
 
256
        self.tree = tree
 
257
        manager = tree.get_shelf_manager()
 
258
        self.manager = manager
 
259
        self.shelf_id = shelf_id
 
260
        self.apply_changes = apply_changes
 
261
        self.delete_shelf = delete_shelf
 
262
        self.read_shelf = read_shelf
 
263
 
 
264
    def run(self):
 
265
        """Perform the unshelving operation."""
 
266
        self.tree.lock_write()
 
267
        cleanups = [self.tree.unlock]
 
268
        try:
 
269
            if self.read_shelf:
 
270
                unshelver = self.manager.get_unshelver(self.shelf_id)
 
271
                cleanups.append(unshelver.finalize)
 
272
                if unshelver.message is not None:
 
273
                    trace.note('Message: %s' % unshelver.message)
 
274
                change_reporter = delta._ChangeReporter()
 
275
                merger = unshelver.make_merger()
 
276
                merger.change_reporter = change_reporter
 
277
                if self.apply_changes:
 
278
                    pb = ui.ui_factory.nested_progress_bar()
 
279
                    try:
 
280
                        merger.do_merge()
 
281
                    finally:
 
282
                        pb.finished()
 
283
                else:
 
284
                    self.show_changes(merger)
 
285
            if self.delete_shelf:
 
286
                self.manager.delete_shelf(self.shelf_id)
 
287
        finally:
 
288
            for cleanup in reversed(cleanups):
 
289
                cleanup()
 
290
 
 
291
    def show_changes(self, merger):
 
292
        """Show the changes that this operation specifies."""
 
293
        tree_merger = merger.make_merger()
 
294
        # This implicitly shows the changes via the reporter, so we're done...
 
295
        tt = tree_merger.make_preview_transform()
 
296
        tt.finalize()