~bzr-pqm/bzr/bzr.dev

4902.1.8 by John Arbash Meinel
Delay grabbing an output stream until we actually go to show a diff.
1
# Copyright (C) 2008, 2009, 2010 Canonical Ltd
0.16.101 by Aaron Bentley
Update GPL formatting and copyright
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
4183.7.1 by Sabin Iacob
update FSF mailing address
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
0.16.1 by Aaron Bentley
Begin implementing UI
16
17
18
from cStringIO import StringIO
0.16.5 by Aaron Bentley
Get text shelving working
19
import shutil
0.16.1 by Aaron Bentley
Begin implementing UI
20
import sys
0.16.5 by Aaron Bentley
Get text shelving working
21
import tempfile
0.16.1 by Aaron Bentley
Begin implementing UI
22
0.16.25 by Aaron Bentley
Show selected changes before shelving
23
from bzrlib import (
24
    builtins,
25
    delta,
26
    diff,
27
    errors,
0.16.79 by Aaron Bentley
Remove dependencies on bzrtools
28
    osutils,
0.16.25 by Aaron Bentley
Show selected changes before shelving
29
    patches,
4603.1.6 by Aaron Bentley
Provide a reasonable count of changes when file edited.
30
    patiencediff,
0.16.74 by Aaron Bentley
Merge with shelf-manager
31
    shelf,
0.16.72 by Aaron Bentley
Allow shelving binary changes
32
    textfile,
0.16.54 by Aaron Bentley
Inform user about shelf ids.
33
    trace,
0.16.64 by Aaron Bentley
Implement dry-run option for Unshelve
34
    ui,
0.16.102 by Aaron Bentley
Minor updates
35
    workingtree,
36
)
6138.3.4 by Jonathan Riddell
add gettext() to uses of trace.note()
37
from bzrlib.i18n import gettext
3835.2.6 by Aaron Bentley
Restore vila's colordiff change
38
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
39
class UseEditor(Exception):
40
    """Use an editor instead of selecting hunks."""
41
42
4459.4.1 by Aaron Bentley
Provide control over switch and shelver messaging.
43
class ShelfReporter(object):
4526.7.2 by Aaron Bentley
Update docs.
44
6143.1.2 by Jonathan Riddell
gettext() shelf_ui.py
45
    vocab = {'add file': gettext('Shelve adding file "%(path)s"?'),
46
             'binary': gettext('Shelve binary changes?'),
47
             'change kind': gettext('Shelve changing "%s" from %(other)s'
48
             ' to %(this)s?'),
49
             'delete file': gettext('Shelve removing file "%(path)s"?'),
50
             'final': gettext('Shelve %d change(s)?'),
51
             'hunk': gettext('Shelve?'),
52
             'modify target': gettext('Shelve changing target of'
53
             ' "%(path)s" from "%(other)s" to "%(this)s"?'),
54
             'rename': gettext('Shelve renaming "%(other)s" =>'
55
                        ' "%(this)s"?')
4526.7.1 by Aaron Bentley
Make vocabulary part of reporter.
56
             }
57
58
    invert_diff = False
4459.4.1 by Aaron Bentley
Provide control over switch and shelver messaging.
59
60
    def __init__(self):
61
        self.delta_reporter = delta._ChangeReporter()
62
63
    def no_changes(self):
4526.7.2 by Aaron Bentley
Update docs.
64
        """Report that no changes were selected to apply."""
4459.4.1 by Aaron Bentley
Provide control over switch and shelver messaging.
65
        trace.warning('No changes to shelve.')
66
67
    def shelved_id(self, shelf_id):
4526.7.2 by Aaron Bentley
Update docs.
68
        """Report the id changes were shelved to."""
6138.3.4 by Jonathan Riddell
add gettext() to uses of trace.note()
69
        trace.note(gettext('Changes shelved with id "%d".') % shelf_id)
4459.4.1 by Aaron Bentley
Provide control over switch and shelver messaging.
70
4526.6.4 by Aaron Bentley
Remove changes_destroyed message.
71
    def changes_destroyed(self):
4526.7.2 by Aaron Bentley
Update docs.
72
        """Report that changes were made without shelving."""
6138.3.4 by Jonathan Riddell
add gettext() to uses of trace.note()
73
        trace.note(gettext('Selected changes destroyed.'))
4526.6.4 by Aaron Bentley
Remove changes_destroyed message.
74
4459.4.1 by Aaron Bentley
Provide control over switch and shelver messaging.
75
    def selected_changes(self, transform):
