1 """This view provides the main git-cola user interface.
5 from PyQt4
import QtCore
6 from PyQt4
import QtGui
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 merge
15 from cola
import settings
16 from cola
import signals
17 from cola
import gitcfg
18 from cola
import qtutils
19 from cola
import qtcompat
21 from cola
import resources
22 from cola
import stash
23 from cola
import utils
24 from cola
import version
25 from cola
.bookmarks
import manage_bookmarks
26 from cola
.classic
import cola_classic
27 from cola
.classic
import classic_widget
28 from cola
.controllers
import compare
29 from cola
.controllers
import createtag
30 from cola
.controllers
import search
31 from cola
.controllers
.createbranch
import create_new_branch
32 from cola
.dag
import git_dag
33 from cola
.prefs
import diff_font
34 from cola
.prefs
import PreferencesModel
35 from cola
.prefs
import preferences
36 from cola
.qt
import create_button
37 from cola
.qt
import create_dock
38 from cola
.qt
import create_menu
39 from cola
.qtutils
import add_action
40 from cola
.qtutils
import connect_button
41 from cola
.qtutils
import emit
42 from cola
.qtutils
import log
43 from cola
.qtutils
import relay_signal
44 from cola
.qtutils
import tr
45 from cola
.views
import standard
46 from cola
.widgets
import cfgactions
47 from cola
.widgets
.about
import launch_about_dialog
48 from cola
.widgets
.about
import show_shortcuts
49 from cola
.widgets
.commitmsg
import CommitMessageEditor
50 from cola
.widgets
.diff
import DiffTextEdit
51 from cola
.widgets
.status
import StatusWidget
54 class MainView(standard
.MainWindow
):
55 def __init__(self
, model
, parent
):
56 standard
.MainWindow
.__init
__(self
, parent
)
57 # Default size; this is thrown out when save/restore is used
60 self
.prefs_model
= prefs_model
= PreferencesModel()
62 # Internal field used by import/export_state().
63 # Change this whenever dockwidgets are removed.
64 self
.widget_version
= 1
66 # Keeps track of merge messages we've seen
67 self
.merge_message_hash
= ''
69 self
.setAcceptDrops(True)
72 qtcompat
.set_common_dock_options(self
)
74 self
.classic_dockable
= gitcfg
.instance().get('cola.classicdockable')
76 if self
.classic_dockable
:
77 self
.classicdockwidget
= create_dock('Cola Classic', self
)
78 self
.classicwidget
= classic_widget(self
)
79 self
.classicdockwidget
.setWidget(self
.classicwidget
)
82 self
.actiondockwidget
= create_dock('Actions', self
)
83 self
.actiondockwidgetcontents
= qt
.QFlowLayoutWidget(self
)
84 layout
= self
.actiondockwidgetcontents
.layout()
85 self
.stage_button
= create_button('Stage', layout
)
86 self
.unstage_button
= create_button('Unstage', layout
)
87 self
.rescan_button
= create_button('Rescan', layout
)
88 self
.fetch_button
= create_button('Fetch...', layout
)
89 self
.push_button
= create_button('Push...', layout
)
90 self
.pull_button
= create_button('Pull...', layout
)
91 self
.stash_button
= create_button('Stash...', layout
)
92 self
.alt_button
= create_button('Exit Diff Mode', layout
)
93 self
.alt_button
.hide()
95 self
.actiondockwidget
.setWidget(self
.actiondockwidgetcontents
)
97 # "Repository Status" widget
98 self
.statusdockwidget
= create_dock('Repository Status', self
)
99 self
.statusdockwidget
.setWidget(StatusWidget(self
))
101 # "Commit Message Editor" widget
102 self
.commitdockwidget
= create_dock('Commit Message Editor', self
)
103 self
.commitmsgeditor
= CommitMessageEditor(model
, self
)
104 relay_signal(self
, self
.commitmsgeditor
, SIGNAL(signals
.amend_mode
))
105 relay_signal(self
, self
.commitmsgeditor
, SIGNAL(signals
.signoff
))
106 relay_signal(self
, self
.commitmsgeditor
,
107 SIGNAL(signals
.load_previous_message
))
108 self
.commitdockwidget
.setWidget(self
.commitmsgeditor
)
110 # "Command Output" widget
111 logwidget
= qtutils
.logger()
112 logwidget
.setFont(diff_font())
113 self
.logdockwidget
= create_dock('Command Output', self
)
114 self
.logdockwidget
.setWidget(logwidget
)
116 # "Diff Viewer" widget
117 self
.diffdockwidget
= create_dock('Diff Viewer', self
)
118 self
.diff_viewer
= DiffTextEdit(self
.diffdockwidget
)
119 self
.diffdockwidget
.setWidget(self
.diff_viewer
)
122 self
.menu_unstage_all
= add_action(self
,
123 'Unstage All', emit(self
, signals
.unstage_all
))
124 self
.menu_unstage_all
.setIcon(qtutils
.icon('remove.svg'))
126 self
.menu_unstage_selected
= add_action(self
,
127 'Unstage From Commit', emit(self
, signals
.unstage_selected
))
128 self
.menu_unstage_selected
.setIcon(qtutils
.icon('remove.svg'))
130 self
.menu_show_diffstat
= add_action(self
,
131 'Diffstat', emit(self
, signals
.diffstat
), 'Alt+D')
133 self
.menu_stage_modified
= add_action(self
,
134 'Stage Changed Files To Commit',
135 emit(self
, signals
.stage_modified
), 'Alt+A')
136 self
.menu_stage_modified
.setIcon(qtutils
.icon('add.svg'))
138 self
.menu_stage_untracked
= add_action(self
,
139 'Stage All Untracked', emit(self
, signals
.stage_untracked
), 'Alt+U')
140 self
.menu_stage_untracked
.setIcon(qtutils
.icon('add.svg'))
142 self
.menu_export_patches
= add_action(self
,
143 'Export Patches...', guicmds
.export_patches
, 'Alt+E')
144 self
.menu_preferences
= add_action(self
,
145 'Preferences', lambda: preferences(model
=prefs_model
),
146 QtGui
.QKeySequence
.Preferences
, 'Ctrl+O')
148 self
.menu_rescan
= add_action(self
,
149 'Rescan', emit(self
, signals
.rescan_and_refresh
), 'Ctrl+R')
150 self
.menu_rescan
.setIcon(qtutils
.reload_icon())
152 self
.menu_cherry_pick
= add_action(self
,
153 'Cherry-Pick...', guicmds
.cherry_pick
, 'Ctrl+P')
155 self
.menu_load_commitmsg
= add_action(self
,
156 'Load Commit Message...', guicmds
.load_commitmsg
)
158 self
.menu_quit
= add_action(self
,
159 'Quit', self
.close
, 'Ctrl+Q')
160 self
.menu_manage_bookmarks
= add_action(self
,
161 'Bookmarks...', manage_bookmarks
)
162 self
.menu_grep
= add_action(self
,
163 'Grep', guicmds
.grep
)
164 self
.menu_merge_local
= add_action(self
,
165 'Merge...', merge
.local_merge
)
167 self
.menu_merge_abort
= add_action(self
,
168 'Abort Merge...', merge
.abort_merge
)
170 self
.menu_fetch
= add_action(self
,
171 'Fetch...', guicmds
.fetch
)
172 self
.menu_push
= add_action(self
,
173 'Push...', guicmds
.push
)
174 self
.menu_pull
= add_action(self
,
175 'Pull...', guicmds
.pull
)
177 self
.menu_open_repo
= add_action(self
,
178 'Open...', guicmds
.open_repo
)
179 self
.menu_open_repo
.setIcon(qtutils
.open_icon())
181 self
.menu_stash
= add_action(self
,
182 'Stash...', stash
.stash
, 'Alt+Shift+S')
183 self
.menu_diff_branch
= add_action(self
,
184 'Apply Changes From Branch...', guicmds
.diff_branch
)
185 self
.menu_branch_compare
= add_action(self
,
186 'Branches...', compare
.branch_compare
)
188 self
.menu_clone_repo
= add_action(self
,
189 'Clone...', guicmds
.clone_repo
)
190 self
.menu_clone_repo
.setIcon(qtutils
.git_icon())
192 self
.menu_help_docs
= add_action(self
,
194 lambda: self
.model
.git
.web__browse(resources
.html_docs()),
195 QtGui
.QKeySequence
.HelpContents
)
197 self
.menu_help_shortcuts
= add_action(self
,
198 'Keyboard Shortcuts',
200 QtCore
.Qt
.Key_Question
)
202 self
.menu_commit_compare
= add_action(self
,
203 'Commits...', compare
.compare
)
204 self
.menu_commit_compare_file
= add_action(self
,
205 'Commits Touching File...', compare
.compare_file
)
206 self
.menu_visualize_current
= add_action(self
,
207 'Visualize Current Branch...',
208 emit(self
, signals
.visualize_current
))
209 self
.menu_visualize_all
= add_action(self
,
210 'Visualize All Branches...',
211 emit(self
, signals
.visualize_all
))
212 self
.menu_search_commits
= add_action(self
,
213 'Search...', search
.search
)
214 self
.menu_browse_branch
= add_action(self
,
215 'Browse Current Branch...', guicmds
.browse_current
)
216 self
.menu_browse_other_branch
= add_action(self
,
217 'Browse Other Branch...', guicmds
.browse_other
)
218 self
.menu_load_commitmsg_template
= add_action(self
,
219 'Get Commit Message Template',
220 emit(self
, signals
.load_commit_template
))
221 self
.menu_help_about
= add_action(self
,
222 'About', launch_about_dialog
)
223 self
.menu_branch_diff
= add_action(self
,
224 'SHA-1...', guicmds
.branch_diff
)
225 self
.menu_diff_expression
= add_action(self
,
226 'Expression...', guicmds
.diff_expression
)
227 self
.menu_create_tag
= add_action(self
,
228 'Create Tag...', createtag
.create_tag
)
230 self
.menu_create_branch
= add_action(self
,
231 'Create...', create_new_branch
, 'Ctrl+B')
233 self
.menu_delete_branch
= add_action(self
,
234 'Delete...', guicmds
.branch_delete
)
236 self
.menu_checkout_branch
= add_action(self
,
237 'Checkout...', guicmds
.checkout_branch
, 'Alt+B')
238 self
.menu_rebase_branch
= add_action(self
,
239 'Rebase...', guicmds
.rebase
)
240 self
.menu_branch_review
= add_action(self
,
241 'Review...', guicmds
.review_branch
)
243 self
.menu_classic
= add_action(self
,
244 'Cola Classic...', cola_classic
)
245 self
.menu_classic
.setIcon(qtutils
.git_icon())
247 self
.menu_dag
= add_action(self
,
248 'DAG...', lambda: git_dag(self
.model
))
249 self
.menu_dag
.setIcon(qtutils
.git_icon())
252 status_tree
= self
.statusdockwidget
.widget().tree
253 self
.addAction(status_tree
.up
)
254 self
.addAction(status_tree
.down
)
255 self
.addAction(status_tree
.process_selection
)
256 self
.addAction(status_tree
.launch_difftool
)
258 # Create the application menu
259 self
.menubar
= QtGui
.QMenuBar(self
)
262 self
.file_menu
= create_menu('&File', self
.menubar
)
263 self
.file_menu
.addAction(self
.menu_preferences
)
264 self
.file_menu
.addSeparator()
265 self
.file_menu
.addAction(self
.menu_open_repo
)
266 self
.file_menu
.addAction(self
.menu_clone_repo
)
267 self
.file_menu
.addAction(self
.menu_manage_bookmarks
)
268 self
.file_menu
.addSeparator()
269 self
.file_menu
.addAction(self
.menu_rescan
)
270 self
.file_menu
.addSeparator()
271 self
.file_menu
.addSeparator()
272 self
.file_menu
.addAction(self
.menu_load_commitmsg
)
273 self
.file_menu
.addAction(self
.menu_load_commitmsg_template
)
274 self
.file_menu
.addSeparator()
275 self
.file_menu
.addAction(self
.menu_quit
)
277 self
.menubar
.addAction(self
.file_menu
.menuAction())
280 self
.commit_menu
= create_menu('Co&mmit', self
.menubar
)
281 self
.commit_menu
.setTitle(tr('Commit@@verb'))
282 self
.commit_menu
.addAction(self
.menu_stage_modified
)
283 self
.commit_menu
.addAction(self
.menu_stage_untracked
)
284 self
.commit_menu
.addSeparator()
285 self
.commit_menu
.addAction(self
.menu_unstage_all
)
286 self
.commit_menu
.addAction(self
.menu_unstage_selected
)
287 self
.commit_menu
.addSeparator()
288 self
.commit_menu
.addAction(self
.menu_search_commits
)
290 self
.menubar
.addAction(self
.commit_menu
.menuAction())
293 self
.branch_menu
= create_menu('B&ranch', self
.menubar
)
294 self
.branch_menu
.addAction(self
.menu_branch_review
)
295 self
.branch_menu
.addSeparator()
296 self
.branch_menu
.addAction(self
.menu_create_branch
)
297 self
.branch_menu
.addAction(self
.menu_checkout_branch
)
298 self
.branch_menu
.addAction(self
.menu_rebase_branch
)
299 self
.branch_menu
.addAction(self
.menu_delete_branch
)
300 self
.branch_menu
.addSeparator()
301 self
.branch_menu
.addAction(self
.menu_browse_branch
)
302 self
.branch_menu
.addAction(self
.menu_browse_other_branch
)
303 self
.branch_menu
.addSeparator()
304 self
.branch_menu
.addAction(self
.menu_visualize_current
)
305 self
.branch_menu
.addAction(self
.menu_visualize_all
)
306 self
.branch_menu
.addSeparator()
307 self
.branch_menu
.addAction(self
.menu_diff_branch
)
309 self
.menubar
.addAction(self
.branch_menu
.menuAction())
312 self
.actions_menu
= create_menu('Act&ions', self
.menubar
)
313 self
.actions_menu
.addAction(self
.menu_merge_local
)
314 self
.actions_menu
.addAction(self
.menu_stash
)
315 self
.actions_menu
.addSeparator()
316 self
.actions_menu
.addAction(self
.menu_fetch
)
317 self
.actions_menu
.addAction(self
.menu_push
)
318 self
.actions_menu
.addAction(self
.menu_pull
)
319 self
.actions_menu
.addSeparator()
320 self
.actions_menu
.addAction(self
.menu_create_tag
)
321 self
.actions_menu
.addSeparator()
322 self
.actions_menu
.addAction(self
.menu_export_patches
)
323 self
.actions_menu
.addAction(self
.menu_cherry_pick
)
324 self
.actions_menu
.addSeparator()
325 self
.actions_menu
.addAction(self
.menu_merge_abort
)
326 self
.actions_menu
.addAction(self
.menu_grep
)
328 self
.menubar
.addAction(self
.actions_menu
.menuAction())
331 self
.diff_menu
= create_menu('&Diff', self
.menubar
)
332 self
.diff_menu
.addAction(self
.menu_branch_diff
)
333 self
.diff_menu
.addAction(self
.menu_diff_expression
)
334 self
.diff_menu
.addSeparator()
335 self
.diff_menu
.addAction(self
.menu_branch_compare
)
336 self
.diff_menu
.addAction(self
.menu_commit_compare
)
337 self
.diff_menu
.addAction(self
.menu_commit_compare_file
)
338 self
.diff_menu
.addSeparator()
339 self
.diff_menu
.addAction(self
.menu_show_diffstat
)
341 self
.menubar
.addAction(self
.diff_menu
.menuAction())
344 self
.tools_menu
= create_menu('&Tools', self
.menubar
)
345 self
.tools_menu
.addAction(self
.menu_classic
)
346 self
.tools_menu
.addAction(self
.menu_dag
)
347 self
.tools_menu
.addSeparator()
348 if self
.classic_dockable
:
349 self
.tools_menu
.addAction(self
.classicdockwidget
.toggleViewAction())
350 self
.tools_menu
.addAction(self
.diffdockwidget
.toggleViewAction())
351 self
.tools_menu
.addAction(self
.actiondockwidget
.toggleViewAction())
352 self
.tools_menu
.addAction(self
.commitdockwidget
.toggleViewAction())
353 self
.tools_menu
.addAction(self
.statusdockwidget
.toggleViewAction())
354 self
.tools_menu
.addAction(self
.logdockwidget
.toggleViewAction())
355 self
.menubar
.addAction(self
.tools_menu
.menuAction())
358 self
.help_menu
= create_menu('&Help', self
.menubar
)
359 self
.help_menu
.addAction(self
.menu_help_docs
)
360 self
.help_menu
.addAction(self
.menu_help_shortcuts
)
361 self
.help_menu
.addAction(self
.menu_help_about
)
363 self
.menubar
.addAction(self
.help_menu
.menuAction())
366 self
.setMenuBar(self
.menubar
)
368 # Arrange dock widgets
369 top
= Qt
.TopDockWidgetArea
370 bottom
= Qt
.BottomDockWidgetArea
372 self
.addDockWidget(top
, self
.commitdockwidget
)
373 if self
.classic_dockable
:
374 self
.addDockWidget(top
, self
.classicdockwidget
)
375 self
.addDockWidget(top
, self
.statusdockwidget
)
376 self
.addDockWidget(top
, self
.actiondockwidget
)
377 self
.addDockWidget(bottom
, self
.logdockwidget
)
378 if self
.classic_dockable
:
379 self
.tabifyDockWidget(self
.classicdockwidget
, self
.commitdockwidget
)
380 self
.tabifyDockWidget(self
.logdockwidget
, self
.diffdockwidget
)
382 # Listen for model notifications
383 model
.add_message_observer(model
.message_mode_changed
,
386 model
.add_message_observer(model
.message_updated
,
389 prefs_model
.add_message_observer(prefs_model
.message_config_updated
,
390 self
._config
_updated
)
393 # Add button callbacks
394 connect_button(self
.rescan_button
,
395 emit(self
, signals
.rescan_and_refresh
))
396 connect_button(self
.alt_button
, emit(self
, signals
.reset_mode
))
397 connect_button(self
.fetch_button
, guicmds
.fetch
)
398 connect_button(self
.push_button
, guicmds
.push
)
399 connect_button(self
.pull_button
, guicmds
.pull
)
400 connect_button(self
.stash_button
, stash
.stash
)
402 connect_button(self
.stage_button
, self
.stage
)
403 connect_button(self
.unstage_button
, self
.unstage
)
405 self
.connect(self
, SIGNAL('update'), self
._update
_callback
)
406 self
.connect(self
, SIGNAL('apply_state'), self
.apply_state
)
407 self
.connect(self
, SIGNAL('install_config_actions'),
408 self
._install
_config
_actions
)
410 # Install .git-config-defined actions
411 self
._config
_task
= None
412 self
.install_config_actions()
414 # Restore saved settings
415 self
._gui
_state
_task
= None
416 self
._load
_gui
_state
()
418 log(0, self
.model
.git_version
+ '\ncola version ' + version
.version())
421 def closeEvent(self
, event
):
422 """Save state in the settings manager."""
423 qtutils
.save_state(self
)
424 standard
.MainWindow
.closeEvent(self
, event
)
427 mode
= property(lambda self
: self
.model
.mode
)
429 def _mode_changed(self
, mode
):
430 """React to mode changes; hide/show the "Exit Diff Mode" button."""
431 if mode
in (self
.model
.mode_branch
,
432 self
.model
.mode_review
,
433 self
.model
.mode_diff
,
434 self
.model
.mode_diff_expr
):
435 height
= self
.stage_button
.minimumHeight()
436 self
.alt_button
.setMinimumHeight(height
)
437 self
.alt_button
.show()
439 self
.alt_button
.setMinimumHeight(1)
440 self
.alt_button
.hide()
442 def _config_updated(self
, source
, config
, value
):
443 if config
== 'cola.fontdiff':
445 if not font
.fromString(value
):
447 qtutils
.logger().setFont(font
)
448 self
.diff_viewer
.setFont(font
)
449 self
.commitmsgeditor
.commitmsg
.setFont(font
)
451 elif config
== 'cola.tabwidth':
452 # variable-tab-width setting
453 self
.diff_viewer
.set_tab_width(value
)
455 def install_config_actions(self
):
456 """Install .gitconfig-defined actions"""
457 self
._config
_task
= self
._start
_config
_actions
_task
()
459 def _start_config_actions_task(self
):
460 """Do the expensive "get_config_actions()" call in the background"""
461 class ConfigActionsTask(QtCore
.QRunnable
):
462 def __init__(self
, sender
):
463 QtCore
.QRunnable
.__init
__(self
)
464 self
._sender
= sender
466 names
= cfgactions
.get_config_actions()
467 self
._sender
.emit(SIGNAL('install_config_actions'), names
)
469 task
= ConfigActionsTask(self
)
470 QtCore
.QThreadPool
.globalInstance().start(task
)
473 def _install_config_actions(self
, names
):
474 """Install .gitconfig-defined actions"""
477 menu
= self
.actions_menu
480 menu
.addAction(name
, emit(self
, signals
.run_config_action
, name
))
482 def _update_view(self
):
483 self
.emit(SIGNAL('update'))
485 def _update_callback(self
):
486 """Update the title with the current branch and directory name."""
487 branch
= self
.model
.currentbranch
488 curdir
= core
.decode(os
.getcwd())
489 msg
= 'Repository: %s\nBranch: %s' % (curdir
, branch
)
490 self
.commitdockwidget
.setToolTip(msg
)
492 title
= '%s: %s' % (self
.model
.project
, branch
)
493 if self
.mode
in (self
.model
.mode_diff
, self
.model
.mode_diff_expr
):
494 title
+= ' *** diff mode***'
495 elif self
.mode
== self
.model
.mode_review
:
496 title
+= ' *** review mode***'
497 elif self
.mode
== self
.model
.mode_amend
:
498 title
+= ' *** amending ***'
499 self
.setWindowTitle(title
)
501 self
.commitmsgeditor
.set_mode(self
.mode
)
503 if not self
.model
.read_only() and self
.mode
!= self
.model
.mode_amend
:
504 # Check if there's a message file in .git/
505 merge_msg_path
= gitcmds
.merge_message_path()
506 if merge_msg_path
is None:
508 merge_msg_hash
= utils
.checksum(core
.decode(merge_msg_path
))
509 if merge_msg_hash
== self
.merge_message_hash
:
511 self
.merge_message_hash
= merge_msg_hash
512 cola
.notifier().broadcast(signals
.load_commit_message
,
513 core
.decode(merge_msg_path
))
515 def apply_state(self
, state
):
516 """Imports data for save/restore"""
517 # 1 is the widget version; change when widgets are added/removed
518 standard
.MainWindow
.apply_state(self
, state
)
519 qtutils
.apply_window_state(self
, state
, 1)
521 def export_state(self
):
522 """Exports data for save/restore"""
523 state
= standard
.MainWindow
.export_state(self
)
524 return qtutils
.export_window_state(self
, state
, self
.widget_version
)
526 def _load_gui_state(self
):
527 """Restores the gui from the preferences file."""
528 self
._gui
_state
_task
= self
._start
_gui
_state
_loading
_thread
()
530 def _start_gui_state_loading_thread(self
):
531 """Do expensive file reading and json decoding in the background"""
532 class LoadGUIStateTask(QtCore
.QRunnable
):
533 def __init__(self
, sender
):
534 QtCore
.QRunnable
.__init
__(self
)
535 self
._sender
= sender
537 state
= settings
.Settings().get_gui_state(self
._sender
)
538 self
._sender
.emit(SIGNAL('apply_state'), state
)
540 task
= LoadGUIStateTask(self
)
541 QtCore
.QThreadPool
.globalInstance().start(task
)
545 """Stage selected files, or all files if no selection exists."""
546 paths
= cola
.selection_model().unstaged
548 cola
.notifier().broadcast(signals
.stage_modified
)
550 cola
.notifier().broadcast(signals
.stage
, paths
)
553 """Unstage selected files, or all files if no selection exists."""
554 paths
= cola
.selection_model().staged
556 cola
.notifier().broadcast(signals
.unstage_all
)
558 cola
.notifier().broadcast(signals
.unstage
, paths
)
560 def dragEnterEvent(self
, event
):
562 standard
.MainWindow
.dragEnterEvent(self
, event
)
563 event
.acceptProposedAction()
565 def dropEvent(self
, event
):
566 """Apply dropped patches with git-am"""
568 urls
= event
.mimeData().urls()
571 paths
= map(lambda x
: unicode(x
.path()), urls
)
572 patches
= [p
for p
in paths
if p
.endswith('.patch')]
573 dirs
= [p
for p
in paths
if os
.path
.isdir(p
)]
576 patches
.extend(self
._gather
_patches
(d
))
577 # Broadcast the patches to apply
578 cola
.notifier().broadcast(signals
.apply_patches
, patches
)
580 def _gather_patches(self
, path
):
581 """Find patches in a subdirectory"""
583 for root
, subdirs
, files
in os
.walk(path
):
584 for name
in [f
for f
in files
if f
.endswith('.patch')]:
585 patches
.append(os
.path
.join(root
, name
))