rebase: update the model when using "git cola rebase"
[git-cola.git] / cola / main.py
blobf97ff3b0cadce23ce50e9113cf5c2171b139269b
1 """Launcher and command line interface to git-cola"""
2 import argparse
3 import sys
5 from . import app
6 from . import cmds
7 from . import compat
8 from . import core
11 def main(argv=None):
12 app.initialize()
13 if argv is None:
14 argv = sys.argv[1:]
15 # we're using argparse with subparsers, but argparse
16 # does not allow us to assign a default subparser
17 # when none has been specified. We fake it by injecting
18 # 'cola' into the command-line so that parse_args()
19 # routes them to the 'cola' parser by default.
20 help_commands = core.encode('--help-commands')
21 args = [core.encode(arg) for arg in argv]
22 if not argv or argv[0].startswith('-') and help_commands not in args:
23 argv.insert(0, 'cola')
24 elif help_commands in argv:
25 argv.append('--help')
26 args = parse_args(argv)
27 return args.func(args)
30 def parse_args(argv):
31 parser = argparse.ArgumentParser()
32 # Newer versions of argparse (Python 3.6+) emit an error message for
33 # "--help-commands" unless we register the flag on the main parser.
34 if compat.PY_VERSION >= (3, 6):
35 add_help_options(parser)
36 parser.set_defaults(func=lambda _: parser.print_help())
38 subparser = parser.add_subparsers(title='valid commands')
39 add_cola_command(subparser)
40 add_about_command(subparser)
41 add_am_command(subparser)
42 add_archive_command(subparser)
43 add_branch_command(subparser)
44 add_browse_command(subparser)
45 add_clone_command(subparser)
46 add_config_command(subparser)
47 add_dag_command(subparser)
48 add_diff_command(subparser)
49 add_fetch_command(subparser)
50 add_find_command(subparser)
51 add_grep_command(subparser)
52 add_merge_command(subparser)
53 add_pull_command(subparser)
54 add_push_command(subparser)
55 add_rebase_command(subparser)
56 add_recent_command(subparser)
57 add_remote_command(subparser)
58 add_search_command(subparser)
59 add_stash_command(subparser)
60 add_tag_command(subparser)
61 add_version_command(subparser)
63 return parser.parse_args(argv)
66 def add_help_options(parser):
67 """Add the --help-commands flag to the parser"""
68 parser.add_argument(
69 '--help-commands',
70 default=False,
71 action='store_true',
72 help='show available commands',
76 def add_command(parent, name, description, func):
77 """Add a "git cola" command with common arguments"""
78 parser = parent.add_parser(str(name), help=description)
79 parser.set_defaults(func=func)
80 app.add_common_arguments(parser)
81 return parser
84 def add_cola_command(subparser):
85 """Add the main "git cola" command. "git cola cola" is valid"""
86 parser = add_command(subparser, 'cola', 'launch git-cola', cmd_cola)
87 parser.add_argument(
88 '--amend', default=False, action='store_true', help='start in amend mode'
90 add_help_options(parser)
91 parser.add_argument(
92 '--status-filter', '-s', metavar='<path>', default='', help='status path filter'
96 def add_about_command(parent):
97 """Add the "git cola about" documentation command"""
98 add_command(parent, 'about', 'about git-cola', cmd_about)
101 def add_am_command(parent):
102 """Add the "git cola am" command for applying patches"""
103 parser = add_command(parent, 'am', 'apply patches using "git am"', cmd_am)
104 parser.add_argument(
105 'patches', metavar='<patches>', nargs='*', help='patches to apply'
109 def add_archive_command(parent):
110 """Add the "git cola archive" tarball export command"""
111 parser = add_command(parent, 'archive', 'save an archive', cmd_archive)
112 parser.add_argument(
113 'ref', metavar='<ref>', nargs='?', default=None, help='commit to archive'
117 def add_branch_command(subparser):
118 """Add the "git cola branch" branch creation command"""
119 add_command(subparser, 'branch', 'create a branch', cmd_branch)
122 def add_browse_command(subparser):
123 """Add the "git cola browse" repository browser command"""
124 add_command(subparser, 'browse', 'browse repository', cmd_browse)
127 def add_clone_command(subparser):
128 """Add the "git cola clone" command for cloning repositories"""
129 add_command(subparser, 'clone', 'clone repository', cmd_clone)
132 def add_config_command(subparser):
133 """Add the "git cola config" command for editing preferences"""
134 add_command(subparser, 'config', 'edit configuration', cmd_config)
137 def add_dag_command(subparser):
138 """Add the "git cola dag" command for visualizing history"""
139 parser = add_command(subparser, 'dag', 'start git-dag', cmd_dag)
140 parser.add_argument(
141 '-c',
142 '--count',
143 metavar='<count>',
144 type=int,
145 default=1000,
146 help='number of commits to display',
148 parser.add_argument(
149 '--all',
150 action='store_true',
151 dest='show_all',
152 help='visualize all branches',
153 default=False,
155 parser.add_argument(
156 'args', nargs=argparse.REMAINDER, metavar='<args>', help='git log arguments'
160 def add_diff_command(subparser):
161 """Add the "git cola diff" command for diffing changes"""
162 parser = add_command(subparser, 'diff', 'view diffs', cmd_diff)
163 parser.add_argument(
164 'args', nargs=argparse.REMAINDER, metavar='<args>', help='git diff arguments'
168 def add_fetch_command(subparser):
169 """Add the "git cola fetch" command for fetching repositories"""
170 add_command(subparser, 'fetch', 'fetch remotes', cmd_fetch)
173 def add_find_command(subparser):
174 """Add the "git cola find" command for finding files"""
175 parser = add_command(subparser, 'find', 'find files', cmd_find)
176 parser.add_argument('paths', nargs='*', metavar='<path>', help='filter by path')
179 def add_grep_command(subparser):
180 """Add the "git cola grep" command for searching files"""
181 parser = add_command(subparser, 'grep', 'grep source', cmd_grep)
182 parser.add_argument('args', nargs='*', metavar='<args>', help='git grep arguments')
185 def add_merge_command(subparser):
186 """Add the "git cola merge" command for merging branches"""
187 parser = add_command(subparser, 'merge', 'merge branches', cmd_merge)
188 parser.add_argument(
189 'ref', nargs='?', metavar='<ref>', help='branch, tag, or commit to merge'
193 def add_pull_command(subparser):
194 """Add the "git cola pull" command for pulling changes from remotes"""
195 parser = add_command(subparser, 'pull', 'pull remote branches', cmd_pull)
196 parser.add_argument(
197 '--rebase',
198 default=False,
199 action='store_true',
200 help='rebase local branch when pulling',
204 def add_push_command(subparser):
205 """Add the "git cola push" command for pushing branches to remotes"""
206 add_command(subparser, 'push', 'push remote branches', cmd_push)
209 def add_rebase_command(subparser):
210 """Add the "git cola rebase" command for rebasing the current branch"""
211 parser = add_command(subparser, 'rebase', 'interactive rebase', cmd_rebase)
212 parser.add_argument(
213 '-v',
214 '--verbose',
215 default=False,
216 action='store_true',
217 help='display a diffstat of what changed upstream',
219 parser.add_argument(
220 '-q',
221 '--quiet',
222 default=False,
223 action='store_true',
224 help='be quiet. implies --no-stat',
226 parser.add_argument(
227 '-i', '--interactive', default=True, action='store_true', help=argparse.SUPPRESS
229 parser.add_argument(
230 '--autostash',
231 default=False,
232 action='store_true',
233 help='automatically stash/stash pop before and after',
235 parser.add_argument(
236 '--fork-point',
237 default=False,
238 action='store_true',
239 help="use 'merge-base --fork-point' to refine upstream",
241 parser.add_argument(
242 '--onto',
243 default=None,
244 metavar='<newbase>',
245 help='rebase onto given branch instead of upstream',
247 parser.add_argument(
248 '-p',
249 '--preserve-merges',
250 default=False,
251 action='store_true',
252 help='try to recreate merges instead of ignoring them',
254 parser.add_argument(
255 '-s',
256 '--strategy',
257 default=None,
258 metavar='<strategy>',
259 help='use the given merge strategy',
261 parser.add_argument(
262 '--no-ff',
263 default=False,
264 action='store_true',
265 help='cherry-pick all commits, even if unchanged',
267 parser.add_argument(
268 '-m',
269 '--merge',
270 default=False,
271 action='store_true',
272 help='use merging strategies to rebase',
274 parser.add_argument(
275 '-x',
276 '--exec',
277 default=None,
278 help='add exec lines after each commit of ' 'the editable list',
280 parser.add_argument(
281 '-k',
282 '--keep-empty',
283 default=False,
284 action='store_true',
285 help='preserve empty commits during rebase',
287 parser.add_argument(
288 '-f',
289 '--force-rebase',
290 default=False,
291 action='store_true',
292 help='force rebase even if branch is up to date',
294 parser.add_argument(
295 '-X',
296 '--strategy-option',
297 default=None,
298 metavar='<arg>',
299 help='pass the argument through to the merge strategy',
301 parser.add_argument(
302 '--stat',
303 default=False,
304 action='store_true',
305 help='display a diffstat of what changed upstream',
307 parser.add_argument(
308 '-n',
309 '--no-stat',
310 default=False,
311 action='store_true',
312 help='do not show diffstat of what changed upstream',
314 parser.add_argument(
315 '--verify',
316 default=False,
317 action='store_true',
318 help='allow pre-rebase hook to run',
320 parser.add_argument(
321 '--rerere-autoupdate',
322 default=False,
323 action='store_true',
324 help='allow rerere to update index with ' 'resolved conflicts',
326 parser.add_argument(
327 '--root',
328 default=False,
329 action='store_true',
330 help='rebase all reachable commits up to the root(s)',
332 parser.add_argument(
333 '--autosquash',
334 default=True,
335 action='store_true',
336 help='move commits that begin with ' 'squash!/fixup! under -i',
338 parser.add_argument(
339 '--no-autosquash',
340 default=True,
341 action='store_false',
342 dest='autosquash',
343 help='do not move commits that begin with ' 'squash!/fixup! under -i',
345 parser.add_argument(
346 '--committer-date-is-author-date',
347 default=False,
348 action='store_true',
349 help="passed to 'git am' by 'git rebase'",
351 parser.add_argument(
352 '--ignore-date',
353 default=False,
354 action='store_true',
355 help="passed to 'git am' by 'git rebase'",
357 parser.add_argument(
358 '--whitespace',
359 default=False,
360 action='store_true',
361 help="passed to 'git apply' by 'git rebase'",
363 parser.add_argument(
364 '--ignore-whitespace',
365 default=False,
366 action='store_true',
367 help="passed to 'git apply' by 'git rebase'",
369 parser.add_argument(
370 '--update-refs',
371 default=False,
372 action='store_true',
373 help='update branches that point to commits that are being rebased',
375 parser.add_argument(
376 '-C',
377 dest='context_lines',
378 default=None,
379 metavar='<n>',
380 help="passed to 'git apply' by 'git rebase'",
383 actions = parser.add_argument_group('actions')
384 actions.add_argument(
385 '--continue', default=False, action='store_true', help='continue'
387 actions.add_argument(
388 '--abort',
389 default=False,
390 action='store_true',
391 help='abort and check out the original branch',
393 actions.add_argument(
394 '--skip',
395 default=False,
396 action='store_true',
397 help='skip current patch and continue',
399 actions.add_argument(
400 '--edit-todo',
401 default=False,
402 action='store_true',
403 help='edit the todo list during an interactive rebase',
406 parser.add_argument(
407 'upstream',
408 nargs='?',
409 default=None,
410 metavar='<upstream>',
411 help='the upstream configured in branch.<name>.remote '
412 'and branch.<name>.merge options will be used '
413 'when <upstream> is omitted; see git-rebase(1) '
414 'for details. If you are currently not on any '
415 'branch or if the current branch does not have '
416 'a configured upstream, the rebase will abort',
418 parser.add_argument(
419 'branch',
420 nargs='?',
421 default=None,
422 metavar='<branch>',
423 help='git rebase will perform an automatic '
424 '"git checkout <branch>" before doing anything '
425 'else when <branch> is specified',
429 def add_recent_command(subparser):
430 """Add the "git cola recent" command for opening recently edited files"""
431 add_command(subparser, 'recent', 'edit recent files', cmd_recent)
434 def add_remote_command(subparser):
435 """Add the "git cola remote" command for editing remotes"""
436 add_command(subparser, 'remote', 'edit remotes', cmd_remote)
439 def add_search_command(subparser):
440 """Add the "git cola search" command for searching over commits"""
441 add_command(subparser, 'search', 'search commits', cmd_search)
444 def add_stash_command(subparser):
445 """Add the "git cola stash" command for creating and applying stashes"""
446 add_command(subparser, 'stash', 'stash and unstash changes', cmd_stash)
449 def add_tag_command(subparser):
450 """Add the "git cola tag" command for creating tags"""
451 parser = add_command(subparser, 'tag', 'create tags', cmd_tag)
452 parser.add_argument(
453 'name', metavar='<name>', nargs='?', default=None, help='tag name'
455 parser.add_argument(
456 'ref', metavar='<ref>', nargs='?', default=None, help='commit to tag'
458 parser.add_argument(
459 '-s',
460 '--sign',
461 default=False,
462 action='store_true',
463 help='annotated and GPG-signed tag',
467 def add_version_command(subparser):
468 """Add the "git cola version" command for displaying Git Cola's version"""
469 parser = add_command(subparser, 'version', 'print the version', cmd_version)
470 parser.add_argument(
471 '--builtin',
472 action='store_true',
473 default=False,
474 help=argparse.SUPPRESS,
476 parser.add_argument(
477 '--brief',
478 action='store_true',
479 default=False,
480 help='print the version number only',
484 # entry points
485 def cmd_cola(args):
486 """The "git cola" entry point"""
487 from .widgets.main import MainView
489 status_filter = args.status_filter
490 if status_filter:
491 status_filter = core.abspath(status_filter)
493 context = app.application_init(args)
495 context.timer.start('view')
496 view = MainView(context)
497 if args.amend:
498 cmds.do(cmds.AmendMode, context, amend=True)
500 if status_filter:
501 view.set_filter(core.relpath(status_filter))
503 context.timer.stop('view')
504 if args.perf:
505 context.timer.display('view')
507 return app.application_run(context, view, start=start_cola, stop=app.default_stop)
510 def start_cola(context, view):
511 app.default_start(context, view)
512 view.start(context)
515 def cmd_about(args):
516 from .widgets import about
518 context = app.application_init(args)
519 view = about.about_dialog(context)
520 return app.application_start(context, view)
523 def cmd_am(args):
524 from .widgets.patch import new_apply_patches
526 context = app.application_init(args)
527 view = new_apply_patches(context, patches=args.patches)
528 return app.application_start(context, view)
531 def cmd_archive(args):
532 from .widgets import archive
534 context = app.application_init(args, update=True)
535 if args.ref is None:
536 args.ref = context.model.currentbranch
537 view = archive.Archive(context, args.ref)
538 return app.application_start(context, view)
541 def cmd_branch(args):
542 from .widgets.createbranch import create_new_branch
544 context = app.application_init(args, update=True)
545 view = create_new_branch(context)
546 return app.application_start(context, view)
549 def cmd_browse(args):
550 from .widgets.browse import worktree_browser
552 context = app.application_init(args)
553 view = worktree_browser(context, show=False, update=False)
554 return app.application_start(context, view)
557 def cmd_clone(args):
558 from .widgets import clone
560 context = app.application_init(args)
561 view = clone.clone(context)
562 context.set_view(view)
563 result = 0 if view.exec_() == view.Accepted else 1
564 app.default_stop(context, view)
565 return result
568 def cmd_config(args):
569 from .widgets.prefs import preferences
571 context = app.application_init(args)
572 view = preferences(context)
573 return app.application_start(context, view)
576 def cmd_dag(args):
577 from .widgets import dag
579 context = app.application_init(args)
580 # cola.main() uses parse_args(), unlike dag.main() which uses
581 # parse_known_args(), thus we aren't able to automatically forward
582 # all unknown arguments. Special-case support for "--all" since it's
583 # used by the history viewer command on Windows.
584 if args.show_all:
585 args.args.insert(0, '--all')
586 view = dag.git_dag(context, args=args, show=False)
587 return app.application_start(context, view)
590 def cmd_diff(args):
591 from .difftool import diff_expression
593 context = app.application_init(args)
594 expr = core.list2cmdline(args.args)
595 view = diff_expression(context, None, expr, create_widget=True)
596 return app.application_start(context, view)
599 def cmd_fetch(args):
600 # TODO: the calls to update_status() can be done asynchronously
601 # by hooking into the message_updated notification.
602 from .widgets import remote
604 context = app.application_init(args)
605 context.model.update_status()
606 view = remote.fetch(context)
607 return app.application_start(context, view)
610 def cmd_find(args):
611 from .widgets import finder
613 context = app.application_init(args)
614 paths = core.list2cmdline(args.paths)
615 view = finder.finder(context, paths=paths)
616 return app.application_start(context, view)
619 def cmd_grep(args):
620 from .widgets import grep
622 context = app.application_init(args)
623 text = core.list2cmdline(args.args)
624 view = grep.new_grep(context, text=text, parent=None)
625 return app.application_start(context, view)
628 def cmd_merge(args):
629 from .widgets.merge import Merge
631 context = app.application_init(args, update=True)
632 view = Merge(context, parent=None, ref=args.ref)
633 return app.application_start(context, view)
636 def cmd_version(args):
637 from . import version
639 version.print_version(builtin=args.builtin, brief=args.brief)
640 return 0
643 def cmd_pull(args):
644 from .widgets import remote
646 context = app.application_init(args, update=True)
647 view = remote.pull(context)
648 if args.rebase:
649 view.set_rebase(True)
650 return app.application_start(context, view)
653 def cmd_push(args):
654 from .widgets import remote
656 context = app.application_init(args, update=True)
657 view = remote.push(context)
658 return app.application_start(context, view)
661 def cmd_rebase(args):
662 kwargs = {
663 'verbose': args.verbose,
664 'quiet': args.quiet,
665 'autostash': args.autostash,
666 'fork_point': args.fork_point,
667 'onto': args.onto,
668 'preserve_merges': args.preserve_merges,
669 'strategy': args.strategy,
670 'no_ff': args.no_ff,
671 'merge': args.merge,
672 'exec': getattr(args, 'exec', None), # python keyword
673 'keep_empty': args.keep_empty,
674 'force_rebase': args.force_rebase,
675 'strategy_option': args.strategy_option,
676 'stat': args.stat,
677 'no_stat': args.no_stat,
678 'verify': args.verify,
679 'rerere_autoupdate': args.rerere_autoupdate,
680 'root': args.root,
681 'autosquash': args.autosquash,
682 'committer_date_is_author_date': args.committer_date_is_author_date,
683 'ignore_date': args.ignore_date,
684 'whitespace': args.whitespace,
685 'ignore_whitespace': args.ignore_whitespace,
686 'C': args.context_lines,
687 'continue': getattr(args, 'continue', False), # python keyword
688 'abort': args.abort,
689 'skip': args.skip,
690 'edit_todo': args.edit_todo,
691 'update_refs': args.update_refs,
692 'upstream': args.upstream,
693 'branch': args.branch,
695 context = app.application_init(args)
696 context.model.update_refs()
697 status, _, _ = cmds.do(cmds.Rebase, context, **kwargs)
698 return status
701 def cmd_recent(args):
702 from .widgets import recent
704 context = app.application_init(args)
705 view = recent.browse_recent_files(context)
706 return app.application_start(context, view)
709 def cmd_remote(args):
710 from .widgets import editremotes
712 context = app.application_init(args)
713 view = editremotes.editor(context, run=False)
714 return app.application_start(context, view)
717 def cmd_search(args):
718 from .widgets.search import search
720 context = app.application_init(args)
721 view = search(context)
722 return app.application_start(context, view)
725 def cmd_stash(args):
726 from .widgets import stash
728 context = app.application_init(args)
729 view = stash.view(context, show=False)
730 return app.application_start(context, view)
733 def cmd_tag(args):
734 from .widgets.createtag import new_create_tag
736 context = app.application_init(args)
737 context.model.update_status()
738 view = new_create_tag(context, name=args.name, ref=args.ref, sign=args.sign)
739 return app.application_start(context, view)
742 # Windows shortcut launch features:
743 def shortcut_launch():
744 """Launch from a shortcut
746 Prompt for the repository by default.
749 argv = sys.argv[1:]
750 if not argv:
751 argv = ['cola', '--prompt']
752 return main(argv=argv)