doc: fix release notes typo
[git-cola.git] / cola / app.py
blobf9236de8ce67fdf71ba480ca720009c45787ca3a
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 import cola
31 from cola import core
32 from cola import compat
33 from cola import git
34 from cola import inotify
35 from cola import i18n
36 from cola import qtcompat
37 from cola import qtutils
38 from cola import resources
39 from cola import utils
40 from cola import version
41 from cola.decorators import memoize
42 from cola.interaction import Interaction
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 # Setup the path so that git finds us when we run 'git cola'
52 path_entries = core.getenv('PATH', '').split(os.pathsep)
53 bindir = os.path.dirname(core.abspath(__file__))
54 path_entries.insert(0, bindir)
55 path = os.pathsep.join(path_entries)
56 compat.setenv('PATH', path)
58 # We don't ever want a pager
59 compat.setenv('GIT_PAGER', '')
61 # Setup *SSH_ASKPASS
62 git_askpass = core.getenv('GIT_ASKPASS')
63 ssh_askpass = core.getenv('SSH_ASKPASS')
64 if git_askpass:
65 askpass = git_askpass
66 elif ssh_askpass:
67 askpass = ssh_askpass
68 elif sys.platform == 'darwin':
69 askpass = resources.share('bin', 'ssh-askpass-darwin')
70 else:
71 askpass = resources.share('bin', 'ssh-askpass')
73 compat.setenv('GIT_ASKPASS', askpass)
74 compat.setenv('SSH_ASKPASS', askpass)
76 # --- >8 --- >8 ---
77 # Git v1.7.10 Release Notes
78 # =========================
80 # Compatibility Notes
81 # -------------------
83 # * From this release on, the "git merge" command in an interactive
84 # session will start an editor when it automatically resolves the
85 # merge for the user to explain the resulting commit, just like the
86 # "git commit" command does when it wasn't given a commit message.
88 # If you have a script that runs "git merge" and keeps its standard
89 # input and output attached to the user's terminal, and if you do not
90 # want the user to explain the resulting merge commits, you can
91 # export GIT_MERGE_AUTOEDIT environment variable set to "no", like
92 # this:
94 # #!/bin/sh
95 # GIT_MERGE_AUTOEDIT=no
96 # export GIT_MERGE_AUTOEDIT
98 # to disable this behavior (if you want your users to explain their
99 # merge commits, you do not have to do anything). Alternatively, you
100 # can give the "--no-edit" option to individual invocations of the
101 # "git merge" command if you know everybody who uses your script has
102 # Git v1.7.8 or newer.
103 # --- >8 --- >8 ---
104 # Longer-term: Use `git merge --no-commit` so that we always
105 # have a chance to explain our merges.
106 compat.setenv('GIT_MERGE_AUTOEDIT', 'no')
109 @memoize
110 def instance(argv):
111 return QtGui.QApplication(list(argv))
114 # style note: we use camelCase here since we're masquerading a Qt class
115 class ColaApplication(object):
116 """The main cola application
118 ColaApplication handles i18n of user-visible data
121 def __init__(self, argv, locale=None, gui=True):
122 cfgactions.install()
123 i18n.install(locale)
124 qtcompat.install()
125 qtutils.install()
127 # Add the default style dir so that we find our icons
128 icon_dir = resources.icon_dir()
129 qtcompat.add_search_path(os.path.basename(icon_dir), icon_dir)
131 if gui:
132 self._app = instance(tuple(argv))
133 self._app.setWindowIcon(qtutils.git_icon())
134 else:
135 self._app = QtCore.QCoreApplication(argv)
137 self._app.setStyleSheet("""
138 QMainWindow::separator {
139 width: 3px;
140 height: 3px;
142 QMainWindow::separator:hover {
143 background: white;
145 """)
147 def activeWindow(self):
148 """Wrap activeWindow()"""
149 return self._app.activeWindow()
151 def desktop(self):
152 return self._app.desktop()
154 def exec_(self):
155 """Wrap exec_()"""
156 return self._app.exec_()
159 def process_args(args):
160 if args.version:
161 # Accept 'git cola --version' or 'git cola version'
162 version.print_version()
163 sys.exit(0)
165 if args.git_path:
166 # Adds git to the PATH. This is needed on Windows.
167 path_entries = core.getenv('PATH', '').split(os.pathsep)
168 path_entries.insert(0, os.path.dirname(core.decode(args.git_path)))
169 compat.setenv('PATH', os.pathsep.join(path_entries))
171 # Bail out if --repo is not a directory
172 repo = core.decode(args.repo)
173 if repo.startswith('file:'):
174 repo = repo[len('file:'):]
175 repo = core.realpath(repo)
176 if not core.isdir(repo):
177 sys.stderr.write("fatal: '%s' is not a directory. "
178 'Consider supplying -r <path>.\n' % repo)
179 sys.exit(-1)
181 # We do everything relative to the repo root
182 os.chdir(args.repo)
183 return repo
186 def application_init(args, update=False):
187 """Parses the command-line arguments and starts git-cola
189 setup_environment()
190 process_args(args)
192 # Ensure that we're working in a valid git repository.
193 # If not, try to find one. When found, chdir there.
194 app = new_application()
195 model = new_model(app, args.repo, prompt=args.prompt)
196 if update:
197 model.update_status()
199 return ApplicationContext(args, app, model)
202 def application_start(context, view):
203 # Make sure that we start out on top
204 view.show()
205 view.raise_()
207 # Scan for the first time
208 task = _start_update_thread(context.model)
210 # Start the inotify thread
211 inotify.start()
213 msg_timer = QtCore.QTimer()
214 msg_timer.setSingleShot(True)
215 msg_timer.connect(msg_timer, SIGNAL('timeout()'), _send_msg)
216 msg_timer.start(0)
218 # Start the event loop
219 result = context.app.exec_()
221 # All done, cleanup
222 inotify.stop()
223 QtCore.QThreadPool.globalInstance().waitForDone()
224 del task
226 pattern = utils.tmp_file_pattern()
227 for filename in glob.glob(pattern):
228 os.unlink(filename)
230 return result
233 def add_common_arguments(parser):
234 # We also accept 'git cola version'
235 parser.add_argument('--version', default=False, action='store_true',
236 help='prints the version')
238 # Specifies a git repository to open
239 parser.add_argument('-r', '--repo', metavar='<repo>', default=os.getcwd(),
240 help='specifies the path to a git repository')
242 # Specifies that we should prompt for a repository at startup
243 parser.add_argument('--prompt', action='store_true', default=False,
244 help='prompts for a repository')
246 # Used on Windows for adding 'git' to the path
247 parser.add_argument('-g', '--git-path', metavar='<path>', default=None,
248 help='specifies the path to the git binary')
251 def new_application():
252 # Allow Ctrl-C to exit
253 signal.signal(signal.SIGINT, signal.SIG_DFL)
255 # Initialize the app
256 return ColaApplication(sys.argv)
259 def new_model(app, repo, prompt=False):
260 model = cola.model()
261 valid = model.set_worktree(repo) and not prompt
262 while not valid:
263 startup_dlg = startup.StartupDialog(app.activeWindow())
264 gitdir = startup_dlg.find_git_repo()
265 if not gitdir:
266 sys.exit(-1)
267 valid = model.set_worktree(gitdir)
269 # Finally, go to the root of the git repo
270 os.chdir(model.git.worktree())
271 return model
274 def _start_update_thread(model):
275 """Update the model in the background
277 git-cola should startup as quickly as possible.
280 class UpdateTask(QtCore.QRunnable):
281 def run(self):
282 model.update_status(update_index=True)
284 # Hold onto a reference to prevent PyQt from dereferencing
285 task = UpdateTask()
286 QtCore.QThreadPool.globalInstance().start(task)
288 return task
291 def _send_msg():
292 if git.GIT_COLA_TRACE == 'trace':
293 msg = ('info: Trace enabled. '
294 'Many of commands reported with "trace" use git\'s stable '
295 '"plumbing" API and are not intended for typical '
296 'day-to-day use. Here be dragons')
297 Interaction.log(msg)
300 class ApplicationContext(object):
302 def __init__(self, args, app, model):
303 self.args = args
304 self.app = app
305 self.model = model