3 from qtpy
import QtWidgets
4 from qtpy
.QtCore
import Qt
13 from .git
import EMPTY_TREE_OID
15 from .interaction
import Interaction
16 from .widgets
import completion
17 from .widgets
import defs
18 from .widgets
import filetree
19 from .widgets
import standard
22 class LaunchDifftool(cmds
.ContextCommand
):
23 """Launch "git difftool" with the currently selected files"""
27 return N_('Launch Diff Tool')
30 s
= self
.selection
.selection()
34 core
.fork(['git', 'mergetool', '--no-prompt', '--'] + paths
)
38 argv
= utils
.shell_split(cmd
)
40 terminal
= os
.path
.basename(argv
[0])
41 shellquote_terms
= {'xfce4-terminal'}
42 shellquote_default
= terminal
in shellquote_terms
44 mergetool
= ['git', 'mergetool', '--no-prompt', '--']
45 mergetool
.extend(paths
)
46 needs_shellquote
= cfg
.get(
47 'cola.terminalshellquote', shellquote_default
51 argv
.append(core
.list2cmdline(mergetool
))
53 argv
.extend(mergetool
)
57 difftool_run(self
.context
)
60 class Difftool(standard
.Dialog
):
72 """Show files with differences and launch difftool"""
74 standard
.Dialog
.__init
__(self
, parent
=parent
)
76 self
.context
= context
82 title
= N_('git-cola diff')
84 self
.setWindowTitle(title
)
85 self
.setWindowModality(Qt
.WindowModal
)
87 self
.expr
= completion
.GitRefLineEdit(context
, parent
=self
)
89 self
.expr
.setText(expr
)
91 if expr
is None or hide_expr
:
94 self
.tree
= filetree
.FileTree(parent
=self
)
96 self
.diff_button
= qtutils
.create_button(
97 text
=N_('Compare'), icon
=icons
.diff(), enabled
=False, default
=True
99 self
.diff_button
.setShortcut(hotkeys
.DIFF
)
101 self
.diff_all_button
= qtutils
.create_button(
102 text
=N_('Compare All'), icon
=icons
.diff()
104 self
.edit_button
= qtutils
.edit_button()
105 self
.edit_button
.setShortcut(hotkeys
.EDIT
)
107 self
.close_button
= qtutils
.close_button()
109 self
.button_layout
= qtutils
.hbox(
115 self
.diff_all_button
,
119 self
.main_layout
= qtutils
.vbox(
120 defs
.margin
, defs
.spacing
, self
.expr
, self
.tree
, self
.button_layout
122 self
.setLayout(self
.main_layout
)
124 # pylint: disable=no-member
125 self
.tree
.itemSelectionChanged
.connect(self
.tree_selection_changed
)
126 self
.tree
.itemDoubleClicked
.connect(self
.tree_double_clicked
)
127 self
.tree
.up
.connect(self
.focus_input
)
129 self
.expr
.textChanged
.connect(self
.text_changed
)
131 self
.expr
.activated
.connect(self
.focus_tree
)
132 self
.expr
.down
.connect(self
.focus_tree
)
133 self
.expr
.enter
.connect(self
.focus_tree
)
135 qtutils
.connect_button(self
.diff_button
, self
.diff
)
136 qtutils
.connect_button(self
.diff_all_button
, lambda: self
.diff(dir_diff
=True))
137 qtutils
.connect_button(self
.edit_button
, self
.edit
)
138 qtutils
.connect_button(self
.close_button
, self
.close
)
140 qtutils
.add_action(self
, 'Focus Input', self
.focus_input
, hotkeys
.FOCUS
)
144 lambda: self
.diff(dir_diff
=True),
148 qtutils
.add_close_action(self
)
150 self
.init_state(None, self
.resize_widget
, parent
)
156 def resize_widget(self
, parent
):
157 """Set the initial size of the widget"""
158 width
, height
= qtutils
.default_size(parent
, 720, 420)
159 self
.resize(width
, height
)
161 def focus_tree(self
):
162 """Focus the files tree"""
165 def focus_input(self
):
166 """Focus the expression input"""
169 def text_changed(self
, txt
):
174 """Redo the diff when the expression changes"""
175 if self
.diff_expr
is not None:
176 self
.diff_arg
= utils
.shell_split(self
.diff_expr
)
178 self
.diff_arg
= [self
.a
]
180 self
.diff_arg
= [self
.a
, self
.b
]
181 self
.refresh_filenames()
183 def refresh_filenames(self
):
184 context
= self
.context
185 if self
.a
and self
.b
is None:
186 filenames
= gitcmds
.diff_index_filenames(context
, self
.a
)
188 filenames
= gitcmds
.diff(context
, self
.diff_arg
)
189 self
.tree
.set_filenames(filenames
, select
=True)
191 def tree_selection_changed(self
):
192 has_selection
= self
.tree
.has_selection()
193 self
.diff_button
.setEnabled(has_selection
)
194 self
.diff_all_button
.setEnabled(has_selection
)
196 def tree_double_clicked(self
, item
, _column
):
197 path
= filetree
.filename_from_item(item
)
198 left
, right
= self
._left
_right
_args
()
199 difftool_launch(self
.context
, left
=left
, right
=right
, paths
=[path
])
201 def diff(self
, dir_diff
=False):
202 paths
= self
.tree
.selected_filenames()
203 left
, right
= self
._left
_right
_args
()
205 self
.context
, left
=left
, right
=right
, paths
=paths
, dir_diff
=dir_diff
208 def _left_right_args(self
):
210 left
= self
.diff_arg
[0]
213 if len(self
.diff_arg
) > 1:
214 right
= self
.diff_arg
[1]
220 paths
= self
.tree
.selected_filenames()
221 cmds
.do(cmds
.Edit
, self
.context
, paths
)
224 def diff_commits(context
, parent
, a
, b
):
225 """Show a dialog for diffing two commits"""
226 dlg
= Difftool(context
, parent
, a
=a
, b
=b
)
229 return dlg
.exec_() == QtWidgets
.QDialog
.Accepted
233 context
, parent
, expr
, create_widget
=False, hide_expr
=False, focus_tree
=False
235 """Show a diff dialog for diff expressions"""
237 context
, parent
, expr
=expr
, hide_expr
=hide_expr
, focus_tree
=focus_tree
243 return dlg
.exec_() == QtWidgets
.QDialog
.Accepted
246 def difftool_run(context
):
247 """Start a default difftool session"""
248 selection
= context
.selection
249 files
= selection
.group()
252 s
= selection
.selection()
253 head
= context
.model
.head
254 difftool_launch_with_head(context
, files
, bool(s
.staged
), head
)
257 def difftool_launch_with_head(context
, filenames
, staged
, head
):
258 """Launch difftool against the provided head"""
263 difftool_launch(context
, left
=left
, staged
=staged
, paths
=filenames
)
273 left_take_magic
=False,
274 left_take_parent
=False,
276 """Launches 'git difftool' with given parameters
278 :param left: first argument to difftool
279 :param right: second argument to difftool_args
280 :param paths: paths to diff
281 :param staged: activate `git difftool --staged`
282 :param dir_diff: activate `git difftool --dir-diff`
283 :param left_take_magic: whether to append the magic ^! diff expression
284 :param left_take_parent: whether to append the first-parent ~ for diffing
288 difftool_args
= ['git', 'difftool', '--no-prompt']
290 difftool_args
.append('--cached')
292 difftool_args
.append('--dir-diff')
295 if left_take_parent
or left_take_magic
:
296 suffix
= '^!' if left_take_magic
else '~'
297 # Check root commit (no parents and thus cannot execute '~')
299 status
, out
, err
= git
.rev_list(left
, parents
=True, n
=1, _readonly
=True)
300 Interaction
.log_status(status
, out
, err
)
302 raise OSError('git rev-list command failed')
304 if len(out
.split()) >= 2:
305 # Commit has a parent, so we can take its child as requested
308 # No parent, assume it's the root commit, so we have to diff
309 # against the empty tree.
310 left
= EMPTY_TREE_OID
311 if not right
and left_take_magic
:
313 difftool_args
.append(left
)
316 difftool_args
.append(right
)
319 difftool_args
.append('--')
320 difftool_args
.extend(paths
)
322 runtask
= context
.runtask
324 Interaction
.async_command(N_('Difftool'), difftool_args
, runtask
)
326 core
.fork(difftool_args
)