requirements: install newer versions of send2trash
[git-cola.git] / cola / widgets / createbranch.py
blob4fc508c08834e8f3f3e03a06627f9c7c9d80c327
1 from qtpy import QtWidgets
2 from qtpy import QtCore
3 from qtpy.QtCore import Qt
4 from qtpy.QtCore import Signal
6 from ..i18n import N_
7 from ..interaction import Interaction
8 from ..qtutils import get
9 from .. import gitcmds
10 from .. import icons
11 from .. import qtutils
12 from . import defs
13 from . import completion
14 from . import standard
17 def create_new_branch(context, revision=''):
18 """Launches a dialog for creating a new branch"""
19 view = CreateBranchDialog(context, parent=qtutils.active_window())
20 if revision:
21 view.set_revision(revision)
22 view.show()
23 return view
26 class CreateOpts:
27 def __init__(self, context):
28 self.context = context
29 self.reset = False
30 self.track = False
31 self.fetch = True
32 self.checkout = True
33 self.revision = 'HEAD'
34 self.branch = ''
37 class CreateThread(QtCore.QThread):
38 command = Signal(object, object, object)
39 result = Signal(object)
41 def __init__(self, opts, parent):
42 QtCore.QThread.__init__(self, parent)
43 self.opts = opts
45 def run(self):
46 branch = self.opts.branch
47 revision = self.opts.revision
48 reset = self.opts.reset
49 checkout = self.opts.checkout
50 track = self.opts.track
51 context = self.opts.context
52 git = context.git
53 model = context.model
54 results = []
55 status = 0
57 if track and '/' in revision:
58 remote = revision.split('/', 1)[0]
59 status, out, err = git.fetch(remote)
60 self.command.emit(status, out, err)
61 results.append(('fetch', status, out, err))
63 if status == 0:
64 status, out, err = model.create_branch(
65 branch, revision, force=reset, track=track
67 self.command.emit(status, out, err)
69 results.append(('branch', status, out, err))
70 if status == 0 and checkout:
71 status, out, err = git.checkout(branch)
72 self.command.emit(status, out, err)
73 results.append(('checkout', status, out, err))
75 model.update_status()
76 self.result.emit(results)
79 class CreateBranchDialog(standard.Dialog):
80 """A dialog for creating branches."""
82 def __init__(self, context, parent=None):
83 standard.Dialog.__init__(self, parent=parent)
84 self.setWindowTitle(N_('Create Branch'))
85 if parent is not None:
86 self.setWindowModality(Qt.WindowModal)
88 self.context = context
89 self.model = context.model
90 self.opts = CreateOpts(context)
91 self.thread = CreateThread(self.opts, self)
93 self.progress = None
95 self.branch_name_label = QtWidgets.QLabel()
96 self.branch_name_label.setText(N_('Branch Name'))
98 self.branch_name = completion.GitCreateBranchLineEdit(context)
99 self.branch_validator = completion.BranchValidator(
100 context.git, parent=self.branch_name
102 self.branch_name.setValidator(self.branch_validator)
104 self.rev_label = QtWidgets.QLabel()
105 self.rev_label.setText(N_('Starting Revision'))
107 self.revision = completion.GitRefLineEdit(context)
108 current = gitcmds.current_branch(context)
109 if current:
110 self.revision.setText(current)
112 self.local_radio = qtutils.radio(text=N_('Local branch'), checked=True)
113 self.remote_radio = qtutils.radio(text=N_('Tracking branch'))
114 self.tag_radio = qtutils.radio(text=N_('Tag'))
116 self.branch_list = QtWidgets.QListWidget()
118 self.update_existing_label = QtWidgets.QLabel()
119 self.update_existing_label.setText(N_('Update Existing Branch:'))
121 self.no_update_radio = qtutils.radio(text=N_('No'))
122 self.ffwd_only_radio = qtutils.radio(text=N_('Fast Forward Only'), checked=True)
123 self.reset_radio = qtutils.radio(text=N_('Reset'))
125 text = N_('Fetch Tracking Branch')
126 self.fetch_checkbox = qtutils.checkbox(text=text, checked=True)
128 text = N_('Checkout After Creation')
129 self.checkout_checkbox = qtutils.checkbox(text=text, checked=True)
131 icon = icons.branch()
132 self.create_button = qtutils.create_button(
133 text=N_('Create Branch'), icon=icon, default=True
135 self.close_button = qtutils.close_button()
137 self.options_checkbox_layout = qtutils.hbox(
138 defs.margin,
139 defs.spacing,
140 self.fetch_checkbox,
141 self.checkout_checkbox,
142 qtutils.STRETCH,
145 self.branch_name_layout = qtutils.hbox(
146 defs.margin, defs.spacing, self.branch_name_label, self.branch_name
149 self.rev_radio_group = qtutils.buttongroup(
150 self.local_radio, self.remote_radio, self.tag_radio
153 self.rev_radio_layout = qtutils.hbox(
154 defs.margin,
155 defs.spacing,
156 self.local_radio,
157 self.remote_radio,
158 self.tag_radio,
159 qtutils.STRETCH,
162 self.rev_start_textinput_layout = qtutils.hbox(
163 defs.no_margin, defs.spacing, self.rev_label, defs.spacing, self.revision
166 self.rev_start_layout = qtutils.vbox(
167 defs.no_margin,
168 defs.spacing,
169 self.rev_radio_layout,
170 self.branch_list,
171 self.rev_start_textinput_layout,
174 self.options_radio_group = qtutils.buttongroup(
175 self.no_update_radio, self.ffwd_only_radio, self.reset_radio
178 self.options_radio_layout = qtutils.hbox(
179 defs.no_margin,
180 defs.spacing,
181 self.update_existing_label,
182 self.no_update_radio,
183 self.ffwd_only_radio,
184 self.reset_radio,
185 qtutils.STRETCH,
188 self.buttons_layout = qtutils.hbox(
189 defs.margin,
190 defs.spacing,
191 qtutils.STRETCH,
192 self.close_button,
193 self.create_button,
196 self.main_layout = qtutils.vbox(
197 defs.margin,
198 defs.spacing,
199 self.branch_name_layout,
200 self.rev_start_layout,
201 defs.button_spacing,
202 self.options_radio_layout,
203 self.options_checkbox_layout,
204 self.buttons_layout,
206 self.setLayout(self.main_layout)
208 qtutils.add_close_action(self)
209 qtutils.connect_button(self.close_button, self.close)
210 qtutils.connect_button(self.create_button, self.create_branch)
211 qtutils.connect_toggle(self.local_radio, self.display_model)
212 qtutils.connect_toggle(self.remote_radio, self.display_model)
213 qtutils.connect_toggle(self.tag_radio, self.display_model)
215 branches = self.branch_list
216 # pylint: disable=no-member
217 branches.itemSelectionChanged.connect(self.branch_item_changed)
219 thread = self.thread
220 thread.command.connect(Interaction.log_status, type=Qt.QueuedConnection)
221 thread.result.connect(self.thread_result, type=Qt.QueuedConnection)
223 self.init_size(settings=context.settings, parent=parent)
224 self.display_model()
226 def set_revision(self, revision):
227 self.revision.setText(revision)
229 def getopts(self):
230 self.opts.revision = get(self.revision)
231 self.opts.branch = get(self.branch_name)
232 self.opts.checkout = get(self.checkout_checkbox)
233 self.opts.reset = get(self.reset_radio)
234 self.opts.fetch = get(self.fetch_checkbox)
235 self.opts.track = get(self.remote_radio)
237 def create_branch(self):
238 """Creates a branch; called by the "Create Branch" button"""
239 context = self.context
240 self.getopts()
241 revision = self.opts.revision
242 branch = self.opts.branch
243 no_update = get(self.no_update_radio)
244 ffwd_only = get(self.ffwd_only_radio)
245 existing_branches = gitcmds.branch_list(context)
246 check_branch = False
248 if not branch or not revision:
249 Interaction.critical(
250 N_('Missing Data'),
251 N_('Please provide both a branch name and revision expression.'),
253 return
254 if branch in existing_branches:
255 if no_update:
256 msg = N_('Branch "%s" already exists.') % branch
257 Interaction.critical(N_('Branch Exists'), msg)
258 return
259 # Whether we should prompt the user for lost commits
260 commits = gitcmds.rev_list_range(context, revision, branch)
261 check_branch = bool(commits)
263 if check_branch:
264 msg = N_('Resetting "%(branch)s" to "%(revision)s" will lose commits.') % {
265 'branch': branch,
266 'revision': revision,
268 if ffwd_only:
269 Interaction.critical(N_('Branch Exists'), msg)
270 return
271 lines = [msg]
272 for idx, commit in enumerate(commits):
273 subject = commit[1][0 : min(len(commit[1]), 16)]
274 if len(subject) < len(commit[1]):
275 subject += '...'
276 lines.append('\t' + commit[0][:8] + '\t' + subject)
277 if idx >= 5:
278 skip = len(commits) - 5
279 lines.append('\t(%s)' % (N_('%d skipped') % skip))
280 break
281 line = N_('Recovering lost commits may not be easy.')
282 lines.append(line)
284 info_text = N_('Reset "%(branch)s" to "%(revision)s"?') % {
285 'branch': branch,
286 '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"')
316 'command': 'git ' + cmd,
317 'status': status,
321 return
323 self.accept()
325 # pylint: disable=unused-argument
326 def branch_item_changed(self, *rest):
327 """This callback is called when the branch selection changes"""
328 # When the branch selection changes then we should update
329 # the "Revision Expression" accordingly.
330 qlist = self.branch_list
331 remote_branch = qtutils.selected_item(qlist, self.branch_sources())
332 if not remote_branch:
333 return
334 # Update the model with the selection
335 self.revision.setText(remote_branch)
337 # Set the branch field if we're branching from a remote branch.
338 if not get(self.remote_radio):
339 return
340 branch = gitcmds.strip_remote(self.model.remotes, remote_branch)
341 if branch == 'HEAD':
342 return
343 # Signal that we've clicked on a remote branch
344 self.branch_name.set_value(branch)
346 def display_model(self):
347 """Sets the branch list to the available branches"""
348 branches = self.branch_sources()
349 qtutils.set_items(self.branch_list, branches)
351 def branch_sources(self):
352 """Get the list of items for populating the branch root list."""
353 if get(self.local_radio):
354 value = self.model.local_branches
355 elif get(self.remote_radio):
356 value = self.model.remote_branches
357 elif get(self.tag_radio):
358 value = self.model.tags
359 else:
360 value = []
361 return value
363 def dispose(self):
364 self.branch_name.dispose()
365 self.revision.dispose()