/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: 2009-04-21 23:54:16 UTC
  • mto: (4300.1.7 groupcompress_info)
  • mto: This revision was merged to the branch mainline in revision 4301.
  • Revision ID: john@arbash-meinel.com-20090421235416-f0cz6ilf5cufbugi
Fix bug #364900, properly remove the 64kB that was just encoded in the copy.
Also, stop supporting None as a copy length in 'encode_copy_instruction'.
It was only used by the test suite, and it is good to pull that sort of thing out of
production code. (Besides, setting the copy to 64kB has the same effect.)

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