~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shelf.py

  • Committer: Aaron Bentley
  • Date: 2008-11-11 15:55:32 UTC
  • Revision ID: aaron@aaronbentley.com-20081111155532-pjxa3kib17yggl9x
Update shelf tests to new command names

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