createbranch: simplify the UI
[git-cola.git] / cola / widgets / createbranch.py
blob98eae60f2350b420fa0185a616115a730c87b25c
1 from __future__ import division, absolute_import, unicode_literals
3 from PyQt4 import QtGui
4 from PyQt4 import QtCore
5 from PyQt4.QtCore import Qt
6 from PyQt4.QtCore import SIGNAL
8 from cola import gitcmds
9 from cola import icons
10 from cola import qtutils
11 from cola.i18n import N_
12 from cola.interaction import Interaction
13 from cola.models import main
14 from cola.widgets import defs
15 from cola.widgets import completion
16 from cola.widgets.standard import Dialog
17 from cola.compat import ustr
20 COMMAND_SIGNAL = 'command(PyQt_PyObject,PyQt_PyObject,PyQt_PyObject)'
23 def create_new_branch(revision='', settings=None):
24 """Launches a dialog for creating a new branch"""
25 model = main.MainModel()
26 model.update_status()
27 view = CreateBranchDialog(model, settings=settings,
28 parent=qtutils.active_window())
29 if revision:
30 view.set_revision(revision)
31 view.show()
32 return view
35 class CreateOpts(object):
36 def __init__(self, model):
37 self.model = model
38 self.reset = False
39 self.track = False
40 self.fetch = True
41 self.checkout = True
42 self.revision = 'HEAD'
43 self.branch = ''
46 class CreateThread(QtCore.QThread):
47 def __init__(self, opts, parent):
48 QtCore.QThread.__init__(self, parent)
49 self.opts = opts
51 def run(self):
52 branch = self.opts.branch
53 revision = self.opts.revision
54 reset = self.opts.reset
55 checkout = self.opts.checkout
56 track = self.opts.track
57 model = self.opts.model
58 results = []
59 status = 0
61 if track and '/' in revision:
62 remote = revision.split('/', 1)[0]
63 status, out, err = model.git.fetch(remote)
64 self.emit(SIGNAL(COMMAND_SIGNAL), status, out, err)
65 results.append(('fetch', status, out, err))
67 if status == 0:
68 status, out, err = model.create_branch(branch, revision,
69 force=reset,
70 track=track)
71 self.emit(SIGNAL(COMMAND_SIGNAL), status, out, err)
73 results.append(('branch', status, out, err))
74 if status == 0 and checkout:
75 status, out, err = model.git.checkout(branch)
76 self.emit(SIGNAL(COMMAND_SIGNAL), status, out, err)
77 results.append(('checkout', status, out, err))
79 main.model().update_status()
80 self.emit(SIGNAL('done(PyQt_PyObject)'), results)
83 class CreateBranchDialog(Dialog):
84 """A dialog for creating branches."""
86 def __init__(self, model, settings=None, parent=None):
87 Dialog.__init__(self, parent=parent)
88 self.setAttribute(Qt.WA_MacMetalStyle)
89 self.setWindowTitle(N_('Create Branch'))
90 if parent is not None:
91 self.setWindowModality(Qt.WindowModal)
93 self.model = model
94 self.opts = CreateOpts(model)
95 self.thread = CreateThread(self.opts, self)
97 self.progress = QtGui.QProgressDialog(self)
98 self.progress.setRange(0, 0)
99 self.progress.setCancelButton(None)
100 self.progress.setWindowTitle(N_('Create Branch'))
101 self.progress.setWindowModality(Qt.WindowModal)
103 self.branch_name_label = QtGui.QLabel()
104 self.branch_name_label.setText(N_('Branch Name'))
106 self.branch_name = QtGui.QLineEdit()
108 self.rev_label = QtGui.QLabel()
109 self.rev_label.setText(N_('Starting Revision'))
111 self.revision = completion.GitRefLineEdit()
112 current = gitcmds.current_branch()
113 if current:
114 self.revision.setText(current)
116 self.local_radio = qtutils.radio(text=N_('Local branch'), checked=True)
117 self.remote_radio = qtutils.radio(text=N_('Tracking branch'))
118 self.tag_radio = qtutils.radio(text=N_('Tag'))
120 self.branch_list = QtGui.QListWidget()
122 self.update_existing_label = QtGui.QLabel()
123 self.update_existing_label.setText(N_('Update Existing Branch:'))
125 self.no_update_radio = qtutils.radio(text=N_('No'))
126 self.ffwd_only_radio = qtutils.radio(text=N_('Fast Forward Only'),
127 checked=True)
128 self.reset_radio = qtutils.radio(text=N_('Reset'))
130 text = N_('Fetch Tracking Branch')
131 self.fetch_checkbox = qtutils.checkbox(text=text, checked=True)
133 text = N_('Checkout After Creation')
134 self.checkout_checkbox = qtutils.checkbox(text=text, checked=True)
136 icon = icons.branch()
137 self.create_button = qtutils.create_button(text=N_('Create Branch'),
138 icon=icon, default=True)
139 self.close_button = qtutils.close_button()
141 self.options_checkbox_layout = qtutils.hbox(defs.margin, defs.spacing,
142 self.fetch_checkbox,
143 self.checkout_checkbox,
144 qtutils.STRETCH)
146 self.branch_name_layout = qtutils.hbox(defs.margin, defs.spacing,
147 self.branch_name_label,
148 self.branch_name)
150 self.rev_radio_group = qtutils.buttongroup(self.local_radio,
151 self.remote_radio,
152 self.tag_radio)
154 self.rev_radio_layout = qtutils.hbox(defs.margin, defs.spacing,
155 self.local_radio,
156 self.remote_radio,
157 self.tag_radio,
158 qtutils.STRETCH)
160 self.rev_start_textinput_layout = qtutils.hbox(defs.no_margin,
161 defs.spacing,
162 self.rev_label,
163 defs.spacing,
164 self.revision)
166 self.rev_start_layout = qtutils.vbox(defs.no_margin, defs.spacing,
167 self.rev_radio_layout,
168 self.branch_list,
169 self.rev_start_textinput_layout)
171 self.options_radio_group = qtutils.buttongroup(self.no_update_radio,
172 self.ffwd_only_radio,
173 self.reset_radio)
175 self.options_radio_layout = qtutils.hbox(defs.no_margin, defs.spacing,
176 self.update_existing_label,
177 self.no_update_radio,
178 self.ffwd_only_radio,
179 self.reset_radio,
180 qtutils.STRETCH)
182 self.buttons_layout = qtutils.hbox(defs.margin, defs.spacing,
183 qtutils.STRETCH,
184 self.create_button,
185 self.close_button)
187 self.main_layout = qtutils.vbox(defs.margin, defs.spacing,
188 self.branch_name_layout,
189 self.rev_start_layout,
190 defs.button_spacing,
191 self.options_radio_layout,
192 self.options_checkbox_layout,
193 self.buttons_layout)
194 self.setLayout(self.main_layout)
196 qtutils.add_close_action(self)
197 qtutils.connect_button(self.close_button, self.close)
198 qtutils.connect_button(self.create_button, self.create_branch)
199 qtutils.connect_button(self.local_radio, self.display_model)
200 qtutils.connect_button(self.remote_radio, self.display_model)
201 qtutils.connect_button(self.tag_radio, self.display_model)
203 self.connect(self.branch_list, SIGNAL('itemSelectionChanged()'),
204 self.branch_item_changed)
206 self.connect(self.thread, SIGNAL(COMMAND_SIGNAL),
207 self.thread_command, Qt.QueuedConnection)
209 self.connect(self.thread, SIGNAL('done(PyQt_PyObject)'),
210 self.thread_done, Qt.QueuedConnection)
212 if not self.restore_state(settings=settings):
213 self.resize(555, 333)
215 self.display_model()
217 def set_revision(self, revision):
218 self.revision.setText(revision)
220 def getopts(self):
221 self.opts.revision = self.revision.value()
222 self.opts.branch = ustr(self.branch_name.text())
223 self.opts.checkout = self.checkout_checkbox.isChecked()
224 self.opts.reset = self.reset_radio.isChecked()
225 self.opts.fetch = self.fetch_checkbox.isChecked()
226 self.opts.track = self.remote_radio.isChecked()
228 def create_branch(self):
229 """Creates a branch; called by the "Create Branch" button"""
230 self.getopts()
231 revision = self.opts.revision
232 branch = self.opts.branch
233 no_update = self.no_update_radio.isChecked()
234 ffwd_only = self.ffwd_only_radio.isChecked()
235 existing_branches = gitcmds.branch_list()
236 check_branch = False
238 if not branch or not revision:
239 qtutils.critical(N_('Missing Data'),
240 N_('Please provide both a branch '
241 'name and revision expression.'))
242 return
243 if branch in existing_branches:
244 if no_update:
245 msg = N_('Branch "%s" already exists.') % branch
246 qtutils.critical(N_('Branch Exists'), msg)
247 return
248 # Whether we should prompt the user for lost commits
249 commits = gitcmds.rev_list_range(revision, branch)
250 check_branch = bool(commits)
252 if check_branch:
253 msg = (N_('Resetting "%(branch)s" to "%(revision)s" '
254 'will lose commits.') %
255 dict(branch=branch, revision=revision))
256 if ffwd_only:
257 qtutils.critical(N_('Branch Exists'), msg)
258 return
259 lines = [msg]
260 for idx, commit in enumerate(commits):
261 subject = commit[1][0:min(len(commit[1]),16)]
262 if len(subject) < len(commit[1]):
263 subject += '...'
264 lines.append('\t' + commit[0][:8]
265 +'\t' + subject)
266 if idx >= 5:
267 skip = len(commits) - 5
268 lines.append('\t(%s)' % (N_('%d skipped') % skip))
269 break
270 line = N_('Recovering lost commits may not be easy.')
271 lines.append(line)
272 if not qtutils.confirm(N_('Reset Branch?'),
273 '\n'.join(lines),
274 (N_('Reset "%(branch)s" to "%(revision)s"?') %
275 dict(branch=branch, revision=revision)),
276 N_('Reset Branch'),
277 default=False,
278 icon=icons.undo()):
279 return
280 self.setEnabled(False)
281 self.progress.setEnabled(True)
282 QtGui.QApplication.setOverrideCursor(Qt.WaitCursor)
284 # Show a nice progress bar
285 self.progress.setLabelText(N_('Updating...'))
286 self.progress.show()
287 self.thread.start()
289 def thread_command(self, status, out, err):
290 Interaction.log_status(status, out, err)
292 def thread_done(self, results):
293 self.setEnabled(True)
294 self.progress.close()
295 QtGui.QApplication.restoreOverrideCursor()
297 for (cmd, status, out, err) in results:
298 if status != 0:
299 Interaction.critical(
300 N_('Error Creating Branch'),
301 (N_('"%(command)s" returned exit status "%(status)d"') %
302 dict(command='git '+cmd, status=status)))
303 return
305 self.accept()
307 def branch_item_changed(self, *rest):
308 """This callback is called when the branch selection changes"""
309 # When the branch selection changes then we should update
310 # the "Revision Expression" accordingly.
311 qlist = self.branch_list
312 remote_branch = qtutils.selected_item(qlist, self.branch_sources())
313 if not remote_branch:
314 return
315 # Update the model with the selection
316 self.revision.setText(remote_branch)
318 # Set the branch field if we're branching from a remote branch.
319 if not self.remote_radio.isChecked():
320 return
321 branch = gitcmds.strip_remote(self.model.remotes, remote_branch)
322 if branch == 'HEAD':
323 return
324 # Signal that we've clicked on a remote branch
325 self.branch_name.setText(branch)
327 def display_model(self):
328 """Sets the branch list to the available branches
330 branches = self.branch_sources()
331 qtutils.set_items(self.branch_list, branches)
333 def branch_sources(self):
334 """Get the list of items for populating the branch root list.
336 if self.local_radio.isChecked():
337 return self.model.local_branches
338 elif self.remote_radio.isChecked():
339 return self.model.remote_branches
340 elif self.tag_radio.isChecked():
341 return self.model.tags