doc: add 0xflotus to the credits
[git-cola.git] / cola / main.py
blob76b6ca2a075973cfcf999b275a1a6a2acfc03b7f
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 if argv is None:
13 argv = sys.argv[1:]
14 # we're using argparse with subparser, but argparse
15 # does not allow us to assign a default subparser
16 # when none has been specified. We fake it by injecting
17 # 'cola' into the command-line so that parse_args()
18 # routes them to the 'cola' parser by default.
19 help_commands = core.encode('--help-commands')
20 args = [core.encode(arg) for arg in argv]
21 if (not argv or
22 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 subparser = parser.add_subparsers(title='valid commands')
38 add_cola_command(subparser)
39 add_about_command(subparser)
40 add_am_command(subparser)
41 add_archive_command(subparser)
42 add_branch_command(subparser)
43 add_browse_command(subparser)
44 add_clone_command(subparser)
45 add_config_command(subparser)
46 add_dag_command(subparser)
47 add_diff_command(subparser)
48 add_fetch_command(subparser)
49 add_find_command(subparser)
50 add_grep_command(subparser)
51 add_merge_command(subparser)
52 add_pull_command(subparser)
53 add_push_command(subparser)
54 add_rebase_command(subparser)
55 add_recent_command(subparser)
56 add_remote_command(subparser)
57 add_search_command(subparser)
58 add_stash_command(subparser)
59 add_tag_command(subparser)
60 add_version_command(subparser)
62 return parser.parse_args(argv)
65 def add_command(parent, name, description, func):
66 parser = parent.add_parser(str(name), help=description)
67 parser.set_defaults(func=func)
68 app.add_common_arguments(parser)
69 return parser
72 def add_cola_command(subparser):
73 parser = add_command(subparser, 'cola', 'start git-cola', cmd_cola)
74 parser.add_argument('--amend', default=False, action='store_true',
75 help='start in amend mode')
76 parser.add_argument('--help-commands', default=False, action='store_true',
77 help='show available sub-commands')
78 parser.add_argument('--status-filter', '-s', metavar='<path>',
79 default='', help='status path filter')
82 def add_about_command(parent):
83 add_command(parent, 'about', 'about git-cola', cmd_about)
86 def add_am_command(parent):
87 parser = add_command(parent, 'am', 'apply patches using "git am"', cmd_am)
88 parser.add_argument('patches', metavar='<patches>', nargs='*',
89 help='patches to apply')
92 def add_archive_command(parent):
93 parser = add_command(parent, 'archive', 'save an archive', cmd_archive)
94 parser.add_argument('ref', metavar='<ref>', nargs='?', default=None,
95 help='commit to archive')
98 def add_branch_command(subparser):
99 add_command(subparser, 'branch', 'create a branch', cmd_branch)
102 def add_browse_command(subparser):
103 add_command(subparser, 'browse', 'browse repository', cmd_browse)
106 def add_clone_command(subparser):
107 add_command(subparser, 'clone', 'clone repository', cmd_clone)
110 def add_config_command(subparser):
111 add_command(subparser, 'config', 'edit configuration', cmd_config)
114 def add_dag_command(subparser):
115 parser = add_command(subparser, 'dag', 'start git-dag', cmd_dag)
116 parser.add_argument('-c', '--count', metavar='<count>',
117 type=int, default=1000,
118 help='number of commits to display')
119 parser.add_argument('--all', action='store_true', dest='show_all',
120 help='visualize all branches', default=False)
121 parser.add_argument('args', nargs='*', metavar='<args>',
122 help='git log arguments')
125 def add_diff_command(subparser):
126 parser = add_command(subparser, 'diff', 'view diffs', cmd_diff)
127 parser.add_argument('args', nargs='*', metavar='<args>',
128 help='git diff arguments')
131 def add_fetch_command(subparser):
132 add_command(subparser, 'fetch', 'fetch remotes', cmd_fetch)
135 def add_find_command(subparser):
136 parser = add_command(subparser, 'find', 'find files', cmd_find)
137 parser.add_argument('paths', nargs='*', metavar='<path>',
138 help='filter by path')
141 def add_grep_command(subparser):
142 parser = add_command(subparser, 'grep', 'grep source', cmd_grep)
143 parser.add_argument('args', nargs='*', metavar='<args>',
144 help='git grep arguments')
147 def add_merge_command(subparser):
148 parser = add_command(subparser, 'merge', 'merge branches', cmd_merge)
149 parser.add_argument('ref', nargs='?', metavar='<ref>',
150 help='branch, tag, or commit to merge')
153 def add_pull_command(subparser):
154 parser = add_command(subparser, 'pull', 'pull remote branches', cmd_pull)
155 parser.add_argument('--rebase', default=False, action='store_true',
156 help='rebase local branch when pulling')
159 def add_push_command(subparser):
160 add_command(subparser, 'push', 'push remote branches', cmd_push)
163 def add_rebase_command(subparser):
164 parser = add_command(subparser, 'rebase', 'interactive rebase', cmd_rebase)
165 parser.add_argument('-v', '--verbose', default=False, action='store_true',
166 help='display a diffstat of what changed upstream')
167 parser.add_argument('-q', '--quiet', default=False, action='store_true',
168 help='be quiet. implies --no-stat')
169 parser.add_argument('-i', '--interactive', default=True,
170 action='store_true', help=argparse.SUPPRESS)
171 parser.add_argument('--autostash', default=False, action='store_true',
172 help='automatically stash/stash pop before and after')
173 parser.add_argument('--fork-point', default=False, action='store_true',
174 help="use 'merge-base --fork-point' to refine upstream")
175 parser.add_argument('--onto', default=None, metavar='<newbase>',
176 help='rebase onto given branch instead of upstream')
177 parser.add_argument('-p', '--preserve-merges',
178 default=False, action='store_true',
179 help='try to recreate merges instead of ignoring them')
180 parser.add_argument('-s', '--strategy', default=None, metavar='<strategy>',
181 help='use the given merge strategy')
182 parser.add_argument('--no-ff', default=False, action='store_true',
183 help='cherry-pick all commits, even if unchanged')
184 parser.add_argument('-m', '--merge', default=False, action='store_true',
185 help='use merging strategies to rebase')
186 parser.add_argument('-x', '--exec', default=None,
187 help='add exec lines after each commit of '
188 'the editable list')
189 parser.add_argument('-k', '--keep-empty', default=False,
190 action='store_true',
191 help='preserve empty commits during rebase')
192 parser.add_argument('-f', '--force-rebase', default=False,
193 action='store_true',
194 help='force rebase even if branch is up to date')
195 parser.add_argument('-X', '--strategy-option', default=None,
196 metavar='<arg>',
197 help='pass the argument through to the merge strategy')
198 parser.add_argument('--stat', default=False, action='store_true',
199 help='display a diffstat of what changed upstream')
200 parser.add_argument('-n', '--no-stat', default=False, action='store_true',
201 help='do not show diffstat of what changed upstream')
202 parser.add_argument('--verify', default=False, action='store_true',
203 help='allow pre-rebase hook to run')
204 parser.add_argument('--rerere-autoupdate',
205 default=False, action='store_true',
206 help='allow rerere to update index with '
207 'resolved conflicts')
208 parser.add_argument('--root', default=False, action='store_true',
209 help='rebase all reachable commits up to the root(s)')
210 parser.add_argument('--autosquash', default=True, action='store_true',
211 help='move commits that begin with '
212 'squash!/fixup! under -i')
213 parser.add_argument('--no-autosquash', default=True, action='store_false',
214 dest='autosquash',
215 help='do not move commits that begin with '
216 'squash!/fixup! under -i')
217 parser.add_argument('--committer-date-is-author-date',
218 default=False, action='store_true',
219 help="passed to 'git am' by 'git rebase'")
220 parser.add_argument('--ignore-date', default=False, action='store_true',
221 help="passed to 'git am' by 'git rebase'")
222 parser.add_argument('--whitespace', default=False, action='store_true',
223 help="passed to 'git apply' by 'git rebase'")
224 parser.add_argument('--ignore-whitespace', default=False,
225 action='store_true',
226 help="passed to 'git apply' by 'git rebase'")
227 parser.add_argument('-C', dest='context_lines', default=None, metavar='<n>',
228 help="passed to 'git apply' by 'git rebase'")
230 actions = parser.add_argument_group('actions')
231 actions.add_argument('--continue', default=False, action='store_true',
232 help='continue')
233 actions.add_argument('--abort', default=False, action='store_true',
234 help='abort and check out the original branch')
235 actions.add_argument('--skip', default=False, action='store_true',
236 help='skip current patch and continue')
237 actions.add_argument('--edit-todo', default=False, action='store_true',
238 help='edit the todo list during an interactive rebase')
240 parser.add_argument('upstream', nargs='?', default=None,
241 metavar='<upstream>',
242 help='the upstream configured in branch.<name>.remote '
243 'and branch.<name>.merge options will be used '
244 'when <upstream> is omitted; see git-rebase(1) '
245 'for details. If you are currently not on any '
246 'branch or if the current branch does not have '
247 'a configured upstream, the rebase will abort')
248 parser.add_argument('branch', nargs='?', default=None, metavar='<branch>',
249 help='git rebase will perform an automatic '
250 '"git checkout <branch>" before doing anything '
251 'else when <branch> is specified')
254 def add_recent_command(subparser):
255 add_command(subparser, 'recent', 'edit recent files', cmd_recent)
258 def add_remote_command(subparser):
259 add_command(subparser, 'remote', 'edit remotes', cmd_remote)
262 def add_search_command(subparser):
263 add_command(subparser, 'search', 'search commits', cmd_search)
266 def add_stash_command(subparser):
267 add_command(subparser, 'stash', 'stash and unstash changes', cmd_stash)
270 def add_tag_command(subparser):
271 parser = add_command(subparser, 'tag', 'create tags', cmd_tag)
272 parser.add_argument('name', metavar='<name>', nargs='?', default=None,
273 help='tag name')
274 parser.add_argument('ref', metavar='<ref>', nargs='?', default=None,
275 help='commit to tag')
276 parser.add_argument('-s', '--sign', default=False, action='store_true',
277 help='annotated and GPG-signed tag')
280 def add_version_command(subparser):
281 parser = add_command(
282 subparser, 'version', 'print the version', cmd_version)
283 parser.add_argument('--brief', action='store_true', default=False,
284 help='print the version number only')
285 parser.add_argument('--build', action='store_true', default=False,
286 help='print the build version')
289 # entry points
290 def cmd_cola(args):
291 from .widgets.main import MainView
292 status_filter = args.status_filter
293 if status_filter:
294 status_filter = core.abspath(status_filter)
296 context = app.application_init(args)
298 context.timer.start('view')
299 view = MainView(context, settings=args.settings)
300 if args.amend:
301 cmds.do(cmds.AmendMode, context, amend=True)
303 if status_filter:
304 view.set_filter(core.relpath(status_filter))
306 context.timer.stop('view')
307 if args.perf:
308 context.timer.display('view')
310 return app.application_run(
311 context, view, start=start_cola, stop=app.default_stop)
314 def start_cola(context, view):
315 app.default_start(context, view)
316 view.start(context)
319 def cmd_about(args):
320 from .widgets import about
321 context = app.application_init(args)
322 view = about.about_dialog(context)
323 return app.application_start(context, view)
326 def cmd_am(args):
327 from .widgets.patch import new_apply_patches
328 context = app.application_init(args)
329 view = new_apply_patches(context, patches=args.patches)
330 return app.application_start(context, view)
333 def cmd_archive(args):
334 from .widgets import archive
335 context = app.application_init(args, update=True)
336 if args.ref is None:
337 args.ref = context.model.currentbranch
338 view = archive.Archive(context, args.ref)
339 return app.application_start(context, view)
342 def cmd_branch(args):
343 from .widgets.createbranch import create_new_branch
344 context = app.application_init(args, update=True)
345 view = create_new_branch(context, settings=args.settings)
346 return app.application_start(context, view)
349 def cmd_browse(args):
350 from .widgets.browse import worktree_browser
351 context = app.application_init(args)
352 view = worktree_browser(
353 context, show=False, update=False, settings=args.settings)
354 return app.application_start(context, view)
357 def cmd_clone(args):
358 from .widgets import clone
359 context = app.application_init(args)
360 view = clone.clone(context, settings=args.settings)
361 context.set_view(view)
362 result = 0 if view.exec_() == view.Accepted else 1
363 app.default_stop(context, view)
364 return result
367 def cmd_config(args):
368 from .widgets.prefs import preferences
369 context = app.application_init(args)
370 view = preferences(context)
371 return app.application_start(context, view)
374 def cmd_dag(args):
375 from .widgets import dag
376 context = app.application_init(args)
377 # cola.main() uses parse_args(), unlike dag.main() which uses
378 # parse_known_args(), thus we aren't able to automatically forward
379 # all unknown arguments. Special-case support for "--all" since it's
380 # used by the history viewer command on Windows.
381 if args.show_all:
382 args.args.insert(0, '--all')
383 view = dag.git_dag(context, args=args, settings=args.settings, show=False)
384 return app.application_start(context, view)
387 def cmd_diff(args):
388 from .difftool import diff_expression
389 context = app.application_init(args)
390 expr = core.list2cmdline(args.args)
391 view = diff_expression(context, None, expr, create_widget=True)
392 return app.application_start(context, view)
395 def cmd_fetch(args):
396 # TODO: the calls to update_status() can be done asynchronously
397 # by hooking into the message_updated notification.
398 from .widgets import remote
399 context = app.application_init(args)
400 context.model.update_status()
401 view = remote.fetch(context)
402 return app.application_start(context, view)
405 def cmd_find(args):
406 from .widgets import finder
407 context = app.application_init(args)
408 paths = core.list2cmdline(args.paths)
409 view = finder.finder(context, paths=paths)
410 return app.application_start(context, view)
413 def cmd_grep(args):
414 from .widgets import grep
415 context = app.application_init(args)
416 text = core.list2cmdline(args.args)
417 view = grep.new_grep(context, text=text, parent=None)
418 return app.application_start(context, view)
421 def cmd_merge(args):
422 from .widgets.merge import Merge
423 context = app.application_init(args, update=True)
424 view = Merge(context, parent=None, ref=args.ref)
425 return app.application_start(context, view)
428 def cmd_version(args):
429 from . import version
430 version.print_version(brief=args.brief, build=args.build)
431 return 0
434 def cmd_pull(args):
435 from .widgets import remote
436 context = app.application_init(args, update=True)
437 view = remote.pull(context)
438 if args.rebase:
439 view.set_rebase(True)
440 return app.application_start(context, view)
443 def cmd_push(args):
444 from .widgets import remote
445 context = app.application_init(args, update=True)
446 view = remote.push(context)
447 return app.application_start(context, view)
450 def cmd_rebase(args):
451 kwargs = {
452 'verbose': args.verbose,
453 'quiet': args.quiet,
454 'autostash': args.autostash,
455 'fork_point': args.fork_point,
456 'onto': args.onto,
457 'preserve_merges': args.preserve_merges,
458 'strategy': args.strategy,
459 'no_ff': args.no_ff,
460 'merge': args.merge,
461 'exec': getattr(args, 'exec', None), # python keyword
462 'keep_empty': args.keep_empty,
463 'force_rebase': args.force_rebase,
464 'strategy_option': args.strategy_option,
465 'stat': args.stat,
466 'no_stat': args.no_stat,
467 'verify': args.verify,
468 'rerere_autoupdate': args.rerere_autoupdate,
469 'root': args.root,
470 'autosquash': args.autosquash,
471 'committer_date_is_author_date': args.committer_date_is_author_date,
472 'ignore_date': args.ignore_date,
473 'whitespace': args.whitespace,
474 'ignore_whitespace': args.ignore_whitespace,
475 'C': args.context_lines,
476 'continue': getattr(args, 'continue', False), # python keyword
477 'abort': args.abort,
478 'skip': args.skip,
479 'edit_todo': args.edit_todo,
480 'upstream': args.upstream,
481 'branch': args.branch,
483 context = app.application_init(args)
484 status, _, _ = cmds.do(cmds.Rebase, context, **kwargs)
485 return status
488 def cmd_recent(args):
489 from .widgets import recent
490 context = app.application_init(args)
491 view = recent.browse_recent_files(context)
492 return app.application_start(context, view)
495 def cmd_remote(args):
496 from .widgets import editremotes
497 context = app.application_init(args)
498 view = editremotes.editor(context, run=False)
499 return app.application_start(context, view)
502 def cmd_search(args):
503 from .widgets.search import search
504 context = app.application_init(args)
505 view = search(context)
506 return app.application_start(context, view)
509 def cmd_stash(args):
510 from .widgets import stash
511 context = app.application_init(args)
512 view = stash.view(context, show=False)
513 return app.application_start(context, view)
516 def cmd_tag(args):
517 from .widgets.createtag import new_create_tag
518 context = app.application_init(args)
519 view = new_create_tag(
520 context, name=args.name, ref=args.ref, sign=args.sign,
521 settings=args.settings)
522 return app.application_start(context, view)
525 # Windows shortcut launch features:
526 def shortcut_launch():
527 """Launch from a shortcut
529 Prompt for the repository by default.
532 argv = sys.argv[1:]
533 if not argv:
534 argv = ['cola', '--prompt']
535 return app.winmain(main, argv)