1 from __future__
import division
, absolute_import
, unicode_literals
5 from qtpy
import QtWidgets
6 from qtpy
.QtCore
import Qt
9 from ..qtutils
import get
12 from .. import hotkeys
13 from .. import observable
15 from .. import qtutils
16 from .standard
import Dialog
17 from .standard
import DraggableTreeWidget
22 def apply_patches(context
):
23 parent
= qtutils
.active_window()
24 dlg
= new_apply_patches(context
, parent
=parent
)
30 def new_apply_patches(context
, patches
=None, parent
=None):
31 dlg
= ApplyPatches(context
, parent
=parent
)
33 dlg
.add_paths(patches
)
37 def get_patches_from_paths(paths
):
38 paths
= [core
.decode(p
) for p
in paths
]
42 if core
.isfile(p
) and (p
.endswith('.patch') or p
.endswith('.mbox'))
44 dirs
= [p
for p
in paths
if core
.isdir(p
)]
47 patches
.extend(get_patches_from_dir(d
))
51 def get_patches_from_mimedata(mimedata
):
52 urls
= mimedata
.urls()
55 paths
= [x
.path() for x
in urls
]
56 return get_patches_from_paths(paths
)
59 def get_patches_from_dir(path
):
60 """Find patches in a subdirectory"""
62 for root
, _
, files
in core
.walk(path
):
63 for name
in [f
for f
in files
if f
.endswith('.patch')]:
64 patches
.append(core
.decode(os
.path
.join(root
, name
)))
68 class ApplyPatches(Dialog
):
69 def __init__(self
, context
, parent
=None):
70 super(ApplyPatches
, self
).__init
__(parent
=parent
)
71 self
.context
= context
72 self
.setWindowTitle(N_('Apply Patches'))
73 self
.setAcceptDrops(True)
74 if parent
is not None:
75 self
.setWindowModality(Qt
.WindowModal
)
77 self
.curdir
= core
.getcwd()
78 self
.inner_drag
= False
80 self
.usage
= QtWidgets
.QLabel()
85 Drag and drop or use the <strong>Add</strong> button to add
92 self
.tree
= PatchTreeWidget(parent
=self
)
93 self
.tree
.setHeaderHidden(True)
94 # pylint: disable=no-member
95 self
.tree
.itemSelectionChanged
.connect(self
._tree
_selection
_changed
)
97 self
.notifier
= notifier
= observable
.Observable()
98 self
.diffwidget
= diff
.DiffWidget(context
, notifier
, self
, is_commit
=True)
100 self
.add_button
= qtutils
.create_toolbutton(
101 text
=N_('Add'), icon
=icons
.add(), tooltip
=N_('Add patches (+)')
104 self
.remove_button
= qtutils
.create_toolbutton(
107 tooltip
=N_('Remove selected (Delete)'),
110 self
.apply_button
= qtutils
.create_button(text
=N_('Apply'), icon
=icons
.ok())
112 self
.close_button
= qtutils
.close_button()
114 self
.add_action
= qtutils
.add_action(
115 self
, N_('Add'), self
.add_files
, hotkeys
.ADD_ITEM
118 self
.remove_action
= qtutils
.add_action(
121 self
.tree
.remove_selected
,
127 self
.top_layout
= qtutils
.hbox(
136 self
.bottom_layout
= qtutils
.hbox(
144 self
.splitter
= qtutils
.splitter(Qt
.Vertical
, self
.tree
, self
.diffwidget
)
146 self
.main_layout
= qtutils
.vbox(
153 self
.setLayout(self
.main_layout
)
155 qtutils
.connect_button(self
.add_button
, self
.add_files
)
156 qtutils
.connect_button(self
.remove_button
, self
.tree
.remove_selected
)
157 qtutils
.connect_button(self
.apply_button
, self
.apply_patches
)
158 qtutils
.connect_button(self
.close_button
, self
.close
)
160 self
.init_state(None, self
.resize
, 666, 420)
162 def apply_patches(self
):
163 items
= self
.tree
.items()
166 context
= self
.context
167 patches
= [i
.data(0, Qt
.UserRole
) for i
in items
]
168 cmds
.do(cmds
.ApplyPatches
, context
, patches
)
172 files
= qtutils
.open_files(
173 N_('Select patch file(s)...'),
174 directory
=self
.curdir
,
175 filters
='Patches (*.patch *.mbox)',
179 self
.curdir
= os
.path
.dirname(files
[0])
180 self
.add_paths([core
.relpath(f
) for f
in files
])
182 def dragEnterEvent(self
, event
):
183 """Accepts drops if the mimedata contains patches"""
184 super(ApplyPatches
, self
).dragEnterEvent(event
)
185 patches
= get_patches_from_mimedata(event
.mimeData())
187 event
.acceptProposedAction()
189 def dropEvent(self
, event
):
190 """Add dropped patches"""
192 patches
= get_patches_from_mimedata(event
.mimeData())
195 self
.add_paths(patches
)
197 def add_paths(self
, paths
):
198 self
.tree
.add_paths(paths
)
200 def _tree_selection_changed(self
):
201 items
= self
.tree
.selected_items()
204 item
= items
[-1] # take the last item
205 path
= item
.data(0, Qt
.UserRole
)
206 if not core
.exists(path
):
208 commit
= parse_patch(path
)
209 self
.diffwidget
.set_details(
210 commit
.oid
, commit
.author
, commit
.email
, commit
.date
, commit
.summary
212 self
.diffwidget
.set_diff(commit
.diff
)
214 def export_state(self
):
215 """Export persistent settings"""
216 state
= super(ApplyPatches
, self
).export_state()
217 state
['sizes'] = get(self
.splitter
)
220 def apply_state(self
, state
):
221 """Apply persistent settings"""
222 result
= super(ApplyPatches
, self
).apply_state(state
)
224 self
.splitter
.setSizes(state
['sizes'])
225 except (AttributeError, KeyError, ValueError, TypeError):
230 # pylint: disable=too-many-ancestors
231 class PatchTreeWidget(DraggableTreeWidget
):
232 def add_paths(self
, paths
):
233 patches
= get_patches_from_paths(paths
)
237 icon
= icons
.file_text()
238 for patch
in patches
:
239 item
= QtWidgets
.QTreeWidgetItem()
240 flags
= item
.flags() & ~Qt
.ItemIsDropEnabled
242 item
.setIcon(0, icon
)
243 item
.setText(0, os
.path
.basename(patch
))
244 item
.setData(0, Qt
.UserRole
, patch
)
245 item
.setToolTip(0, patch
)
247 self
.addTopLevelItems(items
)
249 def remove_selected(self
):
250 idxs
= self
.selectedIndexes()
251 rows
= [idx
.row() for idx
in idxs
]
252 for row
in reversed(sorted(rows
)):
253 self
.invisibleRootItem().takeChild(row
)
256 class Commit(object):
257 """Container for commit details"""
269 def parse_patch(path
):
270 content
= core
.read(path
)
272 parse(content
, commit
)
276 def parse(content
, commit
):
277 """Parse commit details from a patch"""
278 from_rgx
= re
.compile(r
'^From (?P<oid>[a-f0-9]{40}) .*$')
279 author_rgx
= re
.compile(r
'^From: (?P<author>[^<]+) <(?P<email>[^>]+)>$')
280 date_rgx
= re
.compile(r
'^Date: (?P<date>.*)$')
281 subject_rgx
= re
.compile(r
'^Subject: (?P<summary>.*)$')
283 commit
.content
= content
285 lines
= content
.splitlines()
286 for idx
, line
in enumerate(lines
):
287 match
= from_rgx
.match(line
)
289 commit
.oid
= match
.group('oid')
292 match
= author_rgx
.match(line
)
294 commit
.author
= match
.group('author')
295 commit
.email
= match
.group('email')
298 match
= date_rgx
.match(line
)
300 commit
.date
= match
.group('date')
303 match
= subject_rgx
.match(line
)
305 commit
.summary
= match
.group('summary')
306 commit
.diff
= '\n'.join(lines
[idx
+ 1 :])