~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shelf.py

  • Committer: Aaron Bentley
  • Date: 2005-06-22 19:08:29 UTC
  • Revision ID: abentley@panoramicfeedback.com-20050622190829-8813e668768cafc5
Used the bzr 0.5+ plugin stuff

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/python
2
2
 
3
 
from patches import parse_patches
 
3
# Copyright (C) 2005 Michael Ellerman <michael@ellerman.id.au>
 
4
#
 
5
#    This program is free software; you can redistribute it and/or modify
 
6
#    it under the terms of the GNU General Public License as published by
 
7
#    the Free Software Foundation; either version 2 of the License, or
 
8
#    (at your option) any later version.
 
9
#
 
10
#    This program is distributed in the hope that it will be useful,
 
11
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
12
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
13
#    GNU General Public License for more details.
 
14
#
 
15
#    You should have received a copy of the GNU General Public License
 
16
#    along with this program; if not, write to the Free Software
 
17
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
18
 
 
19
import patches
4
20
import os
5
21
import sys
6
22
import string
7
 
import tty, termios
8
 
import glob
9
23
from bzrlib.commands import Command
10
24
 
11
 
def main(args):
12
 
    name = os.path.basename(args.pop(0))
13
 
 
14
 
    if name not in ['shelve', 'unshelve']:
15
 
        raise Exception("Unknown command name '%s'" % name)
16
 
 
17
 
    if len(args) > 0:
18
 
        if args[0] == '--bzr-usage':
19
 
            print '\n'
20
 
            return 0
21
 
        elif args[0] == '--bzr-help':
22
 
            print 'Shelve a patch, you can get it back later with unshelve.'
23
 
            return 0
24
 
        else:
25
 
            raise Exception("Don't understand args %s" % args)
26
 
 
27
 
    if eval(name + "()"):
28
 
        return 0
29
 
 
30
 
    return 1
31
 
 
32
 
def tree_root():
33
 
    return run_bzr('root')[0].strip()
34
 
 
35
 
def shelf_suffix(index):
36
 
    if index == 0:
37
 
        return ""
 
25
def parse_args(args):
 
26
    if len(args) == 2 and args[1] == '--bzr-usage':
 
27
        print '\n'
 
28
        return True
 
29
    elif len(args) == 2 and args[1] == '--bzr-help':
 
30
        print 'Shelve a patch, you can get it back later with unshelve.'
 
31
        return True
 
32
    elif len(args) == 1:
 
33
        pass
38
34
    else:
39
 
        return "-%d" % index
40
 
 
41
 
def next_shelf():
42
 
    def name_sequence():
43
 
        i = 0
44
 
        while True:
45
 
            yield shelf_suffix(i)
46
 
            i = i + 1
47
 
 
48
 
    stem = os.path.join(tree_root(), '.bzr-shelf')
49
 
    for end in name_sequence():
50
 
        name = stem + end
51
 
        if not os.path.exists(name):
52
 
            return name
53
 
 
54
 
def last_shelf():
55
 
    stem = os.path.join(tree_root(), '.bzr-shelf')
56
 
    shelves = glob.glob(stem)
57
 
    shelves.extend(glob.glob(stem + '-*'))
58
 
    def shelf_index(name):
59
 
        if name == stem:
60
 
            return 0
61
 
        return int(name[len(stem)+1:])
62
 
    shelvenums = [shelf_index(f) for f in shelves]
63
 
    shelvenums.sort()
64
 
 
65
 
    if len(shelvenums) == 0:
66
 
        return None
67
 
    return stem + shelf_suffix(shelvenums[-1])
68
 
 
69
 
def get_shelf_message(shelf):
70
 
    prefix = "# shelf: "
71
 
    if not shelf.startswith(prefix):
72
 
        return None
73
 
    return shelf[len(prefix):shelf.index('\n')]
 
35
        raise Exception("Don't understand args %s" % args)
 
36
 
 
37
    return False
74
38
 
75
39
def unshelve():
76
 
    shelf = last_shelf()
 
