Commit | Line | Data |
---|---|---|
41a6d859 CM |
1 | """Basic quilt-like functionality |
2 | """ | |
3 | ||
4 | __copyright__ = """ | |
5 | Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com> | |
6 | ||
7 | This program is free software; you can redistribute it and/or modify | |
8 | it under the terms of the GNU General Public License version 2 as | |
9 | published by the Free Software Foundation. | |
10 | ||
11 | This program is distributed in the hope that it will be useful, | |
12 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | GNU General Public License for more details. | |
15 | ||
16 | You should have received a copy of the GNU General Public License | |
17 | along with this program; if not, write to the Free Software | |
18 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
19 | """ | |
20 | ||
21 | import sys, os | |
22 | from optparse import OptionParser, make_option | |
23 | ||
24 | from utils import * | |
25 | from stgit import stack, git | |
26 | from stgit.version import version | |
27 | from stgit.config import config | |
28 | ||
29 | ||
30 | # Main exception class | |
31 | class MainException(Exception): | |
32 | pass | |
33 | ||
34 | ||
35 | # Utility functions | |
36 | def __git_id(string): | |
37 | """Return the GIT id | |
38 | """ | |
39 | if not string: | |
40 | return None | |
41 | ||
42 | string_list = string.split('/') | |
43 | ||
44 | if len(string_list) == 1: | |
45 | patch_name = None | |
46 | git_id = string_list[0] | |
47 | ||
48 | if git_id == 'HEAD': | |
49 | return git.get_head() | |
50 | if git_id == 'base': | |
51 | return read_string(crt_series.get_base_file()) | |
52 | ||
53 | for path in [os.path.join(git.base_dir, 'refs', 'heads'), | |
54 | os.path.join(git.base_dir, 'refs', 'tags')]: | |
55 | id_file = os.path.join(path, git_id) | |
56 | if os.path.isfile(id_file): | |
57 | return read_string(id_file) | |
58 | elif len(string_list) == 2: | |
59 | patch_name = string_list[0] | |
60 | if patch_name == '': | |
61 | patch_name = crt_series.get_current() | |
62 | git_id = string_list[1] | |
63 | ||
64 | if not patch_name: | |
65 | raise MainException, 'No patches applied' | |
66 | elif not (patch_name in crt_series.get_applied() | |
67 | + crt_series.get_unapplied()): | |
68 | raise MainException, 'Unknown patch "%s"' % patch_name | |
69 | ||
70 | if git_id == 'bottom': | |
71 | return crt_series.get_patch(patch_name).get_bottom() | |
72 | if git_id == 'top': | |
73 | return crt_series.get_patch(patch_name).get_top() | |
74 | ||
75 | raise MainException, 'Unknown id: %s' % string | |
76 | ||
77 | def __check_local_changes(): | |
78 | if git.local_changes(): | |
79 | raise MainException, \ | |
80 | 'local changes in the tree. Use "refresh" to commit them' | |
81 | ||
82 | def __check_head_top_equal(): | |
83 | if not crt_series.head_top_equal(): | |
84 | raise MainException, \ | |
85 | 'HEAD and top are not the same. You probably committed\n' \ | |
86 | ' changes to the tree ouside of StGIT. If you know what you\n' \ | |
87 | ' are doing, use the "refresh -f" command' | |
88 | ||
89 | def __check_conflicts(): | |
90 | if os.path.exists(os.path.join(git.base_dir, 'conflicts')): | |
91 | raise MainException, 'Unsolved conflicts. Please resolve them first' | |
92 | ||
93 | def __print_crt_patch(): | |
94 | patch = crt_series.get_current() | |
95 | if patch: | |
96 | print 'Now at patch "%s"' % patch | |
97 | else: | |
98 | print 'No patches applied' | |
99 | ||
100 | ||
101 | # | |
102 | # Command functions | |
103 | # | |
104 | class Command: | |
105 | """This class is used to store the command details | |
106 | """ | |
107 | def __init__(self, func, help, usage, option_list): | |
108 | self.func = func | |
109 | self.help = help | |
110 | self.usage = usage | |
111 | self.option_list = option_list | |
112 | ||
113 | ||
114 | def init(parser, options, args): | |
115 | """Performs the repository initialisation | |
116 | """ | |
117 | if len(args) != 0: | |
118 | parser.error('incorrect number of arguments') | |
119 | ||
120 | crt_series.init() | |
121 | ||
122 | init_cmd = \ | |
123 | Command(init, | |
124 | 'initialise the tree for use with StGIT', | |
125 | '%prog', | |
126 | []) | |
127 | ||
128 | ||
129 | def add(parser, options, args): | |
130 | """Add files or directories to the repository | |
131 | """ | |
132 | if len(args) < 1: | |
133 | parser.error('incorrect number of arguments') | |
134 | ||
135 | git.add(args) | |
136 | ||
137 | add_cmd = \ | |
138 | Command(add, | |
139 | 'add files or directories to the repository', | |
140 | '%prog <files/dirs...>', | |
141 | []) | |
142 | ||
143 | ||
144 | def rm(parser, options, args): | |
145 | """Remove files from the repository | |
146 | """ | |
147 | if len(args) < 1: | |
148 | parser.error('incorrect number of arguments') | |
149 | ||
150 | git.rm(args, options.force) | |
151 | ||
152 | rm_cmd = \ | |
153 | Command(rm, | |
154 | 'remove files from the repository', | |
155 | '%prog [options] <files...>', | |
156 | [make_option('-f', '--force', | |
157 | help = 'force removing even if the file exists', | |
158 | action = 'store_true')]) | |
159 | ||
160 | ||
161 | def status(parser, options, args): | |
162 | """Show the tree status | |
163 | """ | |
164 | git.status(args, options.modified, options.new, options.deleted, | |
165 | options.conflict, options.unknown) | |
166 | ||
167 | status_cmd = \ | |
168 | Command(status, | |
169 | 'show the tree status', | |
170 | '%prog [options] [<files...>]', | |
171 | [make_option('-m', '--modified', | |
172 | help = 'show modified files only', | |
173 | action = 'store_true'), | |
174 | make_option('-n', '--new', | |
175 | help = 'show new files only', | |
176 | action = 'store_true'), | |
177 | make_option('-d', '--deleted', | |
178 | help = 'show deleted files only', | |
179 | action = 'store_true'), | |
180 | make_option('-c', '--conflict', | |
181 | help = 'show conflict files only', | |
182 | action = 'store_true'), | |
183 | make_option('-u', '--unknown', | |
184 | help = 'show unknown files only', | |
185 | action = 'store_true')]) | |
186 | ||
187 | ||
188 | def diff(parser, options, args): | |
189 | """Show the tree diff | |
190 | """ | |
191 | if options.revs: | |
192 | rev_list = options.revs.split(':') | |
193 | rev_list_len = len(rev_list) | |
194 | if rev_list_len == 1: | |
195 | if rev_list[0][-1] == '/': | |
196 | # the whole patch | |
197 | rev1 = rev_list[0] + 'bottom' | |
198 | rev2 = rev_list[0] + 'top' | |
199 | else: | |
200 | rev1 = rev_list[0] | |
201 | rev2 = None | |
202 | elif rev_list_len == 2: | |
203 | rev1 = rev_list[0] | |
204 | rev2 = rev_list[1] | |
205 | if rev2 == '': | |
206 | rev2 = 'HEAD' | |
207 | else: | |
208 | parser.error('incorrect parameters to -r') | |
209 | else: | |
210 | rev1 = 'HEAD' | |
211 | rev2 = None | |
212 | ||
213 | if options.stat: | |
214 | print git.diffstat(args, __git_id(rev1), __git_id(rev2)) | |
215 | else: | |
216 | git.diff(args, __git_id(rev1), __git_id(rev2)) | |
217 | ||
218 | diff_cmd = \ | |
219 | Command(diff, | |
220 | 'show the tree diff', | |
221 | '%prog [options] [<files...>]\n\n' | |
222 | 'The revision format is "([patch]/[bottom | top]) | <tree-ish>"', | |
223 | [make_option('-r', metavar = 'rev1[:[rev2]]', dest = 'revs', | |
224 | help = 'show the diff between revisions'), | |
225 | make_option('-s', '--stat', | |
226 | help = 'show the stat instead of the diff', | |
227 | action = 'store_true')]) | |
228 | ||
229 | ||
230 | def files(parser, options, args): | |
231 | """Show the files modified by a patch (or the current patch) | |
232 | """ | |
233 | if len(args) == 0: | |
234 | patch = '' | |
235 | elif len(args) == 1: | |
236 | patch = args[0] | |
237 | else: | |
238 | parser.error('incorrect number of arguments') | |
239 | ||
240 | rev1 = __git_id('%s/bottom' % patch) | |
241 | rev2 = __git_id('%s/top' % patch) | |
242 | ||
243 | if options.stat: | |
244 | print git.diffstat(rev1 = rev1, rev2 = rev2) | |
245 | else: | |
246 | print git.files(rev1, rev2) | |
247 | ||
248 | files_cmd = \ | |
249 | Command(files, | |
250 | 'show the files modified by a patch (or the current patch)', | |
251 | '%prog [options] [<patch>]', | |
252 | [make_option('-s', '--stat', | |
253 | help = 'show the diff stat', | |
254 | action = 'store_true')]) | |
255 | ||
256 | ||
257 | def refresh(parser, options, args): | |
258 | if len(args) != 0: | |
259 | parser.error('incorrect number of arguments') | |
260 | ||
261 | if config.has_option('stgit', 'autoresolved'): | |
262 | autoresolved = config.get('stgit', 'autoresolved') | |
263 | else: | |
264 | autoresolved = 'no' | |
265 | ||
266 | if autoresolved != 'yes': | |
267 | __check_conflicts() | |
268 | ||
269 | patch = crt_series.get_current() | |
270 | if not patch: | |
271 | raise MainException, 'No patches applied' | |
272 | ||
273 | if not options.force: | |
274 | __check_head_top_equal() | |
275 | ||
276 | if git.local_changes() \ | |
277 | or not crt_series.head_top_equal() \ | |
278 | or options.edit or options.message \ | |
279 | or options.authname or options.authemail or options.authdate \ | |
280 | or options.commname or options.commemail: | |
281 | print 'Refreshing patch "%s"...' % patch, | |
282 | sys.stdout.flush() | |
283 | ||
284 | if autoresolved == 'yes': | |
285 | __resolved_all() | |
286 | crt_series.refresh_patch(message = options.message, | |
287 | edit = options.edit, | |
288 | author_name = options.authname, | |
289 | author_email = options.authemail, | |
290 | author_date = options.authdate, | |
291 | committer_name = options.commname, | |
292 | committer_email = options.commemail) | |
293 | ||
294 | print 'done' | |
295 | else: | |
296 | print 'Patch "%s" is already up to date' % patch | |
297 | ||
298 | refresh_cmd = \ | |
299 | Command(refresh, | |
300 | 'generate a new commit for the current patch', | |
301 | '%prog [options]', | |
302 | [make_option('-f', '--force', | |
303 | help = 'force the refresh even if HEAD and '\ | |
304 | 'top differ', | |
305 | action = 'store_true'), | |
306 | make_option('-e', '--edit', | |
307 | help = 'invoke an editor for the patch '\ | |
308 | 'description', | |
309 | action = 'store_true'), | |
310 | make_option('-m', '--message', | |
311 | help = 'use MESSAGE as the patch ' \ | |
312 | 'description'), | |
313 | make_option('--authname', | |
314 | help = 'use AUTHNAME as the author name'), | |
315 | make_option('--authemail', | |
316 | help = 'use AUTHEMAIL as the author e-mail'), | |
317 | make_option('--authdate', | |
318 | help = 'use AUTHDATE as the author date'), | |
319 | make_option('--commname', | |
320 | help = 'use COMMNAME as the committer name'), | |
321 | make_option('--commemail', | |
322 | help = 'use COMMEMAIL as the committer ' \ | |
323 | 'e-mail')]) | |
324 | ||
325 | ||
326 | def new(parser, options, args): | |
327 | """Creates a new patch | |
328 | """ | |
329 | if len(args) != 1: | |
330 | parser.error('incorrect number of arguments') | |
331 | ||
332 | __check_local_changes() | |
333 | __check_conflicts() | |
334 | __check_head_top_equal() | |
335 | ||
336 | crt_series.new_patch(args[0], message = options.message, | |
337 | author_name = options.authname, | |
338 | author_email = options.authemail, | |
339 | author_date = options.authdate, | |
340 | committer_name = options.commname, | |
341 | committer_email = options.commemail) | |
342 | ||
343 | new_cmd = \ | |
344 | Command(new, | |
345 | 'create a new patch and make it the topmost one', | |
346 | '%prog [options] <name>', | |
347 | [make_option('-m', '--message', | |
348 | help = 'use MESSAGE as the patch description'), | |
349 | make_option('--authname', | |
350 | help = 'use AUTHNAME as the author name'), | |
351 | make_option('--authemail', | |
352 | help = 'use AUTHEMAIL as the author e-mail'), | |
353 | make_option('--authdate', | |
354 | help = 'use AUTHDATE as the author date'), | |
355 | make_option('--commname', | |
356 | help = 'use COMMNAME as the committer name'), | |
357 | make_option('--commemail', | |
358 | help = 'use COMMEMAIL as the committer e-mail')]) | |
359 | ||
360 | def delete(parser, options, args): | |
361 | """Deletes a patch | |
362 | """ | |
363 | if len(args) != 1: | |
364 | parser.error('incorrect number of arguments') | |
365 | ||
366 | __check_local_changes() | |
367 | __check_conflicts() | |
368 | __check_head_top_equal() | |
369 | ||
370 | crt_series.delete_patch(args[0]) | |
371 | print 'Patch "%s" successfully deleted' % args[0] | |
372 | __print_crt_patch() | |
373 | ||
374 | delete_cmd = \ | |
375 | Command(delete, | |
376 | 'remove the topmost or any unapplied patch', | |
377 | '%prog <name>', | |
378 | []) | |
379 | ||
380 | ||
381 | def push(parser, options, args): | |
382 | """Pushes the given patch or all onto the series | |
383 | """ | |
384 | # If --undo is passed, do the work and exit | |
385 | if options.undo: | |
386 | patch = crt_series.get_current() | |
387 | if not patch: | |
388 | raise MainException, 'No patch to undo' | |
389 | ||
390 | print 'Undoing the "%s" push...' % patch, | |
391 | sys.stdout.flush() | |
392 | __resolved_all() | |
393 | crt_series.undo_push() | |
394 | print 'done' | |
395 | __print_crt_patch() | |
396 | ||
397 | return | |
398 | ||
399 | __check_local_changes() | |
400 | __check_conflicts() | |
401 | __check_head_top_equal() | |
402 | ||
403 | unapplied = crt_series.get_unapplied() | |
404 | if not unapplied: | |
405 | raise MainException, 'No more patches to push' | |
406 | ||
407 | if options.to: | |
408 | boundaries = options.to.split(':') | |
409 | if len(boundaries) == 1: | |
410 | if boundaries[0] not in unapplied: | |
411 | raise MainException, 'Patch "%s" not unapplied' % boundaries[0] | |
412 | patches = unapplied[:unapplied.index(boundaries[0])+1] | |
413 | elif len(boundaries) == 2: | |
414 | if boundaries[0] not in unapplied: | |
415 | raise MainException, 'Patch "%s" not unapplied' % boundaries[0] | |
416 | if boundaries[1] not in unapplied: | |
417 | raise MainException, 'Patch "%s" not unapplied' % boundaries[1] | |
418 | lb = unapplied.index(boundaries[0]) | |
419 | hb = unapplied.index(boundaries[1]) | |
420 | if lb > hb: | |
421 | raise MainException, 'Patch "%s" after "%s"' \ | |
422 | % (boundaries[0], boundaries[1]) | |
423 | patches = unapplied[lb:hb+1] | |
424 | else: | |
425 | raise MainException, 'incorrect parameters to "--to"' | |
426 | elif options.number: | |
427 | patches = unapplied[:options.number] | |
428 | elif options.all: | |
429 | patches = unapplied | |
430 | elif len(args) == 0: | |
431 | patches = [unapplied[0]] | |
432 | elif len(args) == 1: | |
433 | patches = [args[0]] | |
434 | else: | |
435 | parser.error('incorrect number of arguments') | |
436 | ||
437 | if patches == []: | |
438 | raise MainException, 'No patches to push' | |
439 | ||
440 | if options.reverse: | |
441 | patches.reverse() | |
442 | ||
443 | for p in patches: | |
444 | print 'Pushing patch "%s"...' % p, | |
445 | sys.stdout.flush() | |
446 | ||
447 | crt_series.push_patch(p) | |
448 | ||
449 | if crt_series.empty_patch(p): | |
450 | print 'done (empty patch)' | |
451 | else: | |
452 | print 'done' | |
453 | __print_crt_patch() | |
454 | ||
455 | push_cmd = \ | |
456 | Command(push, | |
457 | 'push a patch on top of the series', | |
458 | '%prog [options] [<name>]', | |
459 | [make_option('-a', '--all', | |
460 | help = 'push all the unapplied patches', | |
461 | action = 'store_true'), | |
462 | make_option('-n', '--number', type = 'int', | |
463 | help = 'push the specified number of patches'), | |
464 | make_option('-t', '--to', metavar = 'PATCH1[:PATCH2]', | |
465 | help = 'push all patches to PATCH1 or between ' | |
466 | 'PATCH1 and PATCH2'), | |
467 | make_option('--reverse', | |
468 | help = 'push the patches in reverse order', | |
469 | action = 'store_true'), | |
470 | make_option('--undo', | |
471 | help = 'undo the last push operation', | |
472 | action = 'store_true')]) | |
473 | ||
474 | ||
475 | def pop(parser, options, args): | |
476 | if len(args) != 0: | |
477 | parser.error('incorrect number of arguments') | |
478 | ||
479 | __check_local_changes() | |
480 | __check_conflicts() | |
481 | __check_head_top_equal() | |
482 | ||
483 | applied = crt_series.get_applied() | |
484 | if not applied: | |
485 | raise MainException, 'No patches applied' | |
486 | applied.reverse() | |
487 | ||
488 | if options.to: | |
489 | if options.to not in applied: | |
490 | raise MainException, 'Patch "%s" not applied' % options.to | |
491 | patches = applied[:applied.index(options.to)] | |
492 | elif options.number: | |
493 | patches = applied[:options.number] | |
494 | elif options.all: | |
495 | patches = applied | |
496 | else: | |
497 | patches = [applied[0]] | |
498 | ||
499 | if patches == []: | |
500 | raise MainException, 'No patches to pop' | |
501 | ||
502 | # pop everything to the given patch | |
503 | p = patches[-1] | |
504 | if len(patches) == 1: | |
505 | print 'Popping patch "%s"...' % p, | |
506 | else: | |
507 | print 'Popping "%s" - "%s" patches...' % (patches[0], p), | |
508 | sys.stdout.flush() | |
509 | ||
510 | crt_series.pop_patch(p) | |
511 | ||
512 | print 'done' | |
513 | __print_crt_patch() | |
514 | ||
515 | pop_cmd = \ | |
516 | Command(pop, | |
517 | 'pop the top of the series', | |
518 | '%prog [options]', | |
519 | [make_option('-a', '--all', | |
520 | help = 'pop all the applied patches', | |
521 | action = 'store_true'), | |
522 | make_option('-n', '--number', type = 'int', | |
523 | help = 'pop the specified number of patches'), | |
524 | make_option('-t', '--to', metavar = 'PATCH', | |
525 | help = 'pop all patches up to PATCH')]) | |
526 | ||
527 | ||
528 | def __resolved(filename): | |
529 | for ext in ['.local', '.older', '.remote']: | |
530 | fn = filename + ext | |
531 | if os.path.isfile(fn): | |
532 | os.remove(fn) | |
533 | ||
534 | def __resolved_all(): | |
535 | conflicts = git.get_conflicts() | |
536 | if conflicts: | |
537 | for filename in conflicts: | |
538 | __resolved(filename) | |
539 | os.remove(os.path.join(git.base_dir, 'conflicts')) | |
540 | ||
541 | def resolved(parser, options, args): | |
542 | if options.all: | |
543 | __resolved_all() | |
544 | return | |
545 | ||
546 | if len(args) == 0: | |
547 | parser.error('incorrect number of arguments') | |
548 | ||
549 | conflicts = git.get_conflicts() | |
550 | if not conflicts: | |
551 | raise MainException, 'No more conflicts' | |
552 | # check for arguments validity | |
553 | for filename in args: | |
554 | if not filename in conflicts: | |
555 | raise MainException, 'No conflicts for "%s"' % filename | |
556 | # resolved | |
557 | for filename in args: | |
558 | __resolved(filename) | |
559 | del conflicts[conflicts.index(filename)] | |
560 | ||
561 | # save or remove the conflicts file | |
562 | if conflicts == []: | |
563 | os.remove(os.path.join(git.base_dir, 'conflicts')) | |
564 | else: | |
565 | f = file(os.path.join(git.base_dir, 'conflicts'), 'w+') | |
566 | f.writelines([line + '\n' for line in conflicts]) | |
567 | f.close() | |
568 | ||
569 | resolved_cmd = \ | |
570 | Command(resolved, | |
571 | 'mark a file conflict as solved', | |
572 | '%prog [options] [<file>[ <file>]]', | |
573 | [make_option('-a', '--all', | |
574 | help = 'mark all conflicts as solved', | |
575 | action = 'store_true')]) | |
576 | ||
577 | ||
578 | def series(parser, options, args): | |
579 | if len(args) != 0: | |
580 | parser.error('incorrect number of arguments') | |
581 | ||
582 | applied = crt_series.get_applied() | |
583 | if len(applied) > 0: | |
584 | for p in applied [0:-1]: | |
585 | if crt_series.empty_patch(p): | |
586 | print '0', p | |
587 | else: | |
588 | print '+', p | |
589 | p = applied[-1] | |
590 | ||
591 | if crt_series.empty_patch(p): | |
592 | print '0>%s' % p | |
593 | else: | |
594 | print '> %s' % p | |
595 | ||
596 | for p in crt_series.get_unapplied(): | |
597 | if crt_series.empty_patch(p): | |
598 | print '0', p | |
599 | else: | |
600 | print '-', p | |
601 | ||
602 | series_cmd = \ | |
603 | Command(series, | |
604 | 'print the patch series', | |
605 | '%prog', | |
606 | []) | |
607 | ||
608 | ||
609 | def applied(parser, options, args): | |
610 | if len(args) != 0: | |
611 | parser.error('incorrect number of arguments') | |
612 | ||
613 | for p in crt_series.get_applied(): | |
614 | print p | |
615 | ||
616 | applied_cmd = \ | |
617 | Command(applied, | |
618 | 'print the applied patches', | |
619 | '%prog', | |
620 | []) | |
621 | ||
622 | ||
623 | def unapplied(parser, options, args): | |
624 | if len(args) != 0: | |
625 | parser.error('incorrect number of arguments') | |
626 | ||
627 | for p in crt_series.get_unapplied(): | |
628 | print p | |
629 | ||
630 | unapplied_cmd = \ | |
631 | Command(unapplied, | |
632 | 'print the unapplied patches', | |
633 | '%prog', | |
634 | []) | |
635 | ||
636 | ||
637 | def top(parser, options, args): | |
638 | if len(args) != 0: | |
639 | parser.error('incorrect number of arguments') | |
640 | ||
641 | name = crt_series.get_current() | |
642 | if name: | |
643 | print name | |
644 | else: | |
645 | raise MainException, 'No patches applied' | |
646 | ||
647 | top_cmd = \ | |
648 | Command(top, | |
649 | 'print the name of the top patch', | |
650 | '%prog', | |
651 | []) | |
652 | ||
653 | ||
654 | def export(parser, options, args): | |
655 | if len(args) == 0: | |
656 | dirname = 'patches' | |
657 | elif len(args) == 1: | |
658 | dirname = args[0] | |
659 | else: | |
660 | parser.error('incorrect number of arguments') | |
661 | ||
662 | if git.local_changes(): | |
663 | print 'Warning: local changes in the tree. ' \ | |
664 | 'You might want to commit them first' | |
665 | ||
666 | if not os.path.isdir(dirname): | |
667 | os.makedirs(dirname) | |
668 | series = file(os.path.join(dirname, 'series'), 'w+') | |
669 | ||
670 | patches = crt_series.get_applied() | |
671 | num = len(patches) | |
672 | zpadding = len(str(num)) | |
673 | if zpadding < 2: | |
674 | zpadding = 2 | |
675 | ||
676 | patch_no = 1; | |
677 | for p in patches: | |
678 | pname = p | |
679 | if options.diff: | |
680 | pname = '%s.diff' % pname | |
681 | if options.numbered: | |
682 | pname = '%s-%s' % (str(patch_no).zfill(zpadding), pname) | |
683 | pfile = os.path.join(dirname, pname) | |
684 | print >> series, pname | |
685 | ||
686 | # get the template | |
687 | patch_tmpl = os.path.join(git.base_dir, 'patchexport.tmpl') | |
688 | if os.path.isfile(patch_tmpl): | |
689 | tmpl = file(patch_tmpl).read() | |
690 | else: | |
691 | tmpl = '' | |
692 | ||
693 | # get the patch description | |
694 | patch = crt_series.get_patch(p) | |
695 | ||
696 | tmpl_dict = {'description': patch.get_description().rstrip(), | |
697 | 'diffstat': git.diffstat(rev1 = __git_id('%s/bottom' % p), | |
698 | rev2 = __git_id('%s/top' % p)), | |
699 | 'authname': patch.get_authname(), | |
700 | 'authemail': patch.get_authemail(), | |
701 | 'authdate': patch.get_authdate(), | |
702 | 'commname': patch.get_commname(), | |
703 | 'commemail': patch.get_commemail()} | |
704 | for key in tmpl_dict: | |
705 | if not tmpl_dict[key]: | |
706 | tmpl_dict[key] = '' | |
707 | ||
708 | try: | |
709 | descr = tmpl % tmpl_dict | |
710 | except KeyError, err: | |
711 | raise MainException, 'Unknown patch template variable: %s' \ | |
712 | % err | |
713 | except TypeError: | |
714 | raise MainException, 'Only "%(name)s" variables are ' \ | |
715 | 'supported in the patch template' | |
716 | f = open(pfile, 'w+') | |
717 | f.write(descr) | |
718 | f.close() | |
719 | ||
720 | # write the diff | |
721 | git.diff(rev1 = __git_id('%s/bottom' % p), | |
722 | rev2 = __git_id('%s/top' % p), | |
723 | output = pfile, append = True) | |
724 | patch_no += 1 | |
725 | ||
726 | series.close() | |
727 | ||
728 | export_cmd = \ | |
729 | Command(export, | |
730 | 'exports a series of patches to <dir> (or patches)', | |
731 | '%prog [options] [<dir>]', | |
732 | [make_option('-n', '--numbered', | |
733 | help = 'number the patch names', | |
734 | action = 'store_true'), | |
735 | make_option('-d', '--diff', | |
736 | help = 'append .diff to the patch names', | |
737 | action = 'store_true')]) | |
738 | ||
739 | # | |
740 | # The commands map | |
741 | # | |
742 | commands = { | |
743 | 'init': init_cmd, | |
744 | 'add': add_cmd, | |
745 | 'rm': rm_cmd, | |
746 | 'status': status_cmd, | |
747 | 'diff': diff_cmd, | |
748 | 'files': files_cmd, | |
749 | 'new': new_cmd, | |
750 | 'delete': delete_cmd, | |
751 | 'push': push_cmd, | |
752 | 'pop': pop_cmd, | |
753 | 'resolved': resolved_cmd, | |
754 | 'series': series_cmd, | |
755 | 'applied': applied_cmd, | |
756 | 'unapplied':unapplied_cmd, | |
757 | 'top': top_cmd, | |
758 | 'refresh': refresh_cmd, | |
759 | 'export': export_cmd, | |
760 | } | |
761 | ||
762 | def print_help(): | |
763 | print 'usage: %s <command> [options]' % os.path.basename(sys.argv[0]) | |
764 | ||
765 | print 'commands:' | |
766 | print ' help print this message' | |
767 | ||
768 | cmds = commands.keys() | |
769 | cmds.sort() | |
770 | for cmd in cmds: | |
771 | print ' ' + cmd + ' ' * (12 - len(cmd)) + commands[cmd].help | |
772 | ||
773 | # | |
774 | # The main function (command dispatcher) | |
775 | # | |
776 | def main(): | |
777 | """The main function | |
778 | """ | |
779 | global crt_series | |
780 | ||
781 | prog = os.path.basename(sys.argv[0]) | |
782 | ||
783 | if len(sys.argv) < 2: | |
784 | print >> sys.stderr, 'Unknown command' | |
785 | print >> sys.stderr, \ | |
786 | ' Try "%s help" for a list of supported commands' % prog | |
787 | sys.exit(1) | |
788 | ||
789 | cmd = sys.argv[1] | |
790 | ||
791 | if cmd in ['-h', '--help', 'help']: | |
792 | print_help() | |
793 | sys.exit(0) | |
794 | if cmd in ['-v', '--version']: | |
795 | print '%s %s' % (prog, version) | |
796 | sys.exit(0) | |
797 | if not cmd in commands: | |
798 | print >> sys.stderr, 'Unknown command: %s' % cmd | |
799 | print >> sys.stderr, ' Try "%s help" for a list of supported commands' \ | |
800 | % prog | |
801 | sys.exit(1) | |
802 | ||
803 | # re-build the command line arguments | |
804 | sys.argv[0] += ' %s' % cmd | |
805 | del(sys.argv[1]) | |
806 | ||
807 | command = commands[cmd] | |
808 | parser = OptionParser(usage = command.usage, | |
809 | option_list = command.option_list) | |
810 | options, args = parser.parse_args() | |
811 | try: | |
812 | crt_series = stack.Series() | |
813 | command.func(parser, options, args) | |
814 | except (IOError, MainException, stack.StackException, git.GitException), \ | |
815 | err: | |
816 | print >> sys.stderr, '%s %s: %s' % (prog, cmd, err) | |
817 | sys.exit(2) | |
818 | ||
819 | sys.exit(0) |