CHANGES: mention the documentation improvements and typofixes
[git-cola.git] / cola / main.py
blobd93b1149efdad1c8f56525672d038d9365158712
1 """Launcher and command line interface to git-cola"""
2 from __future__ import absolute_import, division, print_function, unicode_literals
3 import argparse
4 import sys
6 from . import app
7 from . import cmds
8 from . import compat
9 from . import core
12 def main(argv=None):
13 app.initialize()
14 if argv is None:
15 argv = sys.argv[1:]
16 # we're using argparse with subparser, but argparse
17 # does not allow us to assign a default subparser
18 # when none has been specified. We fake it by injecting
19 # 'cola' into the command-line so that parse_args()
20 # routes them to the 'cola' parser by default.
21 help_commands = core.encode('--help-commands')
22 args = [core.encode(arg) for arg in argv]
23 if not argv or argv[0].startswith('-') and help_commands not in args:
24 argv.insert(0, 'cola')
25 elif help_commands in argv:
26 argv.append('--help')
27 args = parse_args(argv)
28 return args.func(args)
31 def winmain():
32 return app.winmain(main)
35 def parse_args(argv):
36 parser = argparse.ArgumentParser()
37 # Newer versions of argparse (Python 3.6+) emit an error message for
38 # "--help-commands" unless we register the flag on the main parser.
39 if compat.PY_VERSION >= (3, 6):
40 add_help_options(parser)
41 parser.set_defaults(func=lambda _: parser.print_help())
43 subparser = parser.add_subparsers(title='valid commands')
44 add_cola_command(subparser)
45 add_about_command(subparser)
46 add_am_command(subparser)
47 add_archive_command(subparser)
48 add_branch_command(subparser)
49 add_browse_command(subparser)
50 add_clone_command(subparser)
51 add_config_command(subparser)
52 add_dag_command(subparser)
53 add_diff_command(subparser)
54 add_fetch_command(subparser)
55 add_find_command(subparser)
56 add_grep_command(subparser)
57 add_merge_command(subparser)
58 add_pull_command(subparser)
59 add_push_command(subparser)
60 add_rebase_command(subparser)
61 add_recent_command(subparser)
62 add_remote_command(subparser)
63 add_search_command(subparser)
64 add_stash_command(subparser)
65 add_tag_command(subparser)
66 add_version_command(subparser)
68 return parser.parse_args(argv)
71 def add_help_options(parser):
72 """Add the --help-commands flag to the parser"""
73 parser.add_argument(
74 '--help-commands',
75 default=False,
76 action='store_true',
77 help='show available sub-commands',
81 def add_command(parent, name, description, func):
82 parser = parent.add_parser(str(name), help=description)
83 parser.set_defaults(func=func)
84 app.add_common_arguments(parser)
85 return parser
88 def add_cola_command(subparser):
89 parser = add_command(subparser, 'cola', 'start git-cola', cmd_cola)
90 parser.add_argument(
91 '--amend', default=False, action='store_true', help='start in amend mode'
93 add_help_options(parser)
94 parser.add_argument(
95 '--status-filter', '-s', metavar='<path>', default='', help='status path filter'
99 def add_about_command(parent):
100 add_command(parent, 'about', 'about git-cola', cmd_about)
103 def add_am_command(parent):
104 parser = add_command(parent, 'am', 'apply patches using "git am"', cmd_am)
105 parser.add_argument(
106 'patches', metavar='<patches>', nargs='*', help='patches to apply'
110 def add_archive_command(parent):
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_command(subparser, 'branch', 'create a branch', cmd_branch)
121 def add_browse_command(subparser):
122 add_command(subparser, 'browse', 'browse repository', cmd_browse)
125 def add_clone_command(subparser):
126 add_command(subparser, 'clone', 'clone repository', cmd_clone)
129 def add_config_command(subparser):
130 add_command(subparser, 'config', 'edit configuration', cmd_config)
133 def add_dag_command(subparser):
134 parser = add_command(subparser, 'dag', 'start git-dag', cmd_dag)
135 parser.add_argument(
136 '-c',
137 '--count',
138 metavar='<count>',
139 type=int,
140 default=1000,
141 help='number of commits to display',
143 parser.add_argument(
144 '--all',
145 action='store_true',
146 dest='show_all',
147 help='visualize all branches',
148 default=False,
150 parser.add_argument('args', nargs='*', metavar='<args>', help='git log arguments')
153 def add_diff_command(subparser):
154 parser = add_command(subparser, 'diff', 'view diffs', cmd_diff)
155 parser.add_argument('args', nargs='*', metavar='<args>', help='git diff arguments')
158 def add_fetch_command(subparser):
159 add_command(subparser, 'fetch', 'fetch remotes', cmd_fetch)
162 def add_find_command(subparser):
163 parser = add_command(subparser, 'find', 'find files', cmd_find)
164 parser.add_argument('paths', nargs='*', metavar='<path>', help='filter by path')
167 def add_grep_command(subparser):
168 parser = add_command(subparser, 'grep', 'grep source', cmd_grep)
169 parser.add_argument('args', nargs='*', metavar='<args>', help='git grep arguments')
172 def add_merge_command(subparser):
173 parser = add_command(subparser, 'merge', 'merge branches', cmd_merge)
174 parser.add_argument(
175 'ref', nargs='?', metavar='<ref>', help='branch, tag, or commit to merge'
179 def add_pull_command(subparser):
180 parser = add_command(subparser, 'pull', 'pull remote branches', cmd_pull)
181 parser.add_argument(
182 '--rebase',
183 default=False,
184 action='store_true',
185 help='rebase local branch when pulling',
189 def add_push_command(subparser):
190 add_command(subparser, 'push', 'push remote branches', cmd_push)
193 def add_rebase_command(subparser):
194 parser = add_command(subparser, 'rebase', 'interactive rebase', cmd_rebase)
195 parser.add_argument(
196 '-v',
197 '--verbose',
198 default=False,
199 action='store_true',
200 help='display a diffstat of what changed upstream',
202 parser.add_argument(
203 '-q',
204 '--quiet',
205 default=False,
206 action='store_true',
207 help='be quiet. implies --no-stat',
209 parser.add_argument(
210 '-i', '--interactive', default=True, action='store_true', help=argparse.SUPPRESS
212 parser.add_argument(
213 '--autostash',
214 default=False,
215 action='store_true',
216 help='automatically stash/stash pop before and after',
218 parser.add_argument(
219 '--fork-point',
220 default=False,
221 action='store_true',
222 help="use 'merge-base --fork-point' to refine upstream",
224 parser.add_argument(
225 '--onto',
226 default=None,
227 metavar='<newbase>',
228 help='rebase onto given branch instead of upstream',
230 parser.add_argument(
231 '-p',
232 '--preserve-merges',
233 default=False,
234 action='store_true',
235 help='try to recreate merges instead of ignoring them',
237 parser.add_argument(
238 '-s',
239 '--strategy',
240 default=None,
241 metavar='<strategy>',
242 help='use the given merge strategy',
244 parser.add_argument(
245 '--no-ff',
246 default=False,
247 action='store_true',
248 help='cherry-pick all commits, even if unchanged',
250 parser.add_argument(
251 '-m',
252 '--merge',
253 default=False,
254 action='store_true',
255 help='use merging strategies to rebase',
257 parser.add_argument(
258 '-x',
259 '--exec',
260 default=None,
261 help='add exec lines after each commit of ' 'the editable list',
263 parser.add_argument(
264 '-k',
265 '--keep-empty',
266 default=False,
267 action='store_true',
268 help='preserve empty commits during rebase',
270 parser.add_argument(
271 '-f',
272 '--force-rebase',
273 default=False,
274 action='store_true',
275 help='force rebase even if branch is up to date',
277 parser.add_argument(
278 '-X',
279 '--strategy-option',
280 default=None,
281 metavar='<arg>',
282 help='pass the argument through to the merge strategy',
284 parser.add_argument(
285 '--stat',
286 default=False,
287 action='store_true',
288 help='display a diffstat of what changed upstream',
290 parser.add_argument(
291 '-n',
292 '--no-stat',
293 default=False,
294 action='store_true',
295 help='do not show diffstat of what changed upstream',
297 parser.add_argument(
298 '--verify',
299 default=False,
300 action='store_true',
301 help='allow pre-rebase hook to run',
303 parser.add_argument(
304 '--rerere-autoupdate',
305 default=False,
306 action='store_true',
307 help='allow rerere to update index with ' 'resolved conflicts',
309 parser.add_argument(
310 '--root',
311 default=False,
312 action='store_true',
313 help='rebase all reachable commits up to the root(s)',
315 parser.add_argument(
316 '--autosquash',
317 default=True,
318 action='store_true',
319 help='move commits that begin with ' 'squash!/fixup! under -i',
321 parser.add_argument(
322 '--no-autosquash',
323 default=True,
324 action='store_false',
325 dest='autosquash',
326 help='do not move commits that begin with ' 'squash!/fixup! under -i',
328 parser.add_argument(
329 '--committer-date-is-author-date',
330 default=False,
331 action='store_true',
332 help="passed to 'git am' by 'git rebase'",
334 parser.add_argument(
335 '--ignore-date',
336 default=False,
337 action='store_true',
338 help="passed to 'git am' by 'git rebase'",
340 parser.add_argument(
341 '--whitespace',
342 default=False,
343 action='store_true',
344 help="passed to 'git apply' by 'git rebase'",
346 parser.add_argument(
347 '--ignore-whitespace',
348 default=False,
349 action='store_true',
350 help="passed to 'git apply' by 'git rebase'",
352 parser.add_argument(
353 '-C',
354 dest='context_lines',
355 default=None,
356 metavar='<n>',
357 help="passed to 'git apply' by 'git rebase'",
360 actions = parser.add_argument_group('actions')
361 actions.add_argument(
362 '--continue', default=False, action='store_true', help='continue'
364 actions.add_argument(
365 '--abort',
366 default=False,
367 action='store_true',
368 help='abort and check out the original branch',
370 actions.add_argument(
371 '--skip',
372 default=False,
373 action='store_true',
374 help='skip current patch and continue',
376 actions.add_argument(
377 '--edit-todo',
378 default=False,
379 action='store_true',
380 help='edit the todo list during an interactive rebase',
383 parser.add_argument(
384 'upstream',
385 nargs='?',
386 default=None,
387 metavar='<upstream>',
388 help='the upstream configured in branch.<name>.remote '
389 'and branch.<name>.merge options will be used '
390 'when <upstream> is omitted; see git-rebase(1) '
391 'for details. If you are currently not on any '
392 'branch or if the current branch does not have '
393 'a configured upstream, the rebase will abort',
395 parser.add_argument(
396 'branch',
397 nargs='?',
398 default=None,
399 metavar='<branch>',
400 help='git rebase will perform an automatic '
401 '"git checkout <branch>" before doing anything '
402 'else when <branch> is specified',
406 def add_recent_command(subparser):
407 add_command(subparser, 'recent', 'edit recent files', cmd_recent)
410 def add_remote_command(subparser):
411 add_command(subparser, 'remote', 'edit remotes', cmd_remote)
414 def add_search_command(subparser):
415 add_command(subparser, 'search', 'search commits', cmd_search)
418 def add_stash_command(subparser):
419 add_command(subparser, 'stash', 'stash and unstash changes', cmd_stash)
422 def add_tag_command(subparser):
423 parser = add_command(subparser, 'tag', 'create tags', cmd_tag)
424 parser.add_argument(
425 'name', metavar='<name>', nargs='?', default=None, help='tag name'
427 parser.add_argument(
428 'ref', metavar='<ref>', nargs='?', default=None, help='commit to tag'
430 parser.add_argument(
431 '-s',
432 '--sign',
433 default=False,
434 action='store_true',
435 help='annotated and GPG-signed tag',
439 def add_version_command(subparser):
440 parser = add_command(subparser, 'version', 'print the version', cmd_version)
441 parser.add_argument(
442 '--brief',
443 action='store_true',
444 default=False,
445 help='print the version number only',
447 parser.add_argument(
448 '--build', action='store_true', default=False, help='print the build version'
452 # entry points
453 def cmd_cola(args):
454 from .widgets.main import MainView # pylint: disable=all
456 status_filter = args.status_filter
457 if status_filter:
458 status_filter = core.abspath(status_filter)
460 context = app.application_init(args)
462 context.timer.start('view')
463 view = MainView(context)
464 if args.amend:
465 cmds.do(cmds.AmendMode, context, amend=True)
467 if status_filter:
468 view.set_filter(core.relpath(status_filter))
470 context.timer.stop('view')
471 if args.perf:
472 context.timer.display('view')
474 return app.application_run(context, view, start=start_cola, stop=app.default_stop)
477 def start_cola(context, view):
478 app.default_start(context, view)
479 view.start(context)
482 def cmd_about(args):
483 from .widgets import about # pylint: disable=all
485 context = app.application_init(args)
486 view = about.about_dialog(context)
487 return app.application_start(context, view)
490 def cmd_am(args):
491 from .widgets.patch import new_apply_patches # pylint: disable=all
493 context = app.application_init(args)
494 view = new_apply_patches(context, patches=args.patches)
495 return app.application_start(context, view)
498 def cmd_archive(args):
499 from .widgets import archive # pylint: disable=all
501 context = app.application_init(args, update=True)
502 if args.ref is None:
503 args.ref = context.model.currentbranch
504 view = archive.Archive(context, args.ref)
505 return app.application_start(context, view)
508 def cmd_branch(args):
509 from .widgets.createbranch import create_new_branch # pylint: disable=all
511 context = app.application_init(args, update=True)
512 view = create_new_branch(context)
513 return app.application_start(context, view)
516 def cmd_browse(args):
517 from .widgets.browse import worktree_browser # pylint: disable=all
519 context = app.application_init(args)
520 view = worktree_browser(context, show=False, update=False)
521 return app.application_start(context, view)
524 def cmd_clone(args):
525 from .widgets import clone # pylint: disable=all
527 context = app.application_init(args)
528 view = clone.clone(context)
529 context.set_view(view)
530 result = 0 if view.exec_() == view.Accepted else 1
531 app.default_stop(context, view)
532 return result
535 def cmd_config(args):
536 from .widgets.prefs import preferences # pylint: disable=all
538 context = app.application_init(args)
539 view = preferences(context)
540 return app.application_start(context, view)
543 def cmd_dag(args):
544 from .widgets import dag # pylint: disable=all
546 context = app.application_init(args)
547 # cola.main() uses parse_args(), unlike dag.main() which uses
548 # parse_known_args(), thus we aren't able to automatically forward
549 # all unknown arguments. Special-case support for "--all" since it's
550 # used by the history viewer command on Windows.
551 if args.show_all:
552 args.args.insert(0, '--all')
553 view = dag.git_dag(context, args=args, show=False)
554 return app.application_start(context, view)
557 def cmd_diff(args):
558 from .difftool import diff_expression # pylint: disable=all
560 context = app.application_init(args)
561 expr = core.list2cmdline(args.args)
562 view = diff_expression(context, None, expr, create_widget=True)
563 return app.application_start(context, view)
566 def cmd_fetch(args):
567 # TODO: the calls to update_status() can be done asynchronously
568 # by hooking into the message_updated notification.
569 from .widgets import remote # pylint: disable=all
571 context = app.application_init(args)
572 context.model.update_status()
573 view = remote.fetch(context)
574 return app.application_start(context, view)
577 def cmd_find(args):
578 from .widgets import finder # pylint: disable=all
580 context = app.application_init(args)
581 paths = core.list2cmdline(args.paths)
582 view = finder.finder(context, paths=paths)
583 return app.application_start(context, view)
586 def cmd_grep(args):
587 from .widgets import grep # pylint: disable=all
589 context = app.application_init(args)
590 text = core.list2cmdline(args.args)
591 view = grep.new_grep(context, text=text, parent=None)
592 return app.application_start(context, view)
595 def cmd_merge(args):
596 from .widgets.merge import Merge # pylint: disable=all
598 context = app.application_init(args, update=True)
599 view = Merge(context, parent=None, ref=args.ref)
600 return app.application_start(context, view)
603 def cmd_version(args):
604 from . import version # pylint: disable=all
606 version.print_version(brief=args.brief, build=args.build)
607 return 0
610 def cmd_pull(args):
611 from .widgets import remote # pylint: disable=all
613 context = app.application_init(args, update=True)
614 view = remote.pull(context)
615 if args.rebase:
616 view.set_rebase(True)
617 return app.application_start(context, view)
620 def cmd_push(args):
621 from .widgets import remote # pylint: disable=all
623 context = app.application_init(args, update=True)
624 view = remote.push(context)
625 return app.application_start(context, view)
628 def cmd_rebase(args):
629 kwargs = {
630 'verbose': args.verbose,
631 'quiet': args.quiet,
632 'autostash': args.autostash,
633 'fork_point': args.fork_point,
634 'onto': args.onto,
635 'preserve_merges': args.preserve_merges,
636 'strategy': args.strategy,
637 'no_ff': args.no_ff,
638 'merge': args.merge,
639 'exec': getattr(args, 'exec', None), # python keyword
640 'keep_empty': args.keep_empty,
641 'force_rebase': args.force_rebase,
642 'strategy_option': args.strategy_option,
643 'stat': args.stat,
644 'no_stat': args.no_stat,
645 'verify': args.verify,
646 'rerere_autoupdate': args.rerere_autoupdate,
647 'root': args.root,
648 'autosquash': args.autosquash,
649 'committer_date_is_author_date': args.committer_date_is_author_date,
650 'ignore_date': args.ignore_date,
651 'whitespace': args.whitespace,
652 'ignore_whitespace': args.ignore_whitespace,
653 'C': args.context_lines,
654 'continue': getattr(args, 'continue', False), # python keyword
655 'abort': args.abort,
656 'skip': args.skip,
657 'edit_todo': args.edit_todo,
658 'upstream': args.upstream,
659 'branch': args.branch,
661 context = app.application_init(args)
662 status, _, _ = cmds.do(cmds.Rebase, context, **kwargs)
663 return status
666 def cmd_recent(args):
667 from .widgets import recent # pylint: disable=all
669 context = app.application_init(args)
670 view = recent.browse_recent_files(context)
671 return app.application_start(context, view)
674 def cmd_remote(args):
675 from .widgets import editremotes # pylint: disable=all
677 context = app.application_init(args)
678 view = editremotes.editor(context, run=False)
679 return app.application_start(context, view)
682 def cmd_search(args):
683 from .widgets.search import search # pylint: disable=all
685 context = app.application_init(args)
686 view = search(context)
687 return app.application_start(context, view)
690 def cmd_stash(args):
691 from .widgets import stash # pylint: disable=all
693 context = app.application_init(args)
694 view = stash.view(context, show=False)
695 return app.application_start(context, view)
698 def cmd_tag(args):
699 from .widgets.createtag import new_create_tag # pylint: disable=all
701 context = app.application_init(args)
702 view = new_create_tag(context, name=args.name, ref=args.ref, sign=args.sign)
703 return app.application_start(context, view)
706 # Windows shortcut launch features:
707 def shortcut_launch():
708 """Launch from a shortcut
710 Prompt for the repository by default.
713 argv = sys.argv[1:]
714 if not argv:
715 argv = ['cola', '--prompt']
716 return app.winmain(main, argv)