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
]
39 patches
= [p
for p
in paths
41 (p
.endswith('.patch') or p
.endswith('.mbox'))]
42 dirs
= [p
for p
in paths
if core
.isdir(p
)]
45 patches
.extend(get_patches_from_dir(d
))
49 def get_patches_from_mimedata(mimedata
):
50 urls
= mimedata
.urls()
53 paths
= [x
.path() for x
in urls
]
54 return get_patches_from_paths(paths
)
57 def get_patches_from_dir(path
):
58 """Find patches in a subdirectory"""
60 for root
, _
, files
in core
.walk(path
):
61 for name
in [f
for f
in files
if f
.endswith('.patch')]:
62 patches
.append(core
.decode(os
.path
.join(root
, name
)))
66 class ApplyPatches(Dialog
):
68 def __init__(self
, context
, parent
=None):
69 super(ApplyPatches
, self
).__init
__(parent
=parent
)
70 self
.context
= context
71 self
.setWindowTitle(N_('Apply Patches'))
72 self
.setAcceptDrops(True)
73 if parent
is not None:
74 self
.setWindowModality(Qt
.WindowModal
)
76 self
.curdir
= core
.getcwd()
77 self
.inner_drag
= False
79 self
.usage
= QtWidgets
.QLabel()
80 self
.usage
.setText(N_("""
82 Drag and drop or use the <strong>Add</strong> button to add
87 self
.tree
= PatchTreeWidget(parent
=self
)
88 self
.tree
.setHeaderHidden(True)
89 self
.tree
.itemSelectionChanged
.connect(self
._tree
_selection
_changed
)
91 self
.notifier
= notifier
= observable
.Observable()
92 self
.diffwidget
= diff
.DiffWidget(context
, notifier
, self
,
95 self
.add_button
= qtutils
.create_toolbutton(
96 text
=N_('Add'), icon
=icons
.add(),
97 tooltip
=N_('Add patches (+)'))
99 self
.remove_button
= qtutils
.create_toolbutton(
100 text
=N_('Remove'), icon
=icons
.remove(),
101 tooltip
=N_('Remove selected (Delete)'))
103 self
.apply_button
= qtutils
.create_button(
104 text
=N_('Apply'), icon
=icons
.ok())
106 self
.close_button
= qtutils
.close_button()
108 self
.add_action
= qtutils
.add_action(
109 self
, N_('Add'), self
.add_files
, hotkeys
.ADD_ITEM
)
111 self
.remove_action
= qtutils
.add_action(
112 self
, N_('Remove'), self
.tree
.remove_selected
,
113 hotkeys
.DELETE
, hotkeys
.BACKSPACE
, hotkeys
.REMOVE_ITEM
)
115 self
.top_layout
= qtutils
.hbox(defs
.no_margin
, defs
.button_spacing
,
116 self
.add_button
, self
.remove_button
,
117 qtutils
.STRETCH
, self
.usage
)
119 self
.bottom_layout
= qtutils
.hbox(defs
.no_margin
, defs
.button_spacing
,
120 self
.close_button
, qtutils
.STRETCH
,
123 self
.splitter
= qtutils
.splitter(Qt
.Vertical
,
124 self
.tree
, self
.diffwidget
)
126 self
.main_layout
= qtutils
.vbox(defs
.margin
, defs
.spacing
,
127 self
.top_layout
, self
.splitter
,
129 self
.setLayout(self
.main_layout
)
131 qtutils
.connect_button(self
.add_button
, self
.add_files
)
132 qtutils
.connect_button(self
.remove_button
, self
.tree
.remove_selected
)
133 qtutils
.connect_button(self
.apply_button
, self
.apply_patches
)
134 qtutils
.connect_button(self
.close_button
, self
.close
)
136 self
.init_state(None, self
.resize
, 666, 420)
138 def apply_patches(self
):
139 items
= self
.tree
.items()
142 context
= self
.context
143 patches
= [i
.data(0, Qt
.UserRole
) for i
in items
]
144 cmds
.do(cmds
.ApplyPatches
, context
, patches
)
148 files
= qtutils
.open_files(N_('Select patch file(s)...'),
149 directory
=self
.curdir
,
150 filters
='Patches (*.patch *.mbox)')
153 self
.curdir
= os
.path
.dirname(files
[0])
154 self
.add_paths([core
.relpath(f
) for f
in files
])
156 def dragEnterEvent(self
, event
):
157 """Accepts drops if the mimedata contains patches"""
158 super(ApplyPatches
, self
).dragEnterEvent(event
)
159 patches
= get_patches_from_mimedata(event
.mimeData())
161 event
.acceptProposedAction()
163 def dropEvent(self
, event
):
164 """Add dropped patches"""
166 patches
= get_patches_from_mimedata(event
.mimeData())
169 self
.add_paths(patches
)
171 def add_paths(self
, paths
):
172 self
.tree
.add_paths(paths
)
174 def _tree_selection_changed(self
):
175 items
= self
.tree
.selected_items()
178 item
= items
[-1] # take the last item
179 path
= item
.data(0, Qt
.UserRole
)
180 if not core
.exists(path
):
182 commit
= parse_patch(path
)
183 self
.diffwidget
.set_details(commit
.oid
, commit
.author
, commit
.email
,
184 commit
.date
, commit
.summary
)
185 self
.diffwidget
.set_diff(commit
.diff
)
187 def export_state(self
):
188 """Export persistent settings"""
189 state
= super(ApplyPatches
, self
).export_state()
190 state
['sizes'] = get(self
.splitter
)
193 def apply_state(self
, state
):
194 """Apply persistent settings"""
195 result
= super(ApplyPatches
, self
).apply_state(state
)
197 self
.splitter
.setSizes(state
['sizes'])
198 except (AttributeError, KeyError, ValueError, TypeError):
203 class PatchTreeWidget(DraggableTreeWidget
):
205 def add_paths(self
, paths
):
206 patches
= get_patches_from_paths(paths
)
210 icon
= icons
.file_text()
211 for patch
in patches
:
212 item
= QtWidgets
.QTreeWidgetItem()
213 flags
= item
.flags() & ~Qt
.ItemIsDropEnabled
215 item
.setIcon(0, icon
)
216 item
.setText(0, os
.path
.basename(patch
))
217 item
.setData(0, Qt
.UserRole
, patch
)
218 item
.setToolTip(0, patch
)
220 self
.addTopLevelItems(items
)
222 def remove_selected(self
):
223 idxs
= self
.selectedIndexes()
224 rows
= [idx
.row() for idx
in idxs
]
225 for row
in reversed(sorted(rows
)):
226 self
.invisibleRootItem().takeChild(row
)
229 class Commit(object):
230 """Container for commit details"""
242 def parse_patch(path
):
243 content
= core
.read(path
)
245 parse(content
, commit
)
249 def parse(content
, commit
):
250 """Parse commit details from a patch"""
251 from_rgx
= re
.compile(r
'^From (?P<oid>[a-f0-9]{40}) .*$')
252 author_rgx
= re
.compile(r
'^From: (?P<author>[^<]+) <(?P<email>[^>]+)>$')
253 date_rgx
= re
.compile(r
'^Date: (?P<date>.*)$')
254 subject_rgx
= re
.compile(r
'^Subject: (?P<summary>.*)$')
256 commit
.content
= content
258 lines
= content
.splitlines()
259 for idx
, line
in enumerate(lines
):
260 match
= from_rgx
.match(line
)
262 commit
.oid
= match
.group('oid')
265 match
= author_rgx
.match(line
)
267 commit
.author
= match
.group('author')
268 commit
.email
= match
.group('email')
271 match
= date_rgx
.match(line
)
273 commit
.date
= match
.group('date')
276 match
= subject_rgx
.match(line
)
278 commit
.summary
= match
.group('summary')
279 commit
.diff
= '\n'.join(lines
[idx
+ 1:])