1 """Launcher and command line interface to git-cola"""
3 from __future__
import absolute_import
, division
, unicode_literals
9 from .app
import add_common_arguments
10 from .app
import application_init
11 from .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.
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.
31 argv
[0].startswith('-') and
32 '--help-commands' not in argv
):
33 argv
.insert(0, 'cola')
34 elif '--help-commands' in argv
:
36 args
= parse_args(argv
)
37 return args
.func(args
)
41 parser
= argparse
.ArgumentParser()
42 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_config_command(subparser
)
51 add_dag_command(subparser
)
52 add_diff_command(subparser
)
53 add_fetch_command(subparser
)
54 add_find_command(subparser
)
55 add_grep_command(subparser
)
56 add_merge_command(subparser
)
57 add_pull_command(subparser
)
58 add_push_command(subparser
)
59 add_rebase_command(subparser
)
60 add_remote_command(subparser
)
61 add_search_command(subparser
)
62 add_stash_command(subparser
)
63 add_tag_command(subparser
)
64 add_version_command(subparser
)
66 return parser
.parse_args(argv
)
69 def add_command(parent
, name
, description
, func
):
70 parser
= parent
.add_parser(str(name
), help=description
)
71 parser
.set_defaults(func
=func
)
72 add_common_arguments(parser
)
76 def add_cola_command(subparser
):
77 parser
= add_command(subparser
, 'cola', 'start git-cola', cmd_cola
)
78 parser
.add_argument('--amend', default
=False, action
='store_true',
79 help='start in amend mode')
80 parser
.add_argument('--help-commands', default
=False, action
='store_true',
81 help='show available sub-commands')
82 parser
.add_argument('--status-filter', '-s', metavar
='<path>',
83 default
='', help='status path filter')
86 def add_about_command(parent
):
87 parser
= add_command(parent
, 'about', 'about git-cola', cmd_about
)
89 def add_am_command(parent
):
90 parser
= add_command(parent
, 'am', 'apply patches using "git am"', cmd_am
)
91 parser
.add_argument('patches', metavar
='<patches>', nargs
='*',
92 help='patches to apply')
95 def add_archive_command(parent
):
96 parser
= add_command(parent
, 'archive', 'save an archive', cmd_archive
)
97 parser
.add_argument('ref', metavar
='<ref>', nargs
='?', default
=None,
98 help='commit to archive')
101 def add_branch_command(subparser
):
102 add_command(subparser
, 'branch', 'create a branch', cmd_branch
)
105 def add_browse_command(subparser
):
106 add_command(subparser
, 'browse', 'browse repository', cmd_browse
)
107 add_command(subparser
, 'classic', 'browse repository', cmd_browse
)
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('args', nargs
='*', metavar
='<args>',
120 help='git log arguments')
123 def add_diff_command(subparser
):
124 parser
= add_command(subparser
, 'diff', 'view diffs', cmd_diff
)
125 parser
.add_argument('args', nargs
='*', metavar
='<args>',
126 help='git diff arguments')
129 def add_fetch_command(subparser
):
130 add_command(subparser
, 'fetch', 'fetch remotes', cmd_fetch
)
133 def add_find_command(subparser
):
134 parser
= add_command(subparser
, 'find', 'find files', cmd_find
)
135 parser
.add_argument('paths', nargs
='*', metavar
='<path>',
136 help='filter by path')
139 def add_grep_command(subparser
):
140 parser
= add_command(subparser
, 'grep', 'grep source', cmd_grep
)
141 parser
.add_argument('args', nargs
='*', metavar
='<args>',
142 help='git grep arguments')
145 def add_merge_command(subparser
):
146 add_command(subparser
, 'merge', 'merge branches', cmd_merge
)
149 def add_pull_command(subparser
):
150 parser
= add_command(subparser
, 'pull', 'pull remote branches', cmd_pull
)
151 parser
.add_argument('--rebase', default
=False, action
='store_true',
152 help='rebase local branch when pulling')
155 def add_push_command(subparser
):
156 add_command(subparser
, 'push', 'push remote branches', cmd_push
)
159 def add_rebase_command(subparser
):
160 parser
= add_command(subparser
, 'rebase', 'interactive rebase', cmd_rebase
)
161 parser
.add_argument('-v', '--verbose', default
=False, action
='store_true',
162 help='display a diffstat of what changed upstream')
163 parser
.add_argument('-q', '--quiet', default
=False, action
='store_true',
164 help='be quiet. implies --no-stat')
165 parser
.add_argument('-i', '--interactive', default
=True,
166 action
='store_true', help=argparse
.SUPPRESS
)
167 parser
.add_argument('--autostash', default
=False, action
='store_true',
168 help='automatically stash/stash pop before and after')
169 parser
.add_argument('--fork-point', default
=False, action
='store_true',
170 help="use 'merge-base --fork-point' to refine upstream")
171 parser
.add_argument('--onto', default
=None, metavar
='<newbase>',
172 help='rebase onto given branch instead of upstream')
173 parser
.add_argument('-p', '--preserve-merges',
174 default
=False, action
='store_true',
175 help='try to recreate merges instead of ignoring them')
176 parser
.add_argument('-s', '--strategy', default
=None, metavar
='<strategy>',
177 help='use the given merge strategy')
178 parser
.add_argument('--no-ff', default
=False, action
='store_true',
179 help='cherry-pick all commits, even if unchanged')
180 parser
.add_argument('-m', '--merge', default
=False, action
='store_true',
181 help='use merging strategies to rebase')
182 parser
.add_argument('-x', '--exec', default
=None,
183 help='add exec lines after each commit of '
185 parser
.add_argument('-k', '--keep-empty', default
=False,
187 help='preserve empty commits during rebase')
188 parser
.add_argument('-f', '--force-rebase', default
=False,
190 help='force rebase even if branch is up to date')
191 parser
.add_argument('-X', '--strategy-option', default
=None,
193 help='pass the argument through to the merge strategy')
194 parser
.add_argument('--stat', default
=False, action
='store_true',
195 help='display a diffstat of what changed upstream')
196 parser
.add_argument('-n', '--no-stat', default
=False, action
='store_true',
197 help='do not show diffstat of what changed upstream')
198 parser
.add_argument('--verify', default
=False, action
='store_true',
199 help='allow pre-rebase hook to run')
200 parser
.add_argument('--rerere-autoupdate',
201 default
=False, action
='store_true',
202 help='allow rerere to update index with '
203 'resolved conflicts')
204 parser
.add_argument('--root', default
=False, action
='store_true',
205 help='rebase all reachable commits up to the root(s)')
206 parser
.add_argument('--autosquash', default
=True, action
='store_true',
207 help='move commits that begin with '
208 'squash!/fixup! under -i')
209 parser
.add_argument('--no-autosquash', default
=True, action
='store_false',
211 help='do not move commits that begin with '
212 'squash!/fixup! under -i')
213 parser
.add_argument('--committer-date-is-author-date',
214 default
=False, action
='store_true',
215 help="passed to 'git am' by 'git rebase'")
216 parser
.add_argument('--ignore-date', default
=False, action
='store_true',
217 help="passed to 'git am' by 'git rebase'")
218 parser
.add_argument('--whitespace', default
=False, action
='store_true',
219 help="passed to 'git apply' by 'git rebase'")
220 parser
.add_argument('--ignore-whitespace', default
=False,
222 help="passed to 'git apply' by 'git rebase'")
223 parser
.add_argument('-C', dest
='context_lines', default
=None, metavar
='<n>',
224 help="passed to 'git apply' by 'git rebase'")
226 actions
= parser
.add_argument_group('actions')
227 actions
.add_argument('--continue', default
=False, action
='store_true',
229 actions
.add_argument('--abort', default
=False, action
='store_true',
230 help='abort and check out the original branch')
231 actions
.add_argument('--skip', default
=False, action
='store_true',
232 help='skip current patch and continue')
233 actions
.add_argument('--edit-todo', default
=False, action
='store_true',
234 help='edit the todo list during an interactive rebase')
236 parser
.add_argument('upstream', nargs
='?', default
=None,
237 metavar
='<upstream>',
238 help='the upstream configured in branch.<name>.remote '
239 'and branch.<name>.merge options will be used '
240 'when <upstream> is omitted; see git-rebase(1) '
241 'for details. If you are currently not on any '
242 'branch or if the current branch does not have '
243 'a configured upstream, the rebase will abort')
244 parser
.add_argument('branch', nargs
='?', default
=None, metavar
='<branch>',
245 help='git rebase will perform an automatic '
246 '"git checkout <branch>" before doing anything '
247 'else when <branch> is specified')
250 def add_remote_command(subparser
):
251 add_command(subparser
, 'remote', 'edit remotes', cmd_remote
)
254 def add_search_command(subparser
):
255 add_command(subparser
, 'search', 'search commits', cmd_search
)
258 def add_stash_command(subparser
):
259 add_command(subparser
, 'stash', 'stash and unstash changes', cmd_stash
)
262 def add_tag_command(subparser
):
263 parser
= add_command(subparser
, 'tag', 'create tags', cmd_tag
)
264 parser
.add_argument('name', metavar
='<name>', nargs
='?', default
=None,
266 parser
.add_argument('ref', metavar
='<ref>', nargs
='?', default
=None,
267 help='commit to tag')
268 parser
.add_argument('-s', '--sign', default
=False, action
='store_true',
269 help='annotated and GPG-signed tag')
272 def add_version_command(subparser
):
273 parser
= add_command(subparser
, 'version', 'print the version', cmd_version
)
274 parser
.add_argument('--brief', action
='store_true', default
=False,
275 help='print the version number only')
280 from .widgets
.main
import MainView
281 status_filter
= args
.status_filter
283 status_filter
= core
.abspath(status_filter
)
285 context
= application_init(args
)
286 view
= MainView(context
.model
, settings
=args
.settings
)
288 cmds
.do(cmds
.AmendMode
, True)
291 view
.set_filter(core
.relpath(status_filter
))
293 return application_start(context
, view
)
297 from .widgets
import about
298 context
= application_init(args
)
299 view
= about
.about_dialog()
300 return application_start(context
, view
)
304 from .widgets
.patch
import new_apply_patches
305 context
= application_init(args
)
306 view
= new_apply_patches(patches
=args
.patches
)
307 return application_start(context
, view
)
310 def cmd_archive(args
):
311 from .widgets
.archive
import GitArchiveDialog
312 context
= application_init(args
, update
=True)
314 args
.ref
= context
.model
.currentbranch
315 view
= GitArchiveDialog(args
.ref
)
316 return application_start(context
, view
)
319 def cmd_branch(args
):
320 from .widgets
.createbranch
import create_new_branch
321 context
= application_init(args
, update
=True)
322 view
= create_new_branch(settings
=args
.settings
)
323 return application_start(context
, view
)
326 def cmd_browse(args
):
327 from .widgets
.browse
import worktree_browser
328 context
= application_init(args
)
329 view
= worktree_browser(update
=False, settings
=args
.settings
)
330 return application_start(context
, view
)
333 def cmd_config(args
):
334 from .widgets
.prefs
import preferences
335 context
= application_init(args
)
337 return application_start(context
, view
)
341 context
= application_init(args
)
342 from .widgets
.dag
import git_dag
343 view
= git_dag(context
.model
, args
=args
, settings
=args
.settings
)
344 return application_start(context
, view
)
348 context
= application_init(args
)
349 from .difftool
import diff_expression
350 expr
= core
.list2cmdline(args
.args
)
351 view
= diff_expression(None, expr
, create_widget
=True)
352 return application_start(context
, view
)
356 # TODO: the calls to update_status() can be done asynchronously
357 # by hooking into the message_updated notification.
358 context
= application_init(args
)
359 from .widgets
import remote
360 context
.model
.update_status()
361 view
= remote
.fetch()
362 return application_start(context
, view
)
366 context
= application_init(args
)
367 from .widgets
import finder
368 paths
= core
.list2cmdline(args
.paths
)
369 view
= finder
.finder(paths
=paths
)
370 return application_start(context
, view
)
374 context
= application_init(args
)
375 from .widgets
import grep
376 text
= core
.list2cmdline(args
.args
)
377 view
= grep
.new_grep(text
=text
, parent
=None)
378 return application_start(context
, view
)
382 context
= application_init(args
, update
=True)
383 from .widgets
.merge
import MergeView
384 view
= MergeView(context
.cfg
, context
.model
, parent
=None)
385 return application_start(context
, view
)
388 def cmd_version(args
):
389 from . import version
390 version
.print_version(brief
=args
.brief
)
395 from .widgets
import remote
396 context
= application_init(args
, update
=True)
399 view
.set_rebase(True)
400 return application_start(context
, view
)
404 from .widgets
import remote
405 context
= application_init(args
, update
=True)
407 return application_start(context
, view
)
410 def cmd_rebase(args
):
412 'verbose': args
.verbose
,
414 'autostash': args
.autostash
,
415 'fork_point': args
.fork_point
,
417 'preserve_merges': args
.preserve_merges
,
418 'strategy': args
.strategy
,
421 'exec': getattr(args
, 'exec', None), # python keyword
422 'keep_empty': args
.keep_empty
,
423 'force_rebase': args
.force_rebase
,
424 'strategy_option': args
.strategy_option
,
426 'no_stat': args
.no_stat
,
427 'verify': args
.verify
,
428 'rerere_autoupdate': args
.rerere_autoupdate
,
430 'autosquash': args
.autosquash
,
431 'committer_date_is_author_date': args
.committer_date_is_author_date
,
432 'ignore_date': args
.ignore_date
,
433 'whitespace': args
.whitespace
,
434 'ignore_whitespace': args
.ignore_whitespace
,
435 'C': args
.context_lines
,
436 'continue': getattr(args
, 'continue', False), # python keyword
439 'edit_todo': args
.edit_todo
,
440 'upstream': args
.upstream
,
441 'branch': args
.branch
,
442 'capture_output': False,
444 status
, out
, err
= cmds
.do(cmds
.Rebase
, **kwargs
)
452 def cmd_remote(args
):
453 from .widgets
import editremotes
454 context
= application_init(args
)
455 view
= editremotes
.new_remote_editor()
456 return application_start(context
, view
)
459 def cmd_search(args
):
460 from .widgets
.search
import search
461 context
= application_init(args
)
463 return application_start(context
, view
)
467 from .widgets
.stash
import stash
468 context
= application_init(args
)
470 return application_start(context
, view
)
474 from .widgets
.createtag
import new_create_tag
475 context
= application_init(args
)
476 view
= new_create_tag(name
=args
.name
, ref
=args
.ref
, sign
=args
.sign
,
477 settings
=args
.settings
)
478 return application_start(context
, view
)
481 # Windows shortcut launch features:
484 """Return the path of git.exe, or None if we can't find it."""
485 if not utils
.is_win32():
486 return None # UNIX systems have git in their $PATH
488 # If the user wants to use a Git/bin/ directory from a non-standard
489 # directory then they can write its location into
490 # ~/.config/git-cola/git-bindir
491 git_bindir
= os
.path
.expanduser(os
.path
.join('~', '.config', 'git-cola',
493 if core
.exists(git_bindir
):
494 custom_path
= core
.read(git_bindir
).strip()
495 if custom_path
and core
.exists(custom_path
):
498 # Try to find Git's bin/ directory in one of the typical locations
499 pf
= os
.environ
.get('ProgramFiles', 'C:\\Program Files')
500 pf32
= os
.environ
.get('ProgramFiles(x86)', 'C:\\Program Files (x86)')
501 for p
in [pf32
, pf
, 'C:\\']:
502 candidate
= os
.path
.join(p
, 'Git\\bin')
503 if os
.path
.isdir(candidate
):
509 def shortcut_launch():
510 """Launch from a shortcut
512 Prompt for the repository by default, and try to find git.
514 argv
= ['cola', '--prompt']
515 git_path
= find_git()
517 prepend_path(git_path
)
522 def prepend_path(path
):
523 # Adds git to the PATH. This is needed on Windows.
524 path
= core
.decode(path
)
525 path_entries
= core
.getenv('PATH', '').split(os
.pathsep
)
526 if path
not in path_entries
:
527 path_entries
.insert(0, path
)
528 compat
.setenv('PATH', os
.pathsep
.join(path_entries
))