git-cola v1.9.4
[git-cola.git] / cola / app.py
blobcdca515f10d0d24ef8bbb0db40e8627952c4be76
1 # Copyright (C) 2009, 2010, 2011, 2012, 2013
2 # David Aguilar <davvid@gmail.com>
3 """Provides the main() routine and ColaApplicaiton"""
5 import glob
6 import os
7 import signal
8 import sys
10 # Make homebrew work by default
11 if sys.platform == 'darwin':
12 from distutils import sysconfig
13 python_version = sysconfig.get_python_version()
14 homebrew_mods = '/usr/local/lib/python%s/site-packages' % python_version
15 if os.path.isdir(homebrew_mods):
16 sys.path.append(homebrew_mods)
18 try:
19 from PyQt4 import QtGui
20 from PyQt4 import QtCore
21 from PyQt4.QtCore import SIGNAL
22 except ImportError:
23 sys.stderr.write('Sorry, you do not seem to have PyQt4 installed.\n')
24 sys.stderr.write('Please install it before using git-cola.\n')
25 sys.stderr.write('e.g.: sudo apt-get install python-qt4\n')
26 sys.exit(-1)
29 # Import cola modules
30 from cola import core
31 from cola import compat
32 from cola import git
33 from cola import inotify
34 from cola import i18n
35 from cola import qtcompat
36 from cola import qtutils
37 from cola import resources
38 from cola import utils
39 from cola import version
40 from cola.decorators import memoize
41 from cola.interaction import Interaction
42 from cola.models import main
43 from cola.widgets import cfgactions
44 from cola.widgets import startup
47 def setup_environment():
48 # Spoof an X11 display for SSH
49 os.environ.setdefault('DISPLAY', ':0')
51 if not core.getenv('SHELL', ''):
52 for shell in ('/bin/zsh', '/bin/bash', '/bin/sh'):
53 if os.path.exists(shell):
54 compat.setenv('SHELL', shell)
55 break
57 # Setup the path so that git finds us when we run 'git cola'
58 path_entries = core.getenv('PATH', '').split(os.pathsep)
59 bindir = os.path.dirname(core.abspath(__file__))
60 path_entries.insert(0, bindir)
61 path = os.pathsep.join(path_entries)
62 compat.setenv('PATH', path)
64 # We don't ever want a pager
65 compat.setenv('GIT_PAGER', '')
67 # Setup *SSH_ASKPASS
68 git_askpass = core.getenv('GIT_ASKPASS')
69 ssh_askpass = core.getenv('SSH_ASKPASS')
70 if git_askpass:
71 askpass = git_askpass
72 elif ssh_askpass:
73 askpass = ssh_askpass
74 elif sys.platform == 'darwin':
75 askpass = resources.share('bin', 'ssh-askpass-darwin')
76 else:
77 askpass = resources.share('bin', 'ssh-askpass')
79 compat.setenv('GIT_ASKPASS', askpass)
80 compat.setenv('SSH_ASKPASS', askpass)
82 # --- >8 --- >8 ---
83 # Git v1.7.10 Release Notes
84 # =========================
86 # Compatibility Notes
87 # -------------------
89 # * From this release on, the "git merge" command in an interactive
90 # session will start an editor when it automatically resolves the
91 # merge for the user to explain the resulting commit, just like the
92 # "git commit" command does when it wasn't given a commit message.
94 # If you have a script that runs "git merge" and keeps its standard
95 # input and output attached to the user's terminal, and if you do not
96 # want the user to explain the resulting merge commits, you can
97 # export GIT_MERGE_AUTOEDIT environment variable set to "no", like
98 # this:
100 # #!/bin/sh
101 # GIT_MERGE_AUTOEDIT=no
102 # export GIT_MERGE_AUTOEDIT
104 # to disable this behavior (if you want your users to explain their
105 # merge commits, you do not have to do anything). Alternatively, you
106 # can give the "--no-edit" option to individual invocations of the
107 # "git merge" command if you know everybody who uses your script has
108 # Git v1.7.8 or newer.
109 # --- >8 --- >8 ---
110 # Longer-term: Use `git merge --no-commit` so that we always
111 # have a chance to explain our merges.
112 compat.setenv('GIT_MERGE_AUTOEDIT', 'no')
115 @memoize
116 def instance(argv):
117 return QtGui.QApplication(list(argv))
120 # style note: we use camelCase here since we're masquerading a Qt class
121 class ColaApplication(object):
122 """The main cola application
124 ColaApplication handles i18n of user-visible data
127 def __init__(self, argv, locale=None, gui=True):
128 cfgactions.install()
129 i18n.install(locale)
130 qtcompat.install()
131 qtutils.install()
133 # Add the default style dir so that we find our icons
134 icon_dir = resources.icon_dir()
135 qtcompat.add_search_path(os.path.basename(icon_dir), icon_dir)
137 if gui:
138 self._app = instance(tuple(argv))
139 self._app.setWindowIcon(qtutils.git_icon())
140 else:
141 self._app = QtCore.QCoreApplication(argv)
143 self._app.setStyleSheet("""
144 QMainWindow::separator {
145 width: 3px;
146 height: 3px;
148 QMainWindow::separator:hover {
149 background: white;
151 """)
153 def activeWindow(self):
154 """Wrap activeWindow()"""
155 return self._app.activeWindow()
157 def desktop(self):
158 return self._app.desktop()
160 def exec_(self):
161 """Wrap exec_()"""
162 return self._app.exec_()
165 def process_args(args):
166 if args.version:
167 # Accept 'git cola --version' or 'git cola version'
168 version.print_version()
169 sys.exit(0)
171 if args.git_path:
172 # Adds git to the PATH. This is needed on Windows.
173 path_entries = core.getenv('PATH', '').split(os.pathsep)
174 path_entries.insert(0, os.path.dirname(core.decode(args.git_path)))
175 compat.setenv('PATH', os.pathsep.join(path_entries))
177 # Bail out if --repo is not a directory
178 repo = core.decode(args.repo)
179 if repo.startswith('file:'):
180 repo = repo[len('file:'):]
181 repo = core.realpath(repo)
182 if not core.isdir(repo):
183 sys.stderr.write("fatal: '%s' is not a directory. "
184 'Consider supplying -r <path>.\n' % repo)
185 sys.exit(-1)
187 # We do everything relative to the repo root
188 os.chdir(args.repo)
189 return repo
192 def application_init(args, update=False):
193 """Parses the command-line arguments and starts git-cola
195 setup_environment()
196 process_args(args)
198 # Ensure that we're working in a valid git repository.
199 # If not, try to find one. When found, chdir there.
200 app = new_application()
201 model = new_model(app, args.repo, prompt=args.prompt)
202 if update:
203 model.update_status()
205 return ApplicationContext(args, app, model)
208 def application_start(context, view):
209 # Make sure that we start out on top
210 view.show()
211 view.raise_()
213 # Scan for the first time
214 task = _start_update_thread(context.model)
216 # Start the inotify thread
217 inotify.start()
219 msg_timer = QtCore.QTimer()
220 msg_timer.setSingleShot(True)
221 msg_timer.connect(msg_timer, SIGNAL('timeout()'), _send_msg)
222 msg_timer.start(0)
224 # Start the event loop
225 result = context.app.exec_()
227 # All done, cleanup
228 inotify.stop()
229 QtCore.QThreadPool.globalInstance().waitForDone()
230 del task
232 pattern = utils.tmp_file_pattern()
233 for filename in glob.glob(pattern):
234 os.unlink(filename)
236 return result
239 def add_common_arguments(parser):
240 # We also accept 'git cola version'
241 parser.add_argument('--version', default=False, action='store_true',
242 help='prints the version')
244 # Specifies a git repository to open
245 parser.add_argument('-r', '--repo', metavar='<repo>', default=os.getcwd(),
246 help='specifies the path to a git repository')
248 # Specifies that we should prompt for a repository at startup
249 parser.add_argument('--prompt', action='store_true', default=False,
250 help='prompts for a repository')
252 # Used on Windows for adding 'git' to the path
253 parser.add_argument('-g', '--git-path', metavar='<path>', default=None,
254 help='specifies the path to the git binary')
257 def new_application():
258 # Allow Ctrl-C to exit
259 signal.signal(signal.SIGINT, signal.SIG_DFL)
261 # Initialize the app
262 return ColaApplication(sys.argv)
265 def new_model(app, repo, prompt=False):
266 model = main.model()
267 valid = model.set_worktree(repo) and not prompt
268 while not valid:
269 startup_dlg = startup.StartupDialog(app.activeWindow())
270 gitdir = startup_dlg.find_git_repo()
271 if not gitdir:
272 sys.exit(-1)
273 valid = model.set_worktree(gitdir)
275 # Finally, go to the root of the git repo
276 os.chdir(model.git.worktree())
277 return model
280 def _start_update_thread(model):
281 """Update the model in the background
283 git-cola should startup as quickly as possible.
286 class UpdateTask(QtCore.QRunnable):
287 def run(self):
288 model.update_status(update_index=True)
290 # Hold onto a reference to prevent PyQt from dereferencing
291 task = UpdateTask()
292 QtCore.QThreadPool.globalInstance().start(task)
294 return task
297 def _send_msg():
298 if git.GIT_COLA_TRACE == 'trace':
299 msg = ('info: Trace enabled. '
300 'Many of commands reported with "trace" use git\'s stable '
301 '"plumbing" API and are not intended for typical '
302 'day-to-day use. Here be dragons')
303 Interaction.log(msg)
306 class ApplicationContext(object):
308 def __init__(self, args, app, model):
309 self.args = args
310 self.app = app
311 self.model = model