40
    root = run_bzr('root')[0].strip()
 
41
    shelf = os.path.join(root, '.bzr-shelf')
77
42
 
78
 
    if shelf is None:
79
 
        raise Exception("No shelf found in '%s'" % tree_root())
 
43
    if not os.path.exists(shelf):
 
44
        raise Exception("No shelf found in '%s'" % shelf)
80
45
 
81
46
    patch = open(shelf, 'r').read()
82
47
 
83
 
    print >>sys.stderr, "Reapplying shelved patches",
84
 
    message = get_shelf_message(patch)
85
 
    if message is not None:
86
 
        print >>sys.stderr, ' "%s"' % message
87
 
    else:
88
 
        print >>sys.stderr, ""
89
 
    pipe = os.popen('patch -d %s -s -p0' % tree_root(), 'w')
 
48
    print >>sys.stderr, "Reapplying shelved patches"
 
49
    pipe = os.popen('patch -d %s -s -p0' % root, 'w')
90
50
    pipe.write(patch)
91
51
    pipe.flush()
92
52
 
99
59
 
100
60
    return True
101
61
 
102
 
class QuitException(Exception):
103
 
    pass
104
 
 
105
 
def shelve(message = None, revision = None, file_list = None):
106
 
    cmd = ['diff']
107
 
    if revision is not None:
108
 
        cmd.extend(['--revision', str(revision[0])])
109
 
    if file_list is not None:
110
 
        cmd.extend(file_list)
111
 
    patches = parse_patches(run_bzr(cmd))
112
 
    try:
113
 
        patches = HunkSelector(patches).select()
114
 
    except QuitException:
115
 
        return False
116
 
 
117
 
    if len(patches) == 0:
 
62
def shelve():
 
63
    diff_lines = run_bzr('diff')
 
64
    patch_list = patches.parse_patches(diff_lines.__iter__())
 
65
    to_shelve = prompt_user(patch_list)
 
66
 
 
67
    if len(to_shelve) == 0:
118
68
        print >>sys.stderr, 'Nothing to shelve'
119
69
        return True
120
70
 
121
 
    shelf = next_shelf()
 
71
    root = run_bzr('root')[0].strip()
 
72
    shelf = os.path.join(root, '.bzr-shelf')
122
73
    print >>sys.stderr, "Saving shelved patches to", shelf
123
74
    shelf = open(shelf, 'a')
124
 
    if message is not None:
125
 
        assert '\n' not in message
126
 
        shelf.write("# shelf: %s\n" % message)
127
 
    for patch in patches:
 
75
 
 
76
    for patch in to_shelve:
128
77
        shelf.write(str(patch))
129
78
 
130
79
    shelf.flush()
132
81
    shelf.close()
133
82
 
134
83
    print >>sys.stderr, "Reverting shelved patches"
135
 
    pipe = os.popen('patch -d %s -sR -p0' % tree_root(), 'w')
136
 
    for patch in patches:
 
84
    pipe = os.popen('patch -d %s -sR -p0' % root, 'w')
 
85
    for patch in to_shelve:
137
86
       pipe.write(str(patch))
138
87
    pipe.flush()
139
88
 
154
103
        raise Exception("Failed running bzr")
155
104
    return lines
156
105
 
 
106
def prompt_user(patch_list):
 
107
    total = 0
 
108
    for patch in patch_list:
 
109
        total += len(patch.hunks)
 
110
 
 
111
    out = sys.stdout
 
112
    inp = sys.stdin
 
113
 
 
114
    to_shelve = []
 
115
    i = 1
 
116
    for patch in patch_list:
 
117
        if patch.oldname != patch.newname:
 
118
            name = "%s -> %s" % (patch.oldname, patch.newname)
 
119
        else:
 
120
            name = patch.oldname
 
121
 
 
122
        hunks = patch.hunks[:]
 
123
        for hunk in hunks:
 
124
            print name
 
125
            print hunk
 
126
            while True:
 
