1from __future__ import division, absolute_import, unicode_literals 2 3from qtpy import QtWidgets 4from qtpy import QtCore 5from qtpy.QtCore import Qt 6from qtpy.QtCore import Signal 7 8from ..i18n import N_ 9from ..interaction import Interaction 10from ..qtutils import get 11from .. import gitcmds 12from .. import icons 13from .. import qtutils 14from . import defs 15from . import completion 16from . import standard 17 18 19def 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 26 27 28class 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 = '' 37 38 39class CreateThread(QtCore.QThread): 40 command = Signal(object, object, object) 41 result = Signal(object) 42 43 def __init__(self, opts, parent): 44 QtCore.QThread.__init__(self, parent) 45 self.opts = opts 46 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 58 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)) 64 65 if status == 0: 66 status, out, err = model.create_branch( 67 branch, revision, force=reset, track=track 68 ) 69 self.command.emit(status, out, err) 70 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)) 76 77 model.update_status() 78 self.result.emit(results) 79 80 81class CreateBranchDialog(standard.Dialog): 82 """A dialog for creating branches.""" 83 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) 89 90 self.context = context 91 self.model = context.model 92 self.opts = CreateOpts(context) 93 self.thread = CreateThread(self.opts, self) 94 95 self.progress = None 96 97 self.branch_name_label = QtWidgets.QLabel() 98 self.branch_name_label.setText(N_('Branch Name')) 99 100 self.branch_name = completion.GitCreateBranchLineEdit(context) 101 self.branch_validator = completion.BranchValidator( 102 context.git, parent=self.branch_name 103 ) 104 self.branch_name.setValidator(self.branch_validator) 105 106 self.rev_label = QtWidgets.QLabel() 107 self.rev_label.setText(N_('Starting Revision')) 108 109 self.revision = completion.GitRefLineEdit(context) 110 current = gitcmds.current_branch(context) 111 if current: 112 self.revision.setText(current) 113 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')) 117 118 self.branch_list = QtWidgets.QListWidget() 119 120 self.update_existing_label = QtWidgets.QLabel() 121 self.update_existing_label.setText(N_('Update Existing Branch:')) 122 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')) 126 127 text = N_('Fetch Tracking Branch') 128 self.fetch_checkbox = qtutils.checkbox(text=text, checked=True) 129 130 text = N_('Checkout After Creation') 131 self.checkout_checkbox = qtutils.checkbox(text=text, checked=True) 132 133 icon = icons.branch() 134 self.create_button = qtutils.create_button( 135 text=N_('Create Branch'), icon=icon, default=True 136 ) 137 self.close_button = qtutils.close_button() 138 139 self.options_checkbox_layout = qtutils.hbox( 140 defs.margin, 141 defs.spacing, 142 self.fetch_checkbox, 143 self.checkout_checkbox, 144 qtutils.STRETCH, 145 ) 146 147 self.branch_name_layout = qtutils.hbox( 148 defs.margin, defs.spacing, self.branch_name_label, self.branch_name 149 ) 150 151 self.rev_radio_group = qtutils.buttongroup( 152 self.local_radio, self.remote_radio, self.tag_radio 153 ) 154 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, 162 ) 163 164 self.rev_start_textinput_layout = qtutils.hbox( 165 defs.no_margin, defs.spacing, self.rev_label, defs.spacing, self.revision 166 ) 167 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, 174 ) 175 176 self.options_radio_group = qtutils.buttongroup( 177 self.no_update_radio, self.ffwd_only_radio, self.reset_radio 178 ) 179 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, 188 ) 189 190 self.buttons_layout = qtutils.hbox( 191 defs.margin, 192 defs.spacing, 193 self.close_button, 194 qtutils.STRETCH, 195 self.create_button, 196 ) 197 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, 207 ) 208 self.setLayout(self.main_layout) 209 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) 216 217 branches = self.branch_list 218 # pylint: disable=no-member 219 branches.itemSelectionChanged.connect(self.branch_item_changed) 220 221 thread = self.thread 222 thread.command.connect(Interaction.log_status, type=Qt.QueuedConnection) 223 thread.result.connect(self.thread_result, type=Qt.QueuedConnection) 224 225 self.init_size(settings=context.settings, parent=parent) 226 self.display_model() 227 228 def set_revision(self, revision): 229 self.revision.setText(revision) 230 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) 238 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 249 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.'), 254 ) 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) 264 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) 284 285 info_text = N_('Reset "%(branch)s" to "%(revision)s"?') % dict( 286 branch=branch, revision=revision 287 ) 288 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(), 296 ): 297 return 298 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() 304 305 def thread_result(self, results): 306 self.progress.hide() 307 del self.progress 308 309 for (cmd, status, _, _) in results: 310 if status != 0: 311 Interaction.critical( 312 N_('Error Creating Branch'), 313 ( 314 N_('"%(command)s" returned exit status "%(status)d"') 315 % dict(command='git ' + cmd, status=status) 316 ), 317 ) 318 return 319 320 self.accept() 321 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) 333 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) 342 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) 347 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 359 360 def dispose(self): 361 self.branch_name.dispose() 362 self.revision.dispose() 363