qtutils: simplify the BlockSignals implementation
[git-cola.git] / cola / main.py
blob7de5c50e275428f76d7de07accdbcb1674e7167d
1 """Launcher and command line interface to git-cola"""
2 from __future__ import absolute_import, division, unicode_literals
3 import argparse
4 import sys
6 from . import app
7 from . import cmds
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 subparser, 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 winmain():
31 return app.winmain(main)
34 def parse_args(argv):
35 parser = argparse.ArgumentParser()
36 # Newer versions of argpares (Python 3.8+) emit an error message for
37 # "--help-commands" unless we register the flag on the main parser.
38 add_help_options(parser)
39 parser.set_defaults(func=lambda _: parser.print_help())
41 subparser = parser.add_subparsers(title='valid commands')
42 add_cola_command(subparser)
43 add_about_command(subparser)
44 add_am_command(subparser)
45 add_archive_command(subparser)
46 add_branch_command(subparser)
47 add_browse_command(subparser)
48 add_clone_command(subparser)
49 add_config_command(subparser)
50 add_dag_command(subparser)
51 add_diff_command(subparser)
52 add_fetch_command(subparser)
53 add_find_command(subparser)
54 add_grep_command(subparser)
55 add_merge_command(subparser)
56 add_pull_command(subparser)
57 add_push_command(subparser)
58 add_rebase_command(subparser)
59 add_recent_command(subparser)
60 add_remote_command(subparser)
61 add_search_command(subparser)
62 add_stash_command(subparser)
63 add_tag_command(subparser)
64 add_version_command(subparser)
66 return parser.parse_args(argv)
69 def add_help_options(parser):
70 """Add the --help-commands flag to the parser"""
71 parser.add_argument(
72 '--help-commands',
73 default=False,
74 action='store_true',
75 help='show available sub-commands',
79 def add_command(parent, name, description, func):
80 parser = parent.add_parser(str(name), help=description)
81 parser.set_defaults(func=func)
82 app.add_common_arguments(parser)
83 return parser
86 def add_cola_command(subparser):
87 parser = add_command(subparser, 'cola', 'start git-cola', cmd_cola)
88 parser.add_argument(
89 '--amend', default=False, action='store_true', help='start in amend mode'
91 add_help_options(parser)
92 parser.add_argument(
93 '--status-filter', '-s', metavar='<path>', default='', help='status path filter'
97 def add_about_command(parent):
98 add_command(parent, 'about', 'about git-cola', cmd_about)
101 def add_am_command(parent):
102 parser = add_command(parent, 'am', 'apply patches using "git am"', cmd_am)
103 parser.add_argument(
104 'patches', metavar='<patches>', nargs='*', help='patches to apply'
108 def add_archive_command(parent):
109 parser = add_command(parent, 'archive', 'save an archive', cmd_archive)
110 parser.add_argument(
111 'ref', metavar='<ref>', nargs='?', default=None, help='commit to archive'
115 def add_branch_command(subparser):
116 add_command(subparser, 'branch', 'create a branch', cmd_branch)
119 def add_browse_command(subparser):
120 add_command(subparser, 'browse', 'browse repository', cmd_browse)
123 def add_clone_command(subparser):
124 add_command(subparser, 'clone', 'clone repository', cmd_clone)
127 def add_config_command(subparser):
128 add_command(subparser, 'config', 'edit configuration', cmd_config)
131 def add_dag_command(subparser):
132 parser = add_command(subparser, 'dag', 'start git-dag', cmd_dag)
133 parser.add_argument(
134 '-c',
135 '--count',
136 metavar='<count>',
137 type=int,
138 default=1000,
139 help='number of commits to display',
141 parser.add_argument(
142 '--all',
143 action='store_true',
144 dest='show_all',
145 help='visualize all branches',
146 default=False,
148 parser.add_argument('args', nargs='*', metavar='<args>', help='git log arguments')
151 def add_diff_command(subparser):
152 parser = add_command(subparser, 'diff', 'view diffs', cmd_diff)
153 parser.add_argument('args', nargs='*', metavar='<args>', help='git diff arguments')
156 def add_fetch_command(subparser):
157 add_command(subparser, 'fetch', 'fetch remotes', cmd_fetch)
160 def add_find_command(subparser):
161 parser = add_command(subparser, 'find', 'find files', cmd_find)
162 parser.add_argument('paths', nargs='*', metavar='<path>', help='filter by path')
165 def add_grep_command(subparser):
166 parser = add_command(subparser, 'grep', 'grep source', cmd_grep)
167 parser.add_argument('args', nargs='*', metavar='<args>', help='git grep arguments')
170 def add_merge_command(subparser):
171 parser = add_command(subparser, 'merge', 'merge branches', cmd_merge)
172 parser.add_argument(
173 'ref', nargs='?', metavar='<ref>', help='branch, tag, or commit to merge'
177 def add_pull_command(subparser):
178 parser = add_command(subparser, 'pull', 'pull remote branches', cmd_pull)
179 parser.add_argument(
180 '--rebase',
181 default=False,
182 action='store_true',
183 help='rebase local branch when pulling',
187 def add_push_command(subparser):
188 add_command(subparser, 'push', 'push remote branches', cmd_push)
191 def add_rebase_command(subparser):
192 parser = add_command(subparser, 'rebase', 'interactive rebase', cmd_rebase)
193 parser.add_argument(
194 '-v',
195 '--verbose',
196 default=False,
197 action='store_true',
198 help='display a diffstat of what changed upstream',
200 parser.add_argument(
201 '-q',
202 '--quiet',
203 default=False,
204 action='store_true',
205 help='be quiet. implies --no-stat',
207 parser.add_argument(
208 '-i', '--interactive', default=True, action='store_true', help=argparse.SUPPRESS
210 parser.add_argument(
211 '--autostash',
212 default=False,
213 action='store_true',
214 help='automatically stash/stash pop before and after',
216 parser.add_argument(
217 '--fork-point',
218 default=False,
219 action='store_true',
220 help="use 'merge-base --fork-point' to refine upstream",
222 parser.add_argument(
223 '--onto',
224 default=None,
225 metavar='<newbase>',
226 help='rebase onto given branch instead of upstream',
228 parser.add_argument(
229 '-p',
230 '--preserve-merges',
231 default=False,
232 action='store_true',
233 help='try to recreate merges instead of ignoring them',
235 parser.add_argument(
236 '-s',
237 '--strategy',
238 default=None,
239 metavar='<strategy>',
240 help='use the given merge strategy',
242 parser.add_argument(
243 '--no-ff',
244 default=False,
245 action='store_true',
246 help='cherry-pick all commits, even if unchanged',
248 parser.add_argument(
249 '-m',
250 '--merge',
251 default=False,
252 action='store_true',
253 help='use merging strategies to rebase',
255 parser.add_argument(
256 '-x',
257 '--exec',
258 default=None,
259 help='add exec lines after each commit of ' 'the editable list',
261 parser.add_argument(
262 '-k',
263 '--keep-empty',
264 default=False,
265 action='store_true',
266 help='preserve empty commits during rebase',
268 parser.add_argument(
269 '-f',
270 '--force-rebase',
271 default=False,
272 action='store_true',
273 help='force rebase even if branch is up to date',
275 parser.add_argument(
276 '-X',
277 '--strategy-option',
278 default=None,
279 metavar='<arg>',
280 help='pass the argument through to the merge strategy',
282 parser.add_argument(
283 '--stat',
284 default=False,
285 action='store_true',
286 help='display a diffstat of what changed upstream',
288 parser.add_argument(
289 '-n',
290 '--no-stat',
291 default=False,
292 action='store_true',
293 help='do not show diffstat of what changed upstream',
295 parser.add_argument(
296 '--verify',
297 default=False,
298 action='store_true',
299 help='allow pre-rebase hook to run',
301 parser.add_argument(
302 '--rerere-autoupdate',
303 default=False,
304 action='store_true',
305 help='allow rerere to update index with ' 'resolved conflicts',
307 parser.add_argument(
308 '--root',
309 default=False,
310 action='store_true',
311 help='rebase all reachable commits up to the root(s)',
313 parser.add_argument(
314 '--autosquash',
315 default=True,
316 action='store_true',
317 help='move commits that begin with ' 'squash!/fixup! under -i',
319 parser.add_argument(
320 '--no-autosquash',
321 default=True,
322 action='store_false',
323 dest='autosquash',
324 help='do not move commits that begin with ' 'squash!/fixup! under -i',
326 parser.add_argument(
327 '--committer-date-is-author-date',
328 default=False,
329 action='store_true',
330 help="passed to 'git am' by 'git rebase'",
332 parser.add_argument(
333 '--ignore-date',
334 default=False,
335 action='store_true',
336 help="passed to 'git am' by 'git rebase'",
338 parser.add_argument(
339 '--whitespace',
340 default=False,
341 action='store_true',
342 help="passed to 'git apply' by 'git rebase'",
344 parser.add_argument(
345 '--ignore-whitespace',
346 default=False,
347 action='store_true',
348 help="passed to 'git apply' by 'git rebase'",
350 parser.add_argument(
351 '-C',
352 dest='context_lines',
353 default=None,
354 metavar='<n>',
355 help="passed to 'git apply' by 'git rebase'",
358 actions = parser.add_argument_group('actions')
359 actions.add_argument(
360 '--continue', default=False, action='store_true', help='continue'
362 actions.add_argument(
363 '--abort',
364 default=False,
365 action='store_true',
366 help='abort and check out the original branch',
368 actions.add_argument(
369 '--skip',
370 default=False,
371 action='store_true',
372 help='skip current patch and continue',
374 actions.add_argument(
375 '--edit-todo',
376 default=False,
377 action='store_true',
378 help='edit the todo list during an interactive rebase',
381 parser.add_argument(
382 'upstream',
383 nargs='?',
384 default=None,
385 metavar='<upstream>',
386 help='the upstream configured in branch.<name>.remote '
387 'and branch.<name>.merge options will be used '
388 'when <upstream> is omitted; see git-rebase(1) '
389 'for details. If you are currently not on any '
390 'branch or if the current branch does not have '
391 'a configured upstream, the rebase will abort',
393 parser.add_argument(
394 'branch',
395 nargs='?',
396 default=None,
397 metavar='<branch>',
398 help='git rebase will perform an automatic '
399 '"git checkout <branch>" before doing anything '
400 'else when <branch> is specified',
404 def add_recent_command(subparser):
405 add_command(subparser, 'recent', 'edit recent files', cmd_recent)
408 def add_remote_command(subparser):
409 add_command(subparser, 'remote', 'edit remotes', cmd_remote)
412 def add_search_command(subparser):
413 add_command(subparser, 'search', 'search commits', cmd_search)
416 def add_stash_command(subparser):
417 add_command(subparser, 'stash', 'stash and unstash changes', cmd_stash)
420 def add_tag_command(subparser):
421 parser = add_command(subparser, 'tag', 'create tags', cmd_tag)
422 parser.add_argument(
423 'name', metavar='<name>', nargs='?', default=None, help='tag name'
425 parser.add_argument(
426 'ref', metavar='<ref>', nargs='?', default=None, help='commit to tag'
428 parser.add_argument(
429 '-s',
430 '--sign',
431 default=False,
432 action='store_true',
433 help='annotated and GPG-signed tag',
437 def add_version_command(subparser):
438 parser = add_command(subparser, 'version', 'print the version', cmd_version)
439 parser.add_argument(
440 '--brief',
441 action='store_true',
442 default=False,
443 help='print the version number only',
445 parser.add_argument(
446 '--build', action='store_true', default=False, help='print the build version'
450 # entry points
451 def cmd_cola(args):
452 from .widgets.main import MainView # pylint: disable=all
454 status_filter = args.status_filter
455 if status_filter:
456 status_filter = core.abspath(status_filter)
458 context = app.application_init(args)
460 context.timer.start('view')
461 view = MainView(context)
462 if args.amend:
463 cmds.do(cmds.AmendMode, context, amend=True)
465 if status_filter:
466 view.set_filter(core.relpath(status_filter))
468 context.timer.stop('view')
469 if args.perf:
470 context.timer.display('view')
472 return app.application_run(context, view, start=start_cola, stop=app.default_stop)
475 def start_cola(context, view):
476 app.default_start(context, view)
477 view.start(context)
480 def cmd_about(args):
481 from .widgets import about # pylint: disable=all
483 context = app.application_init(args)
484 view = about.about_dialog(context)
485 return app.application_start(context, view)
488 def cmd_am(args):
489 from .widgets.patch import new_apply_patches # pylint: disable=all
491 context = app.application_init(args)
492 view = new_apply_patches(context, patches=args.patches)
493 return app.application_start(context, view)
496 def cmd_archive(args):
497 from .widgets import archive # pylint: disable=all
499 context = app.application_init(args, update=True)
500 if args.ref is None:
501 args.ref = context.model.currentbranch
502 view = archive.Archive(context, args.ref)
503 return app.application_start(context, view)
506 def cmd_branch(args):
507 from .widgets.createbranch import create_new_branch # pylint: disable=all
509 context = app.application_init(args, update=True)
510 view = create_new_branch(context)
511 return app.application_start(context, view)
514 def cmd_browse(args):
515 from .widgets.browse import worktree_browser # pylint: disable=all
517 context = app.application_init(args)
518 view = worktree_browser(context, show=False, update=False)
519 return app.application_start(context, view)
522 def cmd_clone(args):
523 from .widgets import clone # pylint: disable=all
525 context = app.application_init(args)
526 view = clone.clone(context)
527 context.set_view(view)
528 result = 0 if view.exec_() == view.Accepted else 1
529 app.default_stop(context, view)
530 return result
533 def cmd_config(args):
534 from .widgets.prefs import preferences # pylint: disable=all
536 context = app.application_init(args)
537 view = preferences(context)
538 return app.application_start(context, view)
541 def cmd_dag(args):
542 from .widgets import dag # pylint: disable=all
544 context = app.application_init(args)
545 # cola.main() uses parse_args(), unlike dag.main() which uses
546 # parse_known_args(), thus we aren't able to automatically forward
547 # all unknown arguments. Special-case support for "--all" since it's
548 # used by the history viewer command on Windows.
549 if args.show_all:
550 args.args.insert(0, '--all')
551 view = dag.git_dag(context, args=args, show=False)
552 return app.application_start(context, view)
555 def cmd_diff(args):
556 from .difftool import diff_expression # pylint: disable=all
558 context = app.application_init(args)
559 expr = core.list2cmdline(args.args)
560 view = diff_expression(context, None, expr, create_widget=True)
561 return app.application_start(context, view)
564 def cmd_fetch(args):
565 # TODO: the calls to update_status() can be done asynchronously
566 # by hooking into the message_updated notification.
567 from .widgets import remote # pylint: disable=all
569 context = app.application_init(args)
570 context.model.update_status()
571 view = remote.fetch(context)
572 return app.application_start(context, view)
575 def cmd_find(args):
576 from .widgets import finder # pylint: disable=all
578 context = app.application_init(args)
579 paths = core.list2cmdline(args.paths)
580 view = finder.finder(context, paths=paths)
581 return app.application_start(context, view)
584 def cmd_grep(args):
585 from .widgets import grep # pylint: disable=all
587 context = app.application_init(args)
588 text = core.list2cmdline(args.args)
589 view = grep.new_grep(context, text=text, parent=None)
590 return app.application_start(context, view)
593 def cmd_merge(args):
594 from .widgets.merge import Merge # pylint: disable=all
596 context = app.application_init(args, update=True)
597 view = Merge(context, parent=None, ref=args.ref)
598 return app.application_start(context, view)
601 def cmd_version(args):
602 from . import version # pylint: disable=all
604 version.print_version(brief=args.brief, build=args.build)
605 return 0
608 def cmd_pull(args):
609 from .widgets import remote # pylint: disable=all
611 context = app.application_init(args, update=True)
612 view = remote.pull(context)
613 if args.rebase:
614 view.set_rebase(True)
615 return app.application_start(context, view)
618 def cmd_push(args):
619 from .widgets import remote # pylint: disable=all
621 context = app.application_init(args, update=True)
622 view = remote.push(context)
623 return app.application_start(context, view)
626 def cmd_rebase(args):
627 kwargs = {
628 'verbose': args.verbose,
629 'quiet': args.quiet,
630 'autostash': args.autostash,
631 'fork_point': args.fork_point,
632 'onto': args.onto,
633 'preserve_merges': args.preserve_merges,
634 'strategy': args.strategy,
635 'no_ff': args.no_ff,
636 'merge': args.merge,
637 'exec': getattr(args, 'exec', None), # python keyword
638 'keep_empty': args.keep_empty,
639 'force_rebase': args.force_rebase,
640 'strategy_option': args.strategy_option,
641 'stat': args.stat,
642 'no_stat': args.no_stat,
643 'verify': args.verify,
644 'rerere_autoupdate': args.rerere_autoupdate,
645 'root': args.root,
646 'autosquash': args.autosquash,
647 'committer_date_is_author_date': args.committer_date_is_author_date,
648 'ignore_date': args.ignore_date,
649 'whitespace': args.whitespace,
650 'ignore_whitespace': args.ignore_whitespace,
651 'C': args.context_lines,
652 'continue': getattr(args, 'continue', False), # python keyword
653 'abort': args.abort,
654 'skip': args.skip,
655 'edit_todo': args.edit_todo,
656 'upstream': args.upstream,
657 'branch': args.branch,
659 context = app.application_init(args)
660 status, _, _ = cmds.do(cmds.Rebase, context, **kwargs)
661 return status
664 def cmd_recent(args):
665 from .widgets import recent # pylint: disable=all
667 context = app.application_init(args)
668 view = recent.browse_recent_files(context)
669 return app.application_start(context, view)
672 def cmd_remote(args):
673 from .widgets import editremotes # pylint: disable=all
675 context = app.application_init(args)
676 view = editremotes.editor(context, run=False)
677 return app.application_start(context, view)
680 def cmd_search(args):
681 from .widgets.search import search # pylint: disable=all
683 context = app.application_init(args)
684 view = search(context)
685 return app.application_start(context, view)
688 def cmd_stash(args):
689 from .widgets import stash # pylint: disable=all
691 context = app.application_init(args)
692 view = stash.view(context, show=False)
693 return app.application_start(context, view)
696 def cmd_tag(args):
697 from .widgets.createtag import new_create_tag # pylint: disable=all
699 context = app.application_init(args)
700 view = new_create_tag(context, name=args.name, ref=args.ref, sign=args.sign)
701 return app.application_start(context, view)
704 # Windows shortcut launch features:
705 def shortcut_launch():
706 """Launch from a shortcut
708 Prompt for the repository by default.
711 argv = sys.argv[1:]
712 if not argv:
713 argv = ['cola', '--prompt']
714 return app.winmain(main, argv)