4526.7.2 by Aaron Bentley
Update docs.
76
        """Report the changes that were selected."""
6138.3.4 by Jonathan Riddell
add gettext() to uses of trace.note()
77
        trace.note(gettext("Selected changes:"))
4459.4.1 by Aaron Bentley
Provide control over switch and shelver messaging.
78
        changes = transform.iter_changes()
79
        delta.report_changes(changes, self.delta_reporter)
80
4526.7.1 by Aaron Bentley
Make vocabulary part of reporter.
81
    def prompt_change(self, change):
4526.7.2 by Aaron Bentley
Update docs.
82
        """Determine the prompt for a change to apply."""
4526.7.1 by Aaron Bentley
Make vocabulary part of reporter.
83
        if change[0] == 'rename':
84
            vals = {'this': change[3], 'other': change[2]}
85
        elif change[0] == 'change kind':
86
            vals = {'path': change[4], 'other': change[2], 'this': change[3]}
87
        elif change[0] == 'modify target':
88
            vals = {'path': change[2], 'other': change[3], 'this': change[4]}
89
        else:
90
            vals = {'path': change[3]}
91
        prompt = self.vocab[change[0]] % vals
92
        return prompt
93
94
95
class ApplyReporter(ShelfReporter):
96
6143.1.2 by Jonathan Riddell
gettext() shelf_ui.py
97
    vocab = {'add file': gettext('Delete file "%(path)s"?'),
98
             'binary': gettext('Apply binary changes?'),
99
             'change kind': gettext('Change "%(path)s" from %(this)s'
100
             ' to %(other)s?'),
101
             'delete file': gettext('Add file "%(path)s"?'),
102
             'final': gettext('Apply %d change(s)?'),
103
             'hunk': gettext('Apply change?'),
104
             'modify target': gettext('Change target of'
105
             ' "%(path)s" from "%(this)s" to "%(other)s"?'),
106
             'rename': gettext('Rename "%(this)s" => "%(other)s"?'),
4526.7.1 by Aaron Bentley
Make vocabulary part of reporter.
107
             }
108
109
    invert_diff = True
110
111
    def changes_destroyed(self):
112
        pass
113
4459.4.1 by Aaron Bentley
Provide control over switch and shelver messaging.
114
0.16.1 by Aaron Bentley
Begin implementing UI
115
class Shelver(object):
0.16.97 by Aaron Bentley
Turn diff_file and text_differ into instance variables.
116
    """Interactively shelve the changes in a working tree."""
0.16.1 by Aaron Bentley
Begin implementing UI
117
0.16.108 by Aaron Bentley
Shelf supports multiple diff writers.
118
    def __init__(self, work_tree, target_tree, diff_writer=None, auto=False,
4100.3.1 by Aaron Bentley
Implement shelve --destroy
119
                 auto_apply=False, file_list=None, message=None,
4603.1.9 by Aaron Bentley
Misc cleanup.
120
                 destroy=False, manager=None, reporter=None):
0.16.97 by Aaron Bentley
Turn diff_file and text_differ into instance variables.
121
        """Constructor.
122
123
        :param work_tree: The working tree to shelve changes from.
124
        :param target_tree: The "unchanged" / old tree to compare the
125
            work_tree to.
126
        :param auto: If True, shelve each possible change.
127
        :param auto_apply: If True, shelve changes with no final prompt.
0.16.102 by Aaron Bentley
Minor updates
128
        :param file_list: If supplied, only files in this list may be shelved.
0.16.97 by Aaron Bentley
Turn diff_file and text_differ into instance variables.
129
        :param message: The message to associate with the shelved changes.
4100.3.2 by Aaron Bentley
Update docs
130
        :param destroy: Change the working tree without storing the shelved
131
            changes.
4465.1.2 by Aaron Bentley
Accept manager as a parameter to Shelver()
132
        :param manager: The shelf manager to use.
4526.7.2 by Aaron Bentley
Update docs.
133
        :param reporter: Object for reporting changes to user.
0.16.97 by Aaron Bentley
Turn diff_file and text_differ into instance variables.
134
        """
0.16.1 by Aaron Bentley
Begin implementing UI
135
        self.work_tree = work_tree
136
        self.target_tree = target_tree
0.16.108 by Aaron Bentley
Shelf supports multiple diff writers.
137
        self.diff_writer = diff_writer
138
        if self.diff_writer is None:
0.16.79 by Aaron Bentley
Remove dependencies on bzrtools
139
            self.diff_writer = sys.stdout
