1 """This view provides the main git-cola user interface.
5 from PyQt4
import QtGui
6 from PyQt4
import QtCore
7 from PyQt4
.QtCore
import Qt
8 from PyQt4
.QtCore
import SIGNAL
12 from cola
import gitcmds
13 from cola
import guicmds
14 from cola
import utils
15 from cola
import qtutils
16 from cola
import settings
17 from cola
import signals
18 from cola
import resources
19 from cola
.compat
import set
20 from cola
.qtutils
import SLOT
21 from cola
.views
import dag
22 from cola
.views
import about
23 from cola
.views
.syntax
import DiffSyntaxHighlighter
24 from cola
.views
.mainwindow
import MainWindow
25 from cola
.controllers
import classic
26 from cola
.controllers
import compare
27 from cola
.controllers
import search
as smod
28 from cola
.controllers
import createtag
29 from cola
.controllers
.bookmark
import manage_bookmarks
30 from cola
.controllers
.bookmark
import save_bookmark
31 from cola
.controllers
.createbranch
import create_new_branch
32 from cola
.controllers
.merge
import local_merge
33 from cola
.controllers
.merge
import abort_merge
34 from cola
.controllers
.options
import update_options
35 from cola
.controllers
.util
import choose_from_combo
36 from cola
.controllers
.util
import choose_from_list
37 from cola
.controllers
.remote
import remote_action
38 from cola
.controllers
.repobrowser
import browse_git_branch
39 from cola
.controllers
.stash
import stash
40 from cola
.controllers
.selectcommits
import select_commits
42 class MainView(MainWindow
):
43 """The main cola interface."""
45 # Read-only mode property
46 mode
= property(lambda self
: self
.model
.mode
)
48 def __init__(self
, parent
=None):
49 MainWindow
.__init
__(self
, parent
)
50 self
.setAcceptDrops(True)
52 # Qt does not support noun/verbs
53 self
.commit_button
.setText(qtutils
.tr('Commit@@verb'))
54 self
.commit_menu
.setTitle(qtutils
.tr('Commit@@verb'))
56 # Diff/patch syntax highlighter
57 self
.syntax
= DiffSyntaxHighlighter(self
.display_text
.document())
59 # Display the current column
60 self
.connect(self
.commitmsg
,
61 SIGNAL('cursorPositionChanged()'),
62 self
.show_cursor_position
)
64 # Keeps track of merge messages we've seen
65 self
.merge_message_hash
= ''
67 # Initialize the seen tree widget indexes
68 self
._seen
_indexes
= set()
70 # Initialize the GUI to show 'Column: 00'
71 self
.show_cursor_position()
73 # Internal field used by import/export_state().
74 # Change this whenever dockwidgets are removed.
75 self
._widget
_version
= 1
77 self
.model
= cola
.model()
78 self
.model
.add_message_observer(self
.model
.message_updated
,
81 # Listen for text and amend messages
82 cola
.notifier().listen(signals
.diff_text
, self
.set_display
)
83 cola
.notifier().listen(signals
.mode
, self
._mode
_changed
)
84 cola
.notifier().listen(signals
.inotify
, self
._inotify
_enabled
)
85 cola
.notifier().listen(signals
.amend
, self
.amend_checkbox
.setChecked
)
87 # Broadcast the amend mode
88 self
.connect(self
.amend_checkbox
, SIGNAL('toggled(bool)'),
89 SLOT(signals
.amend_mode
))
91 # Add button callbacks
92 self
._relay
_button
(self
.alt_button
, signals
.reset_mode
)
93 self
._relay
_button
(self
.rescan_button
, signals
.rescan
)
94 self
._relay
_button
(self
.signoff_button
, signals
.add_signoff
)
96 self
._connect
_button
(self
.stage_button
, self
.stage
)
97 self
._connect
_button
(self
.unstage_button
, self
.unstage
)
98 self
._connect
_button
(self
.commit_button
, self
.commit
)
99 self
._connect
_button
(self
.fetch_button
, self
.fetch
)
100 self
._connect
_button
(self
.push_button
, self
.push
)
101 self
._connect
_button
(self
.pull_button
, self
.pull
)
102 self
._connect
_button
(self
.stash_button
, stash
)
106 (self
.menu_quit
, self
.close
),
107 (self
.menu_branch_compare
, compare
.branch_compare
),
108 (self
.menu_branch_diff
, self
.branch_diff
),
109 (self
.menu_branch_review
, self
.review_branch
),
110 (self
.menu_browse_branch
, self
.browse_current
),
111 (self
.menu_browse_other_branch
, self
.browse_other
),
112 (self
.menu_browse_commits
, self
.browse_commits
),
113 (self
.menu_create_tag
, createtag
.create_tag
),
114 (self
.menu_create_branch
, create_new_branch
),
115 (self
.menu_checkout_branch
, self
.checkout_branch
),
116 (self
.menu_delete_branch
, self
.branch_delete
),
117 (self
.menu_rebase_branch
, self
.rebase
),
118 (self
.menu_clone_repo
, guicmds
.clone_repo
),
119 (self
.menu_commit_compare
, compare
.compare
),
120 (self
.menu_commit_compare_file
, compare
.compare_file
),
121 (self
.menu_cherry_pick
, self
.cherry_pick
),
122 (self
.menu_diff_expression
, self
.diff_expression
),
123 (self
.menu_diff_branch
, self
.diff_branch
),
124 (self
.menu_export_patches
, self
.export_patches
),
125 (self
.menu_help_about
, about
.launch_about_dialog
),
126 (self
.menu_help_docs
,
127 lambda: self
.model
.git
.web__browse(resources
.html_docs())),
128 (self
.menu_load_commitmsg
, self
.load_commitmsg
),
129 (self
.menu_load_commitmsg_template
,
130 SLOT(signals
.load_commit_template
)),
131 (self
.menu_manage_bookmarks
, manage_bookmarks
),
132 (self
.menu_save_bookmark
, save_bookmark
),
133 (self
.menu_merge_local
, local_merge
),
134 (self
.menu_merge_abort
, abort_merge
),
135 (self
.menu_open_repo
, self
.open_repo
),
136 (self
.menu_options
, update_options
),
137 (self
.menu_rescan
, SLOT(signals
.rescan
)),
138 (self
.menu_search_grep
, self
.grep
),
139 (self
.menu_search_revision
, smod
.search(smod
.REVISION_ID
)),
140 (self
.menu_search_revision_range
, smod
.search(smod
.REVISION_RANGE
)),
141 (self
.menu_search_message
, smod
.search(smod
.MESSAGE
)),
142 (self
.menu_search_path
, smod
.search(smod
.PATH
, True)),
143 (self
.menu_search_date_range
, smod
.search(smod
.DATE_RANGE
)),
144 (self
.menu_search_diff
, smod
.search(smod
.DIFF
)),
145 (self
.menu_search_author
, smod
.search(smod
.AUTHOR
)),
146 (self
.menu_search_committer
, smod
.search(smod
.COMMITTER
)),
147 (self
.menu_show_diffstat
, SLOT(signals
.diffstat
)),
148 (self
.menu_stash
, stash
),
149 (self
.menu_stage_modified
, SLOT(signals
.stage_modified
)),
150 (self
.menu_stage_untracked
, SLOT(signals
.stage_untracked
)),
151 (self
.menu_unstage_selected
, SLOT(signals
.unstage_selected
)),
152 (self
.menu_unstage_all
, SLOT(signals
.unstage_all
)),
153 (self
.menu_visualize_all
, SLOT(signals
.visualize_all
)),
154 (self
.menu_visualize_current
, SLOT(signals
.visualize_current
)),
155 # TODO This edit menu stuff should/could be command objects
156 (self
.menu_cut
, self
.action_cut
),
157 (self
.menu_copy
, self
.action_copy
),
158 (self
.menu_paste
, self
.commitmsg
.paste
),
159 (self
.menu_delete
, self
.action_delete
),
160 (self
.menu_select_all
, self
.commitmsg
.selectAll
),
161 (self
.menu_undo
, self
.commitmsg
.undo
),
162 (self
.menu_redo
, self
.commitmsg
.redo
),
163 (self
.menu_classic
, lambda: classic
.cola_classic(self
)),
164 (self
.menu_dag
, lambda: dag
.git_dag(parent
=self
)),
166 for menu
, callback
in actions
:
167 self
.connect(menu
, SIGNAL('triggered()'), callback
)
169 # Install diff shortcut keys for stage/unstage
170 self
.display_text
.keyPressEvent
= self
.diff_key_press_event
171 self
.display_text
.contextMenuEvent
= self
.diff_context_menu_event
173 # Restore saved settings
174 self
._load
_gui
_state
()
176 def _relay_button(self
, button
, signal
):
177 callback
= SLOT(signal
)
178 self
._connect
_button
(button
, callback
)
180 def _connect_button(self
, button
, callback
):
181 self
.connect(button
, SIGNAL('clicked()'), callback
)
183 def _inotify_enabled(self
, enabled
):
184 """Hide the rescan button when inotify is enabled."""
186 self
.rescan_button
.hide()
188 self
.rescan_button
.show()
190 def _update_view(self
):
191 """Update the title with the current branch and directory name."""
192 branch
= self
.model
.currentbranch
194 msg
= 'Repository: %s\nBranch: %s' % (curdir
, branch
)
198 title
= '%s [%s]' % (self
.model
.project
, branch
)
199 if self
.mode
in (self
.model
.mode_diff
, self
.model
.mode_diff_expr
):
200 title
+= ' *** diff mode***'
201 elif self
.mode
== self
.model
.mode_review
:
202 title
+= ' *** review mode***'
203 elif self
.mode
== self
.model
.mode_amend
:
204 title
+= ' *** amending ***'
205 self
.setWindowTitle(title
)
207 if self
.mode
!= self
.model
.mode_amend
:
208 self
.amend_checkbox
.blockSignals(True)
209 self
.amend_checkbox
.setChecked(False)
210 self
.amend_checkbox
.blockSignals(False)
212 if not self
.model
.read_only() and self
.mode
!= self
.model
.mode_amend
:
213 # Check if there's a message file in .git/
214 merge_msg_path
= gitcmds
.merge_message_path()
215 if merge_msg_path
is None:
217 merge_msg_hash
= utils
.checksum(merge_msg_path
)
218 if merge_msg_hash
== self
.merge_message_hash
:
220 self
.merge_message_hash
= merge_msg_hash
221 cola
.notifier().broadcast(signals
.load_commit_message
,
224 def _mode_changed(self
, mode
):
225 """React to mode changes; hide/show the "Exit Diff Mode" button."""
226 if mode
in (self
.model
.mode_review
,
227 self
.model
.mode_diff
,
228 self
.model
.mode_diff_expr
):
229 self
.alt_button
.setMinimumHeight(40)
230 self
.alt_button
.show()
232 self
.alt_button
.setMinimumHeight(1)
233 self
.alt_button
.hide()
235 def set_display(self
, text
):
236 """Set the diff text display."""
237 scrollbar
= self
.display_text
.verticalScrollBar()
238 scrollvalue
= scrollbar
.value()
240 self
.display_text
.setPlainText(text
)
241 scrollbar
.setValue(scrollvalue
)
243 def action_cut(self
):
247 def action_copy(self
):
248 cursor
= self
.commitmsg
.textCursor()
249 selection
= cursor
.selection().toPlainText()
250 qtutils
.set_clipboard(selection
)
252 def action_delete(self
):
253 self
.commitmsg
.textCursor().removeSelectedText()
255 def copy_display(self
):
256 cursor
= self
.display_text
.textCursor()
257 selection
= cursor
.selection().toPlainText()
258 qtutils
.set_clipboard(selection
)
260 def diff_selection(self
):
261 cursor
= self
.display_text
.textCursor()
262 offset
= cursor
.position()
263 selection
= unicode(cursor
.selection().toPlainText())
264 return offset
, selection
266 def selected_line(self
):
267 cursor
= self
.display_text
.textCursor()
268 offset
= cursor
.position()
269 contents
= unicode(self
.display_text
.toPlainText())
271 and contents
[offset
-1]
272 and contents
[offset
-1] != '\n'):
274 data
= contents
[offset
:]
276 line
, rest
= data
.split('\n', 1)
281 def show_cursor_position(self
):
282 """Update the UI with the current row and column."""
283 cursor
= self
.commitmsg
.textCursor()
284 position
= cursor
.position()
285 txt
= unicode(self
.commitmsg
.toPlainText())
286 rows
= txt
[:position
].count('\n') + 1
287 cols
= cursor
.columnNumber()
288 display
= ' %d,%d ' % (rows
, cols
)
290 display
= ('<span style="color: white; '
291 ' background-color: red;"'
292 '>%s</span>' % display
.replace(' ', ' '))
294 display
= ('<span style="color: black; '
295 ' background-color: orange;"'
296 '>%s</span>' % display
.replace(' ', ' '))
298 display
= ('<span style="color: black; '
299 ' background-color: yellow;"'
300 '>%s</span>' % display
.replace(' ', ' '))
301 self
.position_label
.setText(display
)
303 def import_state(self
, state
):
304 """Imports data for save/restore"""
305 MainWindow
.import_state(self
, state
)
306 # Restore the dockwidget, etc. window state
307 if 'windowstate' in state
:
308 windowstate
= state
['windowstate']
309 self
.restoreState(QtCore
.QByteArray
.fromBase64(str(windowstate
)),
310 self
._widget
_version
)
312 def export_state(self
):
313 """Exports data for save/restore"""
314 state
= MainWindow
.export_state(self
)
315 # Save the window state
316 windowstate
= self
.saveState(self
._widget
_version
)
317 state
['windowstate'] = unicode(windowstate
.toBase64().data())
320 def review_branch(self
):
321 """Diff against an arbitrary revision, branch, tag, etc."""
322 branch
= choose_from_combo('Select Branch, Tag, or Commit-ish',
323 self
.model
.all_branches() +
327 cola
.notifier().broadcast(signals
.review_branch_mode
, branch
)
329 def branch_diff(self
):
330 """Diff against an arbitrary revision, branch, tag, etc."""
331 branch
= choose_from_combo('Select Branch, Tag, or Commit-ish',
333 self
.model
.all_branches() +
337 cola
.notifier().broadcast(signals
.diff_mode
, branch
)
339 def diff_expression(self
):
340 """Diff using an arbitrary expression."""
341 expr
= choose_from_combo('Enter Diff Expression',
342 self
.model
.all_branches() +
346 cola
.notifier().broadcast(signals
.diff_expr_mode
, expr
)
349 def diff_branch(self
):
350 """Launches a diff against a branch."""
351 branch
= choose_from_combo('Select Branch, Tag, or Commit-ish',
353 self
.model
.all_branches() +
357 zfiles_str
= self
.model
.git
.diff(branch
, name_only
=True,
360 files
= zfiles_str
.split('\0')
361 filename
= choose_from_list('Select File', files
)
364 cola
.notifier().broadcast(signals
.branch_mode
, branch
, filename
)
366 def _load_gui_state(self
):
367 """Restores the gui from the preferences file."""
368 state
= settings
.SettingsManager
.gui_state(self
)
369 self
.import_state(state
)
371 def load_commitmsg(self
):
372 """Load a commit message from a file."""
373 filename
= qtutils
.open_dialog(self
,
374 'Load Commit Message...',
377 cola
.notifier().broadcast(signals
.load_commit_message
, filename
)
380 def diff_key_press_event(self
, event
):
381 """Handle shortcut keys in the diff view."""
382 if event
.key() != QtCore
.Qt
.Key_H
and event
.key() != QtCore
.Qt
.Key_S
:
385 staged
, modified
, unmerged
, untracked
= cola
.single_selection()
386 if event
.key() == QtCore
.Qt
.Key_H
:
387 if self
.mode
== self
.model
.mode_worktree
and modified
:
389 elif self
.mode
== self
.model
.mode_index
:
391 elif event
.key() == QtCore
.Qt
.Key_S
:
392 if self
.mode
== self
.model
.mode_worktree
and modified
:
393 self
.stage_hunk_selection()
394 elif self
.mode
== self
.model
.mode_index
:
395 self
.unstage_hunk_selection()
397 def process_diff_selection(self
, selected
=False,
398 staged
=True, apply_to_worktree
=False,
400 """Implement un/staging of selected lines or hunks."""
401 offset
, selection
= self
.diff_selection()
402 cola
.notifier().broadcast(signals
.apply_diff_selection
,
410 """Destructively remove a hunk from a worktree file."""
411 if not qtutils
.question(self
,
412 'Destroy Local Changes?',
413 'This operation will drop '
414 'uncommitted changes.\n'
418 self
.process_diff_selection(staged
=False, apply_to_worktree
=True,
421 def undo_selection(self
):
422 """Destructively check out content for the selected file from $head."""
423 if not qtutils
.question(self
,
424 'Destroy Local Changes?',
425 'This operation will drop '
426 'uncommitted changes.\n'
430 self
.process_diff_selection(staged
=False, apply_to_worktree
=True,
431 reverse
=True, selected
=True)
434 """Stage selected files, or all files if no selection exists."""
435 paths
= cola
.selection_model().unstaged
437 cola
.notifier().broadcast(signals
.stage_modified
)
439 cola
.notifier().broadcast(signals
.stage
, paths
)
442 """Unstage selected files, or all files if no selection exists."""
443 paths
= cola
.selection_model().staged
445 cola
.notifier().broadcast(signals
.unstage_all
)
447 cola
.notifier().broadcast(signals
.unstage
, paths
)
449 def stage_hunk(self
):
450 """Stage a specific hunk."""
451 self
.process_diff_selection(staged
=False)
453 def stage_hunk_selection(self
):
454 """Stage selected lines."""
455 self
.process_diff_selection(staged
=False, selected
=True)
457 def unstage_hunk(self
, cached
=True):
458 """Unstage a hunk."""
459 self
.process_diff_selection(staged
=True)
461 def unstage_hunk_selection(self
):
462 """Unstage selected lines."""
463 self
.process_diff_selection(staged
=True, selected
=True)
465 def diff_context_menu_event(self
, event
):
466 """Create the context menu for the diff display."""
467 menu
= self
.diff_context_menu_setup()
468 textedit
= self
.display_text
469 menu
.exec_(textedit
.mapToGlobal(event
.pos()))
471 def diff_context_menu_setup(self
):
472 """Set up the context menu for the diff display."""
473 menu
= QtGui
.QMenu(self
)
474 staged
, modified
, unmerged
, untracked
= cola
.selection()
476 if self
.mode
== self
.model
.mode_worktree
:
477 if modified
and modified
[0] in cola
.model().submodules
:
478 menu
.addAction(self
.tr('Stage'),
479 SLOT(signals
.stage
, modified
))
480 menu
.addAction(self
.tr('Launch git-cola'),
481 SLOT(signals
.open_repo
,
482 os
.path
.abspath(modified
[0])))
484 menu
.addAction(self
.tr('Stage &Hunk For Commit'),
486 menu
.addAction(self
.tr('Stage &Selected Lines'),
487 self
.stage_hunk_selection
)
489 menu
.addAction(self
.tr('Undo Hunk'),
491 menu
.addAction(self
.tr('Undo Selected Lines'),
494 elif self
.mode
== self
.model
.mode_index
:
495 if staged
and staged
[0] in cola
.model().submodules
:
496 menu
.addAction(self
.tr('Unstage'),
497 SLOT(signals
.unstage
, staged
))
498 menu
.addAction(self
.tr('Launch git-cola'),
499 SLOT(signals
.open_repo
,
500 os
.path
.abspath(staged
[0])))
502 menu
.addAction(self
.tr('Unstage &Hunk From Commit'),
504 menu
.addAction(self
.tr('Unstage &Selected Lines'),
505 self
.unstage_hunk_selection
)
507 elif self
.mode
== self
.model
.mode_branch
:
508 menu
.addAction(self
.tr('Apply Diff to Work Tree'), self
.stage_hunk
)
509 menu
.addAction(self
.tr('Apply Diff Selection to Work Tree'), self
.stage_hunk_selection
)
511 elif self
.mode
== self
.model
.mode_grep
:
512 menu
.addAction(self
.tr('Go Here'), self
.goto_grep
)
515 menu
.addAction(self
.tr('Copy'), self
.copy_display
)
519 """Launch the 'fetch' remote dialog."""
520 remote_action(self
, 'fetch')
523 """Launch the 'push' remote dialog."""
524 remote_action(self
, 'push')
527 """Launch the 'pull' remote dialog."""
528 remote_action(self
, 'pull')
531 """Attempt to create a commit from the index and commit message."""
533 msg
= self
.model
.commitmsg
535 # Describe a good commit message
536 error_msg
= self
.tr(''
537 'Please supply a commit message.\n\n'
538 'A good commit message has the following format:\n\n'
539 '- First line: Describe in one sentence what you did.\n'
540 '- Second line: Blank\n'
541 '- Remaining lines: Describe why this change is good.\n')
542 qtutils
.log(1, error_msg
)
543 cola
.notifier().broadcast(signals
.information
,
544 'Missing Commit Message',
547 if not self
.model
.staged
:
548 error_msg
= self
.tr(''
549 'No changes to commit.\n\n'
550 'You must stage at least 1 file before you can commit.\n')
551 qtutils
.log(1, error_msg
)
552 cola
.notifier().broadcast(signals
.information
,
556 # Warn that amending published commits is generally bad
557 amend
= self
.amend_checkbox
.isChecked()
558 if (amend
and self
.model
.is_commit_published() and
559 not qtutils
.question(self
,
560 'Rewrite Published Commit?',
561 'This commit has already been published.\n'
562 'You are rewriting published history.\n'
563 'You probably don\'t want to do this.\n\n'
568 cola
.notifier().broadcast(signals
.commit
, amend
, msg
)
571 """Prompt and use 'git grep' to find the content."""
572 # This should be a command in cola.commands.
573 txt
, ok
= qtutils
.prompt('grep')
576 cola
.notifier().broadcast(signals
.grep
, txt
)
579 """Called when Search -> Grep's right-click 'goto' action."""
580 line
= self
.selected_line()
581 filename
, line_number
, contents
= line
.split(':', 2)
582 filename
= core
.encode(filename
)
583 cola
.notifier().broadcast(signals
.edit
, [filename
], line_number
=line_number
)
586 """Spawn a new cola session."""
587 dirname
= qtutils
.opendir_dialog(self
,
588 'Open Git Repository...',
592 cola
.notifier().broadcast(signals
.open_repo
, dirname
)
594 def cherry_pick(self
):
595 """Launch the 'Cherry-Pick' dialog."""
596 revs
, summaries
= gitcmds
.log_helper(all
=True)
597 commits
= select_commits('Cherry-Pick Commit',
598 revs
, summaries
, multiselect
=False)
601 cola
.notifier().broadcast(signals
.cherry_pick
, commits
)
603 def browse_commits(self
):
604 """Launch the 'Browse Commits' dialog."""
605 revs
, summaries
= gitcmds
.log_helper(all
=True)
606 select_commits('Browse Commits', revs
, summaries
)
608 def export_patches(self
):
609 """Run 'git format-patch' on a list of commits."""
610 revs
, summaries
= gitcmds
.log_helper()
611 to_export
= select_commits('Export Patches', revs
, summaries
)
616 cola
.notifier().broadcast(signals
.format_patch
, to_export
, revs
)
618 def browse_current(self
):
619 """Launch the 'Browse Current Branch' dialog."""
620 browse_git_branch(gitcmds
.current_branch())
622 def browse_other(self
):
623 """Prompt for a branch and inspect content at that point in time."""
624 # Prompt for a branch to browse
625 branch
= choose_from_combo('Browse Revision...', gitcmds
.all_refs())
628 # Launch the repobrowser
629 browse_git_branch(branch
)
631 def branch_create(self
):
632 """Launch the 'Create Branch' dialog."""
635 def branch_delete(self
):
636 """Launch the 'Delete Branch' dialog."""
637 branch
= choose_from_combo('Delete Branch',
638 self
.model
.local_branches
)
641 cola
.notifier().broadcast(signals
.delete_branch
, branch
)
643 def checkout_branch(self
):
644 """Launch the 'Checkout Branch' dialog."""
645 branch
= choose_from_combo('Checkout Branch',
646 self
.model
.local_branches
)
649 cola
.notifier().broadcast(signals
.checkout_branch
, branch
)
652 """Rebase onto a branch."""
653 branch
= choose_from_combo('Rebase Branch',
654 self
.model
.all_branches())
658 status
, output
= self
.model
.git
.rebase(branch
,
661 qtutils
.log(status
, output
)
663 def dragEnterEvent(self
, event
):
665 MainWindow
.dragEnterEvent(self
, event
)
666 event
.acceptProposedAction()
668 def dropEvent(self
, event
):
669 """Apply dropped patches with git-am"""
671 urls
= event
.mimeData().urls()
674 paths
= map(lambda x
: unicode(x
.path()), urls
)
675 patches
= [p
for p
in paths
if p
.endswith('.patch')]
676 dirs
= [p
for p
in paths
if os
.path
.isdir(p
)]
679 patches
.extend(self
._gather
_patches
(d
))
680 # Broadcast the patches to apply
681 cola
.notifier().broadcast(signals
.apply_patches
, patches
)
683 def _gather_patches(self
, path
):
684 """Find patches in a subdirectory"""
686 for root
, subdirs
, files
in os
.walk(path
):
687 for name
in [f
for f
in files
if f
.endswith('.patch')]:
688 patches
.append(os
.path
.join(root
, name
))