Properly handle merge commits in the export/cherry-pick guis
[ugit.git] / ugitlibs / controllers.py
blob922390c0db9a47b3b2ac7ee3732939485673a31d
1 #!/usr/bin/env python
2 import os
3 import commands
4 from PyQt4 import QtGui
5 from PyQt4 import QtCore
6 from PyQt4.QtGui import QDialog
7 from PyQt4.QtGui import QMessageBox
8 from PyQt4.QtGui import QMenu
9 from qobserver import QObserver
10 import cmds
11 import utils
12 import qtutils
13 import defaults
14 from views import GitPushDialog
15 from views import GitBranchDialog
16 from views import GitCreateBranchDialog
17 from views import GitCommitBrowser
18 from repobrowsercontroller import GitRepoBrowserController
19 from createbranchcontroller import GitCreateBranchController
20 from pushcontroller import GitPushController
22 class GitController(QObserver):
23 '''The controller is a mediator between the model and view.
24 It allows for a clean decoupling between view and model classes.'''
26 def __init__(self, model, view):
27 QObserver.__init__(self, model, view)
29 # The diff-display context menu
30 self.__menu = None
31 self.__staged_diff_in_view = True
33 # Diff display context menu
34 view.displayText.controller = self
35 view.displayText.contextMenuEvent = self.__menu_event
37 # Default to creating a new commit(i.e. not an amend commit)
38 view.newCommitRadio.setChecked(True)
40 # Binds a specific model attribute to a view widget,
41 # and vice versa.
42 self.model_to_view('commitmsg', 'commitText')
43 self.model_to_view('staged', 'stagedList')
44 self.model_to_view('all_unstaged', 'unstagedList')
46 # When a model attribute changes, this runs a specific action
47 self.add_actions('staged', self.action_staged)
48 self.add_actions('all_unstaged', self.action_all_unstaged)
50 # Routes signals for multiple widgets to our callbacks
51 # defined below.
52 self.add_signals('textChanged()', view.commitText)
53 self.add_signals('stateChanged(int)', view.untrackedCheckBox)
55 self.add_signals('released()',
56 view.stageButton, view.commitButton,
57 view.pushButton, view.signOffButton,)
59 self.add_signals('triggered()',
60 view.rescan,
61 view.createBranch, view.checkoutBranch,
62 view.rebaseBranch, view.deleteBranch,
63 view.setCommitMessage, view.commit,
64 view.stageChanged, view.stageUntracked,
65 view.stageSelected, view.unstageAll,
66 view.unstageSelected,
67 view.showDiffstat,
68 view.browseBranch, view.browseOtherBranch,
69 view.visualizeAll, view.visualizeCurrent,
70 view.exportPatches, view.cherryPick,
71 view.loadCommitMsg,
72 view.cut, view.copy, view.paste, view.delete,
73 view.selectAll, view.undo, view.redo,)
75 self.add_signals('itemClicked(QListWidgetItem *)',
76 view.stagedList, view.unstagedList,)
78 self.add_signals('itemSelectionChanged()',
79 view.stagedList, view.unstagedList,)
81 self.add_signals('splitterMoved(int,int)',
82 view.splitter_top, view.splitter_bottom)
84 # App cleanup
85 self.connect(QtGui.qApp, 'lastWindowClosed()',
86 self.last_window_closed)
88 # These callbacks are called in response to the signals
89 # defined above. One property of the QObserver callback
90 # mechanism is that the model is passed in as the first
91 # argument to the callback. This allows for a single
92 # controller to manage multiple models, though this
93 # isn't used at the moment.
94 self.add_callbacks({
95 # Actions that delegate directly to the model
96 'signOffButton': model.add_signoff,
97 'setCommitMessage': model.get_prev_commitmsg,
98 # Push Buttons
99 'stageButton': self.stage_selected,
100 'commitButton': self.commit,
101 'pushButton': self.push,
102 # List Widgets
103 'stagedList': self.diff_staged,
104 'unstagedList': self.diff_unstaged,
105 # Checkboxes
106 'untrackedCheckBox': self.rescan,
107 # Menu Actions
108 'rescan': self.rescan,
109 'createBranch': self.branch_create,
110 'deleteBranch': self.branch_delete,
111 'checkoutBranch': self.checkout_branch,
112 'rebaseBranch': self.rebase,
113 'commit': self.commit,
114 'stageChanged': self.stage_changed,
115 'stageUntracked': self.stage_untracked,
116 'stageSelected': self.stage_selected,
117 'unstageAll': self.unstage_all,
118 'unstageSelected': self.unstage_selected,
119 'showDiffstat': self.show_diffstat,
120 'browseBranch': self.browse_current,
121 'browseOtherBranch': self.browse_other,
122 'visualizeCurrent': self.viz_current,
123 'visualizeAll': self.viz_all,
124 'exportPatches': self.export_patches,
125 'cherryPick': self.cherry_pick,
126 'loadCommitMsg': self.load_commitmsg,
127 'cut': self.cut,
128 'copy': self.copy,
129 'paste': self.paste,
130 'delete': self.delete,
131 'selectAll': self.select_all,
132 'undo': self.view.commitText.undo,
133 'redo': self.redo,
134 # Splitters
135 'splitter_top': self.splitter_top_event,
136 'splitter_bottom': self.splitter_bottom_event,
139 # Handle double-clicks in the staged/unstaged lists.
140 # These are vanilla signal/slots since the qobserver
141 # signal routing is already handling these lists' signals.
142 self.connect(view.unstagedList,
143 'itemDoubleClicked(QListWidgetItem*)',
144 self.stage_selected)
146 self.connect(view.stagedList,
147 'itemDoubleClicked(QListWidgetItem*)',
148 self.unstage_selected )
150 # Delegate window move events here
151 self.view.moveEvent = self.move_event
152 self.view.resizeEvent = self.resize_event
154 # Initialize the GUI
155 self.__read_config_settings()
156 self.rescan()
158 # Setup the inotify watchdog
159 self.__start_inotify_thread()
161 #####################################################################
162 # Actions triggered during model updates
164 def action_staged(self, widget):
165 self.__update_listwidget(widget,
166 self.model.get_staged(), staged=True)
168 def action_all_unstaged(self, widget):
169 self.__update_listwidget(widget,
170 self.model.get_unstaged(), staged=False)
172 if self.view.untrackedCheckBox.isChecked():
173 self.__update_listwidget(widget,
174 self.model.get_untracked(),
175 append=True,
176 staged=False,
177 untracked=True)
179 #####################################################################
180 # Qt callbacks
182 def branch_create(self):
183 view = GitCreateBranchDialog(self.view)
184 controller = GitCreateBranchController(self.model, view)
185 view.show()
186 result = view.exec_()
187 if result == QDialog.Accepted:
188 self.rescan()
190 def branch_delete(self):
191 dlg = GitBranchDialog(self.view, branches=cmds.git_branch())
192 branch = dlg.getSelectedBranch()
193 if not branch: return
194 qtutils.show_command(self.view,
195 cmds.git_branch(name=branch, delete=True))
197 def browse_current(self):
198 self.__browse_branch(cmds.git_current_branch())
200 def browse_other(self):
201 # Prompt for a branch to browse
202 branches = self.model.all_branches()
203 dialog = GitBranchDialog(self.view, branches=branches)
205 # Launch the repobrowser
206 self.__browse_branch(dialog.getSelectedBranch())
208 def checkout_branch(self):
209 dlg = GitBranchDialog(self.view, cmds.git_branch())
210 branch = dlg.getSelectedBranch()
211 if not branch: return
212 qtutils.show_command(self.view, cmds.git_checkout(branch))
213 self.rescan()
215 def cherry_pick(self):
216 '''Starts a cherry-picking session.'''
217 (revs, summaries) = cmds.git_log(all=True)
218 selection, idxs = self.__select_commits(revs, summaries)
219 if not selection: return
220 output = cmds.git_cherry_pick(selection)
221 self.__show_command(self.tr(output))
223 def commit(self):
224 '''Sets up data and calls cmds.commit.'''
225 msg = self.model.get_commitmsg()
226 if not msg:
227 error_msg = self.tr(""
228 + "Please supply a commit message.\n"
229 + "\n"
230 + "A good commit message has the following format:\n"
231 + "\n"
232 + "- First line: Describe in one sentence what you did.\n"
233 + "- Second line: Blank\n"
234 + "- Remaining lines: Describe why this change is good.\n")
236 self.__show_command(error_msg)
237 return
239 files = self.model.get_staged()
240 if not files:
241 errmsg = self.tr(""
242 + "No changes to commit.\n"
243 + "\n"
244 + "You must stage at least 1 file before you can commit.\n")
245 self.__show_command(errmsg)
246 return
248 # Perform the commit
249 output = cmds.git_commit(msg,
250 amend=self.view.amendRadio.isChecked())
252 # Reset state
253 self.view.newCommitRadio.setChecked(True)
254 self.view.amendRadio.setChecked(False)
255 self.model.set_commitmsg('')
256 self.__show_command(output)
258 def commit_sha1_selected(self, browser, revs):
259 '''This callback is called when a commit browser's
260 item is selected. This callback puts the current
261 revision sha1 into the commitText field.
262 This callback also puts shows the commit in the
263 browser's commit textedit and copies it into
264 the global clipboard/selection.'''
265 current = browser.commitList.currentRow()
266 item = browser.commitList.item(current)
267 if not item.isSelected():
268 browser.commitText.setText('')
269 browser.revisionLine.setText('')
270 return
272 # Get the sha1 and put it in the revision line
273 sha1 = revs[current]
274 browser.revisionLine.setText(sha1)
275 browser.revisionLine.selectAll()
277 # Lookup the sha1's commit
278 commit_diff = cmds.git_diff(commit=sha1,cached=False)
279 browser.commitText.setText(commit_diff)
281 # Copy the sha1 into the clipboard
282 qtutils.set_clipboard(sha1)
284 # use *rest to handle being called from different signals
285 def diff_staged(self, *rest):
286 self.__staged_diff_in_view = True
287 widget = self.view.stagedList
288 row, selected = qtutils.get_selected_row(widget)
290 if not selected:
291 self.__reset_display()
292 return
294 filename = self.model.get_staged()[row]
295 diff = cmds.git_diff(filename=filename, cached=True)
297 if os.path.exists(filename):
298 self.__set_info(self.tr('Staged for commit'))
299 else:
300 self.__set_info(self.tr('Staged for removal'))
302 self.view.displayText.setText(diff)
304 # use *rest to handle being called from different signals
305 def diff_unstaged(self,*rest):
306 self.__staged_diff_in_view = False
307 widget = self.view.unstagedList
309 row, selected = qtutils.get_selected_row(widget)
310 if not selected:
311 self.__reset_display()
312 return
314 filename =(self.model.get_unstaged()
315 + self.model.get_untracked())[row]
316 if os.path.isdir(filename):
317 self.__set_info(self.tr('Untracked directory'))
318 cmd = 'ls -la %s' % utils.shell_quote(filename)
319 output = commands.getoutput(cmd)
320 self.view.displayText.setText(output )
321 return
323 if filename in self.model.get_unstaged():
324 diff = cmds.git_diff(filename=filename, cached=False)
325 msg = diff
326 self.__set_info(self.tr('Modified, not staged'))
327 else:
328 # untracked file
329 cmd = 'file -b %s' % utils.shell_quote(filename)
330 file_type = commands.getoutput(cmd)
332 if 'binary' in file_type or 'data' in file_type:
333 sq_filename = utils.shell_quote(filename)
334 cmd = 'hexdump -C %s' % sq_filename
335 contents = commands.getoutput(cmd)
336 else:
337 if os.path.exists(filename):
338 file = open(filename, 'r')
339 contents = file.read()
340 file.close()
342 else: contents = ''
344 self.__set_info(self.tr('Untracked, not staged')
345 + ': ' + file_type)
346 msg = contents
348 self.view.displayText.setText(msg)
350 def copy_display(self):
351 cursor = self.view.displayText.textCursor()
352 selection = cursor.selection().toPlainText()
353 qtutils.set_clipboard(selection)
355 def export_patches(self):
356 '''Launches the commit browser and exports the selected
357 patches.'''
359 (revs, summaries) = cmds.git_log()
360 selection, idxs = self.__select_commits(revs, summaries)
361 if not selection: return
363 # now get the selected indices to determine whether
364 # a range of consecutive commits were selected
365 selected_range = range(idxs[0], idxs[-1] + 1)
366 export_range = len(idxs) > 1 and idxs == selected_range
368 output = cmds.git_format_patch(selection, export_range)
369 self.__show_command(output)
371 def get_commit_msg(self):
372 self.model.retrieve_latest_commitmsg()
374 def last_window_closed(self):
375 '''Save config settings and cleanup the any inotify threads.'''
377 self.__save_config_settings()
379 if not self.inotify_thread: return
380 if not self.inotify_thread.isRunning(): return
382 self.inotify_thread.abort = True
383 self.inotify_thread.quit()
384 self.inotify_thread.wait()
386 def load_commitmsg(self):
387 file = qtutils.open_dialog(self.view,
388 self.tr('Load Commit Message...'),
389 defaults.DIRECTORY)
391 if file:
392 defaults.DIRECTORY = os.path.dirname(file)
393 slushy = utils.slurp(file)
394 self.model.set_commitmsg(slushy)
397 def rebase(self):
398 dlg = GitBranchDialog(self.view, cmds.git_branch())
399 dlg.setWindowTitle("Select the current branch's new root")
400 branch = dlg.getSelectedBranch()
401 if not branch: return
402 qtutils.show_command(self.view, cmds.git_rebase(branch))
404 # use *rest to handle being called from the checkbox signal
405 def rescan(self, *rest):
406 '''Populates view widgets with results from "git status."'''
408 self.view.statusBar().showMessage(
409 self.tr('Scanning for modified files ...'))
411 # Rescan for repo updates
412 self.model.update_status()
414 # Scan for branch changes
415 self.__set_branch_ui_items()
417 if not self.model.has_squash_msg(): return
419 if self.model.get_commitmsg():
420 answer = qtutils.question(self.view,
421 self.tr('Import Commit Message?'),
422 self.tr('A commit message from an in-progress'
423 + ' merge was found.\nImport it?'))
425 if not answer: return
427 # Set the new commit message
428 self.model.set_commitmsg(self.model.get_squash_msg())
430 def push(self):
431 model = self.model.clone()
432 view = GitPushDialog(self.view)
433 controller = GitPushController(model,view)
434 view.show()
435 view.exec_()
437 def cut(self):
438 self.copy()
439 self.delete()
441 def copy(self):
442 cursor = self.view.commitText.textCursor()
443 selection = cursor.selection().toPlainText()
444 qtutils.set_clipboard(selection)
446 def paste(self): self.view.commitText.paste()
447 def undo(self): self.view.commitText.undo()
448 def redo(self): self.view.commitText.redo()
449 def select_all(self): self.view.commitText.selectAll()
450 def delete(self):
451 self.view.commitText.textCursor().removeSelectedText()
453 def show_diffstat(self):
454 '''Show the diffstat from the latest commit.'''
455 self.__show_command(cmds.git_diff_stat(), rescan=False)
458 #####################################################################
459 # diff gui
461 def __diff_selection(self):
462 cursor = self.view.displayText.textCursor()
463 offset = cursor.position()
464 selection = cursor.selection().toPlainText()
465 num_selected_lines = selection.count(os.linesep)
466 return offset, selection
468 def process_diff_selection(self,
469 items, widget,
470 cached=True,
471 selected=False,
472 reverse=True,
473 noop=False):
474 filename = qtutils.get_selected_item(widget, items)
475 if not filename: return
477 parser = utils.DiffParser(
478 *cmds.git_diff(filename=filename, with_diff_header=True,
479 cached=cached, reverse=cached))
481 # Always index into the non-reversed diff
482 header, diff = \
483 cmds.git_diff(filename=filename, with_diff_header=True,
484 cached=cached, reverse=False)
486 offset, selection = self.__diff_selection()
487 if selection:
488 start = diff.index(selection)
489 end = start + len(selection)
490 parser.set_diffs_to_range(start, end)
491 else:
492 parser.set_diff_to_offset(offset)
493 selected = False
495 if not parser.diffs: return
497 # Process diff selection only
498 if selected:
499 for idx in parser.selected:
500 contents = parser.get_diff_subset(idx, start, end)
501 if contents:
502 tmpfile = utils.get_tmp_filename()
503 utils.write(tmpfile, contents)
504 self.model.apply_diff(tmpfile)
505 os.unlink(tmpfile)
507 # Process a complete hunk
508 else:
509 for idx, diff in enumerate(parser.diffs):
510 tmpfile = utils.get_tmp_filename()
511 if parser.write_diff(tmpfile,idx):
512 self.model.apply_diff(tmpfile)
513 os.unlink(tmpfile)
514 self.rescan()
516 def stage_hunk(self):
517 self.process_diff_selection(
518 self.model.get_unstaged(),
519 self.view.unstagedList,
520 cached=False)
522 def stage_hunks(self):
523 self.process_diff_selection(
524 self.model.get_unstaged(),
525 self.view.unstagedList,
526 cached=False,
527 selected=True)
529 def unstage_hunk(self, cached=True):
530 self.process_diff_selection(
531 self.model.get_staged(),
532 self.view.stagedList,
533 cached=True)
535 def unstage_hunks(self):
536 self.process_diff_selection(
537 self.model.get_staged(),
538 self.view.stagedList,
539 cached=True,
540 selected=True)
542 # #######################################################################
543 # end diff gui
545 def stage_changed(self):
546 '''Stage all changed files for commit.'''
547 output = cmds.git_add(self.model.get_unstaged())
548 self.__show_command(output)
550 def stage_untracked(self):
551 '''Stage all untracked files for commit.'''
552 output = cmds.git_add(self.model.get_untracked())
553 self.__show_command(output)
555 # use *rest to handle being called from different signals
556 def stage_selected(self,*rest):
557 '''Use "git add" to add items to the git index.
558 This is a thin wrapper around __apply_to_list.'''
559 command = cmds.git_add_or_remove
560 widget = self.view.unstagedList
561 items = self.model.get_all_unstaged()
562 self.__show_command(self.__apply_to_list(command,widget,items))
564 # use *rest to handle being called from different signals
565 def unstage_selected(self, *rest):
566 '''Use "git reset" to remove items from the git index.
567 This is a thin wrapper around __apply_to_list.'''
568 command = cmds.git_reset
569 widget = self.view.stagedList
570 items = self.model.get_staged()
571 self.__apply_to_list(command, widget, items)
573 def unstage_all(self):
574 '''Use "git reset" to remove all items from the git index.'''
575 cmds.git_reset(self.model.get_staged())
576 self.rescan()
578 def viz_all(self):
579 '''Visualizes the entire git history using gitk.'''
580 utils.fork('gitk','--all')
582 def viz_current(self):
583 '''Visualizes the current branch's history using gitk.'''
584 utils.fork('gitk', cmds.git_current_branch())
586 # These actions monitor window resizes, splitter changes, etc.
587 def move_event(self, event):
588 defaults.X = event.pos().x()
589 defaults.Y = event.pos().y()
591 def resize_event(self, event):
592 defaults.WIDTH = event.size().width()
593 defaults.HEIGHT = event.size().height()
595 def splitter_top_event(self,*rest):
596 sizes = self.view.splitter_top.sizes()
597 defaults.SPLITTER_TOP_0 = sizes[0]
598 defaults.SPLITTER_TOP_1 = sizes[1]
600 def splitter_bottom_event(self,*rest):
601 sizes = self.view.splitter_bottom.sizes()
602 defaults.SPLITTER_BOTTOM_0 = sizes[0]
603 defaults.SPLITTER_BOTTOM_1 = sizes[1]
605 #####################################################################
608 def __apply_to_list(self, command, widget, items):
609 '''This is a helper method that retrieves the current
610 selection list, applies a command to that list,
611 displays a dialog showing the output of that command,
612 and calls rescan to pickup changes.'''
613 apply_items = qtutils.get_selection_list(widget, items)
614 output = command(apply_items)
615 self.rescan()
616 return output
618 def __browse_branch(self, branch):
619 if not branch: return
620 # Clone the model to allow opening multiple browsers
621 # with different sets of data
622 model = self.model.clone()
623 model.set_branch(branch)
624 view = GitCommitBrowser()
625 controller = GitRepoBrowserController(model, view)
626 view.show()
627 view.exec_()
629 def __menu_about_to_show(self):
631 unstaged_item = qtutils.get_selected_item(
632 self.view.unstagedList,
633 self.model.get_all_unstaged())
635 is_tracked= unstaged_item not in self.model.get_untracked()
637 enable_staged= (
638 unstaged_item
639 and not self.__staged_diff_in_view
640 and is_tracked)
642 enable_unstaged= (
643 self.__staged_diff_in_view
644 and qtutils.get_selected_item(
645 self.view.stagedList,
646 self.model.get_staged()))
648 self.__stage_hunk_action.setEnabled(bool(enable_staged))
649 self.__stage_hunks_action.setEnabled(bool(enable_staged))
651 self.__unstage_hunk_action.setEnabled(bool(enable_unstaged))
652 self.__unstage_hunks_action.setEnabled(bool(enable_unstaged))
654 def __menu_event(self, event):
655 self.__menu_setup()
656 textedit = self.view.displayText
657 self.__menu.exec_(textedit.mapToGlobal(event.pos()))
659 def __menu_setup(self):
660 if self.__menu: return
662 menu = self.__menu = QMenu(self.view)
663 self.__stage_hunk_action = menu.addAction(
664 self.tr('Stage Hunk For Commit'),
665 self.stage_hunk)
667 self.__stage_hunks_action = menu.addAction(
668 self.tr('Stage Selected Lines'),
669 self.stage_hunks)
671 self.__unstage_hunk_action = menu.addAction(
672 self.tr('Unstage Hunk From Commit'),
673 self.unstage_hunk)
675 self.__unstage_hunks_action = menu.addAction(
676 self.tr('Unstage Selected Lines'),
677 self.unstage_hunks)
679 self.__copy_action = menu.addAction(
680 self.tr('Copy'),
681 self.copy_display)
683 self.connect(self.__menu, 'aboutToShow()', self.__menu_about_to_show)
685 def __file_to_widget_item(self, filename, staged, untracked=False):
686 '''Given a filename, return a QListWidgetItem suitable
687 for adding to a QListWidget. "staged" controls whether
688 to use icons for the staged or unstaged list widget.'''
689 if staged:
690 icon_file = utils.get_staged_icon(filename)
691 elif untracked:
692 icon_file = utils.get_untracked_icon()
693 else:
694 icon_file = utils.get_icon(filename)
696 return qtutils.create_listwidget_item(filename, icon_file)
698 def __read_config_settings(self):
699 (w,h,x,y,
700 st0,st1,
701 sb0,sb1) = utils.parse_geom(cmds.git_config('ugit.geometry'))
702 self.view.resize(w,h)
703 self.view.move(x,y)
704 self.view.splitter_top.setSizes([st0,st1])
705 self.view.splitter_bottom.setSizes([sb0,sb1])
707 def __save_config_settings(self):
708 cmds.git_config('ugit.geometry', utils.get_geom())
710 def __select_commits(self, revs, summaries):
711 '''Use the GitCommitBrowser to select commits from a list.'''
712 if not summaries:
713 msg = self.tr('ERROR: No commits exist in this branch.')
714 self.__show_command(msg)
715 return([],[])
717 browser = GitCommitBrowser(self.view)
718 self.connect(browser.commitList,
719 'itemSelectionChanged()',
720 lambda: self.commit_sha1_selected(
721 browser, revs) )
723 qtutils.set_items(browser.commitList, summaries)
725 browser.show()
726 result = browser.exec_()
727 if result != QDialog.Accepted:
728 return([],[])
730 list_widget = browser.commitList
731 selection = qtutils.get_selection_list(list_widget, revs)
732 if not selection: return([],[])
734 # also return the selected index numbers
735 index_nums = range(len(revs))
736 idxs = qtutils.get_selection_list(list_widget, index_nums)
738 return(selection, idxs)
740 def __set_branch_ui_items(self):
741 '''Sets up items that mention the current branch name.'''
742 branch = cmds.git_current_branch()
744 status_text = self.tr('Current Branch:') + ' ' + branch
745 self.view.statusBar().showMessage(status_text)
747 project = self.model.get_project()
748 title = '%s [%s]' % ( project, branch )
750 self.view.setWindowTitle(title)
752 def __reset_display(self):
753 self.view.displayText.setText('')
754 self.__set_info('')
756 def __set_info(self,text):
757 self.view.displayLabel.setText(text)
759 def __start_inotify_thread(self):
760 # Do we have inotify? If not, return.
761 # Recommend installing inotify if we're on Linux.
762 self.inotify_thread = None
763 try:
764 from inotify import GitNotifier
765 except ImportError:
766 import platform
767 if platform.system() == 'Linux':
768 msg =(self.tr('Unable import pyinotify.\n'
769 + 'inotify support has been'
770 + 'disabled.')
771 + '\n\n')
773 plat = platform.platform().lower()
774 if 'debian' in plat or 'ubuntu' in plat:
775 msg += (self.tr('Hint:')
776 + 'sudo apt-get install'
777 + ' python-pyinotify')
779 qtutils.information(self.view,
780 self.tr('inotify disabled'), msg)
781 return
783 self.inotify_thread = GitNotifier(os.getcwd())
784 self.connect(self.inotify_thread,
785 'timeForRescan()', self.rescan)
787 # Start the notification thread
788 self.inotify_thread.start()
790 def __show_command(self, output, rescan=True):
791 '''Shows output and optionally rescans for changes.'''
792 qtutils.show_command(self.view, output)
793 if rescan: self.rescan()
795 def __update_listwidget(self, widget, items,
796 staged, untracked=False, append=False):
797 '''Populate a QListWidget with the custom icon items.'''
798 if not append: widget.clear()
799 qtutils.add_items( widget,
800 [ self.__file_to_widget_item(i, staged, untracked)
801 for i in items ])