~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shelf.py

  • Committer: abentley
  • Date: 2005-10-15 01:00:06 UTC
  • Revision ID: abentley@lappy-20051015010006-ed880d280fb52391
Added --dry-run, --detrius options to clean-tree

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 glob
23
8
from bzrlib.commands import Command
24
 
 
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
 
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 ""
34
41
    else:
35
 
        raise Exception("Don't understand args %s" % args)
36
 
 
37
 
    return False
 
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')]
38
77
 
39
78
def unshelve():
40
 
    root = run_bzr('root')[0].strip()
41
 
    shelf = os.path.join(root, '.bzr-shelf')
 
79
    shelf = last_shelf()
42
80
 
43
 
    if not os.path.exists(shelf):
44
 
        raise Exception("No shelf found in '%s'" % shelf)
 
81
    if shelf is None:
 
82
        raise Exception("No shelf found in '%s'" % tree_root())
45
83
 
46
84
    patch = open(shelf, 'r').read()
47
85
 
48
 
    print >>sys.stderr, "Reapplying shelved patches"
49
 
    pipe = os.popen('patch -d %s -s -p0' % root, 'w')
 
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')
50
93
    pipe.write(patch)
51
94
    pipe.flush()
52
95
 
59
102
 
60
103
    return True
61
104
 
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:
 
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:
68
121
        print >>sys.stderr, 'Nothing to shelve'
69
122
        return True
70
123
 
71
 
    root = run_bzr('root')[0].strip()
72
 
    shelf = os.path.join(root, '.bzr-shelf')
 
124
    shelf = next_shelf()
73
125
    print >>sys.stderr, "Saving shelved patches to", shelf
74
126
    shelf = open(shelf, 'a')
75
 
 
76
 
    for patch in to_shelve:
 
127
    if message is not None:
 
128
        assert '\n' not in message
 
129
        shelf.write("# shelf: %s\n" % message)
 
130
    for patch in patches:
77
131
        shelf.write(str(patch))
78
132
 
79
133
    shelf.flush()
81
135
    shelf.close()
82
136
 
83
137
    print >>sys.stderr, "Reverting shelved patches"
84
 
    pipe = os.popen('patch -d %s -sR -p0' % root, 'w')
85
 
    for patch in to_shelve:
 
138
    pipe = os.popen('patch -d %s -sR -p0' % tree_root(), 'w')
 
139
    for patch in patches:
86
140
       pipe.write(str(patch))
87
141
    pipe.flush()
88
142
 
103
157
        raise Exception("Failed running bzr")
104
158
    return lines
105
159
 
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
160
class cmd_shelve(Command):
143
161
    """Temporarily remove some changes from the current tree.
144
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.
145
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]
146
179
 
147
 
    def run(self):
148
 
        return shelve()
 
180
        return shelve(message=message, file_list=file_list, 
 
181
                      revision=revision_list)
149
182
 
150
183
class cmd_unshelve(Command):
151
184
    """Restore previously-shelved changes to the current tree.
153
186
    """
154
187
    def run(self):
155
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)