~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to shelf.py

  • Committer: Aaron Bentley
  • Date: 2005-12-03 20:42:25 UTC
  • mfrom: (147.4.25 trunk)
  • mto: (147.4.27 trunk)
  • mto: This revision was merged to the branch mainline in revision 324.
  • Revision ID: aaron.bentley@utoronto.ca-20051203204225-25678bc921de4fc1
MergeĀ fromĀ lifeless

Show diffs side-by-side

added added

removed removed

Lines of Context:
4
4
import os
5
5
import sys
6
6
import string
7
 
import tty, termios
8
 
 
9
 
def main(args):
10
 
    name = os.path.basename(args.pop(0))
11
 
 
12
 
    if name not in ['shelve', 'unshelve']:
13
 
        raise Exception("Unknown command name '%s'" % name)
14
 
 
15
 
    if len(args) > 0:
16
 
        if args[0] == '--bzr-usage':
17
 
            print '\n'
18
 
            return 0
19
 
        elif args[0] == '--bzr-help':
20
 
            print 'Shelve a patch, you can get it back later with unshelve.'
21
 
            return 0
22
 
        else:
23
 
            raise Exception("Don't understand args %s" % args)
24
 
 
25
 
    if eval(name + "()"):
26
 
        return 0
27
 
 
28
 
    return 1
29
 
 
30
 
def unshelve():
31
 
    root = run_bzr('root')[0].strip()
32
 
    shelf = os.path.join(root, '.bzr-shelf')
33
 
 
34
 
    if not os.path.exists(shelf):
35
 
        raise Exception("No shelf found in '%s'" % shelf)
36
 
 
37
 
    patch = open(shelf, 'r').read()
38
 
 
39
 
    print >>sys.stderr, "Reapplying shelved patches"
40
 
    pipe = os.popen('patch -d %s -s -p0' % root, 'w')
41
 
    pipe.write(patch)
42
 
    pipe.flush()
43
 
 
44
 
    if pipe.close() is not None:
45
 
        raise Exception("Failed running patch!")
46
 
 
47
 
    os.remove(shelf)
48
 
    print 'Diff status is now:'
49
 
    os.system('bzr diff | diffstat')
50
 
 
51
 
    return True
 
7
import glob
 
8
import bzrlib
 
9
from bzrlib.commands import Command
 
10
from bzrlib.branch import Branch
 
11
from bzrlib import DEFAULT_IGNORE
 
12
from hunk_selector import HunkSelector
 
13
from diffstat import DiffStat
 
14
from subprocess import Popen, PIPE
 
15
 
 
16
DEFAULT_IGNORE.append('./.bzr-shelf*')
52
17
 
53
18
class QuitException(Exception):
54
19
    pass
55
20
 
56
 
def shelve():
57
 
    patches = parse_patches(run_bzr('diff'))
58
 
    try:
59
 
        patches = HunkSelector(patches).select()
60
 
    except QuitException:
61
 
        return False
62
 
 
63
 
    if len(patches) == 0:
64
 
        print >>sys.stderr, 'Nothing to shelve'
65
 
        return True
66
 
 
67
 
    root = run_bzr('root')[0].strip()
68
 
    shelf = os.path.join(root, '.bzr-shelf')
69
 
    print >>sys.stderr, "Saving shelved patches to", shelf
70
 
    shelf = open(shelf, 'a')
71
 
 
72
 
    for patch in patches:
73
 
        shelf.write(str(patch))
74
 
 
75
 
    shelf.flush()
76
 
    os.fsync(shelf.fileno())
77
 
    shelf.close()
78
 
 
79
 
    print >>sys.stderr, "Reverting shelved patches"
80
 
    pipe = os.popen('patch -d %s -sR -p0' % root, 'w')
81
 
    for patch in patches:
82
 
       pipe.write(str(patch))
83
 
    pipe.flush()
84
 
 
85
 
    if pipe.close() is not None:
86
 
        raise Exception("Failed running patch!")
