3
from patches import parse_patches
4
from datetime import datetime
5
from errors import CommandError, PatchFailed, PatchInvokeError
6
from hunk_selector import ShelveHunkSelector, UnshelveHunkSelector
7
from patchsource import PatchSource, FilePatchSource
8
from bzrlib.osutils import rename
11
MESSAGE_PREFIX = "# Shelved patch: "
15
'shelves' : '.shelf/shelves',
16
'current-shelf' : '.shelf/current-shelf',
19
def __init__(self, base, name=None):
24
current = os.path.join(self.base, self._paths['current-shelf'])
25
name = open(current).read().strip()
27
assert '\n' not in name
30
self.dir = os.path.join(self.base, self._paths['shelves'], name)
31
if not os.path.isdir(self.dir):
35
# Create required directories etc.
36
for dir in [self._paths['base'], self._paths['shelves']]:
37
dir = os.path.join(self.base, dir)
38
if not os.path.isdir(dir):
41
current = os.path.join(self.base, self._paths['current-shelf'])
42
if not os.path.exists(current):
43
f = open(current, 'w')
47
def make_default(self):
48
f = open(os.path.join(self.base, self._paths['current-shelf']), 'w')
51
self.log("Default shelf is now '%s'\n" % self.name)
56
def delete(self, patch):
57
path = self.__path_from_user(patch)
58
rename(path, '%s~' % path)
60
def display(self, patch=None):
62
path = self.last_patch()
64
path = self.__path_from_user(patch)
65
sys.stdout.write(open(path).read())
68
indexes = self.__list()
69
self.log("Patches on shelf '%s':" % self.name)
75
msg = self.get_patch_message(self.__path(index))
77
msg = "No message saved with patch."
78
self.log(' %.2d: %s\n' % (index, msg))
80
def __path_from_user(self, patch_id):
82
patch_index = int(patch_id)
83
except (TypeError, ValueError):
84
raise CommandError("Invalid patch name '%s'" % patch_id)
86
path = self.__path(patch_index)
88
if not os.path.exists(path):
89
raise CommandError("Patch '%s' doesn't exist on shelf %s!" % \
90
(patch_id, self.name))
94
def __path(self, index):
95
return os.path.join(self.dir, '%.2d' % index)
98
indexes = self.__list()
100
if len(indexes) == 0:
103
next = indexes[-1] + 1
104
return self.__path(next)
107
patches = os.listdir(self.dir)
111
continue # ignore backup files
113
indexes.append(int(f))
115
self.log("Warning: Ignoring junk file '%s' on shelf.\n" % f)
120
def last_patch(self):
121
indexes = self.__list()
123
if len(indexes) == 0:
126
return self.__path(indexes[-1])
128
def get_patch_message(self, patch_path):
129
patch = open(patch_path, 'r').read()
131
if not patch.startswith(self.MESSAGE_PREFIX):
133
return patch[len(self.MESSAGE_PREFIX):patch.index('\n')]
135
def unshelve(self, patch_source, patch_name=None, all=False, force=False,
137
self._check_upgrade()
139
if no_color is False:
143
if patch_name is None:
144
patch_path = self.last_patch()
146
patch_path = self.__path_from_user(patch_name)
148
if patch_path is None:
149
raise CommandError("No patch found on shelf %s" % self.name)
151
patches = FilePatchSource(patch_path).readpatches()
153
to_unshelve = patches
156
hs = UnshelveHunkSelector(patches, color)
157
to_unshelve, to_remain = hs.select()
159
if len(to_unshelve) == 0:
160
raise CommandError('Nothing to unshelve')
162
message = self.get_patch_message(patch_path)
164
message = "No message saved with patch."
165
self.log('Unshelving from %s/%s: "%s"\n' % \
166
(self.name, os.path.basename(patch_path), message))
169
self._run_patch(to_unshelve, dry_run=True)
170
self._run_patch(to_unshelve)
173
self._run_patch(to_unshelve, strip=1, dry_run=True)
174
self._run_patch(to_unshelve, strip=1)
177
self.log('Warning: Unshelving failed, forcing as ' \
178
'requested. Shelf will not be modified.\n')
180
self._run_patch(to_unshelve)
184
raise CommandError("Your shelved patch no " \
185
"longer applies cleanly to the working tree!")
187
# Backup the shelved patch
188
rename(patch_path, '%s~' % patch_path)
190
if len(to_remain) > 0:
191
f = open(patch_path, 'w')
192
for patch in to_remain:
196
def shelve(self, patch_source, all=False, message=None, no_color=False):
197
self._check_upgrade()
198
if no_color is False:
203
patches = patch_source.readpatches()
208
to_shelve = ShelveHunkSelector(patches, color).select()[0]
210
if len(to_shelve) == 0:
211
raise CommandError('Nothing to shelve')
214
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
215
message = "Changes shelved on %s" % timestamp
217
patch_path = self.next_patch()
218
self.log('Shelving to %s/%s: "%s"\n' % \
219
(self.name, os.path.basename(patch_path), message))
221
f = open(patch_path, 'a')
223
assert '\n' not in message
224
f.write("%s%s\n" % (self.MESSAGE_PREFIX, message))
226
for patch in to_shelve:
234
self._run_patch(to_shelve, reverse=True, dry_run=True)
235
self._run_patch(to_shelve, reverse=True)
238
self._run_patch(to_shelve, reverse=True, strip=1, dry_run=True)
239
self._run_patch(to_shelve, reverse=True, strip=1)
241
raise CommandError("Failed removing shelved changes from the"
244
def _run_patch(self, patches, strip=0, reverse=False, dry_run=False):
245
args = ['patch', '-d', self.base, '-s', '-p%d' % strip, '-f']
247
if sys.platform == "win32":
248
args.append('--binary')
253
args.append('--dry-run')
254
stdout = stderr = subprocess.PIPE
256
stdout = stderr = None
259
process = subprocess.Popen(args, stdin=subprocess.PIPE,
260
stdout=stdout, stderr=stderr)
261
for patch in patches:
262
process.stdin.write(str(patch))
265
raise PatchInvokeError(e, process.stderr.read())
267
process.communicate()
269
result = process.wait()
275
def _check_upgrade(self):
276
if len(self._list_old_shelves()) > 0:
277
raise CommandError("Old format shelves found, either upgrade " \
280
def _list_old_shelves(self):
282
stem = os.path.join(self.base, '.bzr-shelf')
284
patches = glob.glob(stem)
285
patches.extend(glob.glob(stem + '-*[!~]'))
287
if len(patches) == 0:
10
name = os.path.basename(args.pop(0))
12
if name not in ['shelve', 'unshelve']:
13
raise Exception("Unknown command name '%s'" % name)
16
if args[0] == '--bzr-usage':
19
elif args[0] == '--bzr-help':
20
print 'Shelve a patch, you can get it back later with unshelve.'
23
raise Exception("Don't understand args %s" % args)
31
root = run_bzr('root')[0].strip()
32
shelf = os.path.join(root, '.bzr-shelf')
34
if not os.path.exists(shelf):
35
raise Exception("No shelf found in '%s'" % shelf)
37
patch = open(shelf, 'r').read()
39
print >>sys.stderr, "Reapplying shelved patches"
40
pipe = os.popen('patch -d %s -s -p0' % root, 'w')
44
if pipe.close() is not None:
45
raise Exception("Failed running patch!")
48
print 'Diff status is now:'
49
os.system('bzr diff | diffstat')
53
class QuitException(Exception):
57
patches = parse_patches(run_bzr('diff'))
59
patches = HunkSelector(patches).select()
64
print >>sys.stderr, 'Nothing to shelve'
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')
73
shelf.write(str(patch))
76
os.fsync(shelf.fileno())
79
print >>sys.stderr, "Reverting shelved patches"
80
pipe = os.popen('patch -d %s -sR -p0' % root, 'w')
82
pipe.write(str(patch))
85
if pipe.close() is not None:
86
raise Exception("Failed running patch!")
88
print 'Diff status is now:'
89
os.system('bzr diff | diffstat')
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")
105
def __init__(self, char, action, help, default=False):
108
self.default = default
112
Option('n', 'shelve', 'shelve this change for the moment.',
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')
122
Option('y', 'continue', 'proceed to shelve selected changes.',
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')
130
def __init__(self, patches):
131
self.patches = patches
133
for patch in patches:
134
for hunk in patch.hunks:
135
# everything's shelved by default
137
self.total_hunks += 1
139
def __get_option(self, char):
140
for opt in self.standard_options:
143
raise Exception('Option "%s" not found!' % char)
145
def __select_loop(self):
147
for patch in self.patches:
150
while i < len(patch.hunks):
151
hunk = patch.hunks[i]
153
print patch.get_header(), hunk
157
prompt = 'Keep this change? (%d of %d)' \
158
% (j, self.total_hunks)
161
self.__get_option('n').default = True
162
self.__get_option('y').default = False
164
self.__get_option('n').default = False
165
self.__get_option('y').default = True
167
action = self.__ask_user(prompt, self.standard_options)
170
hunk.selected = False
171
elif action == 'shelve':
173
elif action == 'done':
175
elif action == 'invert':
176
self.__invert_selection()
179
elif action == 'status':
182
elif action == 'quit':
189
if self.total_hunks == 0:
290
def patch_index(name):
293
return int(name[len(stem) + 1:])
295
# patches might not be sorted in the right order
297
for patch in patches:
301
patch_ids.append(int(patch[len(stem) + 1:]))
310
patches.append('%s-%s' % (stem, id))
315
patches = self._list_old_shelves()
317
if len(patches) == 0:
318
self.log('No old-style shelves found to upgrade.\n')
321
for patch in patches:
322
old_file = open(patch, 'r')
323
new_path = self.next_patch()
324
new_file = open(new_path, 'w')
325
new_file.write(old_file.read())
328
self.log('Copied %s to %s/%s\n' % (os.path.basename(patch),
329
self.name, os.path.basename(new_path)))
330
rename(patch, patch + '~')
194
if not self.__select_loop():
199
prompt = "Shelve these changes, or restart?"
200
action = self.__ask_user(prompt, self.end_options)
202
if action == 'continue':
205
elif action == 'quit':
207
elif action == 'status':
209
elif action == 'invert':
210
self.__invert_selection()
211
elif action == 'restart':
215
for patch in self.patches:
217
for hunk in patch.hunks:
223
for patch in self.patches:
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
238
def __show_status(self):
240
for patch in self.patches:
241
print ' %s' % patch.oldname
244
for hunk in patch.hunks:
250
print ' %d hunks to be shelved' % shelve
251
print ' %d hunks to be kept' % keep
255
fd = sys.stdin.fileno()
256
settings = termios.tcgetattr(fd)
259
ch = sys.stdin.read(1)
261
termios.tcsetattr(fd, termios.TCSADRAIN, settings)
264
def __ask_user(self, prompt, options):
266
sys.stdout.write(prompt)
267
sys.stdout.write(' [')
271
sys.stdout.write(opt.char)
272
sys.stdout.write('?] (%s): ' % default.char)
274
response = self.__getchar()
276
# default, which we see as newline, is 'n'
277
if response in ['\n', '\r', '\r\n']:
278
response = default.char
280
print response # because echo is off
283
if opt.char == response:
287
print ' %s - %s' % (opt.char, opt.help)