127
                out.write('Shelve this change? (%d of %d) [yn] ' % (i, total))
 
128
                line = inp.readline().strip().lower()
 
129
                if line == 'y' or line == 'yes':
 
130
                    break
 
131
                elif line == 'n' or line == 'no':
 
132
                    patch.hunks.remove(hunk)
 
133
                    break
 
134
 
 
135
            i += 1
 
136
 
 
137
        if len(patch.hunks) > 0:
 
138
            to_shelve.append(patch)
 
139
 
 
140
    return to_shelve
 
141
 
157
142
class cmd_shelve(Command):
158
143
    """Temporarily remove some changes from the current tree.
159
144
    Use 'unshelve' to restore these changes.
160
 
 
161
 
    If filenames are specified, only changes to those files will be unshelved.
162
 
    If a revision is specified, all changes since that revision will may be
163
 
    unshelved.
164
145
    """
165
 
    takes_args = ['file*']
166
 
    takes_options = ['message', 'revision']
167
 
    def run(self, file_list=None, message=None, revision=None):
168
 
        return shelve(message=message, file_list=file_list, revision=revision)
 
146
 
 
147
    def run(self):
 
148
        return shelve()
169
149
 
170
150
class cmd_unshelve(Command):
171
151
    """Restore previously-shelved changes to the current tree.
173
153
    """
174
154
    def run(self):
175
155
        return unshelve()
176
 
 
177
 
class HunkSelector:
178
 
    class Option:
179
 
        def __init__(self, char, action, help, default=False):
180
 
            self.char = char
181
 
            self.action = action
182
 
            self.default = default
183
 
            self.help = help
184
 
 
185
 
    standard_options = [
186
 
        Option('n', 'shelve', 'shelve this change for the moment.',
187
 
            default=True),
188
 
        Option('y', 'keep', 'keep this change in your tree.'),
189
 
        Option('d', 'done', 'done, skip to the end.'),
190
 
        Option('i', 'invert', 'invert the current selection of all hunks.'),
191
 
        Option('s', 'status', 'show status of hunks.'),
192
 
        Option('q', 'quit', 'quit')
193
 
    ]
194
 
 
195
 
    end_options = [
196
 
        Option('y', 'continue', 'proceed to shelve selected changes.',
197
 
            default=True),
198
 
        Option('r', 'restart', 'restart the hunk selection loop.'),
199
 
        Option('s', 'status', 'show status of hunks.'),
200
 
        Option('i', 'invert', 'invert the current selection of all hunks.'),
201
 
        Option('q', 'quit', 'quit')
202
 
    ]
203
 
 
204
 
    def __init__(self, patches):
205
 
        self.patches = patches
206
 
        self.total_hunks = 0
207
 
        for patch in patches:
208
 
            for hunk in patch.hunks:
209
 
                # everything's shelved by default
210
 
                hunk.selected = True
211
 
                self.total_hunks += 1
212
 
 
213
 
    def __get_option(self, char):
214
 
        for opt in self.standard_options:
215
 
            if opt.char == char:
216
 
                return opt
217
 
        raise Exception('Option "%s" not found!' % char)
218
 
 
219
 
    def __select_loop(self):
220
 
        j = 0
221
 
        for patch in self.patches:
222
 
            i = 0
223
 
            lasti = -1
224
 
            while i < len(patch.hunks):
225
 
                hunk = patch.hunks[i]
226
 
                if lasti != i:
227
 
                    print patch.get_header(), hunk
228
 
                    j += 1
229
 
                lasti = i
230
 
 
231
 
                prompt = 'Keep this change? (%d of %d)' \
232
 
                            % (j, self.total_hunks)
233
 
 
234
 
                if hunk.selected:
235
 
                    self.__get_option('n').default = True
236
 
                    self.__get_option('y').default = False
237
 
                else:
238
 
                    self.__get_option('n').default = False
239
 
                    self.__get_option('y').default = True
240
 
 
241
 
                action = self.__ask_user(prompt, self.standard_options)