87
 
 
88
 
    print 'Diff status is now:'
89
 
    os.system('bzr diff | diffstat')
90
 
 
91
 
    return True
92
 
 
93
 
def run_bzr(args):
94
 
    if type(args) is str:
95
 
        args = [ args ]
96
 
    pipe = os.popen('bzr %s' % string.join(args, ' '), 'r')
97
 
    lines = pipe.readlines()
98
 
    if pipe.close() is not None:
99
 
        raise Exception("Failed running bzr")
100
 
    return lines
101
 
 
102
 
 
103
 
class HunkSelector:
104
 
    class Option:
105
 
        def __init__(self, char, action, help, default=False):
106
 
            self.char = char
107
 
            self.action = action
108
 
            self.default = default
109
 
            self.help = help
110
 
 
111
 
    standard_options = [
112
 
        Option('n', 'shelve', 'shelve this change for the moment.',
113
 
            default=True),
114
 
        Option('y', 'keep', 'keep this change in your tree.'),
115
 
        Option('d', 'done', 'done, skip to the end.'),
116
 
        Option('i', 'invert', 'invert the current selection of all hunks.'),
117
 
        Option('s', 'status', 'show status of hunks.'),
118
 
        Option('q', 'quit', 'quit')
119
 
    ]
120
 
 
121
 
    end_options = [
122
 
        Option('y', 'continue', 'proceed to shelve selected changes.',
123
 
            default=True),
124
 
        Option('r', 'restart', 'restart the hunk selection loop.'),
125
 
        Option('s', 'status', 'show status of hunks.'),
126
 
        Option('i', 'invert', 'invert the current selection of all hunks.'),
127
 
        Option('q', 'quit', 'quit')
128
 
    ]
129
 
 
130
 
    def __init__(self, patches):
131
 
        self.patches = patches
132
 
        self.total_hunks = 0
133
 
        for patch in patches:
134
 
            for hunk in patch.hunks:
135
 
                # everything's shelved by default
136
 
                hunk.selected = True
137
 
                self.total_hunks += 1
138
 
 
139
 
    def __get_option(self, char):
140
 
        for opt in self.standard_options:
141
 
            if opt.char == char:
142
 
                return opt
143
 
        raise Exception('Option "%s" not found!' % char)
144
 
 
145
 
    def __select_loop(self):
146
 
        j = 0
147
 
        for patch in self.patches:
 
21
class Shelf(object):
 
22
    def __init__(self, location):
 
23
        self.branch = Branch.open_containing(location)[0]
 
24
 
 
25
    def shelf_suffix(self, index):
 
26
        if index == 0:
 
27
            return ""
 
28
        else:
 
29
            return "-%d" % index
 
30
 
 
31
    def next_shelf(self):
 
32
        def name_sequence():
148
33
            i = 0
149
 
            lasti = -1
150
 
            while i < len(patch.hunks):
151
 
                hunk = patch.hunks[i]
152
 
                if lasti != i:
153
 
                    print patch.get_header(), hunk
154
 
                    j += 1
155
 
                lasti = i
156
 
 
157
 
                prompt = 'Keep this change? (%d of %d)' \
158
 
                            % (j, self.total_hunks)
159
 
 
160
 
                if hunk.selected:
161
 
                    self.__get_option('n').default = True
162
 
                    self.__get_option('y').default = False
163
 
                else:
164
 
                    self.__get_option('n').default = False
165
 
                    self.__get_option('y').default = True
166
 
 
167
 
                action = self.__ask_user(prompt, self.standard_options)
168
 
 
169
 
                if action == 'keep':
170
 
                    hunk.selected = False
171
 
                elif action == 'shelve':
172
 
                    hunk.selected = True
173
 
                elif action == 'done':
174
 
                    return True
175
 
                elif action == 'invert':
176
 
                    self.__invert_selection()
177
 
                    self.__show_status()
178
 
                    continue
179
 
                elif action == 'status':
180
 
                    self.__show_status()
