1 """A widget for searching git commits"""
2 from __future__
import absolute_import
, division
, print_function
, unicode_literals
5 from qtpy
import QtCore
6 from qtpy
import QtWidgets
7 from qtpy
.QtCore
import Qt
10 from ..interaction
import Interaction
11 from ..git
import STDOUT
12 from ..qtutils
import connect_button
13 from ..qtutils
import create_toolbutton
14 from ..qtutils
import get
16 from .. import gitcmds
19 from .. import qtutils
22 from . import standard
26 return '%04d-%02d-%02d' % time
.localtime(timespec
)[:3]
29 class SearchOptions(object):
37 class SearchWidget(standard
.Dialog
):
38 def __init__(self
, context
, parent
):
39 standard
.Dialog
.__init
__(self
, parent
)
41 self
.context
= context
42 self
.setWindowTitle(N_('Search'))
44 self
.mode_combo
= QtWidgets
.QComboBox()
45 self
.browse_button
= create_toolbutton(
46 icon
=icons
.folder(), tooltip
=N_('Browse...')
48 self
.query
= QtWidgets
.QLineEdit()
50 self
.start_date
= QtWidgets
.QDateEdit()
51 self
.start_date
.setCurrentSection(QtWidgets
.QDateTimeEdit
.YearSection
)
52 self
.start_date
.setCalendarPopup(True)
53 self
.start_date
.setDisplayFormat(N_('yyyy-MM-dd'))
55 self
.end_date
= QtWidgets
.QDateEdit()
56 self
.end_date
.setCurrentSection(QtWidgets
.QDateTimeEdit
.YearSection
)
57 self
.end_date
.setCalendarPopup(True)
58 self
.end_date
.setDisplayFormat(N_('yyyy-MM-dd'))
61 self
.search_button
= qtutils
.create_button(
62 text
=N_('Search'), icon
=icon
, default
=True
64 self
.max_count
= standard
.SpinBox(value
=500, mini
=5, maxi
=9995, step
=5)
66 self
.commit_list
= QtWidgets
.QListWidget()
67 self
.commit_list
.setMinimumSize(QtCore
.QSize(10, 10))
68 self
.commit_list
.setAlternatingRowColors(True)
69 selection_mode
= QtWidgets
.QAbstractItemView
.SingleSelection
70 self
.commit_list
.setSelectionMode(selection_mode
)
72 self
.commit_text
= diff
.DiffTextEdit(context
, self
, whitespace
=False)
74 self
.button_export
= qtutils
.create_button(
75 text
=N_('Export Patches'), icon
=icons
.diff()
78 self
.button_cherrypick
= qtutils
.create_button(
79 text
=N_('Cherry Pick'), icon
=icons
.cherry_pick()
81 self
.button_close
= qtutils
.close_button()
83 self
.top_layout
= qtutils
.hbox(
96 self
.splitter
= qtutils
.splitter(
97 Qt
.Vertical
, self
.commit_list
, self
.commit_text
100 self
.bottom_layout
= qtutils
.hbox(
106 self
.button_cherrypick
,
109 self
.main_layout
= qtutils
.vbox(
116 self
.setLayout(self
.main_layout
)
118 self
.init_size(parent
=parent
)
122 """Return a callback to handle various search actions."""
123 return search_commits(context
, qtutils
.active_window())
126 class SearchEngine(object):
127 def __init__(self
, context
, model
):
128 self
.context
= context
132 max_count
= self
.model
.max_count
135 'max-count': max_count
,
136 'pretty': 'format:%H %aN - %s - %ar',
139 def common_args(self
):
140 return (self
.model
.query
, self
.rev_args())
144 return self
.results()
148 return len(self
.model
.query
) > 1
150 def revisions(self
, *args
, **kwargs
):
151 git
= self
.context
.git
152 revlist
= git
.log(*args
, **kwargs
)[STDOUT
]
153 return gitcmds
.parse_rev_list(revlist
)
159 class RevisionSearch(SearchEngine
):
161 query
, opts
= self
.common_args()
162 args
= utils
.shell_split(query
)
163 return self
.revisions(*args
, **opts
)
166 class PathSearch(SearchEngine
):
168 query
, args
= self
.common_args()
169 paths
= ['--'] + utils
.shell_split(query
)
170 return self
.revisions(all
=True, *paths
, **args
)
173 class MessageSearch(SearchEngine
):
175 query
, kwargs
= self
.common_args()
176 return self
.revisions(all
=True, grep
=query
, **kwargs
)
179 class AuthorSearch(SearchEngine
):
181 query
, kwargs
= self
.common_args()
182 return self
.revisions(all
=True, author
=query
, **kwargs
)
185 class CommitterSearch(SearchEngine
):
187 query
, kwargs
= self
.common_args()
188 return self
.revisions(all
=True, committer
=query
, **kwargs
)
191 class DiffSearch(SearchEngine
):
193 git
= self
.context
.git
194 query
, kwargs
= self
.common_args()
195 return gitcmds
.parse_rev_list(git
.log('-S' + query
, all
=True, **kwargs
)[STDOUT
])
198 class DateRangeSearch(SearchEngine
):
200 return self
.model
.start_date
< self
.model
.end_date
203 kwargs
= self
.rev_args()
204 start_date
= self
.model
.start_date
205 end_date
= self
.model
.end_date
206 return self
.revisions(
207 date
='iso', all
=True, after
=start_date
, before
=end_date
, **kwargs
211 class Search(SearchWidget
):
212 def __init__(self
, context
, model
, parent
):
214 Search diffs and commit logs
216 :param model: SearchOptions instance
219 SearchWidget
.__init
__(self
, context
, parent
)
222 self
.EXPR
= N_('Search by Expression')
223 self
.PATH
= N_('Search by Path')
224 self
.MESSAGE
= N_('Search Commit Messages')
225 self
.DIFF
= N_('Search Diffs')
226 self
.AUTHOR
= N_('Search Authors')
227 self
.COMMITTER
= N_('Search Committers')
228 self
.DATE_RANGE
= N_('Search Date Range')
231 # Each search type is handled by a distinct SearchEngine subclass
233 self
.EXPR
: RevisionSearch
,
234 self
.PATH
: PathSearch
,
235 self
.MESSAGE
: MessageSearch
,
236 self
.DIFF
: DiffSearch
,
237 self
.AUTHOR
: AuthorSearch
,
238 self
.COMMITTER
: CommitterSearch
,
239 self
.DATE_RANGE
: DateRangeSearch
,
251 self
.mode_combo
.addItems(self
.modes
)
253 connect_button(self
.search_button
, self
.search_callback
)
254 connect_button(self
.browse_button
, self
.browse_callback
)
255 connect_button(self
.button_export
, self
.export_patch
)
256 connect_button(self
.button_cherrypick
, self
.cherry_pick
)
257 connect_button(self
.button_close
, self
.accept
)
259 # pylint: disable=no-member
260 self
.mode_combo
.currentIndexChanged
.connect(self
.mode_changed
)
261 self
.commit_list
.itemSelectionChanged
.connect(self
.display
)
263 self
.set_start_date(mkdate(time
.time() - (87640 * 31)))
264 self
.set_end_date(mkdate(time
.time() + 87640))
265 self
.set_mode(self
.EXPR
)
267 self
.query
.setFocus()
269 def mode_changed(self
, _idx
):
271 self
.update_shown_widgets(mode
)
272 if mode
== self
.PATH
:
273 self
.browse_callback()
275 def set_commits(self
, commits
):
276 widget
= self
.commit_list
278 widget
.addItems(commits
)
280 def set_start_date(self
, datestr
):
281 set_date(self
.start_date
, datestr
)
283 def set_end_date(self
, datestr
):
284 set_date(self
.end_date
, datestr
)
286 def set_mode(self
, mode
):
287 idx
= self
.modes
.index(mode
)
288 self
.mode_combo
.setCurrentIndex(idx
)
289 self
.update_shown_widgets(mode
)
291 def update_shown_widgets(self
, mode
):
292 date_shown
= mode
== self
.DATE_RANGE
293 browse_shown
= mode
== self
.PATH
294 self
.query
.setVisible(not date_shown
)
295 self
.browse_button
.setVisible(browse_shown
)
296 self
.start_date
.setVisible(date_shown
)
297 self
.end_date
.setVisible(date_shown
)
300 return self
.mode_combo
.currentText()
302 # pylint: disable=unused-argument
303 def search_callback(self
, *args
):
304 engineclass
= self
.engines
[self
.mode()]
305 self
.model
.query
= get(self
.query
)
306 self
.model
.max_count
= get(self
.max_count
)
308 self
.model
.start_date
= get(self
.start_date
)
309 self
.model
.end_date
= get(self
.end_date
)
311 self
.results
= engineclass(self
.context
, self
.model
).search()
313 self
.display_results()
315 self
.commit_list
.clear()
316 self
.commit_text
.setText('')
318 def browse_callback(self
):
319 paths
= qtutils
.open_files(N_('Choose Paths'))
323 curdir
= core
.getcwd()
324 prefix_len
= len(curdir
) + 1
326 if not path
.startswith(curdir
):
328 relpath
= path
[prefix_len
:]
330 filepaths
.append(relpath
)
332 query
= core
.list2cmdline(filepaths
)
333 self
.query
.setText(query
)
335 self
.search_callback()
337 def display_results(self
):
338 commits
= [result
[1] for result
in self
.results
]
339 self
.set_commits(commits
)
341 def selected_revision(self
):
342 result
= qtutils
.selected_item(self
.commit_list
, self
.results
)
343 return result
[0] if result
else None
345 # pylint: disable=unused-argument
346 def display(self
, *args
):
347 context
= self
.context
348 revision
= self
.selected_revision()
350 self
.commit_text
.setText('')
352 qtutils
.set_clipboard(revision
)
353 diff_text
= gitcmds
.commit_diff(context
, revision
)
354 self
.commit_text
.setText(diff_text
)
356 def export_patch(self
):
357 context
= self
.context
358 revision
= self
.selected_revision()
359 if revision
is not None:
360 Interaction
.log_status(
361 *gitcmds
.export_patchset(context
, revision
, revision
)
364 def cherry_pick(self
):
365 git
= self
.context
.git
366 revision
= self
.selected_revision()
367 if revision
is not None:
368 Interaction
.log_status(*git
.cherry_pick(revision
))
371 def set_date(widget
, datestr
):
373 date
= QtCore
.QDate
.fromString(datestr
, fmt
)
378 def search_commits(context
, parent
):
379 opts
= SearchOptions()
380 widget
= Search(context
, opts
, parent
)