| 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 | print |
| 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) |