filelist: use the sorted commit list to get the diff range
[git-cola.git] / cola / main.py
blobe7cac114010dc770e8cc5088a348817f5d9fed59
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 parse_args(argv):
32 parser = argparse.ArgumentParser()
33 # Newer versions of argparse (Python 3.6+) emit an error message for
34 # "--help-commands" unless we register the flag on the main parser.
35 if compat.PY_VERSION >= (3, 6):
36 add_help_options(parser)
37 parser.set_defaults(func=lambda _: parser.print_help())
39 subparser = parser.add_subparsers(title='valid commands')
40 add_cola_command(subparser)
41 add_about_command(subparser)
42 add_am_command(subparser)
43 add_archive_command(subparser)
44 add_branch_command(subparser)
45 add_browse_command(subparser)
46 add_clone_command(subparser)
47 add_config_command(subparser)
48 add_dag_command(subparser)
49 add_diff_command(subparser)
50 add_fetch_command(subparser)
51 add_find_command(subparser)
52 add_grep_command(subparser)
53 add_merge_command(subparser)
54 add_pull_command(subparser)
55 add_push_command(subparser)
56 add_rebase_command(subparser)
57 add_recent_command(subparser)
58 add_remote_command(subparser)
59 add_search_command(subparser)
60 add_stash_command(subparser)
61 add_tag_command(subparser)
62 add_version_command(subparser)
64 return parser.parse_args(argv)
67 def add_help_options(parser):
68 """Add the --help-commands flag to the parser"""
69 parser.add_argument(
70 '--help-commands',
71 default=False,
72 action='store_true',
73 help='show available sub-commands',
77 def add_command(parent, name, description, func):
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 parser = add_command(subparser, 'cola', 'start git-cola', cmd_cola)
86 parser.add_argument(
87 '--amend', default=False, action='store_true', help='start in amend mode'
89 add_help_options(parser)
90 parser.add_argument(
91 '--status-filter', '-s', metavar='<path>', default='', help='status path filter'
95 def add_about_command(parent):
96 add_command(parent, 'about', 'about git-cola', cmd_about)
99 def add_am_command(parent):
100 parser = add_command(parent, 'am', 'apply patches using "git am"', cmd_am)
101 parser.add_argument(
102 'patches', metavar='<patches>', nargs='*', help='patches to apply'
106 def add_archive_command(parent):
107 parser = add_command(parent, 'archive', 'save an archive', cmd_archive)
108 parser.add_argument(
109 'ref', metavar='<ref>', nargs='?', default=None, help='commit to archive'
113 def add_branch_command(subparser):
114 add_command(subparser, 'branch', 'create a branch', cmd_branch)
117 def add_browse_command(subparser):
118 add_command(subparser, 'browse', 'browse repository', cmd_browse)
121 def add_clone_command(subparser):
122 add_command(subparser, 'clone', 'clone repository', cmd_clone)
125 def add_config_command(subparser):
126 add_command(subparser, 'config', 'edit configuration', cmd_config)
129 def add_dag_command(subparser):
130 parser = add_command(subparser, 'dag', 'start git-dag', cmd_dag)
131 parser.add_argument(
132 '-c',
133 '--count',
134 metavar='<count>',
135 type=int,
136 default=1000,
137 help='number of commits to display',
139 parser.add_argument(
140 '--all',
141 action='store_true',
142 dest='show_all',
143 help='visualize all branches',
144 default=False,
146 parser.add_argument('args', nargs='*', metavar='<args>', help='git log arguments')
149 def add_diff_command(subparser):
150 parser = add_command(subparser, 'diff', 'view diffs', cmd_diff)
151 parser.add_argument('args', nargs='*', metavar='<args>', help='git diff arguments')
154 def add_fetch_command(subparser):
155 add_command(subparser, 'fetch', 'fetch remotes', cmd_fetch)
158 def add_find_command(subparser):
159 parser = add_command(subparser, 'find', 'find files', cmd_find)
160 parser.add_argument('paths', nargs='*', metavar='<path>', help='filter by path')
163 def add_grep_command(subparser):
164 parser = add_command(subparser, 'grep', 'grep source', cmd_grep)
165 parser.add_argument('args', nargs='*', metavar='<args>', help='git grep arguments')
168 def add_merge_command(subparser):
169 parser = add_command(subparser, 'merge', 'merge branches', cmd_merge)
170 parser.add_argument(
171 'ref', nargs='?', metavar='<ref>', help='branch, tag, or commit to merge'
175 def add_pull_command(subparser):
176 parser = add_command(subparser, 'pull', 'pull remote branches', cmd_pull)
177 parser.add_argument(
178 '--rebase',
179 default=False,
180 action='store_true',
181 help='rebase local branch when pulling',
185 def add_push_command(subparser):
186 add_command(subparser, 'push', 'push remote branches', cmd_push)
189 def add_rebase_command(subparser):
190 parser = add_command(subparser, 'rebase', 'interactive rebase', cmd_rebase)
191 parser.add_argument(
192 '-v',
193 '--verbose',
194 default=False,
195 action='store_true',
196 help='display a diffstat of what changed upstream',
198 parser.add_argument(
199 '-q',
200 '--quiet',
201 default=False,
202 action='store_true',
203 help='be quiet. implies --no-stat',
205 parser.add_argument(
206 '-i', '--interactive', default=True, action='store_true', help=argparse.SUPPRESS
208 parser.add_argument(
209 '--autostash',
210 default=False,
211 action='store_true',
212 help='automatically stash/stash pop before and after',
214 parser.add_argument(
215 '--fork-point',
216 default=False,
217 action='store_true',
218 help="use 'merge-base --fork-point' to refine upstream",
220 parser.add_argument(
221 '--onto',
222 default=None,
223 metavar='<newbase>',
224 help='rebase onto given branch instead of upstream',
226 parser.add_argument(
227 '-p',
228 '--preserve-merges',
229 default=False,
230 action='store_true',
231 help='try to recreate merges instead of ignoring them',
233 parser.add_argument(
234 '-s',
235 '--strategy',
236 default=None,
237 metavar='<strategy>',
238 help='use the given merge strategy',
240 parser.add_argument(
241 '--no-ff',
242 default=False,
243 action='store_true',
244 help='cherry-pick all commits, even if unchanged',
246 parser.add_argument(
247 '-m',
248 '--merge',
249 default=False,
250 action='store_true',
251 help='use merging strategies to rebase',
253 parser.add_argument(
254 '-x',
255 '--exec',
256 default=None,
257 help='add exec lines after each commit of ' 'the editable list',
259 parser.add_argument(
260 '-k',
261 '--keep-empty',
262 default=False,
263 action='store_true',
264 help='preserve empty commits during rebase',
266 parser.add_argument(
267 '-f',
268 '--force-rebase',
269 default=False,
270 action='store_true',
271 help='force rebase even if branch is up to date',
273 parser.add_argument(
274 '-X',
275 '--strategy-option',
276 default=None,
277 metavar='<arg>',
278 help='pass the argument through to the merge strategy',
280 parser.add_argument(
281 '--stat',
282 default=False,
283 action='store_true',
284 help='display a diffstat of what changed upstream',
286 parser.add_argument(
287 '-n',
288 '--no-stat',
289 default=False,
290 action='store_true',
291 help='do not show diffstat of what changed upstream',
293 parser.add_argument(
294 '--verify',
295 default=False,
296 action='store_true',
297 help='allow pre-rebase hook to run',
299 parser.add_argument(
300 '--rerere-autoupdate',
301 default=False,
302 action='store_true',
303 help='allow rerere to update index with ' 'resolved conflicts',
305 parser.add_argument(
306 '--root',
307 default=False,
308 action='store_true',
309 help='rebase all reachable commits up to the root(s)',
311 parser.add_argument(
312 '--autosquash',
313 default=True,
314 action='store_true',
315 help='move commits that begin with ' 'squash!/fixup! under -i',
317 parser.add_argument(
318 '--no-autosquash',
319 default=True,
320 action='store_false',
321 dest='autosquash',
322 help='do not move commits that begin with ' 'squash!/fixup! under -i',
324 parser.add_argument(
325 '--committer-date-is-author-date',
326 default=False,
327 action='store_true',
328 help="passed to 'git am' by 'git rebase'",
330 parser.add_argument(
331 '--ignore-date',
332 default=False,
333 action='store_true',
334 help="passed to 'git am' by 'git rebase'",
336 parser.add_argument(
337 '--whitespace',
338 default=False,
339 action='store_true',
340 help="passed to 'git apply' by 'git rebase'",
342 parser.add_argument(
343 '--ignore-whitespace',
344 default=False,
345 action='store_true',
346 help="passed to 'git apply' by 'git rebase'",
348 parser.add_argument(
349 '--update-refs',
350 default=False,
351 action='store_true',
352 help='update branches that point to commits that are being rebased',
354 parser.add_argument(
355 '-C',
356 dest='context_lines',
357 default=None,
358 metavar='<n>',
359 help="passed to 'git apply' by 'git rebase'",
362 actions = parser.add_argument_group('actions')
363 actions.add_argument(
364 '--continue', default=False, action='store_true', help='continue'
366 actions.add_argument(
367 '--abort',
368 default=False,
369 action='store_true',
370 help='abort and check out the original branch',
372 actions.add_argument(
373 '--skip',
374 default=False,
375 action='store_true',
376 help='skip current patch and continue',
378 actions.add_argument(
379 '--edit-todo',
380 default=False,
381 action='store_true',
382 help='edit the todo list during an interactive rebase',
385 parser.add_argument(
386 'upstream',
387 nargs='?',
388 default=None,
389 metavar='<upstream>',
390 help='the upstream configured in branch.<name>.remote '
391 'and branch.<name>.merge options will be used '
392 'when <upstream> is omitted; see git-rebase(1) '
393 'for details. If you are currently not on any '
394 'branch or if the current branch does not have '
395 'a configured upstream, the rebase will abort',
397 parser.add_argument(
398 'branch',
399 nargs='?',
400 default=None,
401 metavar='<branch>',
402 help='git rebase will perform an automatic '
403 '"git checkout <branch>" before doing anything '
404 'else when <branch> is specified',
408 def add_recent_command(subparser):
409 add_command(subparser, 'recent', 'edit recent files', cmd_recent)
412 def add_remote_command(subparser):
413 add_command(subparser, 'remote', 'edit remotes', cmd_remote)
416 def add_search_command(subparser):
417 add_command(subparser, 'search', 'search commits', cmd_search)
420 def add_stash_command(subparser):
421 add_command(subparser, 'stash', 'stash and unstash changes', cmd_stash)
424 def add_tag_command(subparser):
425 parser = add_command(subparser, 'tag', 'create tags', cmd_tag)
426 parser.add_argument(
427 'name', metavar='<name>', nargs='?', default=None, help='tag name'
429 parser.add_argument(
430 'ref', metavar='<ref>', nargs='?', default=None, help='commit to tag'
432 parser.add_argument(
433 '-s',
434 '--sign',
435 default=False,
436 action='store_true',
437 help='annotated and GPG-signed tag',
441 def add_version_command(subparser):
442 parser = add_command(subparser, 'version', 'print the version', cmd_version)
443 parser.add_argument(
444 '--brief',
445 action='store_true',
446 default=False,
447 help='print the version number only',
451 # entry points
452 def cmd_cola(args):
453 from .widgets.main import MainView # pylint: disable=all
455 status_filter = args.status_filter
456 if status_filter:
457 status_filter = core.abspath(status_filter)
459 context = app.application_init(args)
461 context.timer.start('view')
462 view = MainView(context)
463 if args.amend:
464 cmds.do(cmds.AmendMode, context, amend=True)
466 if status_filter:
467 view.set_filter(core.relpath(status_filter))
469 context.timer.stop('view')
470 if args.perf:
471 context.timer.display('view')
473 return app.application_run(context, view, start=start_cola, stop=app.default_stop)
476 def start_cola(context, view):
477 app.default_start(context, view)
478 view.start(context)
481 def cmd_about(args):
482 from .widgets import about # pylint: disable=all
484 context = app.application_init(args)
485 view = about.about_dialog(context)
486 return app.application_start(context, view)
489 def cmd_am(args):
490 from .widgets.patch import new_apply_patches # pylint: disable=all
492 context = app.application_init(args)
493 view = new_apply_patches(context, patches=args.patches)
494 return app.application_start(context, view)
497 def cmd_archive(args):
498 from .widgets import archive # pylint: disable=all
500 context = app.application_init(args, update=True)
501 if args.ref is None:
502 args.ref = context.model.currentbranch
503 view = archive.Archive(context, args.ref)
504 return app.application_start(context, view)
507 def cmd_branch(args):
508 from .widgets.createbranch import create_new_branch # pylint: disable=all
510 context = app.application_init(args, update=True)
511 view = create_new_branch(context)
512 return app.application_start(context, view)
515 def cmd_browse(args):
516 from .widgets.browse import worktree_browser # pylint: disable=all
518 context = app.application_init(args)
519 view = worktree_browser(context, show=False, update=False)
520 return app.application_start(context, view)
523 def cmd_clone(args):
524 from .widgets import clone # pylint: disable=all
526 context = app.application_init(args)
527 view = clone.clone(context)
528 context.set_view(view)
529 result = 0 if view.exec_() == view.Accepted else 1
530 app.default_stop(context, view)
531 return result
534 def cmd_config(args):
535 from .widgets.prefs import preferences # pylint: disable=all
537 context = app.application_init(args)
538 view = preferences(context)
539 return app.application_start(context, view)
542 def cmd_dag(args):
543 from .widgets import dag # pylint: disable=all
545 context = app.application_init(args)
546 # cola.main() uses parse_args(), unlike dag.main() which uses
547 # parse_known_args(), thus we aren't able to automatically forward
548 # all unknown arguments. Special-case support for "--all" since it's
549 # used by the history viewer command on Windows.
550 if args.show_all:
551 args.args.insert(0, '--all')
552 view = dag.git_dag(context, args=args, show=False)
553 return app.application_start(context, view)
556 def cmd_diff(args):
557 from .difftool import diff_expression # pylint: disable=all
559 context = app.application_init(args)
560 expr = core.list2cmdline(args.args)
561 view = diff_expression(context, None, expr, create_widget=True)
562 return app.application_start(context, view)
565 def cmd_fetch(args):
566 # TODO: the calls to update_status() can be done asynchronously
567 # by hooking into the message_updated notification.
568 from .widgets import remote # pylint: disable=all
570 context = app.application_init(args)
571 context.model.update_status()
572 view = remote.fetch(context)
573 return app.application_start(context, view)
576 def cmd_find(args):
577 from .widgets import finder # pylint: disable=all
579 context = app.application_init(args)
580 paths = core.list2cmdline(args.paths)
581 view = finder.finder(context, paths=paths)
582 return app.application_start(context, view)
585 def cmd_grep(args):
586 from .widgets import grep # pylint: disable=all
588 context = app.application_init(args)
589 text = core.list2cmdline(args.args)
590 view = grep.new_grep(context, text=text, parent=None)
591 return app.application_start(context, view)
594 def cmd_merge(args):
595 from .widgets.merge import Merge # pylint: disable=all
597 context = app.application_init(args, update=True)
598 view = Merge(context, parent=None, ref=args.ref)
599 return app.application_start(context, view)
602 def cmd_version(args):
603 from . import version # pylint: disable=all
605 version.print_version(brief=args.brief)
606 return 0
609 def cmd_pull(args):
610 from .widgets import remote # pylint: disable=all
612 context = app.application_init(args, update=True)
613 view = remote.pull(context)
614 if args.rebase:
615 view.set_rebase(True)
616 return app.application_start(context, view)
619 def cmd_push(args):
620 from .widgets import remote # pylint: disable=all
622 context = app.application_init(args, update=True)
623 view = remote.push(context)
624 return app.application_start(context, view)
627 def cmd_rebase(args):
628 kwargs = {
629 'verbose': args.verbose,
630 'quiet': args.quiet,
631 'autostash': args.autostash,
632 'fork_point': args.fork_point,
633 'onto': args.onto,
634 'preserve_merges': args.preserve_merges,
635 'strategy': args.strategy,
636 'no_ff': args.no_ff,
637 'merge': args.merge,
638 'exec': getattr(args, 'exec', None), # python keyword
639 'keep_empty': args.keep_empty,
640 'force_rebase': args.force_rebase,
641 'strategy_option': args.strategy_option,
642 'stat': args.stat,
643 'no_stat': args.no_stat,
644 'verify': args.verify,
645 'rerere_autoupdate': args.rerere_autoupdate,
646 'root': args.root,
647 'autosquash': args.autosquash,
648 'committer_date_is_author_date': args.committer_date_is_author_date,
649 'ignore_date': args.ignore_date,
650 'whitespace': args.whitespace,
651 'ignore_whitespace': args.ignore_whitespace,
652 'C': args.context_lines,
653 'continue': getattr(args, 'continue', False), # python keyword
654 'abort': args.abort,
655 'skip': args.skip,
656 'edit_todo': args.edit_todo,
657 'update_refs': args.update_refs,
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 main(argv=argv)