~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shelf.py

  • Committer: Aaron Bentley
  • Date: 2005-10-26 14:34:44 UTC
  • mfrom: (0.1.40)
  • Revision ID: abentley@panoramicfeedback.com-20051026143444-3bf99414170b7e97
Merged shelf_v2

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)[0]
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)