fetch: add support for the traditional FETCH_HEAD behavior
[git-cola.git] / cola / widgets / createbranch.py
blob9d400c2bc5c7b715d82c9b3e562fc0e76ddd0705
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 branches.itemSelectionChanged.connect(self.branch_item_changed)
218 thread = self.thread
219 thread.command.connect(Interaction.log_status, type=Qt.QueuedConnection)
220 thread.result.connect(self.thread_result, type=Qt.QueuedConnection)
222 self.init_size(settings=context.settings, parent=parent)
223 self.display_model()
225 def set_revision(self, revision):
226 self.revision.setText(revision)
228 def getopts(self):
229 self.opts.revision = get(self.revision)
230 self.opts.branch = get(self.branch_name)
231 self.opts.checkout = get(self.checkout_checkbox)
232 self.opts.reset = get(self.reset_radio)
233 self.opts.fetch = get(self.fetch_checkbox)
234 self.opts.track = get(self.remote_radio)
236 def create_branch(self):
237 """Creates a branch; called by the "Create Branch" button"""
238 context = self.context
239 self.getopts()
240 revision = self.opts.revision
241 branch = self.opts.branch
242 no_update = get(self.no_update_radio)
243 ffwd_only = get(self.ffwd_only_radio)
244 existing_branches = gitcmds.branch_list(context)
245 check_branch = False
247 if not branch or not revision:
248 Interaction.critical(
249 N_('Missing Data'),
250 N_('Please provide both a branch name and revision expression.'),
252 return
253 if branch in existing_branches:
254 if no_update:
255 msg = N_('Branch "%s" already exists.') % branch
256 Interaction.critical(N_('Branch Exists'), msg)
257 return
258 # Whether we should prompt the user for lost commits
259 commits = gitcmds.rev_list_range(context, revision, branch)
260 check_branch = bool(commits)
262 if check_branch:
263 msg = N_('Resetting "%(branch)s" to "%(revision)s" will lose commits.') % {
264 'branch': branch,
265 'revision': revision,
267 if ffwd_only:
268 Interaction.critical(N_('Branch Exists'), msg)
269 return
270 lines = [msg]
271 for idx, commit in enumerate(commits):
272 subject = commit[1][0 : min(len(commit[1]), 16)]
273 if len(subject) < len(commit[1]):
274 subject += '...'
275 lines.append('\t' + commit[0][:8] + '\t' + subject)
276 if idx >= 5:
277 skip = len(commits) - 5
278 lines.append('\t(%s)' % (N_('%d skipped') % skip))
279 break
280 line = N_('Recovering lost commits may not be easy.')
281 lines.append(line)
283 info_text = N_('Reset "%(branch)s" to "%(revision)s"?') % {
284 'branch': branch,
285 'revision': revision,
288 if not Interaction.confirm(
289 N_('Reset Branch?'),
290 '\n'.join(lines),
291 info_text,
292 N_('Reset Branch'),
293 default=False,
294 icon=icons.undo(),
296 return
298 title = N_('Create Branch')
299 label = N_('Updating')
300 self.progress = standard.progress(title, label, self)
301 self.progress.show()
302 self.thread.start()
304 def thread_result(self, results):
305 self.progress.hide()
306 del self.progress
308 for cmd, status, _, _ in results:
309 if status != 0:
310 Interaction.critical(
311 N_('Error Creating Branch'),
313 N_('"%(command)s" returned exit status "%(status)d"')
315 'command': 'git ' + cmd,
316 'status': status,
320 return
322 self.accept()
324 def branch_item_changed(self, *rest):
325 """This callback is called when the branch selection changes"""
326 # When the branch selection changes then we should update
327 # the "Revision Expression" accordingly.
328 qlist = self.branch_list
329 remote_branch = qtutils.selected_item(qlist, self.branch_sources())
330 if not remote_branch:
331 return
332 # Update the model with the selection
333 self.revision.setText(remote_branch)
335 # Set the branch field if we're branching from a remote branch.
336 if not get(self.remote_radio):
337 return
338 branch = gitcmds.strip_remote(self.model.remotes, remote_branch)
339 if branch == 'HEAD':
340 return
341 # Signal that we've clicked on a remote branch
342 self.branch_name.set_value(branch)
344 def display_model(self):
345 """Sets the branch list to the available branches"""
346 branches = self.branch_sources()
347 qtutils.set_items(self.branch_list, branches)
349 def branch_sources(self):
350 """Get the list of items for populating the branch root list."""
351 if get(self.local_radio):
352 value = self.model.local_branches
353 elif get(self.remote_radio):
354 value = self.model.remote_branches
355 elif get(self.tag_radio):
356 value = self.model.tags
357 else:
358 value = []
359 return value
361 def dispose(self):
362 self.branch_name.dispose()
363 self.revision.dispose()