242
 
 
243
 
                if action == 'keep':
244
 
                    hunk.selected = False
245
 
                elif action == 'shelve':
246
 
                    hunk.selected = True
247
 
                elif action == 'done':
248
 
                    return True
249
 
                elif action == 'invert':
250
 
                    self.__invert_selection()
251
 
                    self.__show_status()
252
 
                    continue
253
 
                elif action == 'status':
254
 
                    self.__show_status()
255
 
                    continue
256
 
                elif action == 'quit':
257
 
                    return False
258
 
 
259
 
                i += 1
260
 
        return True
261
 
 
262
 
    def select(self):
263
 
        if self.total_hunks == 0:
264
 
            return []
265
 
 
266
 
        done = False
267
 
        while not done:
268
 
            if not self.__select_loop():
269
 
                return []
270
 
 
271
 
            while True:
272
 
                self.__show_status()
273
 
                prompt = "Shelve these changes, or restart?"
274
 
                action = self.__ask_user(prompt, self.end_options)
275
 
 
276
 
                if action == 'continue':
277
 
                    done = True
278
 
                    break
279
 
                elif action == 'quit':
280
 
                    return []
281
 
                elif action == 'status':
282
 
                    self.__show_status()
283
 
                elif action == 'invert':
284
 
                    self.__invert_selection()
285
 
                elif action == 'restart':
286
 
                    break
287
 
 
288
 
 
289
 
        for patch in self.patches:
290
 
            tmp = []
291
 
            for hunk in patch.hunks:
292
 
                if hunk.selected:
293
 
                    tmp.append(hunk)
294
 
            patch.hunks = tmp
295
 
 
296
 
        tmp = []
297
 
        for patch in self.patches:
298
 
            if len(patch.hunks):
299
 
                tmp.append(patch)
300
 
        self.patches = tmp
301
 
 
302
 
        return self.patches
303
 
 
304
 
    def __invert_selection(self):
305
 
        for patch in self.patches:
306
 
            for hunk in patch.hunks:
307
 
                if hunk.__dict__.has_key('selected'):
308
 
                    hunk.selected = not hunk.selected
309
 
                else:
310
 
                    hunk.selected = True
311
 
 
312
 
    def __show_status(self):
313
 
        print '\nStatus:'
314
 
        for patch in self.patches:
315
 
            print '  %s' % patch.oldname
316
 
            shelve = 0
317
 
            keep = 0
318
 
            for hunk in patch.hunks:
319
 
                if hunk.selected:
320
 
                    shelve += 1
321
 
                else:
322
 
                    keep += 1
323
 
 
324
 
            print '    %d hunks to be shelved' % shelve
325
 
            print '    %d hunks to be kept' % keep
326
 
            print
327
 
 
328
 
    def __getchar(self):
329
 
        fd = sys.stdin.fileno()
330
 
        settings = termios.tcgetattr(fd)
331
 
        try:
332
 
            tty.setraw(fd)
333
 
            ch = sys.stdin.read(1)
334
 
        finally:
335
 
            termios.tcsetattr(fd, termios.TCSADRAIN, settings)
336
 
        return ch
337
 
 
338
 
    def __ask_user(self, prompt, options):
339
 
        while True:
340
 
            sys.stdout.write(prompt)
341
 
            sys.stdout.write(' [')
342
 
            for opt in options:
343
 
                if opt.default:
344
 
                    default = opt
345
 
                sys.stdout.write(opt.char)
346
 
            sys.stdout.write('?] (%s): ' % default.char)
347
 
 
348
 
            response = self.__getchar()
349
 
 
350
 
            # default, which we see as newline, is 'n'
351
 
            if response in ['\n', '\r', '\r\n']:
352
 
                response = default.char
353
 
 
354
 
            print response # because echo is off
355
 
 
356
 
            for opt in options:
357
 
                if opt.char == response:
358
 
                    return opt.action
359
 
 
360
 
            for opt in options:
361
 
                print '  %s - %s' % (opt.char, opt.help)