commitmsg: hide the menu indicator
[git-cola.git] / cola / widgets / createbranch.py
blob58709f9d32090ea592619dd9a3a370ecef89303a
1 from PyQt4 import QtGui
2 from PyQt4 import QtCore
3 from PyQt4.QtCore import Qt
4 from PyQt4.QtCore import SIGNAL
6 from cola import gitcmds
7 from cola import qtutils
8 from cola import utils
9 from cola.i18n import N_
10 from cola.interaction import Interaction
11 from cola.models import main
12 from cola.widgets import defs
13 from cola.widgets import completion
14 from cola.widgets.standard import Dialog
17 def create_new_branch(revision=''):
18 """Launches a dialog for creating a new branch"""
19 model = main.MainModel()
20 model.update_status()
21 view = CreateBranchDialog(model, qtutils.active_window())
22 if revision:
23 view.set_revision(revision)
24 view.show()
25 return view
28 class CreateOpts(object):
29 def __init__(self, model):
30 self.model = model
31 self.reset = False
32 self.track = False
33 self.fetch = True
34 self.checkout = True
35 self.revision = 'HEAD'
36 self.branch = ''
39 class CreateThread(QtCore.QThread):
40 def __init__(self, opts, parent):
41 QtCore.QThread.__init__(self, parent)
42 self.opts = opts
44 def run(self):
45 branch = self.opts.branch
46 revision = self.opts.revision
47 reset = self.opts.reset
48 checkout = self.opts.checkout
49 track = self.opts.track
50 model = self.opts.model
51 results = []
52 status = 0
54 if track and '/' in revision:
55 remote = revision.split('/', 1)[0]
56 status, out, err = model.git.fetch(remote)
57 self.emit(SIGNAL('command'), status, out, err)
58 results.append(('fetch', status, out, err))
60 if status == 0:
61 status, out, err = model.create_branch(branch, revision,
62 force=reset,
63 track=track)
64 self.emit(SIGNAL('command'), status, out, err)
66 results.append(('branch', status, out, err))
67 if status == 0 and checkout:
68 status, out, err = model.git.checkout(branch)
69 self.emit(SIGNAL('command'), status, out, err)
70 results.append(('checkout', status, out, err))
72 main.model().update_status()
73 self.emit(SIGNAL('done'), results)
76 class CreateBranchDialog(Dialog):
77 """A dialog for creating branches."""
79 def __init__(self, model, parent=None):
80 Dialog.__init__(self, parent=parent)
81 self.setAttribute(Qt.WA_MacMetalStyle)
82 self.setWindowTitle(N_('Create Branch'))
83 if parent is not None:
84 self.setWindowModality(Qt.WindowModal)
86 self.model = model
87 self.opts = CreateOpts(model)
88 self.thread = CreateThread(self.opts, self)
90 self.progress = QtGui.QProgressDialog(self)
91 self.progress.setRange(0, 0)
92 self.progress.setCancelButton(None)
93 self.progress.setWindowTitle(N_('Create Branch'))
94 self.progress.setWindowModality(Qt.WindowModal)
96 self.branch_name_label = QtGui.QLabel()
97 self.branch_name_label.setText(N_('Branch Name'))
99 self.branch_name = QtGui.QLineEdit()
101 self.rev_label = QtGui.QLabel()
102 self.rev_label.setText(N_('Starting Revision'))
104 self.revision = completion.GitRefLineEdit()
105 current = gitcmds.current_branch()
106 if current:
107 self.revision.setText(current)
109 self.local_radio = QtGui.QRadioButton()
110 self.local_radio.setText(N_('Local branch'))
111 self.local_radio.setChecked(True)
113 self.remote_radio = QtGui.QRadioButton()
114 self.remote_radio.setText(N_('Tracking branch'))
116 self.tag_radio = QtGui.QRadioButton()
117 self.tag_radio.setText(N_('Tag'))
119 self.branch_list = QtGui.QListWidget()
121 self.update_existing_label = QtGui.QLabel()
122 self.update_existing_label.setText(N_('Update Existing Branch:'))
124 self.no_update_radio = QtGui.QRadioButton()
125 self.no_update_radio.setText(N_('No'))
127 self.ffwd_only_radio = QtGui.QRadioButton()
128 self.ffwd_only_radio.setText(N_('Fast Forward Only'))
129 self.ffwd_only_radio.setChecked(True)
131 self.reset_radio = QtGui.QRadioButton()
132 self.reset_radio.setText(N_('Reset'))
134 self.options_bottom_layout = QtGui.QHBoxLayout()
135 self.options_checkbox_layout = QtGui.QVBoxLayout()
137 self.fetch_checkbox = QtGui.QCheckBox()
138 self.fetch_checkbox.setText(N_('Fetch Tracking Branch'))
139 self.fetch_checkbox.setChecked(True)
140 self.options_checkbox_layout.addWidget(self.fetch_checkbox)
142 self.checkout_checkbox = QtGui.QCheckBox()
143 self.checkout_checkbox.setText(N_('Checkout After Creation'))
144 self.checkout_checkbox.setChecked(True)
145 self.options_checkbox_layout.addWidget(self.checkout_checkbox)
147 self.options_bottom_layout.addLayout(self.options_checkbox_layout)
148 self.options_bottom_layout.addStretch()
150 self.create_button = qtutils.create_button(text=N_('Create Branch'),
151 icon=qtutils.git_icon())
152 self.create_button.setDefault(True)
154 self.close_button = qtutils.create_button(text=N_('Close'))
156 self.branch_name_layout = QtGui.QHBoxLayout()
157 self.branch_name_layout.addWidget(self.branch_name_label)
158 self.branch_name_layout.addWidget(self.branch_name)
160 self.rev_start_radiobtn_layout = QtGui.QHBoxLayout()
161 self.rev_start_radiobtn_layout.addWidget(self.local_radio)
162 self.rev_start_radiobtn_layout.addWidget(self.remote_radio)
163 self.rev_start_radiobtn_layout.addWidget(self.tag_radio)
164 self.rev_start_radiobtn_layout.addStretch()
166 self.rev_start_textinput_layout = QtGui.QHBoxLayout()
167 self.rev_start_textinput_layout.setMargin(0)
168 self.rev_start_textinput_layout.setSpacing(defs.spacing)
169 self.rev_start_textinput_layout.addWidget(self.rev_label)
170 self.rev_start_textinput_layout.addWidget(self.revision)
172 self.rev_start_group = QtGui.QGroupBox()
173 self.rev_start_group.setTitle(N_('Starting Revision'))
175 self.rev_start_layout = QtGui.QVBoxLayout(self.rev_start_group)
176 self.rev_start_layout.setMargin(defs.margin)
177 self.rev_start_layout.setSpacing(defs.spacing)
178 self.rev_start_layout.addLayout(self.rev_start_radiobtn_layout)
179 self.rev_start_layout.addWidget(self.branch_list)
180 self.rev_start_layout.addLayout(self.rev_start_textinput_layout)
182 self.options_radio_layout = QtGui.QHBoxLayout()
183 self.options_radio_layout.addWidget(self.update_existing_label)
184 self.options_radio_layout.addWidget(self.no_update_radio)
185 self.options_radio_layout.addWidget(self.ffwd_only_radio)
186 self.options_radio_layout.addWidget(self.reset_radio)
188 self.option_group = QtGui.QGroupBox()
189 self.option_group.setTitle(N_('Options'))
191 self.options_grp_layout = QtGui.QVBoxLayout(self.option_group)
192 self.options_grp_layout.setMargin(defs.margin)
193 self.options_grp_layout.setSpacing(defs.spacing)
194 self.options_grp_layout.addLayout(self.options_radio_layout)
195 self.options_grp_layout.addLayout(self.options_bottom_layout)
197 self.buttons_layout = QtGui.QHBoxLayout()
198 self.buttons_layout.setMargin(defs.margin)
199 self.buttons_layout.setSpacing(defs.spacing)
200 self.buttons_layout.addWidget(self.create_button)
201 self.buttons_layout.addWidget(self.close_button)
203 self.options_section_layout = QtGui.QHBoxLayout()
204 self.options_section_layout.setMargin(defs.margin)
205 self.options_section_layout.setSpacing(defs.spacing)
206 self.options_section_layout.addWidget(self.option_group)
207 self.options_section_layout.addLayout(self.buttons_layout)
209 self.main_layout = QtGui.QVBoxLayout()
210 self.main_layout.setMargin(defs.margin)
211 self.main_layout.setSpacing(defs.spacing)
212 self.main_layout.addLayout(self.branch_name_layout)
213 self.main_layout.addWidget(self.rev_start_group)
214 self.main_layout.addLayout(self.options_section_layout)
215 self.setLayout(self.main_layout)
217 qtutils.connect_button(self.close_button, self.reject)
218 qtutils.connect_button(self.create_button, self.create_branch)
219 qtutils.connect_button(self.local_radio, self.display_model)
220 qtutils.connect_button(self.remote_radio, self.display_model)
221 qtutils.connect_button(self.tag_radio, self.display_model)
223 self.connect(self.branch_list, SIGNAL('itemSelectionChanged()'),
224 self.branch_item_changed)
226 self.connect(self.thread, SIGNAL('command'), self.thread_command)
227 self.connect(self.thread, SIGNAL('done'), self.thread_done)
229 self.resize(555, 333)
230 self.display_model()
232 def set_revision(self, revision):
233 self.revision.setText(revision)
235 def getopts(self):
236 self.opts.revision = self.revision.value()
237 self.opts.branch = unicode(self.branch_name.text())
238 self.opts.checkout = self.checkout_checkbox.isChecked()
239 self.opts.reset = self.reset_radio.isChecked()
240 self.opts.fetch = self.fetch_checkbox.isChecked()
241 self.opts.track = self.remote_radio.isChecked()
243 def create_branch(self):
244 """Creates a branch; called by the "Create Branch" button"""
245 self.getopts()
246 revision = self.opts.revision
247 branch = self.opts.branch
248 no_update = self.no_update_radio.isChecked()
249 ffwd_only = self.ffwd_only_radio.isChecked()
250 existing_branches = gitcmds.branch_list()
251 check_branch = False
253 if not branch or not revision:
254 qtutils.critical(N_('Missing Data'),
255 N_('Please provide both a branch '
256 'name and revision expression.'))
257 return
258 if branch in existing_branches:
259 if no_update:
260 msg = N_('Branch "%s" already exists.') % branch
261 qtutils.critical(N_('Branch Exists'), msg)
262 return
263 # Whether we should prompt the user for lost commits
264 commits = gitcmds.rev_list_range(revision, branch)
265 check_branch = bool(commits)
267 if check_branch:
268 msg = (N_('Resetting "%(branch)s" to "%(revision)s" '
269 'will lose commits.') %
270 dict(branch=branch, revision=revision))
271 if ffwd_only:
272 qtutils.critical(N_('Branch Exists'), msg)
273 return
274 lines = [msg]
275 for idx, commit in enumerate(commits):
276 subject = commit[1][0:min(len(commit[1]),16)]
277 if len(subject) < len(commit[1]):
278 subject += '...'
279 lines.append('\t' + commit[0][:8]
280 +'\t' + subject)
281 if idx >= 5:
282 skip = len(commits) - 5
283 lines.append('\t(%s)' % (N_('%d skipped') % skip))
284 break
285 line = N_('Recovering lost commits may not be easy.')
286 lines.append(line)
287 if not qtutils.confirm(N_('Reset Branch?'),
288 '\n'.join(lines),
289 (N_('Reset "%(branch)s" to "%(revision)s"?') %
290 dict(branch=branch, revision=revision)),
291 N_('Reset Branch'),
292 default=False,
293 icon=qtutils.icon('undo.svg')):
294 return
295 self.setEnabled(False)
296 self.progress.setEnabled(True)
297 QtGui.QApplication.setOverrideCursor(Qt.WaitCursor)
299 # Show a nice progress bar
300 self.progress.setLabelText(N_('Updating...'))
301 self.progress.show()
302 self.thread.start()
304 def thread_command(self, status, out, err):
305 Interaction.log_status(status, out, err)
307 def thread_done(self, results):
308 self.setEnabled(True)
309 self.progress.close()
310 QtGui.QApplication.restoreOverrideCursor()
312 detail_lines = []
313 for (cmd, status, out, err) in results:
314 if status != 0:
315 Interaction.critical(
316 N_('Error Creating Branch'),
317 (N_('"%(command)s" returned exit status "%(status)d"') %
318 dict(command='git '+cmd, status=status)))
319 return
320 line = '"git %s" returned exit status %d' % (cmd, status)
321 detail_lines.append(line)
322 if out:
323 detail_lines.append(out)
324 if err:
325 detail_lines.append(err)
326 detail_lines.append('')
327 details = '\n'.join(detail_lines)
328 qtutils.information(N_('Create Branch'),
329 N_('Branch created'),
330 details=details)
331 self.accept()
333 def branch_item_changed(self, *rest):
334 """This callback is called when the branch selection changes"""
335 # When the branch selection changes then we should update
336 # the "Revision Expression" accordingly.
337 qlist = self.branch_list
338 (row, selected) = qtutils.selected_row(qlist)
339 if not selected:
340 return
341 # Update the model with the selection
342 sources = self.branch_sources()
343 rev = sources[row]
344 self.revision.setText(rev)
346 # Set the branch field if we're branching from a remote branch.
347 if not self.remote_radio.isChecked():
348 return
349 branch = utils.basename(rev)
350 if branch == 'HEAD':
351 return
352 # Signal that we've clicked on a remote branch
353 self.branch_name.setText(branch)
355 def display_model(self):
356 """Sets the branch list to the available branches
358 branches = self.branch_sources()
359 qtutils.set_items(self.branch_list, branches)
361 def branch_sources(self):
362 """Get the list of items for populating the branch root list.
364 if self.local_radio.isChecked():
365 return self.model.local_branches
366 elif self.remote_radio.isChecked():
367 return self.model.remote_branches
368 elif self.tag_radio.isChecked():
369 return self.model.tags