~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shelf.py

  • Committer: Jelmer Vernooij
  • Date: 2005-10-17 19:17:07 UTC
  • Revision ID: jelmer@samba.org-20051017191707-69bfef4a840168a6
Leaver generating help for options to Option class

Show diffs side-by-side

added added

removed removed

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