~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
)
3835.2.6 by Aaron Bentley
Restore vila's colordiff change
37
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
4526.7.1 by Aaron Bentley
Make vocabulary part of reporter.
45
    vocab = {'add file': 'Shelve adding file "%(path)s"?',
46
             'binary': 'Shelve binary changes?',
47
             'change kind': 'Shelve changing "%s" from %(other)s'
48
             ' to %(this)s?',
49
             'delete file': 'Shelve removing file "%(path)s"?',
50
             'final': 'Shelve %d change(s)?',
51
             'hunk': 'Shelve?',
52
             'modify target': 'Shelve changing target of'
53
             ' "%(path)s" from "%(other)s" to "%(this)s"?',
54
             'rename': 'Shelve renaming "%(other)s" =>'
55
                        ' "%(this)s"?'
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."""
4459.4.1 by Aaron Bentley
Provide control over switch and shelver messaging.
69
        trace.note('Changes shelved with id "%d".' % shelf_id)
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."""
4526.6.4 by Aaron Bentley
Remove changes_destroyed message.
73
        trace.note('Selected changes destroyed.')
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."""
4459.4.1 by Aaron Bentley
Provide control over switch and shelver messaging.
77
        trace.note("Selected changes:")
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
97
    vocab = {'add file': 'Delete file "%(path)s"?',
98
             'binary': 'Apply binary changes?',
99
             'change kind': 'Change "%(path)s" from %(this)s'
100
             ' to %(other)s?',
101
             'delete file': 'Add file "%(path)s"?',
102
             'final': 'Apply %d change(s)?',
103
             'hunk': 'Apply change?',
104
             'modify target': 'Change target of'
105
             ' "%(path)s" from "%(this)s" to "%(other)s"?',
106
             'rename': 'Rename "%(this)s" => "%(other)s"?',
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,
4100.3.1 by Aaron Bentley
Implement shelve --destroy
157
                  message=None, directory='.', 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
        """
0.16.94 by Aaron Bentley
Add unshelve tests
171
        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)
172
        # Ensure that tree is locked for the lifetime of target_tree, as
173
        # target tree may be reading from the same dirstate.
174
        tree.lock_tree_write()
175
        try:
176
            target_tree = builtins._get_one_revision_tree('shelf2', revision,
177
                tree.branch, tree)
178
            files = builtins.safe_relpath_files(tree, file_list)
4603.1.17 by Aaron Bentley
Fix shelf_ui tests to finalize.
179
            return klass(tree, target_tree, diff_writer, all, all, files,
180
                         message, destroy)
181
        finally:
4595.13.2 by Alexander Belchenko
[cherrypick revno 4650 from bzr.dev] Fix shelve on windows. (Robert Collins, #305006)
182
            tree.unlock()
0.16.1 by Aaron Bentley
Begin implementing UI
183
184
    def run(self):
0.16.97 by Aaron Bentley
Turn diff_file and text_differ into instance variables.
185
        """Interactively shelve the changes."""
0.16.47 by Aaron Bentley
Support selecting files to shelve
186
        creator = shelf.ShelfCreator(self.work_tree, self.target_tree,
187
                                     self.file_list)
0.16.5 by Aaron Bentley
Get text shelving working
188
        self.tempdir = tempfile.mkdtemp()
0.16.22 by Aaron Bentley
Only prompt when there are changes to shelve.
189
        changes_shelved = 0
0.16.1 by Aaron Bentley
Begin implementing UI
190
        try:
4603.1.7 by Aaron Bentley
Allow configuring change editor.
191
            for change in creator.iter_shelvable():
192
                if change[0] == 'modify text':
193
                    try:
4603.1.9 by Aaron Bentley
Misc cleanup.
194
                        changes_shelved += self.handle_modify_text(creator,
195
                                                                   change[1])
4603.1.7 by Aaron Bentley
Allow configuring change editor.
196
                    except errors.BinaryFile:
197
                        if self.prompt_bool(self.reporter.vocab['binary']):
0.16.72 by Aaron Bentley
Allow shelving binary changes
198
                            changes_shelved += 1
4603.1.7 by Aaron Bentley
Allow configuring change editor.
199
                            creator.shelve_content_change(change[1])
4526.6.1 by Aaron Bentley
Reverse the way changes are described by Shelver.
200
                else:
4603.1.7 by Aaron Bentley
Allow configuring change editor.
201
                    if self.prompt_bool(self.reporter.prompt_change(change)):
202
                        creator.shelve_change(change)
203
                        changes_shelved += 1
204
            if changes_shelved > 0:
205
                self.reporter.selected_changes(creator.work_transform)
206
                if (self.auto_apply or self.prompt_bool(
207
                    self.reporter.vocab['final'] % changes_shelved)):
208
                    if self.destroy:
209
                        creator.transform()
210
                        self.reporter.changes_destroyed()
211
                    else:
212
                        shelf_id = self.manager.shelve_changes(creator,
213
                                                               self.message)
214
                        self.reporter.shelved_id(shelf_id)
215
            else:
216
                self.reporter.no_changes()
0.16.1 by Aaron Bentley
Begin implementing UI
217
        finally:
0.16.5 by Aaron Bentley
Get text shelving working
218
            shutil.rmtree(self.tempdir)
0.16.1 by Aaron Bentley
Begin implementing UI
219
            creator.finalize()
220
4603.1.11 by Aaron Bentley
Implement shelver.finalize
221
    def finalize(self):
4603.1.17 by Aaron Bentley
Fix shelf_ui tests to finalize.
222
        if self.change_editor is not None:
223
            self.change_editor.finish()
4603.1.14 by Aaron Bentley
Make change editor mandatory
224
        self.work_tree.unlock()
225
4603.1.11 by Aaron Bentley
Implement shelver.finalize
226
4526.6.2 by Aaron Bentley
Fix display of diffs for apply mode
227
    def get_parsed_patch(self, file_id, invert=False):
0.16.98 by Aaron Bentley
Update docs and prompting
228
        """Return a parsed version of a file's patch.
229
230
        :param file_id: The id of the file to generate a patch for.
4526.7.2 by Aaron Bentley
Update docs.
231
        :param invert: If True, provide an inverted patch (insertions displayed
232
            as removals, removals displayed as insertions).
0.16.98 by Aaron Bentley
Update docs and prompting
233
        :return: A patches.Patch.
234
        """
0.16.97 by Aaron Bentley
Turn diff_file and text_differ into instance variables.
235
        diff_file = StringIO()
4526.6.2 by Aaron Bentley
Fix display of diffs for apply mode
236
        if invert:
237
            old_tree = self.work_tree
238
            new_tree = self.target_tree
239
        else:
240
            old_tree = self.target_tree
241
            new_tree = self.work_tree
4526.7.1 by Aaron Bentley
Make vocabulary part of reporter.
242
        old_path = old_tree.id2path(file_id)
243
        new_path = new_tree.id2path(file_id)
4526.6.2 by Aaron Bentley
Fix display of diffs for apply mode
244
        text_differ = diff.DiffText(old_tree, new_tree, diff_file)
0.16.97 by Aaron Bentley
Turn diff_file and text_differ into instance variables.
245
        patch = text_differ.diff(file_id, old_path, new_path, 'file', 'file')
246
        diff_file.seek(0)
247
        return patches.parse_patch(diff_file)
0.16.1 by Aaron Bentley
Begin implementing UI
248
0.16.89 by Aaron Bentley
Add tests for Shelver
249
    def prompt(self, message):
0.16.98 by Aaron Bentley
Update docs and prompting
250
        """Prompt the user for a character.
251
252
        :param message: The message to prompt a user with.
253
        :return: A character.
254
        """
4797.4.2 by Vincent Ladeuil
Fix failing tests by checking stdin when needed only.
255
        if not sys.stdin.isatty():
256
            # Since there is no controlling terminal we will hang when trying
257
            # to prompt the user, better abort now.  See
258
            # https://code.launchpad.net/~bialix/bzr/shelve-no-tty/+merge/14905
259
            # for more context.
260
            raise errors.BzrError("You need a controlling terminal.")
0.16.89 by Aaron Bentley
Add tests for Shelver
261
        sys.stdout.write(message)
262
        char = osutils.getchar()
263
        sys.stdout.write("\r" + ' ' * len(message) + '\r')
264
        sys.stdout.flush()
265
        return char
266
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
267
    def prompt_bool(self, question, long=False, allow_editor=False):
0.16.98 by Aaron Bentley
Update docs and prompting
268
        """Prompt the user with a yes/no question.
269
0.16.102 by Aaron Bentley
Minor updates
270
        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
271
        may also raise UserAbort.
0.16.98 by Aaron Bentley
Update docs and prompting
272
        :param question: The question to ask the user.
273
        :return: True or False
274
        """
275
        if self.auto:
0.16.23 by Aaron Bentley
Improve prompting
276
            return True
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
277
        editor_string = ''
3990.4.3 by Daniel Watkins
Added help option to shelve prompt.
278
        if long:
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
279
            if allow_editor:
280
                editor_string = '(E)dit manually, '
281
            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.
282
        else:
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
283
            if allow_editor:
284
                editor_string = 'e'
285
            prompt = ' [yN%sfq?]' % editor_string
3990.4.3 by Daniel Watkins
Added help option to shelve prompt.
286
        char = self.prompt(question + prompt)
0.16.23 by Aaron Bentley
Improve prompting
287
        if char == 'y':
288
            return True
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
289
        elif char == 'e' and allow_editor:
290
            raise UseEditor
0.16.23 by Aaron Bentley
Improve prompting
291
        elif char == 'f':
292
            self.auto = True
293
            return True
3990.4.3 by Daniel Watkins
Added help option to shelve prompt.
294
        elif char == '?':
295
            return self.prompt_bool(question, long=True)
0.16.24 by Aaron Bentley
Regularize prompts
296
        if char == 'q':
0.16.103 by Aaron Bentley
raise UserAbort instead of doing sys.exit
297
            raise errors.UserAbort()
0.16.23 by Aaron Bentley
Improve prompting
298
        else:
299
            return False
0.16.1 by Aaron Bentley
Begin implementing UI
300
4603.1.7 by Aaron Bentley
Allow configuring change editor.
301
    def handle_modify_text(self, creator, file_id):
4603.1.9 by Aaron Bentley
Misc cleanup.
302
        """Handle modified text, by using hunk selection or file editing.
303
304
        :param creator: A ShelfCreator.
305
        :param file_id: The id of the file that was modified.
306
        :return: The number of changes.
307
        """
308
        work_tree_lines = self.work_tree.get_file_lines(file_id)
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
309
        try:
4603.1.9 by Aaron Bentley
Misc cleanup.
310
            lines, change_count = self._select_hunks(creator, file_id,
311
                                                     work_tree_lines)
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
312
        except UseEditor:
4603.1.9 by Aaron Bentley
Misc cleanup.
313
            lines, change_count = self._edit_file(file_id, work_tree_lines)
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
314
        if change_count != 0:
315
            creator.shelve_lines(file_id, lines)
316
        return change_count
317
4603.1.9 by Aaron Bentley
Misc cleanup.
318
    def _select_hunks(self, creator, file_id, work_tree_lines):
0.16.98 by Aaron Bentley
Update docs and prompting
319
        """Provide diff hunk selection for modified text.
320
4526.7.2 by Aaron Bentley
Update docs.
321
        If self.reporter.invert_diff is True, the diff is inverted so that
322
        insertions are displayed as removals and vice versa.
323
0.16.98 by Aaron Bentley
Update docs and prompting
324
        :param creator: a ShelfCreator
325
        :param file_id: The id of the file to shelve.
4603.1.9 by Aaron Bentley
Misc cleanup.
326
        :param work_tree_lines: Line contents of the file in the working tree.
0.16.98 by Aaron Bentley
Update docs and prompting
327
        :return: number of shelved hunks.
328
        """
4526.7.1 by Aaron Bentley
Make vocabulary part of reporter.
329
        if self.reporter.invert_diff:
4603.1.9 by Aaron Bentley
Misc cleanup.
330
            target_lines = work_tree_lines
4526.6.2 by Aaron Bentley
Fix display of diffs for apply mode
331
        else:
332
            target_lines = self.target_tree.get_file_lines(file_id)
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
333
        textfile.check_text_lines(work_tree_lines)
0.16.72 by Aaron Bentley
Allow shelving binary changes
334
        textfile.check_text_lines(target_lines)
4526.7.1 by Aaron Bentley
Make vocabulary part of reporter.
335
        parsed = self.get_parsed_patch(file_id, self.reporter.invert_diff)
0.16.43 by Aaron Bentley
Reduce API friction.
336
        final_hunks = []
0.16.15 by Aaron Bentley
Implement auto mode
337
        if not self.auto:
0.16.41 by Aaron Bentley
Implement shelving with internal patch
338
            offset = 0
0.16.61 by Aaron Bentley
Show file name when shelving
339
            self.diff_writer.write(parsed.get_header())
0.16.15 by Aaron Bentley
Implement auto mode
340
            for hunk in parsed.hunks:
341
                self.diff_writer.write(str(hunk))
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
342
                selected = self.prompt_bool(self.reporter.vocab['hunk'],
4603.1.7 by Aaron Bentley
Allow configuring change editor.
343
                                            allow_editor=(self.change_editor
344
                                                          is not None))
4526.7.1 by Aaron Bentley
Make vocabulary part of reporter.
345
                if not self.reporter.invert_diff:
4526.6.2 by Aaron Bentley
Fix display of diffs for apply mode
346
                    selected = (not selected)
347
                if selected:
0.16.41 by Aaron Bentley
Implement shelving with internal patch
348
                    hunk.mod_pos += offset
0.16.43 by Aaron Bentley
Reduce API friction.
349
                    final_hunks.append(hunk)
0.16.41 by Aaron Bentley
Implement shelving with internal patch
350
                else:
351
                    offset -= (hunk.mod_range - hunk.orig_range)
0.16.68 by Aaron Bentley
Avoid having escape codes affect the wrong text.
352
        sys.stdout.flush()
4526.7.1 by Aaron Bentley
Make vocabulary part of reporter.
353
        if self.reporter.invert_diff:
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
354
            change_count = len(final_hunks)
355
        else:
356
            change_count = len(parsed.hunks) - len(final_hunks)
4603.1.2 by Aaron Bentley
Simplify unchanged case.
357
        patched = patches.iter_patched_from_hunks(target_lines,
358
                                                  final_hunks)
359
        lines = list(patched)
4603.1.1 by Aaron Bentley
Initial pass at shelve-via-editor.
360
        return lines, change_count
361
4603.1.9 by Aaron Bentley
Misc cleanup.
362
    def _edit_file(self, file_id, work_tree_lines):
363
        """
364
        :param file_id: id of the file to edit.
365
        :param work_tree_lines: Line contents of the file in the working tree.
366
        :return: (lines, change_region_count), where lines is the new line
367
            content of the file, and change_region_count is the number of
368
            changed regions.
369
        """
4603.1.7 by Aaron Bentley
Allow configuring change editor.
370
        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.
371
        return lines, self._count_changed_regions(work_tree_lines, lines)
372
373
    @staticmethod
374
    def _count_changed_regions(old_lines, new_lines):
375
        matcher = patiencediff.PatienceSequenceMatcher(None, old_lines,
376
                                                       new_lines)
377
        blocks = matcher.get_matching_blocks()
378
        return len(blocks) - 2
0.16.8 by Aaron Bentley
Implement unshelve2, tidy shelve2
379
380
381
class Unshelver(object):
0.16.98 by Aaron Bentley
Update docs and prompting
382
    """Unshelve changes into a working tree."""
0.16.8 by Aaron Bentley
Implement unshelve2, tidy shelve2
383
384
    @classmethod
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
385
    def from_args(klass, shelf_id=None, action='apply', directory='.',
386
                  write_diff_to=None):
0.16.98 by Aaron Bentley
Update docs and prompting
387
        """Create an unshelver from commandline arguments.
388
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
389
        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)
390
        be unlocked.
391
0.16.98 by Aaron Bentley
Update docs and prompting
392
        :param shelf_id: Integer id of the shelf, as a string.
393
        :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
394
            'delete', 'preview'.
0.16.98 by Aaron Bentley
Update docs and prompting
395
        :param directory: The directory to unshelve changes into.
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
396
        :param write_diff_to: See Unshelver.__init__().
0.16.98 by Aaron Bentley
Update docs and prompting
397
        """
0.16.94 by Aaron Bentley
Add unshelve tests
398
        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)
