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 self
.tree
.itemSelectionChanged
.connect(self
.tree_selection_changed
)
125 self
.tree
.itemDoubleClicked
.connect(self
.tree_double_clicked
)
126 self
.tree
.up
.connect(self
.focus_input
)
128 self
.expr
.textChanged
.connect(self
.text_changed
)
130 self
.expr
.activated
.connect(self
.focus_tree
)
131 self
.expr
.down
.connect(self
.focus_tree
)
132 self
.expr
.enter
.connect(self
.focus_tree
)
134 qtutils
.connect_button(self
.diff_button
, self
.diff
)
135 qtutils
.connect_button(self
.diff_all_button
, lambda: self
.diff(dir_diff
=True))
136 qtutils
.connect_button(self
.edit_button
, self
.edit
)
137 qtutils
.connect_button(self
.close_button
, self
.close
)
139 qtutils
.add_action(self
, 'Focus Input', self
.focus_input
, hotkeys
.FOCUS
)
143 lambda: self
.diff(dir_diff
=True),
147 qtutils
.add_close_action(self
)
149 self
.init_state(None, self
.resize_widget
, parent
)
155 def resize_widget(self
, parent
):
156 """Set the initial size of the widget"""
157 width
, height
= qtutils
.default_size(parent
, 720, 420)
158 self
.resize(width
, height
)
160 def focus_tree(self
):
161 """Focus the files tree"""
164 def focus_input(self
):
165 """Focus the expression input"""
168 def text_changed(self
, txt
):
173 """Redo the diff when the expression changes"""
174 if self
.diff_expr
is not None:
175 self
.diff_arg
= utils
.shell_split(self
.diff_expr
)
177 self
.diff_arg
= [self
.a
]
179 self
.diff_arg
= [self
.a
, self
.b
]
180 self
.refresh_filenames()
182 def refresh_filenames(self
):
183 context
= self
.context
184 if self
.a
and self
.b
is None:
185 filenames
= gitcmds
.diff_index_filenames(context
, self
.a
)
187 filenames
= gitcmds
.diff(context
, self
.diff_arg
)
188 self
.tree
.set_filenames(filenames
, select
=True)
190 def tree_selection_changed(self
):
191 has_selection
= self
.tree
.has_selection()
192 self
.diff_button
.setEnabled(has_selection
)
193 self
.diff_all_button
.setEnabled(has_selection
)
195 def tree_double_clicked(self
, item
, _column
):
196 path
= filetree
.filename_from_item(item
)
197 left
, right
= self
._left
_right
_args
()
198 difftool_launch(self
.context
, left
=left
, right
=right
, paths
=[path
])
200 def diff(self
, dir_diff
=False):
201 paths
= self
.tree
.selected_filenames()
202 left
, right
= self
._left
_right
_args
()
204 self
.context
, left
=left
, right
=right
, paths
=paths
, dir_diff
=dir_diff
207 def _left_right_args(self
):
209 left
= self
.diff_arg
[0]
212 if len(self
.diff_arg
) > 1:
213 right
= self
.diff_arg
[1]
219 paths
= self
.tree
.selected_filenames()
220 cmds
.do(cmds
.Edit
, self
.context
, paths
)
223 def diff_commits(context
, parent
, a
, b
):
224 """Show a dialog for diffing two commits"""
225 dlg
= Difftool(context
, parent
, a
=a
, b
=b
)
228 return dlg
.exec_() == QtWidgets
.QDialog
.Accepted
232 context
, parent
, expr
, create_widget
=False, hide_expr
=False, focus_tree
=False
234 """Show a diff dialog for diff expressions"""
236 context
, parent
, expr
=expr
, hide_expr
=hide_expr
, focus_tree
=focus_tree
242 return dlg
.exec_() == QtWidgets
.QDialog
.Accepted
245 def difftool_run(context
):
246 """Start a default difftool session"""
247 selection
= context
.selection
248 files
= selection
.group()
251 s
= selection
.selection()
252 head
= context
.model
.head
253 difftool_launch_with_head(context
, files
, bool(s
.staged
), head
)
256 def difftool_launch_with_head(context
, filenames
, staged
, head
):
257 """Launch difftool against the provided head"""
262 difftool_launch(context
, left
=left
, staged
=staged
, paths
=filenames
)
272 left_take_magic
=False,
273 left_take_parent
=False,
275 """Launches 'git difftool' with given parameters
277 :param left: first argument to difftool
278 :param right: second argument to difftool_args
279 :param paths: paths to diff
280 :param staged: activate `git difftool --staged`
281 :param dir_diff: activate `git difftool --dir-diff`
282 :param left_take_magic: whether to append the magic "^!" diff expression
283 :param left_take_parent: whether to append the first-parent ~ for diffing
287 difftool_args
= ['git', 'difftool', '--no-prompt']
289 difftool_args
.append('--cached')
291 difftool_args
.append('--dir-diff')
294 if left_take_parent
or left_take_magic
:
295 suffix
= '^!' if left_take_magic
else '~'
296 # Check root commit (no parents and thus cannot execute '~')
298 status
, out
, err
= git
.rev_list(left
, parents
=True, n
=1, _readonly
=True)
299 Interaction
.log_status(status
, out
, err
)
301 raise OSError('git rev-list command failed')
303 if len(out
.split()) >= 2:
304 # Commit has a parent, so we can take its child as requested
307 # No parent, assume it's the root commit, so we have to diff
308 # against the empty tree.
309 left
= EMPTY_TREE_OID
310 if not right
and left_take_magic
:
312 difftool_args
.append(left
)
315 difftool_args
.append(right
)
318 difftool_args
.append('--')
319 difftool_args
.extend(paths
)
321 runtask
= context
.runtask
323 Interaction
.async_command(N_('Difftool'), difftool_args
, runtask
)
325 core
.fork(difftool_args
)