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