maint: format code using black
[git-cola.git] / cola / widgets / createbranch.py
blob31a24615983d1cd1e9c87a2dbf6c94ef449edfe3
1 from __future__ import division, absolute_import, unicode_literals
3 from qtpy import QtWidgets
4 from qtpy import QtCore
5 from qtpy.QtCore import Qt
6 from qtpy.QtCore import Signal
8 from ..i18n import N_
9 from ..interaction import Interaction
10 from ..qtutils import get
11 from .. import gitcmds
12 from .. import icons
13 from .. import qtutils
14 from . import defs
15 from . import completion
16 from . import standard
19 def create_new_branch(context, revision=''):
20 """Launches a dialog for creating a new branch"""
21 view = CreateBranchDialog(context, parent=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, context):
30 self.context = context
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 command = Signal(object, object, object)
41 result = Signal(object)
43 def __init__(self, opts, parent):
44 QtCore.QThread.__init__(self, parent)
45 self.opts = opts
47 def run(self):
48 branch = self.opts.branch
49 revision = self.opts.revision
50 reset = self.opts.reset
51 checkout = self.opts.checkout
52 track = self.opts.track
53 context = self.opts.context
54 git = context.git
55 model = context.model
56 results = []
57 status = 0
59 if track and '/' in revision:
60 remote = revision.split('/', 1)[0]
61 status, out, err = git.fetch(remote)
62 self.command.emit(status, out, err)
63 results.append(('fetch', status, out, err))
65 if status == 0:
66 status, out, err = model.create_branch(
67 branch, revision, force=reset, track=track
69 self.command.emit(status, out, err)
71 results.append(('branch', status, out, err))
72 if status == 0 and checkout:
73 status, out, err = git.checkout(branch)
74 self.command.emit(status, out, err)
75 results.append(('checkout', status, out, err))
77 model.update_status()
78 self.result.emit(results)
81 class CreateBranchDialog(standard.Dialog):
82 """A dialog for creating branches."""
84 def __init__(self, context, parent=None):
85 standard.Dialog.__init__(self, parent=parent)
86 self.setWindowTitle(N_('Create Branch'))
87 if parent is not None:
88 self.setWindowModality(Qt.WindowModal)
90 self.context = context
91 self.model = context.model
92 self.opts = CreateOpts(context)
93 self.thread = CreateThread(self.opts, self)
95 self.progress = None
97 self.branch_name_label = QtWidgets.QLabel()
98 self.branch_name_label.setText(N_('Branch Name'))
100 self.branch_name = completion.GitCreateBranchLineEdit(context)
101 self.branch_validator = completion.BranchValidator(
102 context.git, parent=self.branch_name
104 self.branch_name.setValidator(self.branch_validator)
106 self.rev_label = QtWidgets.QLabel()
107 self.rev_label.setText(N_('Starting Revision'))
109 self.revision = completion.GitRefLineEdit(context)
110 current = gitcmds.current_branch(context)
111 if current:
112 self.revision.setText(current)
114 self.local_radio = qtutils.radio(text=N_('Local branch'), checked=True)
115 self.remote_radio = qtutils.radio(text=N_('Tracking branch'))
116 self.tag_radio = qtutils.radio(text=N_('Tag'))
118 self.branch_list = QtWidgets.QListWidget()
120 self.update_existing_label = QtWidgets.QLabel()
121 self.update_existing_label.setText(N_('Update Existing Branch:'))
123 self.no_update_radio = qtutils.radio(text=N_('No'))
124 self.ffwd_only_radio = qtutils.radio(text=N_('Fast Forward Only'), checked=True)
125 self.reset_radio = qtutils.radio(text=N_('Reset'))
127 text = N_('Fetch Tracking Branch')
128 self.fetch_checkbox = qtutils.checkbox(text=text, checked=True)
130 text = N_('Checkout After Creation')
131 self.checkout_checkbox = qtutils.checkbox(text=text, checked=True)
133 icon = icons.branch()
134 self.create_button = qtutils.create_button(
135 text=N_('Create Branch'), icon=icon, default=True
137 self.close_button = qtutils.close_button()
139 self.options_checkbox_layout = qtutils.hbox(
140 defs.margin,
141 defs.spacing,
142 self.fetch_checkbox,
143 self.checkout_checkbox,
144 qtutils.STRETCH,
147 self.branch_name_layout = qtutils.hbox(
148 defs.margin, defs.spacing, self.branch_name_label, self.branch_name
151 self.rev_radio_group = qtutils.buttongroup(
152 self.local_radio, self.remote_radio, self.tag_radio
155 self.rev_radio_layout = qtutils.hbox(
156 defs.margin,
157 defs.spacing,
158 self.local_radio,
159 self.remote_radio,
160 self.tag_radio,
161 qtutils.STRETCH,
164 self.rev_start_textinput_layout = qtutils.hbox(
165 defs.no_margin, defs.spacing, self.rev_label, defs.spacing, self.revision
168 self.rev_start_layout = qtutils.vbox(
169 defs.no_margin,
170 defs.spacing,
171 self.rev_radio_layout,
172 self.branch_list,
173 self.rev_start_textinput_layout,
176 self.options_radio_group = qtutils.buttongroup(
177 self.no_update_radio, self.ffwd_only_radio, self.reset_radio
180 self.options_radio_layout = qtutils.hbox(
181 defs.no_margin,
182 defs.spacing,
183 self.update_existing_label,
184 self.no_update_radio,
185 self.ffwd_only_radio,
186 self.reset_radio,
187 qtutils.STRETCH,
190 self.buttons_layout = qtutils.hbox(
191 defs.margin,
192 defs.spacing,
193 self.close_button,
194 qtutils.STRETCH,
195 self.create_button,
198 self.main_layout = qtutils.vbox(
199 defs.margin,
200 defs.spacing,
201 self.branch_name_layout,
202 self.rev_start_layout,
203 defs.button_spacing,
204 self.options_radio_layout,
205 self.options_checkbox_layout,
206 self.buttons_layout,
208 self.setLayout(self.main_layout)
210 qtutils.add_close_action(self)
211 qtutils.connect_button(self.close_button, self.close)
212 qtutils.connect_button(self.create_button, self.create_branch)
213 qtutils.connect_toggle(self.local_radio, self.display_model)
214 qtutils.connect_toggle(self.remote_radio, self.display_model)
215 qtutils.connect_toggle(self.tag_radio, self.display_model)
217 branches = self.branch_list
218 # pylint: disable=no-member
219 branches.itemSelectionChanged.connect(self.branch_item_changed)
221 thread = self.thread
222 thread.command.connect(Interaction.log_status, type=Qt.QueuedConnection)
223 thread.result.connect(self.thread_result, type=Qt.QueuedConnection)
225 self.init_size(settings=context.settings, parent=parent)
226 self.display_model()
228 def set_revision(self, revision):
229 self.revision.setText(revision)
231 def getopts(self):
232 self.opts.revision = get(self.revision)
233 self.opts.branch = get(self.branch_name)
234 self.opts.checkout = get(self.checkout_checkbox)
235 self.opts.reset = get(self.reset_radio)
236 self.opts.fetch = get(self.fetch_checkbox)
237 self.opts.track = get(self.remote_radio)
239 def create_branch(self):
240 """Creates a branch; called by the "Create Branch" button"""
241 context = self.context
242 self.getopts()
243 revision = self.opts.revision
244 branch = self.opts.branch
245 no_update = get(self.no_update_radio)
246 ffwd_only = get(self.ffwd_only_radio)
247 existing_branches = gitcmds.branch_list(context)
248 check_branch = False
250 if not branch or not revision:
251 Interaction.critical(
252 N_('Missing Data'),
253 N_('Please provide both a branch ' 'name and revision expression.'),
255 return
256 if branch in existing_branches:
257 if no_update:
258 msg = N_('Branch "%s" already exists.') % branch
259 Interaction.critical(N_('Branch Exists'), msg)
260 return
261 # Whether we should prompt the user for lost commits
262 commits = gitcmds.rev_list_range(context, revision, branch)
263 check_branch = bool(commits)
265 if check_branch:
266 msg = N_(
267 'Resetting "%(branch)s" to "%(revision)s" ' 'will lose commits.'
268 ) % dict(branch=branch, revision=revision)
269 if ffwd_only:
270 Interaction.critical(N_('Branch Exists'), msg)
271 return
272 lines = [msg]
273 for idx, commit in enumerate(commits):
274 subject = commit[1][0 : min(len(commit[1]), 16)]
275 if len(subject) < len(commit[1]):
276 subject += '...'
277 lines.append('\t' + commit[0][:8] + '\t' + subject)
278 if idx >= 5:
279 skip = len(commits) - 5
280 lines.append('\t(%s)' % (N_('%d skipped') % skip))
281 break
282 line = N_('Recovering lost commits may not be easy.')
283 lines.append(line)
285 info_text = N_('Reset "%(branch)s" to "%(revision)s"?') % dict(
286 branch=branch, revision=revision
289 if not Interaction.confirm(
290 N_('Reset Branch?'),
291 '\n'.join(lines),
292 info_text,
293 N_('Reset Branch'),
294 default=False,
295 icon=icons.undo(),
297 return
299 title = N_('Create Branch')
300 label = N_('Updating')
301 self.progress = standard.progress(title, label, self)
302 self.progress.show()
303 self.thread.start()
305 def thread_result(self, results):
306 self.progress.hide()
307 del self.progress
309 for (cmd, status, _, _) in results:
310 if status != 0:
311 Interaction.critical(
312 N_('Error Creating Branch'),
314 N_('"%(command)s" returned exit status "%(status)d"')
315 % dict(command='git ' + cmd, status=status)
318 return
320 self.accept()
322 # pylint: disable=unused-argument
323 def branch_item_changed(self, *rest):
324 """This callback is called when the branch selection changes"""
325 # When the branch selection changes then we should update
326 # the "Revision Expression" accordingly.
327 qlist = self.branch_list
328 remote_branch = qtutils.selected_item(qlist, self.branch_sources())
329 if not remote_branch:
330 return
331 # Update the model with the selection
332 self.revision.setText(remote_branch)
334 # Set the branch field if we're branching from a remote branch.
335 if not get(self.remote_radio):
336 return
337 branch = gitcmds.strip_remote(self.model.remotes, remote_branch)
338 if branch == 'HEAD':
339 return
340 # Signal that we've clicked on a remote branch
341 self.branch_name.set_value(branch)
343 def display_model(self):
344 """Sets the branch list to the available branches"""
345 branches = self.branch_sources()
346 qtutils.set_items(self.branch_list, branches)
348 def branch_sources(self):
349 """Get the list of items for populating the branch root list."""
350 if get(self.local_radio):
351 value = self.model.local_branches
352 elif get(self.remote_radio):
353 value = self.model.remote_branches
354 elif get(self.tag_radio):
355 value = self.model.tags
356 else:
357 value = []
358 return value
360 def dispose(self):
361 self.branch_name.dispose()
362 self.revision.dispose()