~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shelf.py

  • Committer: Aaron Bentley
  • Date: 2005-10-03 17:44:39 UTC
  • mto: (147.2.17)
  • mto: This revision was merged to the branch mainline in revision 324.
  • Revision ID: abentley@panoramicfeedback.com-20051003174439-e83c9f189855a62a
Quieten commits

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)