399
        tree.lock_tree_write()
400
        try:
401
            manager = tree.get_shelf_manager()
402
            if shelf_id is not None:
403
                try:
404
                    shelf_id = int(shelf_id)
405
                except ValueError:
406
                    raise errors.InvalidShelfId(shelf_id)
407
            else:
408
                shelf_id = manager.last_shelf()
409
                if shelf_id is None:
410
                    raise errors.BzrCommandError('No changes are shelved.')
411
            apply_changes = True
412
            delete_shelf = True
413
            read_shelf = True
4902.1.2 by Guilherme Salgado
First round of the new approach, using a new action (--preview) on the unshelve command
414
            show_diff = False
4595.13.2 by Alexander Belchenko
[cherrypick revno 4650 from bzr.dev] Fix shelve on windows. (Robert Collins, #305006)
415
            if action == 'dry-run':
416
                apply_changes = False
417
                delete_shelf = False
4902.1.2 by Guilherme Salgado
First round of the new approach, using a new action (--preview) on the unshelve command
418
            elif action == 'preview':
419
                apply_changes = False
420
                delete_shelf = False
421
                show_diff = True
4889.1.3 by Martin Pool
New option unshelve --keep
422
            elif action == 'delete-only':
4595.13.2 by Alexander Belchenko
[cherrypick revno 4650 from bzr.dev] Fix shelve on windows. (Robert Collins, #305006)
423
                apply_changes = False
424
                read_shelf = False
4889.1.3 by Martin Pool
New option unshelve --keep
425
            elif action == 'keep':
426
                apply_changes = True
427
                delete_shelf = False
4595.13.2 by Alexander Belchenko
[cherrypick revno 4650 from bzr.dev] Fix shelve on windows. (Robert Collins, #305006)
428
        except:
429
            tree.unlock()
430
            raise
0.16.65 by Aaron Bentley
Implement unshelve --delete
431
        return klass(tree, manager, shelf_id, apply_changes, delete_shelf,
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
432
                     read_shelf, show_diff, write_diff_to)
0.16.8 by Aaron Bentley
Implement unshelve2, tidy shelve2
433
0.16.94 by Aaron Bentley
Add unshelve tests
434
    def __init__(self, tree, manager, shelf_id, apply_changes=True,
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
435
                 delete_shelf=True, read_shelf=True, show_diff=False,
436
                 write_diff_to=None):
0.16.98 by Aaron Bentley
Update docs and prompting
437
        """Constructor.
438
439
        :param tree: The working tree to unshelve into.
440
        :param manager: The ShelveManager containing the shelved changes.
441
        :param shelf_id:
442
        :param apply_changes: If True, apply the shelved changes to the
443
            working tree.
444
        :param delete_shelf: If True, delete the changes from the shelf.
445
        :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
446
        :param show_diff: If True, show the diff that would result from
447
            unshelving the changes.
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
448
        :param write_diff_to: A file-like object where the diff will be
449
            written to. If None, ui.ui_factory.make_output_stream() will
450
            be used.
0.16.98 by Aaron Bentley
Update docs and prompting
451
        """
0.16.8 by Aaron Bentley
Implement unshelve2, tidy shelve2
452
        self.tree = tree
0.16.98 by Aaron Bentley
Update docs and prompting
453
        manager = tree.get_shelf_manager()
0.16.13 by Aaron Bentley
Appy shelve-management updates to shelver
454
        self.manager = manager
455
        self.shelf_id = shelf_id
0.16.64 by Aaron Bentley
Implement dry-run option for Unshelve
456
        self.apply_changes = apply_changes
457
        self.delete_shelf = delete_shelf
0.16.65 by Aaron Bentley
Implement unshelve --delete
458
        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
459
        self.show_diff = show_diff
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
460
        self.write_diff_to = write_diff_to
0.16.8 by Aaron Bentley
Implement unshelve2, tidy shelve2
461
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
462
    def run(self):
0.16.98 by Aaron Bentley
Update docs and prompting
463
        """Perform the unshelving operation."""
4595.13.2 by Alexander Belchenko
[cherrypick revno 4650 from bzr.dev] Fix shelve on windows. (Robert Collins, #305006)
464
        self.tree.lock_tree_write()
0.16.37 by Aaron Bentley
Use cleanups list to reduce nested try blocks
465
        cleanups = [self.tree.unlock]
0.16.8 by Aaron Bentley
Implement unshelve2, tidy shelve2
466
        try:
0.16.65 by Aaron Bentley
Implement unshelve --delete
467
            if self.read_shelf:
4899.2.1 by Neil Martinsen-Burrell
add beter feedback from the unshelve command
468
                trace.note('Using changes with id "%d".' % self.shelf_id)
0.16.65 by Aaron Bentley
Implement unshelve --delete
469
                unshelver = self.manager.get_unshelver(self.shelf_id)
470
                cleanups.append(unshelver.finalize)
471
                if unshelver.message is not None:
472
                    trace.note('Message: %s' % unshelver.message)
473
                change_reporter = delta._ChangeReporter()
4961.2.13 by Martin Pool
Further progress bar string-pulling
474
                merger = unshelver.make_merger(None)
475
                merger.change_reporter = change_reporter
476
                if self.apply_changes:
477
                    merger.do_merge()
478
                elif self.show_diff:
479
                    self.write_diff(merger)
480
                else:
481
                    self.show_changes(merger)
0.16.64 by Aaron Bentley
Implement dry-run option for Unshelve
482
            if self.delete_shelf:
483
                self.manager.delete_shelf(self.shelf_id)
4899.2.1 by Neil Martinsen-Burrell
add beter feedback from the unshelve command
484
                trace.note('Deleted changes with id "%d".' % self.shelf_id)
0.16.8 by Aaron Bentley
Implement unshelve2, tidy shelve2
485
        finally:
0.16.37 by Aaron Bentley
Use cleanups list to reduce nested try blocks
486
            for cleanup in reversed(cleanups):
487
                cleanup()
0.16.64 by Aaron Bentley
Implement dry-run option for Unshelve
488
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
489
    def write_diff(self, merger):
490
        """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
491
        tree_merger = merger.make_merger()
492
        tt = tree_merger.make_preview_transform()
493
        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.
494
        if self.write_diff_to is None:
495
            self.write_diff_to = ui.ui_factory.make_output_stream()
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
496
        diff.show_diff_trees(merger.this_tree, new_tree, 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
497
        tt.finalize()
498
0.16.64 by Aaron Bentley
Implement dry-run option for Unshelve
499
    def show_changes(self, merger):
0.16.98 by Aaron Bentley
Update docs and prompting
500
        """Show the changes that this operation specifies."""
0.16.64 by Aaron Bentley
Implement dry-run option for Unshelve
501
        tree_merger = merger.make_merger()
4902.1.3 by Guilherme Salgado
A few tweaks as per John's review
502
        # This implicitly shows the changes via the reporter, so we're done...
0.16.64 by Aaron Bentley
Implement dry-run option for Unshelve
503
        tt = tree_merger.make_preview_transform()
504
        tt.finalize()