181
 
                    continue
182
 
                elif action == 'quit':
183
 
                    return False
184
 
 
185
 
                i += 1
186
 
        return True
187
 
 
188
 
    def select(self):
189
 
        if self.total_hunks == 0:
190
 
            return []
191
 
 
192
 
        done = False
193
 
        while not done:
194
 
            if not self.__select_loop():
195
 
                return []
196
 
 
197
34
            while True:
198
 
                self.__show_status()
199
 
                prompt = "Shelve these changes, or restart?"
200
 
                action = self.__ask_user(prompt, self.end_options)
201
 
 
202
 
                if action == 'continue':
203
 
                    done = True
204
 
                    break
205
 
                elif action == 'quit':
206
 
                    return []
207
 
                elif action == 'status':
208
 
                    self.__show_status()
209
 
                elif action == 'invert':
210
 
                    self.__invert_selection()
211
 
                elif action == 'restart':
212
 
                    break
213
 
 
214
 
 
215
 
        for patch in self.patches:
216
 
            tmp = []
217
 
            for hunk in patch.hunks:
218
 
                if hunk.selected:
219
 
                    tmp.append(hunk)
220
 
            patch.hunks = tmp
221
 
 
222
 
        tmp = []
223
 
        for patch in self.patches:
224
 
            if len(patch.hunks):
225
 
                tmp.append(patch)
226
 
        self.patches = tmp
227
 
 
228
 
        return self.patches
229
 
 
230
 
    def __invert_selection(self):
231
 
        for patch in self.patches:
232
 
            for hunk in patch.hunks:
233
 
                if hunk.__dict__.has_key('selected'):
234
 
                    hunk.selected = not hunk.selected
235
 
                else:
236
 
                    hunk.selected = True
237
 
 
238
 
    def __show_status(self):
239
 
        print '\nStatus:'
240
 
        for patch in self.patches:
241
 
            print '  %s' % patch.oldname
242
 
            shelve = 0
243
 
            keep = 0
244
 
            for hunk in patch.hunks:
245
 
                if hunk.selected:
246
 
                    shelve += 1
247
 
                else:
248
 
                    keep += 1
249
 
 
250
 
            print '    %d hunks to be shelved' % shelve
251
 
            print '    %d hunks to be kept' % keep
252
 
            print
253
 
 
254
 
    def __getchar(self):
255
 
        fd = sys.stdin.fileno()
256
 
        settings = termios.tcgetattr(fd)
257
 
        try:
258
 
            tty.setraw(fd)
259
 
            ch = sys.stdin.read(1)
260
 
        finally:
261
 
            termios.tcsetattr(fd, termios.TCSADRAIN, settings)
262
 
        return ch
263
 
 
264
 
    def __ask_user(self, prompt, options):
265
 
        while True:
266
 
            sys.stdout.write(prompt)
267
 
            sys.stdout.write(' [')
268
 
            for opt in options:
269
 
                if opt.default:
270
 
                    default = opt
271
 
                sys.stdout.write(opt.char)
272
 
            sys.stdout.write('?] (%s): ' % default.char)
273
 
 
274
 
            response = self.__getchar()
275
 
 
276
 
            # default, which we see as newline, is 'n'
277
 
            if response in ['\n', '\r', '\r\n']:
278
 
                response = default.char
279
 
 
280
 
            print response # because echo is off
281
 
 
282
 
            for opt in options:
283
 
                if opt.char == response:
284
 
                    return opt.action
285
 
 
286
 
            for opt in options:
287
 
                print '  %s - %s' % (opt.char, opt.help)
 
35
                yield self.shelf_suffix(i)
 
36
                i = i + 1
 
37
 
 
38
        stem = os.path.join(self.branch.base, '.bzr-shelf')
 
39
        for end in name_sequence():
 
40
            name = stem + end
 
41
            if not os.path.exists(name):
 
42
                return name
 
43
 
 
44
    def last_shelf(self):
 
