1 """A widget for searching git commits"""
4 from qtpy
import QtCore
5 from qtpy
import QtWidgets
6 from qtpy
.QtCore
import Qt
9 from ..interaction
import Interaction
10 from ..git
import STDOUT
11 from ..qtutils
import connect_button
12 from ..qtutils
import create_toolbutton
13 from ..qtutils
import get
15 from .. import gitcmds
18 from .. import qtutils
21 from . import standard
25 return '%04d-%02d-%02d' % time
.localtime(timespec
)[:3]
36 class SearchWidget(standard
.Dialog
):
37 def __init__(self
, context
, parent
):
38 standard
.Dialog
.__init
__(self
, parent
)
40 self
.context
= context
41 self
.setWindowTitle(N_('Search'))
43 self
.mode_combo
= QtWidgets
.QComboBox()
44 self
.browse_button
= create_toolbutton(
45 icon
=icons
.folder(), tooltip
=N_('Browse...')
47 self
.query
= QtWidgets
.QLineEdit()
49 self
.start_date
= QtWidgets
.QDateEdit()
50 self
.start_date
.setCurrentSection(QtWidgets
.QDateTimeEdit
.YearSection
)
51 self
.start_date
.setCalendarPopup(True)
52 self
.start_date
.setDisplayFormat(N_('yyyy-MM-dd'))
54 self
.end_date
= QtWidgets
.QDateEdit()
55 self
.end_date
.setCurrentSection(QtWidgets
.QDateTimeEdit
.YearSection
)
56 self
.end_date
.setCalendarPopup(True)
57 self
.end_date
.setDisplayFormat(N_('yyyy-MM-dd'))
60 self
.search_button
= qtutils
.create_button(
61 text
=N_('Search'), icon
=icon
, default
=True
63 self
.max_count
= standard
.SpinBox(value
=500, mini
=5, maxi
=9995, step
=5)
65 self
.commit_list
= QtWidgets
.QListWidget()
66 self
.commit_list
.setMinimumSize(QtCore
.QSize(10, 10))
67 self
.commit_list
.setAlternatingRowColors(True)
68 selection_mode
= QtWidgets
.QAbstractItemView
.SingleSelection
69 self
.commit_list
.setSelectionMode(selection_mode
)
71 self
.commit_text
= diff
.DiffTextEdit(context
, self
, whitespace
=False)
73 self
.button_export
= qtutils
.create_button(
74 text
=N_('Export Patches'), icon
=icons
.diff()
77 self
.button_cherrypick
= qtutils
.create_button(
78 text
=N_('Cherry Pick'), icon
=icons
.cherry_pick()
80 self
.button_close
= qtutils
.close_button()
82 self
.top_layout
= qtutils
.hbox(
95 self
.splitter
= qtutils
.splitter(
96 Qt
.Vertical
, self
.commit_list
, self
.commit_text
99 self
.bottom_layout
= qtutils
.hbox(
105 self
.button_cherrypick
,
108 self
.main_layout
= qtutils
.vbox(
115 self
.setLayout(self
.main_layout
)
117 self
.init_size(parent
=parent
)
121 """Return a callback to handle various search actions."""
122 return search_commits(context
, qtutils
.active_window())
126 def __init__(self
, context
, model
):
127 self
.context
= context
131 max_count
= self
.model
.max_count
134 'max-count': max_count
,
135 'pretty': 'format:%H %aN - %s - %ar',
138 def common_args(self
):
139 return (self
.model
.query
, self
.rev_args())
143 return self
.results()
147 return len(self
.model
.query
) > 1
149 def revisions(self
, *args
, **kwargs
):
150 git
= self
.context
.git
151 revlist
= git
.log(*args
, **kwargs
)[STDOUT
]
152 return gitcmds
.parse_rev_list(revlist
)
158 class RevisionSearch(SearchEngine
):
160 query
, opts
= self
.common_args()
161 args
= utils
.shell_split(query
)
162 return self
.revisions(*args
, **opts
)
165 class PathSearch(SearchEngine
):
167 query
, args
= self
.common_args()
168 paths
= ['--'] + utils
.shell_split(query
)
169 return self
.revisions(all
=True, *paths
, **args
)
172 class MessageSearch(SearchEngine
):
174 query
, kwargs
= self
.common_args()
175 return self
.revisions(all
=True, grep
=query
, **kwargs
)
178 class AuthorSearch(SearchEngine
):
180 query
, kwargs
= self
.common_args()
181 return self
.revisions(all
=True, author
=query
, **kwargs
)
184 class CommitterSearch(SearchEngine
):
186 query
, kwargs
= self
.common_args()
187 return self
.revisions(all
=True, committer
=query
, **kwargs
)
190 class DiffSearch(SearchEngine
):
192 git
= self
.context
.git
193 query
, kwargs
= self
.common_args()
194 return gitcmds
.parse_rev_list(git
.log('-S' + query
, all
=True, **kwargs
)[STDOUT
])
197 class DateRangeSearch(SearchEngine
):
199 return self
.model
.start_date
< self
.model
.end_date
202 kwargs
= self
.rev_args()
203 start_date
= self
.model
.start_date
204 end_date
= self
.model
.end_date
205 return self
.revisions(
206 date
='iso', all
=True, after
=start_date
, before
=end_date
, **kwargs
210 class Search(SearchWidget
):
211 def __init__(self
, context
, model
, parent
):
213 Search diffs and commit logs
215 :param model: SearchOptions instance
218 SearchWidget
.__init
__(self
, context
, parent
)
221 self
.EXPR
= N_('Search by Expression')
222 self
.PATH
= N_('Search by Path')
223 self
.MESSAGE
= N_('Search Commit Messages')
224 self
.DIFF
= N_('Search Diffs')
225 self
.AUTHOR
= N_('Search Authors')
226 self
.COMMITTER
= N_('Search Committers')
227 self
.DATE_RANGE
= N_('Search Date Range')
230 # Each search type is handled by a distinct SearchEngine subclass
232 self
.EXPR
: RevisionSearch
,
233 self
.PATH
: PathSearch
,
234 self
.MESSAGE
: MessageSearch
,
235 self
.DIFF
: DiffSearch
,
236 self
.AUTHOR
: AuthorSearch
,
237 self
.COMMITTER
: CommitterSearch
,
238 self
.DATE_RANGE
: DateRangeSearch
,
250 self
.mode_combo
.addItems(self
.modes
)
252 connect_button(self
.search_button
, self
.search_callback
)
253 connect_button(self
.browse_button
, self
.browse_callback
)
254 connect_button(self
.button_export
, self
.export_patch
)
255 connect_button(self
.button_cherrypick
, self
.cherry_pick
)
256 connect_button(self
.button_close
, self
.accept
)
258 self
.mode_combo
.currentIndexChanged
.connect(self
.mode_changed
)
259 self
.commit_list
.itemSelectionChanged
.connect(self
.display
)
261 self
.set_start_date(mkdate(time
.time() - (87640 * 31)))
262 self
.set_end_date(mkdate(time
.time() + 87640))
263 self
.set_mode(self
.EXPR
)
265 self
.query
.setFocus()
267 def mode_changed(self
, _idx
):
269 self
.update_shown_widgets(mode
)
270 if mode
== self
.PATH
:
271 self
.browse_callback()
273 def set_commits(self
, commits
):
274 widget
= self
.commit_list
276 widget
.addItems(commits
)
278 def set_start_date(self
, datestr
):
279 set_date(self
.start_date
, datestr
)
281 def set_end_date(self
, datestr
):
282 set_date(self
.end_date
, datestr
)
284 def set_mode(self
, mode
):
285 idx
= self
.modes
.index(mode
)
286 self
.mode_combo
.setCurrentIndex(idx
)
287 self
.update_shown_widgets(mode
)
289 def update_shown_widgets(self
, mode
):
290 date_shown
= mode
== self
.DATE_RANGE
291 browse_shown
= mode
== self
.PATH
292 self
.query
.setVisible(not date_shown
)
293 self
.browse_button
.setVisible(browse_shown
)
294 self
.start_date
.setVisible(date_shown
)
295 self
.end_date
.setVisible(date_shown
)
298 return self
.mode_combo
.currentText()
300 def search_callback(self
, *args
):
301 engineclass
= self
.engines
[self
.mode()]
302 self
.model
.query
= get(self
.query
)
303 self
.model
.max_count
= get(self
.max_count
)
305 self
.model
.start_date
= get(self
.start_date
)
306 self
.model
.end_date
= get(self
.end_date
)
308 self
.results
= engineclass(self
.context
, self
.model
).search()
310 self
.display_results()
312 self
.commit_list
.clear()
313 self
.commit_text
.setText('')
315 def browse_callback(self
):
316 paths
= qtutils
.open_files(N_('Choose Paths'))
320 curdir
= core
.getcwd()
321 prefix_len
= len(curdir
) + 1
323 if not path
.startswith(curdir
):
325 relpath
= path
[prefix_len
:]
327 filepaths
.append(relpath
)
329 query
= core
.list2cmdline(filepaths
)
330 self
.query
.setText(query
)
332 self
.search_callback()
334 def display_results(self
):
335 commits
= [result
[1] for result
in self
.results
]
336 self
.set_commits(commits
)
338 def selected_revision(self
):
339 result
= qtutils
.selected_item(self
.commit_list
, self
.results
)
340 return result
[0] if result
else None
342 def display(self
, *args
):
343 context
= self
.context
344 revision
= self
.selected_revision()
346 self
.commit_text
.setText('')
348 qtutils
.set_clipboard(revision
)
349 diff_text
= gitcmds
.commit_diff(context
, revision
)
350 self
.commit_text
.setText(diff_text
)
352 def export_patch(self
):
353 context
= self
.context
354 revision
= self
.selected_revision()
355 if revision
is not None:
356 Interaction
.log_status(
357 *gitcmds
.export_patchset(context
, revision
, revision
)
360 def cherry_pick(self
):
361 git
= self
.context
.git
362 revision
= self
.selected_revision()
363 if revision
is not None:
364 Interaction
.log_status(*git
.cherry_pick(revision
))
367 def set_date(widget
, datestr
):
369 date
= QtCore
.QDate
.fromString(datestr
, fmt
)
374 def search_commits(context
, parent
):
375 opts
= SearchOptions()
376 widget
= Search(context
, opts
, parent
)