createbranch: save and restore window settings
[git-cola.git] / cola / main.py
blob317783819591ee5b75d25f8d793158a4f62e8c3e
1 """Launcher and command line interface to git-cola"""
2 import argparse
3 import os
4 import subprocess
5 import sys
7 from cola.app import add_common_arguments
8 from cola.app import application_init
9 from cola.app import application_start
11 # NOTE: these must be imported *after* cola.app.
12 # PyQt4 may not be available until after cola.app has gotten a chance to
13 # install the homebrew modules in sys.path.
14 from cola import cmds
15 from cola import compat
16 from cola import core
17 from cola import utils
20 def main(argv=None):
21 if argv is None:
22 argv = sys.argv[1:]
23 # we're using argparse with subparser, but argparse
24 # does not allow us to assign a default subparser
25 # when none has been specified. We fake it by injecting
26 # 'cola' into the command-line so that parse_args()
27 # routes them to the 'cola' parser by default.
28 if (len(argv) < 1 or
29 argv[0].startswith('-') and
30 '--help-commands' not in argv):
31 argv.insert(0, 'cola')
32 elif '--help-commands' in argv:
33 argv.append('--help')
34 args = parse_args(argv)
35 return args.func(args)
38 def parse_args(argv):
39 parser = argparse.ArgumentParser()
40 subparser = parser.add_subparsers(title='valid commands')
42 add_cola_command(subparser)
43 add_am_command(subparser)
44 add_archive_command(subparser)
45 add_branch_command(subparser)
46 add_browse_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_remote_command(subparser)
58 add_search_command(subparser)
59 add_stash_command(subparser)
60 add_tag_command(subparser)
61 add_version_command(subparser)
63 return parser.parse_args(argv)
66 def add_command(parent, name, description, func):
67 parser = parent.add_parser(str(name), help=description)
68 parser.set_defaults(func=func)
69 add_common_arguments(parser)
70 return parser
73 def add_cola_command(subparser):
74 parser = add_command(subparser, 'cola', 'start git-cola', cmd_cola)
75 parser.add_argument('--amend', default=False, action='store_true',
76 help='start in amend mode')
77 parser.add_argument('--help-commands', default=False, action='store_true',
78 help='show available sub-commands')
79 parser.add_argument('--status-filter', '-s', metavar='<path>',
80 default='', help='status path filter')
83 def add_am_command(parent):
84 parser = add_command(parent, 'am', 'apply patches using "git am"', cmd_am)
85 parser.add_argument('patches', metavar='<patches>', nargs='*',
86 help='patches to apply')
89 def add_archive_command(parent):
90 parser = add_command(parent, 'archive', 'save an archive', cmd_archive)
91 parser.add_argument('ref', metavar='<ref>', nargs='?', default=None,
92 help='SHA-1 to archive')
95 def add_branch_command(subparser):
96 add_command(subparser, 'branch', 'create a branch', cmd_branch)
99 def add_browse_command(subparser):
100 add_command(subparser, 'browse', 'browse repository', cmd_browse)
101 add_command(subparser, 'classic', 'browse repository', cmd_browse)
104 def add_config_command(subparser):
105 add_command(subparser, 'config', 'edit configuration', cmd_config)
108 def add_dag_command(subparser):
109 parser = add_command(subparser, 'dag', 'start git-dag', cmd_dag)
110 parser.add_argument('-c', '--count', metavar='<count>',
111 type=int, default=1000,
112 help='number of commits to display')
113 parser.add_argument('args', nargs='*', metavar='<args>',
114 help='git log arguments')
116 def add_diff_command(subparser):
117 parser = add_command(subparser, 'diff', 'view diffs', cmd_diff)
118 parser.add_argument('args', nargs='*', metavar='<args>',
119 help='git diff arguments')
122 def add_fetch_command(subparser):
123 add_command(subparser, 'fetch', 'fetch remotes', cmd_fetch)
126 def add_find_command(subparser):
127 parser = add_command(subparser, 'find', 'find files', cmd_find)
128 parser.add_argument('paths', nargs='*', metavar='<path>',
129 help='filter by path')
132 def add_grep_command(subparser):
133 parser = add_command(subparser, 'grep', 'grep source', cmd_grep)
134 parser.add_argument('args', nargs='*', metavar='<args>',
135 help='git grep arguments')
137 def add_merge_command(subparser):
138 add_command(subparser, 'merge', 'merge branches', cmd_merge)
141 def add_pull_command(subparser):
142 parser = add_command(subparser, 'pull', 'pull remote branches', cmd_pull)
143 parser.add_argument('--rebase', default=False, action='store_true',
144 help='rebase local branch when pulling')
147 def add_push_command(subparser):
148 add_command(subparser, 'push', 'push remote branches', cmd_push)
151 def add_rebase_command(subparser):
152 parser = add_command(subparser, 'rebase', 'interactive rebase', cmd_rebase)
153 parser.add_argument('-v', '--verbose', default=False, action='store_true',
154 help='display a diffstat of what changed upstream')
155 parser.add_argument('-q', '--quiet', default=False, action='store_true',
156 help='be quiet. implies --no-stat')
157 parser.add_argument('-i', '--interactive', default=True, action='store_true',
158 help=argparse.SUPPRESS)
159 parser.add_argument('--autostash', default=False, action='store_true',
160 help='automatically stash/stash pop before and after')
161 parser.add_argument('--fork-point', default=False, action='store_true',
162 help="use 'merge-base --fork-point' to refine upstream")
163 parser.add_argument('--onto', default=None, metavar='<newbase>',
164 help='rebase onto given branch instead of upstream')
165 parser.add_argument('-p', '--preserve-merges',
166 default=False, action='store_true',
167 help='try to recreate merges instead of ignoring them')
168 parser.add_argument('-s', '--strategy', default=None, metavar='<strategy>',
169 help='use the given merge strategy')
170 parser.add_argument('--no-ff', default=False, action='store_true',
171 help='cherry-pick all commits, even if unchanged')
172 parser.add_argument('-m', '--merge', default=False, action='store_true',
173 help='use merging strategies to rebase')
174 parser.add_argument('-x', '--exec', default=None,
175 help='add exec lines after each commit of the editable list')
176 parser.add_argument('-k', '--keep-empty', default=False, action='store_true',
177 help='preserve empty commits during rebase')
178 parser.add_argument('-f', '--force-rebase', default=False, action='store_true',
179 help='force rebase even if branch is up to date')
180 parser.add_argument('-X', '--strategy-option', default=None, metavar='<arg>',
181 help='pass the argument through to the merge strategy')
182 parser.add_argument('--stat', default=False, action='store_true',
183 help='display a diffstat of what changed upstream')
184 parser.add_argument('-n', '--no-stat', default=False, action='store_true',
185 help='do not show diffstat of what changed upstream')
186 parser.add_argument('--verify', default=False, action='store_true',
187 help='allow pre-rebase hook to run')
188 parser.add_argument('--rerere-autoupdate',
189 default=False, action='store_true',
190 help='allow rerere to update index with '
191 'resolved conflicts')
192 parser.add_argument('--root', default=False, action='store_true',
193 help='rebase all reachable commits up to the root(s)')
194 parser.add_argument('--autosquash', default=True, action='store_true',
195 help='move commits that begin with '
196 'squash!/fixup! under -i')
197 parser.add_argument('--no-autosquash', default=True, action='store_false',
198 dest='autosquash',
199 help='do not move commits that begin with '
200 'squash!/fixup! under -i')
201 parser.add_argument('--committer-date-is-author-date',
202 default=False, action='store_true',
203 help="passed to 'git am' by 'git rebase'")
204 parser.add_argument('--ignore-date', default=False, action='store_true',
205 help="passed to 'git am' by 'git rebase'")
206 parser.add_argument('--whitespace', default=False, action='store_true',
207 help="passed to 'git apply' by 'git rebase'")
208 parser.add_argument('--ignore-whitespace', default=False, action='store_true',
209 help="passed to 'git apply' by 'git rebase'")
210 parser.add_argument('-C', dest='context_lines', default=None, metavar='<n>',
211 help="passed to 'git apply' by 'git rebase'")
213 actions = parser.add_argument_group('actions')
214 actions.add_argument('--continue', default=False, action='store_true',
215 help='continue')
216 actions.add_argument('--abort', default=False, action='store_true',
217 help='abort and check out the original branch')
218 actions.add_argument('--skip', default=False, action='store_true',
219 help='skip current patch and continue')
220 actions.add_argument('--edit-todo', default=False, action='store_true',
221 help='edit the todo list during an interactive rebase')
223 parser.add_argument('upstream', nargs='?', default=None, metavar='<upstream>',
224 help='the upstream configured in branch.<name>.remote '
225 'and branch.<name>.merge options will be used '
226 'when <upstream> is omitted; see git-rebase(1) '
227 'for details. If you are currently not on any '
228 'branch or if the current branch does not have '
229 'a configured upstream, the rebase will abort')
230 parser.add_argument('branch', nargs='?', default=None, metavar='<branch>',
231 help='git rebase will perform an automatic '
232 '"git checkout <branch>" before doing anything '
233 'else when <branch> is specified')
236 def add_remote_command(subparser):
237 add_command(subparser, 'remote', 'edit remotes', cmd_remote)
240 def add_search_command(subparser):
241 add_command(subparser, 'search', 'search commits', cmd_search)
244 def add_stash_command(subparser):
245 add_command(subparser, 'stash', 'stash and unstash changes', cmd_stash)
248 def add_tag_command(subparser):
249 parser = add_command(subparser, 'tag', 'create tags', cmd_tag)
250 parser.add_argument('name', metavar='<name>', nargs='?', default=None,
251 help='tag name')
252 parser.add_argument('ref', metavar='<ref>', nargs='?', default=None,
253 help='SHA-1 to tag')
254 parser.add_argument('-s', '--sign', default=False, action='store_true',
255 help='annotated and GPG-signed tag')
257 def add_version_command(subparser):
258 parser = add_command(subparser, 'version', 'print the version', cmd_version)
259 parser.add_argument('--brief', action='store_true', default=False,
260 help='print the version number only')
262 # entry points
264 def cmd_cola(args):
265 status_filter = args.status_filter
266 if status_filter:
267 status_filter = core.abspath(status_filter)
269 context = application_init(args)
270 from cola.widgets.main import MainView
271 view = MainView(context.model, settings=args.settings)
272 if args.amend:
273 cmds.do(cmds.AmendMode, True)
275 if status_filter:
276 view.set_filter(core.relpath(status_filter))
278 return application_start(context, view)
281 def cmd_am(args):
282 context = application_init(args)
283 from cola.widgets.patch import new_apply_patches
284 view = new_apply_patches(patches=args.patches)
285 return application_start(context, view)
288 def cmd_archive(args):
289 context = application_init(args, update=True)
290 if args.ref is None:
291 args.ref = context.model.currentbranch
293 from cola.widgets.archive import GitArchiveDialog
294 view = GitArchiveDialog(args.ref)
295 return application_start(context, view)
298 def cmd_branch(args):
299 context = application_init(args, update=True)
300 from cola.widgets.createbranch import create_new_branch
301 view = create_new_branch(settings=args.settings)
302 return application_start(context, view)
305 def cmd_browse(args):
306 context = application_init(args)
307 from cola.widgets.browse import worktree_browser
308 view = worktree_browser(update=False, settings=args.settings)
309 return application_start(context, view)
312 def cmd_config(args):
313 context = application_init(args)
314 from cola.widgets.prefs import preferences
315 view = preferences()
316 return application_start(context, view)
319 def cmd_dag(args):
320 context = application_init(args)
321 from cola.widgets.dag import git_dag
322 view = git_dag(context.model, args=args, settings=args.settings)
323 return application_start(context, view)
326 def cmd_diff(args):
327 context = application_init(args)
328 from cola.difftool import diff_expression
329 expr = subprocess.list2cmdline(map(core.decode, args.args))
330 view = diff_expression(None, expr, create_widget=True)
331 return application_start(context, view)
334 def cmd_fetch(args):
335 # TODO: the calls to update_status() can be done asynchronously
336 # by hooking into the message_updated notification.
337 context = application_init(args)
338 from cola.widgets import remote
339 context.model.update_status()
340 view = remote.fetch()
341 return application_start(context, view)
344 def cmd_find(args):
345 context = application_init(args)
346 from cola.widgets import finder
347 paths = subprocess.list2cmdline(map(core.decode, args.paths))
348 view = finder.finder(paths=paths)
349 return application_start(context, view)
352 def cmd_grep(args):
353 context = application_init(args)
354 from cola.widgets import grep
355 text = subprocess.list2cmdline(map(core.decode, args.args))
356 view = grep.new_grep(text=text, parent=None)
357 return application_start(context, view)
360 def cmd_merge(args):
361 context = application_init(args, update=True)
362 from cola.widgets.merge import MergeView
363 view = MergeView(context.cfg, context.model, parent=None)
364 return application_start(context, view)
367 def cmd_version(args):
368 from cola import version
369 version.print_version(brief=args.brief)
370 return 0
373 def cmd_pull(args):
374 context = application_init(args, update=True)
375 from cola.widgets import remote
376 view = remote.pull()
377 if args.rebase:
378 view.set_rebase(True)
379 return application_start(context, view)
382 def cmd_push(args):
383 context = application_init(args, update=True)
384 from cola.widgets import remote
385 view = remote.push()
386 return application_start(context, view)
389 def cmd_rebase(args):
390 kwargs = {
391 'verbose': args.verbose,
392 'quiet': args.quiet,
393 'autostash': args.autostash,
394 'fork_point': args.fork_point,
395 'onto': args.onto,
396 'preserve_merges': args.preserve_merges,
397 'strategy': args.strategy,
398 'no_ff': args.no_ff,
399 'merge': args.merge,
400 'exec': getattr(args, 'exec', None), # python keyword
401 'keep_empty': args.keep_empty,
402 'force_rebase': args.force_rebase,
403 'strategy_option': args.strategy_option,
404 'stat': args.stat,
405 'no_stat': args.no_stat,
406 'verify': args.verify,
407 'rerere_autoupdate': args.rerere_autoupdate,
408 'root': args.root,
409 'autosquash': args.autosquash,
410 'committer_date_is_author_date': args.committer_date_is_author_date,
411 'ignore_date': args.ignore_date,
412 'whitespace': args.whitespace,
413 'ignore_whitespace': args.ignore_whitespace,
414 'C': args.context_lines,
415 'continue': getattr(args, 'continue', False), # python keyword
416 'abort': args.abort,
417 'skip': args.skip,
418 'edit_todo': args.edit_todo,
419 'upstream': args.upstream,
420 'branch': args.branch,
421 'capture_output': False,
423 status, out, err = cmds.do(cmds.Rebase, **kwargs)
424 if out:
425 core.stdout(out)
426 if err:
427 core.stderr(err)
428 return status
431 def cmd_remote(args):
432 context = application_init(args)
433 from cola.widgets import editremotes
434 view = editremotes.new_remote_editor()
435 return application_start(context, view)
438 def cmd_search(args):
439 context = application_init(args)
440 from cola.widgets.search import search
441 view = search()
442 return application_start(context, view)
445 def cmd_stash(args):
446 context = application_init(args)
447 from cola.widgets.stash import stash
448 view = stash()
449 return application_start(context, view)
452 def cmd_tag(args):
453 context = application_init(args)
454 from cola.widgets.createtag import new_create_tag
455 view = new_create_tag(name=args.name, ref=args.ref, sign=args.sign,
456 settings=args.settings)
457 return application_start(context, view)
460 # Windows shortcut launch features:
462 def find_git():
463 """Return the path of git.exe, or None if we can't find it."""
464 if not utils.is_win32():
465 return None # UNIX systems have git in their $PATH
467 # If the user wants to use a Git/bin/ directory from a non-standard
468 # directory then they can write its location into
469 # ~/.config/git-cola/git-bindir
470 git_bindir = os.path.expanduser(os.path.join('~', '.config', 'git-cola',
471 'git-bindir'))
472 if core.exists(git_bindir):
473 custom_path = core.read(git_bindir).strip()
474 if custom_path and core.exists(custom_path):
475 return custom_path
477 # Try to find Git's bin/ directory in one of the typical locations
478 pf = os.environ.get('ProgramFiles', 'C:\\Program Files')
479 pf32 = os.environ.get('ProgramFiles(x86)', 'C:\\Program Files (x86)')
480 for p in [pf32, pf, 'C:\\']:
481 candidate = os.path.join(p, 'Git\\bin')
482 if os.path.isdir(candidate):
483 return candidate
485 return None
488 def shortcut_launch():
489 """Launch from a shortcut
491 Prompt for the repository by default, and try to find git.
493 argv = ['cola', '--prompt']
494 git_path = find_git()
495 if git_path:
496 prepend_path(git_path)
498 return main(argv)
501 def prepend_path(path):
502 # Adds git to the PATH. This is needed on Windows.
503 path = core.decode(path)
504 path_entries = core.getenv('PATH', '').split(os.pathsep)
505 if path not in path_entries:
506 path_entries.insert(0, path)
507 compat.setenv('PATH', os.pathsep.join(path_entries))