~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shelf.py

  • Committer: Aaron Bentley
  • Date: 2005-09-13 22:49:40 UTC
  • mto: (147.1.21)
  • mto: This revision was merged to the branch mainline in revision 154.
  • Revision ID: abentley@panoramicfeedback.com-20050913224940-c6db07f39dd303be
Updated docs, obsoleted old executibles

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
#!/usr/bin/python
2
2
 
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
 
3
from patches import parse_patches
20
4
import os
21
5
import sys
22
6
import string
 
7
import tty, termios
 
8
import glob
23
9
from bzrlib.commands import Command
24
10
 
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
 
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 ""
34
38
    else:
35
 
        raise Exception("Don't understand args %s" % args)
36
 
 
37
 
    return False
 
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')]
38
74
 
39
75
def unshelve():
40
 
    root = run_bzr('root')[0].strip()
41
 
    shelf = os.path.join(root, '.bzr-shelf')
 
76
    shelf = last_shelf()
42
77
 
43
 
    if not os.path.exists(shelf):
44
 
        raise Exception("No shelf found in '%s'" % shelf)
 
78
    if shelf is None:
 
79
        raise Exception("No shelf found in '%s'" % tree_root())
45
80
 
46
81
    patch = open(shelf, 'r').read()
47
82
 
48
 
    print >>sys.stderr, "Reapplying shelved patches"
49
 
    pipe = os.popen('patch -d %s -s -p0' % root, 'w')
 
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')
50
90
    pipe.write(patch)
51
91
    pipe.flush()
52
92
 
59
99
 
60
100
    return True
61
101
 
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:
 
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:
68
118
        print >>sys.stderr, 'Nothing to shelve'
69
119
        return True
70
120
 
71
 
    root = run_bzr('root')[0].strip()
72
 
    shelf = os.path.join(root, '.bzr-shelf')
 
121
    shelf = next_shelf()
73
122
    print >>sys.stderr, "Saving shelved patches to", shelf
74
123
    shelf = open(shelf, 'a')
75
 
 
76
 
    for patch in to_shelve:
 
124
    if message is not None:
 
125
        assert '\n' not in message
 
126
        shelf.write("# shelf: %s\n" % message)
 
127
    for patch in patches:
77
128
        shelf.write(str(patch))
78
129
 
79
130
    shelf.flush()
81
132
    shelf.close()
82
133
 
83
134
    print >>sys.stderr, "Reverting shelved patches"
84
 
    pipe = os.popen('patch -d %s -sR -p0' % root, 'w')
85
 
    for patch in to_shelve:
 
135
    pipe = os.popen('patch -d %s -sR -p0' % tree_root(), 'w')
 
136
    for patch in patches:
86
137
       pipe.write(str(patch))
87
138
    pipe.flush()
88
139
 
103
154
        raise Exception("Failed running bzr")
104
155
    return lines
105
156
 
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
 
 
142
157
class cmd_shelve(Command):
143
158
    """Temporarily remove some changes from the current tree.
144
159
    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.
145
164
    """
146
 
 
147
 
    def run(self):
148
 
        return shelve()
 
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)
149
169
 
150
170
class cmd_unshelve(Command):
151
171
    """Restore previously-shelved changes to the current tree.
153
173
    """
154
174
    def run(self):
155
175
        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)