1 # Copyright (C) 2009, 2010, 2011, 2012, 2013
2 # David Aguilar <davvid@gmail.com>
3 """Provides the main() routine and ColaApplicaiton"""
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
)
19 from PyQt4
import QtGui
20 from PyQt4
import QtCore
21 from PyQt4
.QtCore
import SIGNAL
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')
31 from cola
import compat
33 from cola
import inotify
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
)
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', '')
68 git_askpass
= core
.getenv('GIT_ASKPASS')
69 ssh_askpass
= core
.getenv('SSH_ASKPASS')
74 elif sys
.platform
== 'darwin':
75 askpass
= resources
.share('bin', 'ssh-askpass-darwin')
77 askpass
= resources
.share('bin', 'ssh-askpass')
79 compat
.setenv('GIT_ASKPASS', askpass
)
80 compat
.setenv('SSH_ASKPASS', askpass
)
83 # Git v1.7.10 Release Notes
84 # =========================
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
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.
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')
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):
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
)
138 self
._app
= instance(tuple(argv
))
139 self
._app
.setWindowIcon(qtutils
.git_icon())
141 self
._app
= QtCore
.QCoreApplication(argv
)
143 self
._app
.setStyleSheet("""
144 QMainWindow::separator {
148 QMainWindow::separator:hover {
153 def activeWindow(self
):
154 """Wrap activeWindow()"""
155 return self
._app
.activeWindow()
158 return self
._app
.desktop()
162 return self
._app
.exec_()
165 def process_args(args
):
167 # Accept 'git cola --version' or 'git cola version'
168 version
.print_version()
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
)
187 # We do everything relative to the repo root
192 def application_init(args
, update
=False):
193 """Parses the command-line arguments and starts git-cola
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
)
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
213 # Scan for the first time
214 task
= _start_update_thread(context
.model
)
216 # Start the inotify thread
219 msg_timer
= QtCore
.QTimer()
220 msg_timer
.setSingleShot(True)
221 msg_timer
.connect(msg_timer
, SIGNAL('timeout()'), _send_msg
)
224 # Start the event loop
225 result
= context
.app
.exec_()
229 QtCore
.QThreadPool
.globalInstance().waitForDone()
232 pattern
= utils
.tmp_file_pattern()
233 for filename
in glob
.glob(pattern
):
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
)
262 return ColaApplication(sys
.argv
)
265 def new_model(app
, repo
, prompt
=False):
267 valid
= model
.set_worktree(repo
) and not prompt
269 startup_dlg
= startup
.StartupDialog(app
.activeWindow())
270 gitdir
= startup_dlg
.find_git_repo()
273 valid
= model
.set_worktree(gitdir
)
275 # Finally, go to the root of the git repo
276 os
.chdir(model
.git
.worktree())
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
):
288 model
.update_status(update_index
=True)
290 # Hold onto a reference to prevent PyQt from dereferencing
292 QtCore
.QThreadPool
.globalInstance().start(task
)
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')
306 class ApplicationContext(object):
308 def __init__(self
, args
, app
, model
):