4465.1.2 by Aaron Bentley
Accept manager as a parameter to Shelver()
140
        if manager is None:
141
            manager = work_tree.get_shelf_manager()
142
        self.manager = manager
0.16.15 by Aaron Bentley
Implement auto mode
143
        self.auto = auto
0.16.23 by Aaron Bentley
Improve prompting
144
        self.auto_apply = auto_apply
0.16.47 by Aaron Bentley
Support selecting files to shelve
145
        self.file_list = file_list
0.16.57 by Aaron Bentley
Expose messages in the UI
146
        self.message = message
4100.3.1 by Aaron Bentley
Implement shelve --destroy
147
        self.destroy = destroy
4459.4.1 by Aaron Bentley
Provide control over switch and shelver messaging.
148
        if reporter is None:
149
            reporter = ShelfReporter()
150
        self.reporter = reporter
4603.1.14 by Aaron Bentley
Make change editor mandatory
151
        config = self.work_tree.branch.get_config()
152
        self.change_editor = config.get_change_editor(target_tree, work_tree)
4603.2.1 by Benoît Pierre
Lock the work tree in Shelver only after everything else has been setup.
153
        self.work_tree.lock_tree_write()
0.16.1 by Aaron Bentley
Begin implementing UI
154
155
    @classmethod
0.16.108 by Aaron Bentley
Shelf supports multiple diff writers.
156
    def from_args(klass, diff_writer, revision=None, all=False, file_list=None,
5521.1.1 by Vincent Ladeuil
Handle --directory when paths are also provided to shelve and restore.
157
                  message=None, directory=None, destroy=False):
0.16.97 by Aaron Bentley
Turn diff_file and text_differ into instance variables.
158
        """Create a shelver from commandline arguments.
159
4595.13.2 by Alexander Belchenko
[cherrypick revno 4650 from bzr.dev] Fix shelve on windows. (Robert Collins, #305006)
160
        The returned shelver wil have a work_tree that is locked and should
161
        be unlocked.
162
0.16.97 by Aaron Bentley
Turn diff_file and text_differ into instance variables.
163
        :param revision: RevisionSpec of the revision to compare to.
164
        :param all: If True, shelve all changes without prompting.
165
        :param file_list: If supplied, only files in this list may be  shelved.
166
        :param message: The message to associate with the shelved changes.
167
        :param directory: The directory containing the working tree.
4100.3.1 by Aaron Bentley
Implement shelve --destroy
168
        :param destroy: Change the working tree without storing the shelved
169
            changes.
0.16.97 by Aaron Bentley
Turn diff_file and text_differ into instance variables.
170
        """
5521.1.1 by Vincent Ladeuil
Handle --directory when paths are also provided to shelve and restore.
171
        if directory is None:
172
            directory = u'.'
173
        elif file_list:
174
            file_list = [osutils.pathjoin(directory, f) for f in file_list]
0.16.94 by Aaron Bentley
Add unshelve tests
175
        tree, path = workingtree.WorkingTree.open_containing(directory)
