setup: apply flake8 suggestions
[git-cola.git] / cola / main.py
blob1bdbf3f9f55581eadf63fe86b34762a111ddcd96
1 """Launcher and command line interface to git-cola"""
3 from __future__ import absolute_import, division, unicode_literals
5 import argparse
6 import os
7 import sys
9 from cola.app import add_common_arguments
10 from cola.app import application_init
11 from cola.app import application_start
13 # NOTE: these must be imported *after* cola.app.
14 # PyQt4 may not be available until after cola.app has gotten a chance to
15 # install the homebrew modules in sys.path.
16 from cola import cmds
17 from cola import compat
18 from cola import core
19 from cola import utils
22 def main(argv=None):
23 if argv is None:
24 argv = sys.argv[1:]
25 # we're using argparse with subparser, but argparse
26 # does not allow us to assign a default subparser
27 # when none has been specified. We fake it by injecting
28 # 'cola' into the command-line so that parse_args()
29 # routes them to the 'cola' parser by default.
30 if (len(argv) < 1 or
31 argv[0].startswith('-') and
32 '--help-commands' not in argv):
33 argv.insert(0, 'cola')
34 elif '--help-commands' in argv:
35 argv.append('--help')
36 args = parse_args(argv)
37 return args.func(args)
40 def parse_args(argv):
41 parser = argparse.ArgumentParser()
42 subparser = parser.add_subparsers(title='valid commands')
44 add_cola_command(subparser)
45 add_am_command(subparser)
46 add_archive_command(subparser)
47 add_branch_command(subparser)
48 add_browse_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_remote_command(subparser)
60 add_search_command(subparser)
61 add_stash_command(subparser)
62 add_tag_command(subparser)
63 add_version_command(subparser)
65 return parser.parse_args(argv)
68 def add_command(parent, name, description, func):
69 parser = parent.add_parser(str(name), help=description)
70 parser.set_defaults(func=func)
71 add_common_arguments(parser)
72 return parser
75 def add_cola_command(subparser):
76 parser = add_command(subparser, 'cola', 'start git-cola', cmd_cola)
77 parser.add_argument('--amend', default=False, action='store_true',
78 help='start in amend mode')
79 parser.add_argument('--help-commands', default=False, action='store_true',
80 help='show available sub-commands')
81 parser.add_argument('--status-filter', '-s', metavar='<path>',
82 default='', help='status path filter')
85 def add_am_command(parent):
86 parser = add_command(parent, 'am', 'apply patches using "git am"', cmd_am)
87 parser.add_argument('patches', metavar='<patches>', nargs='*',
88 help='patches to apply')
91 def add_archive_command(parent):
92 parser = add_command(parent, 'archive', 'save an archive', cmd_archive)
93 parser.add_argument('ref', metavar='<ref>', nargs='?', default=None,
94 help='SHA-1 to archive')
97 def add_branch_command(subparser):
98 add_command(subparser, 'branch', 'create a branch', cmd_branch)
101 def add_browse_command(subparser):
102 add_command(subparser, 'browse', 'browse repository', cmd_browse)
103 add_command(subparser, 'classic', 'browse repository', cmd_browse)
106 def add_config_command(subparser):
107 add_command(subparser, 'config', 'edit configuration', cmd_config)
110 def add_dag_command(subparser):
111 parser = add_command(subparser, 'dag', 'start git-dag', cmd_dag)
112 parser.add_argument('-c', '--count', metavar='<count>',
113 type=int, default=1000,
114 help='number of commits to display')
115 parser.add_argument('args', nargs='*', metavar='<args>',
116 help='git log arguments')
119 def add_diff_command(subparser):
120 parser = add_command(subparser, 'diff', 'view diffs', cmd_diff)
121 parser.add_argument('args', nargs='*', metavar='<args>',
122 help='git diff arguments')
125 def add_fetch_command(subparser):
126 add_command(subparser, 'fetch', 'fetch remotes', cmd_fetch)
129 def add_find_command(subparser):
130 parser = add_command(subparser, 'find', 'find files', cmd_find)
131 parser.add_argument('paths', nargs='*', metavar='<path>',
132 help='filter by path')
135 def add_grep_command(subparser):
136 parser = add_command(subparser, 'grep', 'grep source', cmd_grep)
137 parser.add_argument('args', nargs='*', metavar='<args>',
138 help='git grep arguments')
141 def add_merge_command(subparser):
142 add_command(subparser, 'merge', 'merge branches', cmd_merge)
145 def add_pull_command(subparser):
146 parser = add_command(subparser, 'pull', 'pull remote branches', cmd_pull)
147 parser.add_argument('--rebase', default=False, action='store_true',
148 help='rebase local branch when pulling')
151 def add_push_command(subparser):
152 add_command(subparser, 'push', 'push remote branches', cmd_push)
155 def add_rebase_command(subparser):
156 parser = add_command(subparser, 'rebase', 'interactive rebase', cmd_rebase)
157 parser.add_argument('-v', '--verbose', default=False, action='store_true',
158 help='display a diffstat of what changed upstream')
159 parser.add_argument('-q', '--quiet', default=False, action='store_true',
160 help='be quiet. implies --no-stat')
161 parser.add_argument('-i', '--interactive', default=True,
162 action='store_true', help=argparse.SUPPRESS)
163 parser.add_argument('--autostash', default=False, action='store_true',
164 help='automatically stash/stash pop before and after')
165 parser.add_argument('--fork-point', default=False, action='store_true',
166 help="use 'merge-base --fork-point' to refine upstream")
167 parser.add_argument('--onto', default=None, metavar='<newbase>',
168 help='rebase onto given branch instead of upstream')
169 parser.add_argument('-p', '--preserve-merges',
170 default=False, action='store_true',
171 help='try to recreate merges instead of ignoring them')
172 parser.add_argument('-s', '--strategy', default=None, metavar='<strategy>',
173 help='use the given merge strategy')
174 parser.add_argument('--no-ff', default=False, action='store_true',
175 help='cherry-pick all commits, even if unchanged')
176 parser.add_argument('-m', '--merge', default=False, action='store_true',
177 help='use merging strategies to rebase')
178 parser.add_argument('-x', '--exec', default=None,
179 help='add exec lines after each commit of '
180 'the editable list')
181 parser.add_argument('-k', '--keep-empty', default=False,
182 action='store_true',
183 help='preserve empty commits during rebase')
184 parser.add_argument('-f', '--force-rebase', default=False,
185 action='store_true',
186 help='force rebase even if branch is up to date')
187 parser.add_argument('-X', '--strategy-option', default=None,
188 metavar='<arg>',
189 help='pass the argument through to the merge strategy')
190 parser.add_argument('--stat', default=False, action='store_true',
191 help='display a diffstat of what changed upstream')
192 parser.add_argument('-n', '--no-stat', default=False, action='store_true',
193 help='do not show diffstat of what changed upstream')
194 parser.add_argument('--verify', default=False, action='store_true',
195 help='allow pre-rebase hook to run')
196 parser.add_argument('--rerere-autoupdate',
197 default=False, action='store_true',
198 help='allow rerere to update index with '
199 'resolved conflicts')
200 parser.add_argument('--root', default=False, action='store_true',
201 help='rebase all reachable commits up to the root(s)')
202 parser.add_argument('--autosquash', default=True, action='store_true',
203 help='move commits that begin with '
204 'squash!/fixup! under -i')
205 parser.add_argument('--no-autosquash', default=True, action='store_false',
206 dest='autosquash',
207 help='do not move commits that begin with '
208 'squash!/fixup! under -i')
209 parser.add_argument('--committer-date-is-author-date',
210 default=False, action='store_true',
211 help="passed to 'git am' by 'git rebase'")
212 parser.add_argument('--ignore-date', default=False, action='store_true',
213 help="passed to 'git am' by 'git rebase'")
214 parser.add_argument('--whitespace', default=False, action='store_true',
215 help="passed to 'git apply' by 'git rebase'")
216 parser.add_argument('--ignore-whitespace', default=False,
217 action='store_true',
218 help="passed to 'git apply' by 'git rebase'")
219 parser.add_argument('-C', dest='context_lines', default=None, metavar='<n>',
220 help="passed to 'git apply' by 'git rebase'")
222 actions = parser.add_argument_group('actions')
223 actions.add_argument('--continue', default=False, action='store_true',
224 help='continue')
225 actions.add_argument('--abort', default=False, action='store_true',
226 help='abort and check out the original branch')
227 actions.add_argument('--skip', default=False, action='store_true',
228 help='skip current patch and continue')
229 actions.add_argument('--edit-todo', default=False, action='store_true',
230 help='edit the todo list during an interactive rebase')
232 parser.add_argument('upstream', nargs='?', default=None,
233 metavar='<upstream>',
234 help='the upstream configured in branch.<name>.remote '
235 'and branch.<name>.merge options will be used '
236 'when <upstream> is omitted; see git-rebase(1) '
237 'for details. If you are currently not on any '
238 'branch or if the current branch does not have '
239 'a configured upstream, the rebase will abort')
240 parser.add_argument('branch', nargs='?', default=None, metavar='<branch>',
241 help='git rebase will perform an automatic '
242 '"git checkout <branch>" before doing anything '
243 'else when <branch> is specified')
246 def add_remote_command(subparser):
247 add_command(subparser, 'remote', 'edit remotes', cmd_remote)
250 def add_search_command(subparser):
251 add_command(subparser, 'search', 'search commits', cmd_search)
254 def add_stash_command(subparser):
255 add_command(subparser, 'stash', 'stash and unstash changes', cmd_stash)
258 def add_tag_command(subparser):
259 parser = add_command(subparser, 'tag', 'create tags', cmd_tag)
260 parser.add_argument('name', metavar='<name>', nargs='?', default=None,
261 help='tag name')
262 parser.add_argument('ref', metavar='<ref>', nargs='?', default=None,
263 help='SHA-1 to tag')
264 parser.add_argument('-s', '--sign', default=False, action='store_true',
265 help='annotated and GPG-signed tag')
268 def add_version_command(subparser):
269 parser = add_command(subparser, 'version', 'print the version', cmd_version)
270 parser.add_argument('--brief', action='store_true', default=False,
271 help='print the version number only')
274 # entry points
275 def cmd_cola(args):
276 status_filter = args.status_filter
277 if status_filter:
278 status_filter = core.abspath(status_filter)
280 context = application_init(args)
281 from cola.widgets.main import MainView
282 view = MainView(context.model, settings=args.settings)
283 if args.amend:
284 cmds.do(cmds.AmendMode, True)
286 if status_filter:
287 view.set_filter(core.relpath(status_filter))
289 return application_start(context, view)
292 def cmd_am(args):
293 context = application_init(args)
294 from cola.widgets.patch import new_apply_patches
295 view = new_apply_patches(patches=args.patches)
296 return application_start(context, view)
299 def cmd_archive(args):
300 context = application_init(args, update=True)
301 if args.ref is None:
302 args.ref = context.model.currentbranch
304 from cola.widgets.archive import GitArchiveDialog
305 view = GitArchiveDialog(args.ref)
306 return application_start(context, view)
309 def cmd_branch(args):
310 context = application_init(args, update=True)
311 from cola.widgets.createbranch import create_new_branch
312 view = create_new_branch(settings=args.settings)
313 return application_start(context, view)
316 def cmd_browse(args):
317 context = application_init(args)
318 from cola.widgets.browse import worktree_browser
319 view = worktree_browser(update=False, settings=args.settings)
320 return application_start(context, view)
323 def cmd_config(args):
324 context = application_init(args)
325 from cola.widgets.prefs import preferences
326 view = preferences()
327 return application_start(context, view)
330 def cmd_dag(args):
331 context = application_init(args)
332 from cola.widgets.dag import git_dag
333 view = git_dag(context.model, args=args, settings=args.settings)
334 return application_start(context, view)
337 def cmd_diff(args):
338 context = application_init(args)
339 from cola.difftool import diff_expression
340 expr = core.list2cmdline(args.args)
341 view = diff_expression(None, expr, create_widget=True)
342 return application_start(context, view)
345 def cmd_fetch(args):
346 # TODO: the calls to update_status() can be done asynchronously
347 # by hooking into the message_updated notification.
348 context = application_init(args)
349 from cola.widgets import remote
350 context.model.update_status()
351 view = remote.fetch()
352 return application_start(context, view)
355 def cmd_find(args):
356 context = application_init(args)
357 from cola.widgets import finder
358 paths = core.list2cmdline(args.paths)
359 view = finder.finder(paths=paths)
360 return application_start(context, view)
363 def cmd_grep(args):
364 context = application_init(args)
365 from cola.widgets import grep
366 text = core.list2cmdline(args.args)
367 view = grep.new_grep(text=text, parent=None)
368 return application_start(context, view)
371 def cmd_merge(args):
372 context = application_init(args, update=True)
373 from cola.widgets.merge import MergeView
374 view = MergeView(context.cfg, context.model, parent=None)
375 return application_start(context, view)
378 def cmd_version(args):
379 from cola import version
380 version.print_version(brief=args.brief)
381 return 0
384 def cmd_pull(args):
385 context = application_init(args, update=True)
386 from cola.widgets import remote
387 view = remote.pull()
388 if args.rebase:
389 view.set_rebase(True)
390 return application_start(context, view)
393 def cmd_push(args):
394 context = application_init(args, update=True)
395 from cola.widgets import remote
396 view = remote.push()
397 return application_start(context, view)
400 def cmd_rebase(args):
401 kwargs = {
402 'verbose': args.verbose,
403 'quiet': args.quiet,
404 'autostash': args.autostash,
405 'fork_point': args.fork_point,
406 'onto': args.onto,
407 'preserve_merges': args.preserve_merges,
408 'strategy': args.strategy,
409 'no_ff': args.no_ff,
410 'merge': args.merge,
411 'exec': getattr(args, 'exec', None), # python keyword
412 'keep_empty': args.keep_empty,
413 'force_rebase': args.force_rebase,
414 'strategy_option': args.strategy_option,
415 'stat': args.stat,
416 'no_stat': args.no_stat,
417 'verify': args.verify,
418 'rerere_autoupdate': args.rerere_autoupdate,
419 'root': args.root,
420 'autosquash': args.autosquash,
421 'committer_date_is_author_date': args.committer_date_is_author_date,
422 'ignore_date': args.ignore_date,
423 'whitespace': args.whitespace,
424 'ignore_whitespace': args.ignore_whitespace,
425 'C': args.context_lines,
426 'continue': getattr(args, 'continue', False), # python keyword
427 'abort': args.abort,
428 'skip': args.skip,
429 'edit_todo': args.edit_todo,
430 'upstream': args.upstream,
431 'branch': args.branch,
432 'capture_output': False,
434 status, out, err = cmds.do(cmds.Rebase, **kwargs)
435 if out:
436 core.stdout(out)
437 if err:
438 core.stderr(err)
439 return status
442 def cmd_remote(args):
443 context = application_init(args)
444 from cola.widgets import editremotes
445 view = editremotes.new_remote_editor()
446 return application_start(context, view)
449 def cmd_search(args):
450 context = application_init(args)
451 from cola.widgets.search import search
452 view = search()
453 return application_start(context, view)
456 def cmd_stash(args):
457 context = application_init(args)
458 from cola.widgets.stash import stash
459 view = stash()
460 return application_start(context, view)
463 def cmd_tag(args):
464 context = application_init(args)
465 from cola.widgets.createtag import new_create_tag
466 view = new_create_tag(name=args.name, ref=args.ref, sign=args.sign,
467 settings=args.settings)
468 return application_start(context, view)
471 # Windows shortcut launch features:
473 def find_git():
474 """Return the path of git.exe, or None if we can't find it."""
475 if not utils.is_win32():
476 return None # UNIX systems have git in their $PATH
478 # If the user wants to use a Git/bin/ directory from a non-standard
479 # directory then they can write its location into
480 # ~/.config/git-cola/git-bindir
481 git_bindir = os.path.expanduser(os.path.join('~', '.config', 'git-cola',
482 'git-bindir'))
483 if core.exists(git_bindir):
484 custom_path = core.read(git_bindir).strip()
485 if custom_path and core.exists(custom_path):
486 return custom_path
488 # Try to find Git's bin/ directory in one of the typical locations
489 pf = os.environ.get('ProgramFiles', 'C:\\Program Files')
490 pf32 = os.environ.get('ProgramFiles(x86)', 'C:\\Program Files (x86)')
491 for p in [pf32, pf, 'C:\\']:
492 candidate = os.path.join(p, 'Git\\bin')
493 if os.path.isdir(candidate):
494 return candidate
496 return None
499 def shortcut_launch():
500 """Launch from a shortcut
502 Prompt for the repository by default, and try to find git.
504 argv = ['cola', '--prompt']
505 git_path = find_git()
506 if git_path:
507 prepend_path(git_path)
509 return main(argv)
512 def prepend_path(path):
513 # Adds git to the PATH. This is needed on Windows.
514 path = core.decode(path)
515 path_entries = core.getenv('PATH', '').split(os.pathsep)
516 if path not in path_entries:
517 path_entries.insert(0, path)
518 compat.setenv('PATH', os.pathsep.join(path_entries))