Use os.listdir intead of ls
[ugit.git] / ugitlibs / controllers.py
blobbfdb7a071536a620cde5020fc621360d0c042dda
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 output = os.linesep.join(os.listdir(filename))
319 self.view.displayText.setText(output)
320 return
322 if filename in self.model.get_unstaged():
323 diff = cmds.git_diff(filename=filename, cached=False)
324 msg = diff
325 self.__set_info(self.tr('Modified, not staged'))
326 else:
327 # untracked file
328 cmd = 'file -b %s' % utils.shell_quote(filename)
329 file_type = commands.getoutput(cmd)
331 if 'binary' in file_type or 'data' in file_type:
332 sq_filename = utils.shell_quote(filename)
333 cmd = 'hexdump -C %s' % sq_filename
334 contents = commands.getoutput(cmd)
335 else:
336 if os.path.exists(filename):
337 file = open(filename, 'r')
338 contents = file.read()
339 file.close()
341 else: contents = ''
343 self.__set_info(self.tr('Untracked, not staged')
344 + ': ' + file_type)
345 msg = contents
347 self.view.displayText.setText(msg)
349 def copy_display(self):
350 cursor = self.view.displayText.textCursor()
351 selection = cursor.selection().toPlainText()
352 qtutils.set_clipboard(selection)
354 def export_patches(self):
355 '''Launches the commit browser and exports the selected
356 patches.'''
358 (revs, summaries) = cmds.git_log()
359 selection, idxs = self.__select_commits(revs, summaries)
360 if not selection: return
362 # now get the selected indices to determine whether
363 # a range of consecutive commits were selected
364 selected_range = range(idxs[0], idxs[-1] + 1)
365 export_range = len(idxs) > 1 and idxs == selected_range
367 output = cmds.git_format_patch(selection, export_range)
368 self.__show_command(output)
370 def get_commit_msg(self):
371 self.model.retrieve_latest_commitmsg()
373 def last_window_closed(self):
374 '''Save config settings and cleanup the any inotify threads.'''
376 self.__save_config_settings()
378 if not self.inotify_thread: return
379 if not self.inotify_thread.isRunning(): return
381 self.inotify_thread.abort = True
382 self.inotify_thread.quit()
383 self.inotify_thread.wait()
385 def load_commitmsg(self):
386 file = qtutils.open_dialog(self.view,
387 self.tr('Load Commit Message...'),
388 defaults.DIRECTORY)
390 if file:
391 defaults.DIRECTORY = os.path.dirname(file)
392 slushy = utils.slurp(file)
393 self.model.set_commitmsg(slushy)
395 def rebase(self):
396 dlg = GitBranchDialog(self.view, cmds.git_branch())
397 dlg.setWindowTitle("Select the current branch's new root")
398 branch = dlg.getSelectedBranch()
399 if not branch: return
400 qtutils.show_command(self.view, cmds.git_rebase(branch))
402 # use *rest to handle being called from the checkbox signal
403 def rescan(self, *rest):
404 '''Populates view widgets with results from "git status."'''
406 self.view.statusBar().showMessage(
407 self.tr('Scanning for modified files ...'))
409 # Rescan for repo updates
410 self.model.update_status()
412 # Scan for branch changes
413 self.__set_branch_ui_items()
415 if not self.model.has_squash_msg(): return
417 if self.model.get_commitmsg():
418 answer = qtutils.question(self.view,
419 self.tr('Import Commit Message?'),
420 self.tr('A commit message from an in-progress'
421 + ' merge was found.\nImport it?'))
423 if not answer: return
425 # Set the new commit message
426 self.model.set_commitmsg(self.model.get_squash_msg())
428 def push(self):
429 model = self.model.clone()
430 view = GitPushDialog(self.view)
431 controller = GitPushController(model,view)
432 view.show()
433 view.exec_()
435 def cut(self):
436 self.copy()
437 self.delete()
439 def copy(self):
440 cursor = self.view.commitText.textCursor()
441 selection = cursor.selection().toPlainText()
442 qtutils.set_clipboard(selection)
444 def paste(self): self.view.commitText.paste()
445 def undo(self): self.view.commitText.undo()
446 def redo(self): self.view.commitText.redo()
447 def select_all(self): self.view.commitText.selectAll()
448 def delete(self):
449 self.view.commitText.textCursor().removeSelectedText()
451 def show_diffstat(self):
452 '''Show the diffstat from the latest commit.'''
453 self.__show_command(cmds.git_diff_stat(), rescan=False)
456 #####################################################################
457 # diff gui
459 def __diff_selection(self):
460 cursor = self.view.displayText.textCursor()
461 offset = cursor.position()
462 selection = cursor.selection().toPlainText()
463 num_selected_lines = selection.count(os.linesep)
464 return offset, selection
466 def process_diff_selection(self,
467 items, widget,
468 cached=True,
469 selected=False,
470 reverse=True,
471 noop=False):
472 filename = qtutils.get_selected_item(widget, items)
473 if not filename: return
475 parser = utils.DiffParser(
476 *cmds.git_diff(filename=filename, with_diff_header=True,
477 cached=cached, reverse=cached))
479 # Always index into the non-reversed diff
480 header, diff = \
481 cmds.git_diff(filename=filename, with_diff_header=True,
482 cached=cached, reverse=False)
484 offset, selection = self.__diff_selection()
485 if selection:
486 start = diff.index(selection)
487 end = start + len(selection)
488 parser.set_diffs_to_range(start, end)
489 else:
490 parser.set_diff_to_offset(offset)
491 selected = False
493 if not parser.diffs: return
495 # Process diff selection only
496 if selected:
497 for idx in parser.selected:
498 contents = parser.get_diff_subset(idx, start, end)
499 if contents:
500 tmpfile = utils.get_tmp_filename()
501 utils.write(tmpfile, contents)
502 self.model.apply_diff(tmpfile)
503 os.unlink(tmpfile)
505 # Process a complete hunk
506 else:
507 for idx, diff in enumerate(parser.diffs):
508 tmpfile = utils.get_tmp_filename()
509 if parser.write_diff(tmpfile,idx):
510 self.model.apply_diff(tmpfile)
511 os.unlink(tmpfile)
512 self.rescan()
514 def stage_hunk(self):
515 self.process_diff_selection(
516 self.model.get_unstaged(),
517 self.view.unstagedList,
518 cached=False)
520 def stage_hunks(self):
521 self.process_diff_selection(
522 self.model.get_unstaged(),
523 self.view.unstagedList,
524 cached=False,
525 selected=True)
527 def unstage_hunk(self, cached=True):
528 self.process_diff_selection(
529 self.model.get_staged(),
530 self.view.stagedList,
531 cached=True)
533 def unstage_hunks(self):
534 self.process_diff_selection(
535 self.model.get_staged(),
536 self.view.stagedList,
537 cached=True,
538 selected=True)
540 # #######################################################################
541 # end diff gui
543 def stage_changed(self):
544 '''Stage all changed files for commit.'''
545 output = cmds.git_add(self.model.get_unstaged())
546 self.__show_command(output)
548 def stage_untracked(self):
549 '''Stage all untracked files for commit.'''
550 output = cmds.git_add(self.model.get_untracked())
551 self.__show_command(output)
553 # use *rest to handle being called from different signals
554 def stage_selected(self,*rest):
555 '''Use "git add" to add items to the git index.
556 This is a thin wrapper around __apply_to_list.'''
557 command = cmds.git_add_or_remove
558 widget = self.view.unstagedList
559 items = self.model.get_all_unstaged()
560 self.__show_command(self.__apply_to_list(command,widget,items))
562 # use *rest to handle being called from different signals
563 def unstage_selected(self, *rest):
564 '''Use "git reset" to remove items from the git index.
565 This is a thin wrapper around __apply_to_list.'''
566 command = cmds.git_reset
567 widget = self.view.stagedList
568 items = self.model.get_staged()
569 self.__apply_to_list(command, widget, items)
571 def unstage_all(self):
572 '''Use "git reset" to remove all items from the git index.'''
573 cmds.git_reset(self.model.get_staged())
574 self.rescan()
576 def viz_all(self):
577 '''Visualizes the entire git history using gitk.'''
578 utils.fork('gitk','--all')
580 def viz_current(self):
581 '''Visualizes the current branch's history using gitk.'''
582 utils.fork('gitk', cmds.git_current_branch())
584 # These actions monitor window resizes, splitter changes, etc.
585 def move_event(self, event):
586 defaults.X = event.pos().x()
587 defaults.Y = event.pos().y()
589 def resize_event(self, event):
590 defaults.WIDTH = event.size().width()
591 defaults.HEIGHT = event.size().height()
593 def splitter_top_event(self,*rest):
594 sizes = self.view.splitter_top.sizes()
595 defaults.SPLITTER_TOP_0 = sizes[0]
596 defaults.SPLITTER_TOP_1 = sizes[1]
598 def splitter_bottom_event(self,*rest):
599 sizes = self.view.splitter_bottom.sizes()
600 defaults.SPLITTER_BOTTOM_0 = sizes[0]
601 defaults.SPLITTER_BOTTOM_1 = sizes[1]
603 #####################################################################
606 def __apply_to_list(self, command, widget, items):
607 '''This is a helper method that retrieves the current
608 selection list, applies a command to that list,
609 displays a dialog showing the output of that command,
610 and calls rescan to pickup changes.'''
611 apply_items = qtutils.get_selection_list(widget, items)
612 output = command(apply_items)
613 self.rescan()
614 return output
616 def __browse_branch(self, branch):
617 if not branch: return
618 # Clone the model to allow opening multiple browsers
619 # with different sets of data
620 model = self.model.clone()
621 model.set_branch(branch)
622 view = GitCommitBrowser()
623 controller = GitRepoBrowserController(model, view)
624 view.show()
625 view.exec_()
627 def __menu_about_to_show(self):
629 unstaged_item = qtutils.get_selected_item(
630 self.view.unstagedList,
631 self.model.get_all_unstaged())
633 is_tracked= unstaged_item not in self.model.get_untracked()
635 enable_staged= (
636 unstaged_item
637 and not self.__staged_diff_in_view
638 and is_tracked)
640 enable_unstaged= (
641 self.__staged_diff_in_view
642 and qtutils.get_selected_item(
643 self.view.stagedList,
644 self.model.get_staged()))
646 self.__stage_hunk_action.setEnabled(bool(enable_staged))
647 self.__stage_hunks_action.setEnabled(bool(enable_staged))
649 self.__unstage_hunk_action.setEnabled(bool(enable_unstaged))
650 self.__unstage_hunks_action.setEnabled(bool(enable_unstaged))
652 def __menu_event(self, event):
653 self.__menu_setup()
654 textedit = self.view.displayText
655 self.__menu.exec_(textedit.mapToGlobal(event.pos()))
657 def __menu_setup(self):
658 if self.__menu: return
660 menu = self.__menu = QMenu(self.view)
661 self.__stage_hunk_action = menu.addAction(
662 self.tr('Stage Hunk For Commit'),
663 self.stage_hunk)
665 self.__stage_hunks_action = menu.addAction(
666 self.tr('Stage Selected Lines'),
667 self.stage_hunks)
669 self.__unstage_hunk_action = menu.addAction(
670 self.tr('Unstage Hunk From Commit'),
671 self.unstage_hunk)
673 self.__unstage_hunks_action = menu.addAction(
674 self.tr('Unstage Selected Lines'),
675 self.unstage_hunks)
677 self.__copy_action = menu.addAction(
678 self.tr('Copy'),
679 self.copy_display)
681 self.connect(self.__menu, 'aboutToShow()', self.__menu_about_to_show)
683 def __file_to_widget_item(self, filename, staged, untracked=False):
684 '''Given a filename, return a QListWidgetItem suitable
685 for adding to a QListWidget. "staged" controls whether
686 to use icons for the staged or unstaged list widget.'''
687 if staged:
688 icon_file = utils.get_staged_icon(filename)
689 elif untracked:
690 icon_file = utils.get_untracked_icon()
691 else:
692 icon_file = utils.get_icon(filename)
694 return qtutils.create_listwidget_item(filename, icon_file)
696 def __read_config_settings(self):
697 (w,h,x,y,
698 st0,st1,
699 sb0,sb1) = utils.parse_geom(cmds.git_config('ugit.geometry'))
700 self.view.resize(w,h)
701 self.view.move(x,y)
702 self.view.splitter_top.setSizes([st0,st1])
703 self.view.splitter_bottom.setSizes([sb0,sb1])
705 def __save_config_settings(self):
706 cmds.git_config('ugit.geometry', utils.get_geom())
708 def __select_commits(self, revs, summaries):
709 '''Use the GitCommitBrowser to select commits from a list.'''
710 if not summaries:
711 msg = self.tr('ERROR: No commits exist in this branch.')
712 self.__show_command(msg)
713 return([],[])
715 browser = GitCommitBrowser(self.view)
716 self.connect(browser.commitList,
717 'itemSelectionChanged()',
718 lambda: self.commit_sha1_selected(
719 browser, revs) )
721 qtutils.set_items(browser.commitList, summaries)
723 browser.show()
724 result = browser.exec_()
725 if result != QDialog.Accepted:
726 return([],[])
728 list_widget = browser.commitList
729 selection = qtutils.get_selection_list(list_widget, revs)
730 if not selection: return([],[])
732 # also return the selected index numbers
733 index_nums = range(len(revs))
734 idxs = qtutils.get_selection_list(list_widget, index_nums)
736 return(selection, idxs)
738 def __set_branch_ui_items(self):
739 '''Sets up items that mention the current branch name.'''
740 branch = cmds.git_current_branch()
742 status_text = self.tr('Current Branch:') + ' ' + branch
743 self.view.statusBar().showMessage(status_text)
745 project = self.model.get_project()
746 title = '%s [%s]' % ( project, branch )
748 self.view.setWindowTitle(title)
750 def __reset_display(self):
751 self.view.displayText.setText('')
752 self.__set_info('')
754 def __set_info(self,text):
755 self.view.displayLabel.setText(text)
757 def __start_inotify_thread(self):
758 # Do we have inotify? If not, return.
759 # Recommend installing inotify if we're on Linux.
760 self.inotify_thread = None
761 try:
762 from inotify import GitNotifier
763 except ImportError:
764 import platform
765 if platform.system() == 'Linux':
766 msg =(self.tr('Unable import pyinotify.\n'
767 + 'inotify support has been'
768 + 'disabled.')
769 + '\n\n')
771 plat = platform.platform().lower()
772 if 'debian' in plat or 'ubuntu' in plat:
773 msg += (self.tr('Hint:')
774 + 'sudo apt-get install'
775 + ' python-pyinotify')
777 qtutils.information(self.view,
778 self.tr('inotify disabled'), msg)
779 return
781 self.inotify_thread = GitNotifier(os.getcwd())
782 self.connect(self.inotify_thread,
783 'timeForRescan()', self.rescan)
785 # Start the notification thread
786 self.inotify_thread.start()
788 def __show_command(self, output, rescan=True):
789 '''Shows output and optionally rescans for changes.'''
790 qtutils.show_command(self.view, output)
791 if rescan: self.rescan()
793 def __update_listwidget(self, widget, items,
794 staged, untracked=False, append=False):
795 '''Populate a QListWidget with the custom icon items.'''
796 if not append: widget.clear()
797 qtutils.add_items( widget,
798 [ self.__file_to_widget_item(i, staged, untracked)
799 for i in items ])