4595.13.2 by Alexander Belchenko
[cherrypick revno 4650 from bzr.dev] Fix shelve on windows. (Robert Collins, #305006)
176
        # Ensure that tree is locked for the lifetime of target_tree, as
177
        # target tree may be reading from the same dirstate.
178
        tree.lock_tree_write()
179
        try:
180
            target_tree = builtins._get_one_revision_tree('shelf2', revision,
181
                tree.branch, tree)
5346.4.2 by Martin Pool
Move internal_tree_files and safe_relpath_files onto WorkingTree
182
            files = tree.safe_relpath_files(file_list)
4603.1.17 by Aaron Bentley
Fix shelf_ui tests to finalize.
183
            return klass(tree, target_tree, diff_writer, all, all, files,
184
                         message, destroy)
185
        finally:
4595.13.2 by Alexander Belchenko
[cherrypick revno 4650 from bzr.dev] Fix shelve on windows. (Robert Collins, #305006)
186
            tree.unlock()
0.16.1 by Aaron Bentley
Begin implementing UI
187
188
    def run(self):
0.16.97 by Aaron Bentley
Turn diff_file and text_differ into instance variables.
189
        """Interactively shelve the changes."""
0.16.47 by Aaron Bentley
Support selecting files to shelve
190
        creator = shelf.ShelfCreator(self.work_tree, self.target_tree,
191
                                     self.file_list)
0.16.5 by Aaron Bentley
Get text shelving working
192
        self.tempdir = tempfile.mkdtemp()
0.16.22 by Aaron Bentley
Only prompt when there are changes to shelve.
193
        changes_shelved = 0
0.16.1 by Aaron Bentley
Begin implementing UI
194
        try:
4603.1.7 by Aaron Bentley
Allow configuring change editor.
195
            for change in creator.iter_shelvable():
196
                if change[0] == 'modify text':
197
                    try:
4603.1.9 by Aaron Bentley
Misc cleanup.
198
                        changes_shelved += self.handle_modify_text(creator,
199
                                                                   change[1])
4603.1.7 by Aaron Bentley
Allow configuring change editor.
200
                    except errors.BinaryFile:
201
                        if self.prompt_bool(self.reporter.vocab['binary']):
0.16.72 by Aaron Bentley
Allow shelving binary changes
202
                            changes_shelved += 1
4603.1.7 by Aaron Bentley
Allow configuring change editor.
203
                            creator.shelve_content_change(change[1])
4526.6.1 by Aaron Bentley
Reverse the way changes are described by Shelver.
204
                else:
4603.1.7 by Aaron Bentley
Allow configuring change editor.
205
                    if self.prompt_bool(self.reporter.prompt_change(change)):
206
                        creator.shelve_change(change)
207
                        changes_shelved += 1
208
            if changes_shelved > 0:
209
                self.reporter.selected_changes(creator.work_transform)
210
                if (self.auto_apply or self.prompt_bool(
211
                    self.reporter.vocab['final'] % changes_shelved)):
212
                    if self.destroy:
213
                        creator.transform()
214
                        self.reporter.changes_destroyed()
215
                    else:
216
                        shelf_id = self.manager.shelve_changes(creator,
217
                                                               self.message)
218
                        self.reporter.shelved_id(shelf_id)
219
            else:
220
                self.reporter.no_changes()
0.16.1 by Aaron Bentley
Begin implementing UI
221
        finally:
0.16.5 by Aaron Bentley
Get text shelving working
222
            shutil.rmtree(self.tempdir)
0.16.1 by Aaron Bentley
Begin implementing UI
223
            creator.finalize()
224
4603.1.11 by Aaron Bentley
Implement shelver.finalize
225
    def finalize(self):
4603.1.17 by Aaron Bentley
Fix shelf_ui tests to finalize.
226
        if self.change_editor is not None:
227
            self.change_editor.finish()
4603.1.14 by Aaron Bentley
Make change editor mandatory
228
        self.work_tree.unlock()
229
4603.1.11 by Aaron Bentley
Implement shelver.finalize
230
4526.6.2 by Aaron Bentley
Fix display of diffs for apply mode
231
    def get_parsed_patch(self, file_id, invert=False):
0.16.98 by Aaron Bentley
Update docs and prompting
232
        """Return a parsed version of a file's patch.
233
234
        :param file_id: The id of the file to generate a patch for.
4526.7.2 by Aaron Bentley
Update docs.
235
        :param invert: If True, provide an inverted patch (insertions displayed
236
            as removals, removals displayed as insertions).
0.16.98 by Aaron Bentley
Update docs and prompting
237
        :return: A patches.Patch.
238
        """
0.16.97 by Aaron Bentley
Turn diff_file and text_differ into instance variables.
239
        diff_file = StringIO()
4526.6.2 by Aaron Bentley
Fix display of diffs for apply mode
240
        if invert:
241
            old_tree = self.work_tree
242
            new_tree = self.target_tree
243
        else:
244
            old_tree = self.target_tree
245
            new_tree = self.work_tree
4526.7.1 by Aaron Bentley
Make vocabulary part of reporter.
246
        old_path = old_tree.id2path(file_id)
247
        new_path = new_tree.id2path(file_id)
4797.57.8 by Alexander Belchenko
using appropriate encoing for diff in shelve/unshelve.
248
        text_differ = diff.DiffText(old_tree, new_tree, diff_file,
249
            path_encoding=osutils.get_terminal_encoding())
0.16.97 by Aaron Bentley
Turn diff_file and text_differ into instance variables.
250
        patch = text_differ.diff(file_id, old_path, new_path, 'file', 'file')
251
        diff_file.seek(0)
252
        return patches.parse_patch(diff_file)
0.16.1 by Aaron Bentley
Begin implementing UI
253
6182.2.7 by Benoît Pierre
Update shelf UI to use ui.confirm.
254
    def prompt(self, message, choices, default):
6182.2.13 by Benoît Pierre
Rename ui.confirm to ui.choose.
255
        return ui.ui_factory.choose(message, choices, default=default)
6182.2.7 by Benoît Pierre
Update shelf UI to use ui.confirm.
256
257
    def prompt_bool(self, question, allow_editor=False):
0.16.98 by Aaron Bentley
Update docs and prompting
258
        """Prompt the user with a yes/no question.
259
0.16.102 by Aaron Bentley
Minor updates
260
        This may be overridden by self.auto.  It may also *set* self.auto.  It
0.16.103 by Aaron Bentley
raise UserAbort instead of doing sys.exit
261
        may also raise UserAbort.
0.16.98 by Aaron Bentley
Update docs and prompting
262
        :param question: The question to ask the user.
263
        :return: True or False
264
        """
265
        if self.auto:
0.16.23 by Aaron Bentley
Improve prompting
266
            return True
6182.2.7 by Benoît Pierre
Update shelf UI to use ui.confirm.
267
        alternatives_chars = 'yn'
268
        alternatives = '&yes\n&No'
269
        if allow_editor:
270
            alternatives_chars += 'e'
271
            alternatives += '\n&edit manually'
272
        alternatives_chars += 'fq'
273
        alternatives += '\n&finish\n&quit'
274
        choice = self.prompt(question, alternatives, 1)
275
        if choice is None:
276
            # EOF.
277
            char = 'n'
3990.4.3 by Daniel Watkins
Added help option to shelve prompt.
278
        else:
6182.2.7 by Benoît Pierre
Update shelf UI to use ui.confirm.
279
            char = alternatives_chars[choice]
0.16.23 by Aaron Bentley
Improve prompting
280
        if char == 'y':
281
            return True
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
282
        elif char == 'e' and allow_editor:
283
            raise UseEditor
0.16.23 by Aaron Bentley
Improve prompting
284
        elif char == 'f':
285
            self.auto = True
286
            return True
0.16.24 by Aaron Bentley
Regularize prompts
287
        if char == 'q':
0.16.103 by Aaron Bentley
raise UserAbort instead of doing sys.exit
288
            raise errors.UserAbort()
0.16.23 by Aaron Bentley
Improve prompting
289
        else:
290
            return False
0.16.1 by Aaron Bentley
Begin implementing UI
291
4603.1.7 by Aaron Bentley
Allow configuring change editor.
292
    def handle_modify_text(self, creator, file_id):
4603.1.9 by Aaron Bentley
Misc cleanup.
293
        """Handle modified text, by using hunk selection or file editing.
294
295
        :param creator: A ShelfCreator.
296
        :param file_id: The id of the file that was modified.
297
        :return: The number of changes.
298
        """
299
        work_tree_lines = self.work_tree.get_file_lines(file_id)
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
300
        try:
4603.1.9 by Aaron Bentley
Misc cleanup.
301
            lines, change_count = self._select_hunks(creator, file_id,
302
                                                     work_tree_lines)
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
303
        except UseEditor:
4603.1.9 by Aaron Bentley
Misc cleanup.
304
            lines, change_count = self._edit_file(file_id, work_tree_lines)
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
305
        if change_count != 0:
306
            creator.shelve_lines(file_id, lines)
307
        return change_count
308
4603.1.9 by Aaron Bentley
Misc cleanup.
309
    def _select_hunks(self, creator, file_id, work_tree_lines):
0.16.98 by Aaron Bentley
Update docs and prompting
310
        """Provide diff hunk selection for modified text.
311
4526.7.2 by Aaron Bentley
Update docs.
312
        If self.reporter.invert_diff is True, the diff is inverted so that
313
        insertions are displayed as removals and vice versa.
314
0.16.98 by Aaron Bentley
Update docs and prompting
315
        :param creator: a ShelfCreator
316
        :param file_id: The id of the file to shelve.
4603.1.9 by Aaron Bentley
Misc cleanup.
317
        :param work_tree_lines: Line contents of the file in the working tree.
0.16.98 by Aaron Bentley
Update docs and prompting
318
        :return: number of shelved hunks.
319
        """
4526.7.1 by Aaron Bentley
Make vocabulary part of reporter.
320
        if self.reporter.invert_diff:
4603.1.9 by Aaron Bentley
Misc cleanup.
321
            target_lines = work_tree_lines
4526.6.2 by Aaron Bentley
Fix display of diffs for apply mode
322
        else:
323
            target_lines = self.target_tree.get_file_lines(file_id)
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
324
        textfile.check_text_lines(work_tree_lines)
0.16.72 by Aaron Bentley
Allow shelving binary changes
325
        textfile.check_text_lines(target_lines)
4526.7.1 by Aaron Bentley
Make vocabulary part of reporter.
326
        parsed = self.get_parsed_patch(file_id, self.reporter.invert_diff)
0.16.43 by Aaron Bentley
Reduce API friction.
327
        final_hunks = []
0.16.15 by Aaron Bentley
Implement auto mode
328
        if not self.auto:
0.16.41 by Aaron Bentley
Implement shelving with internal patch
329
            offset = 0
0.16.61 by Aaron Bentley
Show file name when shelving
330
            self.diff_writer.write(parsed.get_header())
0.16.15 by Aaron Bentley
Implement auto mode
331
            for hunk in parsed.hunks:
332
                self.diff_writer.write(str(hunk))
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
333
                selected = self.prompt_bool(self.reporter.vocab['hunk'],
4603.1.7 by Aaron Bentley
Allow configuring change editor.
334
                                            allow_editor=(self.change_editor
335
                                                          is not None))
4526.7.1 by Aaron Bentley
Make vocabulary part of reporter.
336
                if not self.reporter.invert_diff:
4526.6.2 by Aaron Bentley
Fix display of diffs for apply mode
337
                    selected = (not selected)
338
                if selected:
0.16.41 by Aaron Bentley
Implement shelving with internal patch
339
                    hunk.mod_pos += offset
0.16.43 by Aaron Bentley
Reduce API friction.
340
                    final_hunks.append(hunk)
0.16.41 by Aaron Bentley
Implement shelving with internal patch
341
                else:
342
                    offset -= (hunk.mod_range - hunk.orig_range)
0.16.68 by Aaron Bentley
Avoid having escape codes affect the wrong text.
343
        sys.stdout.flush()
4526.7.1 by Aaron Bentley
Make vocabulary part of reporter.
344
        if self.reporter.invert_diff:
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
345
            change_count = len(final_hunks)
346
        else:
347
            change_count = len(parsed.hunks) - len(final_hunks)
4603.1.2 by Aaron Bentley
Simplify unchanged case.
348
        patched = patches.iter_patched_from_hunks(target_lines,
349
                                                  final_hunks)
350
        lines = list(patched)
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
351
        return lines, change_count
352
4603.1.9 by Aaron Bentley
Misc cleanup.
353
    def _edit_file(self, file_id, work_tree_lines):
354
        """
355
        :param file_id: id of the file to edit.
356
        :param work_tree_lines: Line contents of the file in the working tree.
357
        :return: (lines, change_region_count), where lines is the new line
358
            content of the file, and change_region_count is the number of
359
            changed regions.
360
        """
4603.1.7 by Aaron Bentley
Allow configuring change editor.
361
        lines = osutils.split_lines(self.change_editor.edit_file(file_id))
4603.1.6 by Aaron Bentley
Provide a reasonable count of changes when file edited.
362
        return lines, self._count_changed_regions(work_tree_lines, lines)
363
364
    @staticmethod
365
    def _count_changed_regions(old_lines, new_lines):
366
        matcher = patiencediff.PatienceSequenceMatcher(None, old_lines,
367
                                                       new_lines)
368
        blocks = matcher.get_matching_blocks()
369
        return len(blocks) - 2
0.16.8 by Aaron Bentley
Implement unshelve2, tidy shelve2
370
371
372
class Unshelver(object):
0.16.98 by Aaron Bentley
Update docs and prompting
373
    """Unshelve changes into a working tree."""
0.16.8 by Aaron Bentley
Implement unshelve2, tidy shelve2
374
375
    @classmethod
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
376
    def from_args(klass, shelf_id=None, action='apply', directory='.',
377
                  write_diff_to=None):
0.16.98 by Aaron Bentley
Update docs and prompting
378
        """Create an unshelver from commandline arguments.
379
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
380
        The returned shelver will have a tree that is locked and should
4595.13.2 by Alexander Belchenko
[cherrypick revno 4650 from bzr.dev] Fix shelve on windows. (Robert Collins, #305006)
381
        be unlocked.
382
0.16.98 by Aaron Bentley
Update docs and prompting
383
        :param shelf_id: Integer id of the shelf, as a string.
384
        :param action: action to perform.  May be 'apply', 'dry-run',
4902.1.2 by Guilherme Salgado
First round of the new approach, using a new action (--preview) on the unshelve command
385
            'delete', 'preview'.
0.16.98 by Aaron Bentley
Update docs and prompting
386
        :param directory: The directory to unshelve changes into.
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
387
        :param write_diff_to: See Unshelver.__init__().
0.16.98 by Aaron Bentley
Update docs and prompting
388
        """
0.16.94 by Aaron Bentley
Add unshelve tests
389
        tree, path = workingtree.WorkingTree.open_containing(directory)
4595.13.2 by Alexander Belchenko
[cherrypick revno 4650 from bzr.dev] Fix shelve on windows. (Robert Collins, #305006)
390
        tree.lock_tree_write()
391
        try:
392
            manager = tree.get_shelf_manager()
393
            if shelf_id is not None:
394
                try:
395
                    shelf_id = int(shelf_id)
396
                except ValueError:
397
                    raise errors.InvalidShelfId(shelf_id)
398
            else:
399
                shelf_id = manager.last_shelf()
400
                if shelf_id is None:
6138.3.8 by Jonathan Riddell
more error gettext()ing
401
                    raise errors.BzrCommandError(gettext('No changes are shelved.'))
4595.13.2 by Alexander Belchenko
[cherrypick revno 4650 from bzr.dev] Fix shelve on windows. (Robert Collins, #305006)
402
            apply_changes = True
403
            delete_shelf = True
404
            read_shelf = True
4902.1.2 by Guilherme Salgado
First round of the new approach, using a new action (--preview) on the unshelve command
405
            show_diff = False
4595.13.2 by Alexander Belchenko
[cherrypick revno 4650 from bzr.dev] Fix shelve on windows. (Robert Collins, #305006)
406
            if action == 'dry-run':
407
                apply_changes = False
408
                delete_shelf = False
4902.1.2 by Guilherme Salgado
First round of the new approach, using a new action (--preview) on the unshelve command
409
            elif action == 'preview':
410
                apply_changes = False
411
                delete_shelf = False
412
                show_diff = True
4889.1.3 by Martin Pool
New option unshelve --keep
413
            elif action == 'delete-only':
4595.13.2 by Alexander Belchenko
[cherrypick revno 4650 from bzr.dev] Fix shelve on windows. (Robert Collins, #305006)
414
                apply_changes = False
415
                read_shelf = False
4889.1.3 by Martin Pool
New option unshelve --keep
416
            elif action == 'keep':
417
                apply_changes = True
418
                delete_shelf = False
4595.13.2 by Alexander Belchenko
[cherrypick revno 4650 from bzr.dev] Fix shelve on windows. (Robert Collins, #305006)
419
        except:
420
            tree.unlock()
421
            raise
0.16.65 by Aaron Bentley
Implement unshelve --delete
422
        return klass(tree, manager, shelf_id, apply_changes, delete_shelf,
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
423
                     read_shelf, show_diff, write_diff_to)
0.16.8 by Aaron Bentley
Implement unshelve2, tidy shelve2
424
0.16.94 by Aaron Bentley
Add unshelve tests
425
    def __init__(self, tree, manager, shelf_id, apply_changes=True,
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
426
                 delete_shelf=True, read_shelf=True, show_diff=False,
427
                 write_diff_to=None):
0.16.98 by Aaron Bentley
Update docs and prompting
428
        """Constructor.
429
430
        :param tree: The working tree to unshelve into.
431
        :param manager: The ShelveManager containing the shelved changes.
432
        :param shelf_id:
433
        :param apply_changes: If True, apply the shelved changes to the
434
            working tree.
435
        :param delete_shelf: If True, delete the changes from the shelf.
436
        :param read_shelf: If True, read the changes from the shelf.
4902.1.2 by Guilherme Salgado
First round of the new approach, using a new action (--preview) on the unshelve command
437
        :param show_diff: If True, show the diff that would result from
438
            unshelving the changes.
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
439
        :param write_diff_to: A file-like object where the diff will be
440
            written to. If None, ui.ui_factory.make_output_stream() will
441
            be used.
0.16.98 by Aaron Bentley
Update docs and prompting
442
        """
0.16.8 by Aaron Bentley
Implement unshelve2, tidy shelve2
443
        self.tree = tree
0.16.98 by Aaron Bentley
Update docs and prompting
444
        manager = tree.get_shelf_manager()
0.16.13 by Aaron Bentley
Appy shelve-management updates to shelver
445
        self.manager = manager
446
        self.shelf_id = shelf_id
0.16.64 by Aaron Bentley
Implement dry-run option for Unshelve
447
        self.apply_changes = apply_changes
448
        self.delete_shelf = delete_shelf
0.16.65 by Aaron Bentley
Implement unshelve --delete
449
        self.read_shelf = read_shelf
4902.1.2 by Guilherme Salgado
First round of the new approach, using a new action (--preview) on the unshelve command
450
        self.show_diff = show_diff
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
451
        self.write_diff_to = write_diff_to
0.16.8 by Aaron Bentley
Implement unshelve2, tidy shelve2
452
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
453
    def run(self):
0.16.98 by Aaron Bentley
Update docs and prompting
454
        """Perform the unshelving operation."""
4595.13.2 by Alexander Belchenko
[cherrypick revno 4650 from bzr.dev] Fix shelve on windows. (Robert Collins, #305006)
455
        self.tree.lock_tree_write()
0.16.37 by Aaron Bentley
Use cleanups list to reduce nested try blocks
456
        cleanups = [self.tree.unlock]
0.16.8 by Aaron Bentley
Implement unshelve2, tidy shelve2
457
        try:
0.16.65 by Aaron Bentley
Implement unshelve --delete
458
            if self.read_shelf:
6138.3.4 by Jonathan Riddell
add gettext() to uses of trace.note()
459
                trace.note(gettext('Using changes with id "%d".') % self.shelf_id)
0.16.65 by Aaron Bentley
Implement unshelve --delete
460
                unshelver = self.manager.get_unshelver(self.shelf_id)
461
                cleanups.append(unshelver.finalize)
462
                if unshelver.message is not None:
6138.3.4 by Jonathan Riddell
add gettext() to uses of trace.note()
463
                    trace.note(gettext('Message: %s') % unshelver.message)
0.16.65 by Aaron Bentley
Implement unshelve --delete
464
                change_reporter = delta._ChangeReporter()
4961.2.13 by Martin Pool
Further progress bar string-pulling
465
                merger = unshelver.make_merger(None)
466
                merger.change_reporter = change_reporter
467
                if self.apply_changes:
468
                    merger.do_merge()
469
                elif self.show_diff:
470
                    self.write_diff(merger)
471
                else:
472
                    self.show_changes(merger)
0.16.64 by Aaron Bentley
Implement dry-run option for Unshelve
473
            if self.delete_shelf:
474
                self.manager.delete_shelf(self.shelf_id)
6138.3.4 by Jonathan Riddell
add gettext() to uses of trace.note()
475
                trace.note(gettext('Deleted changes with id "%d".') % self.shelf_id)
0.16.8 by Aaron Bentley
Implement unshelve2, tidy shelve2
476
        finally:
0.16.37 by Aaron Bentley
Use cleanups list to reduce nested try blocks
477
            for cleanup in reversed(cleanups):
478
                cleanup()
0.16.64 by Aaron Bentley
Implement dry-run option for Unshelve
479
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
480
    def write_diff(self, merger):
481
        """Write this operation's diff to self.write_diff_to."""
4902.1.2 by Guilherme Salgado
First round of the new approach, using a new action (--preview) on the unshelve command
482
        tree_merger = merger.make_merger()
483
        tt = tree_merger.make_preview_transform()
484
        new_tree = tt.get_preview_tree()
4902.1.8 by John Arbash Meinel
Delay grabbing an output stream until we actually go to show a diff.
485
        if self.write_diff_to is None:
5433.2.1 by Andrej A Antonov
fixed bug: `unshelve --preview` fails with unicode error
486
            self.write_diff_to = ui.ui_factory.make_output_stream(encoding_type='exact')
4797.57.10 by Alexander Belchenko
path_encoding selection logic extracted as helper function
487
        path_encoding = osutils.get_diff_header_encoding()
4797.57.8 by Alexander Belchenko
using appropriate encoing for diff in shelve/unshelve.
488
        diff.show_diff_trees(merger.this_tree, new_tree, self.write_diff_to,
489
            path_encoding=path_encoding)
4902.1.2 by Guilherme Salgado
First round of the new approach, using a new action (--preview) on the unshelve command
490
        tt.finalize()
491
0.16.64 by Aaron Bentley
Implement dry-run option for Unshelve
492
    def show_changes(self, merger):
0.16.98 by Aaron Bentley
Update docs and prompting
493
        """Show the changes that this operation specifies."""
0.16.64 by Aaron Bentley
Implement dry-run option for Unshelve
494
        tree_merger = merger.make_merger()
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
495
        # This implicitly shows the changes via the reporter, so we're done...
0.16.64 by Aaron Bentley
Implement dry-run option for Unshelve
496
        tt = tree_merger.make_preview_transform()
497
        tt.finalize()