45
        stem = os.path.join(self.branch.base, '.bzr-shelf')
 
46
        shelves = glob.glob(stem)
 
47
        shelves.extend(glob.glob(stem + '-*'))
 
48
        def shelf_index(name):
 
49
            if name == stem:
 
50
                return 0
 
51
            return int(name[len(stem)+1:])
 
52
        shelvenums = [shelf_index(f) for f in shelves]
 
53
        shelvenums.sort()
 
54
 
 
55
        if len(shelvenums) == 0:
 
56
            return None
 
57
        return stem + self.shelf_suffix(shelvenums[-1])
 
58
 
 
59
    def get_shelf_message(self, shelf):
 
60
        prefix = "# shelf: "
 
61
        if not shelf.startswith(prefix):
 
62
            return None
 
63
        return shelf[len(prefix):shelf.index('\n')]
 
64
 
 
65
    def unshelve(self):
 
66
        shelf = self.last_shelf()
 
67
 
 
68
        if shelf is None:
 
69
            raise Exception("No shelf found in '%s'" % self.branch.base)
 
70
 
 
71
        patch = open(shelf, 'r').read()
 
72
 
 
73
        print >>sys.stderr, "Reapplying shelved patches",
 
74
        message = self.get_shelf_message(patch)
 
75
        if message is not None:
 
76
            print >>sys.stderr, ' "%s"' % message
 
77
        else:
 
78
            print >>sys.stderr, ""
 
79
        run_patch(self.branch.base, (patch,))
 
80
        os.remove(shelf)
 
81
 
 
82
        diff_stat = DiffStat(self.get_patches(None, None))
 
83
        print 'Diff status is now:\n', diff_stat
 
84
 
 
85
        return 1
 
86
 
 
87
    def get_patches(self, revision, file_list):
 
88
        from StringIO import StringIO
 
89
        from bzrlib.diff import show_diff
 
90
        out = StringIO()
 
91
        show_diff(self.branch, revision, specific_files=file_list, output=out)
 
92
        out.seek(0)
 
93
        return out.readlines()
 
94
 
 
95
    def shelve(self, all_hunks=False, message=None, revision=None,
 
96
             file_list=None):
 
97
        patches = parse_patches(self.get_patches(revision, file_list))
 
98
 
 
99
        if not all_hunks:
 
100
            try:
 
101
                patches = HunkSelector(patches).select()
 
102
            except QuitException:
 
103
                return False
 
104
 
 
105
        if len(patches) == 0:
 
106
            print >>sys.stderr, 'Nothing to shelve'
 
107
            return 0
 
108
 
 
109
        shelf = self.next_shelf()
 
110
        print >>sys.stderr, "Saving shelved patches to", shelf
 
111
        shelf = open(shelf, 'a')
 
112
        if message is not None:
 
113
            assert '\n' not in message
 
114
            shelf.write("# shelf: %s\n" % message)
 
115
        for patch in patches:
 
116
            shelf.write(str(patch))
 
117
 
 
118
        shelf.flush()
 
119
        os.fsync(shelf.fileno())
 
120
        shelf.close()
 
121
 
 
122
        print >>sys.stderr, "Reverting shelved patches"
 
123
        run_patch(self.branch.base, patches, reverse=True)
 
124
 
 
125
        diff_stat = DiffStat(self.get_patches(None, None))
 
126
        print 'Diff status is now:\n', diff_stat
 
127
 
 
128
        return 1
 
129
 
 
130
def run_patch(branch_base, patches, reverse=False):
 
131
    args = ['patch', '-d', branch_base, '-s', '-p0', '-f']
 
132
    if reverse:
 
133
        args.append('-R')
 
134
    process = Popen(args, stdin=PIPE)
 
135
    for patch in patches:
 
136
        process.stdin.write(str(patch))
 
137
    process.stdin.close()
 
138
    result = process.wait()
 
139
    if result not in (0, 1):
 
140
        raise Exception("Error applying patches")
 
141
    return result