1 from __future__
import division
, absolute_import
, unicode_literals
3 from qtpy
import QtCore
4 from qtpy
import QtWidgets
5 from qtpy
.QtCore
import Qt
14 from .interaction
import Interaction
15 from .models
import main
16 from .models
import selection
17 from .widgets
import completion
18 from .widgets
import defs
19 from .widgets
import filetree
23 """Start a default difftool session"""
24 files
= selection
.selected_group()
27 s
= selection
.selection()
29 launch_with_head(files
, bool(s
.staged
), model
.head
)
32 def launch_with_head(filenames
, staged
, head
):
33 """Launch difftool against the provided head"""
38 launch(left
=left
, staged
=staged
, paths
=filenames
)
41 def launch(left
=None, right
=None, paths
=None, staged
=False, dir_diff
=False,
42 left_take_magic
=False, left_take_parent
=False):
43 """Launches 'git difftool' with given parameters
45 :param left: first argument to difftool
46 :param right: second argument to difftool_args
47 :param paths: paths to diff
48 :param staged: activate `git difftool --staged`
49 :param dir_diff: activate `git difftool --dir-diff`
50 :param left_take_magic: whether to append the magic ^! diff expression
51 :param left_take_parent: whether to append the first-parent ~ for diffing
55 difftool_args
= ['git', 'difftool', '--no-prompt']
57 difftool_args
.append('--cached')
59 difftool_args
.append('--dir-diff')
62 if left_take_parent
or left_take_magic
:
63 suffix
= left_take_magic
and '^!' or '~'
64 # Check root commit (no parents and thus cannot execute '~')
67 status
, out
, err
= git
.rev_list(left
, parents
=True, n
=1)
68 Interaction
.log_status(status
, out
, err
)
70 raise OSError('git rev-list command failed')
72 if len(out
.split()) >= 2:
73 # Commit has a parent, so we can take its child as requested
76 # No parent, assume it's the root commit, so we have to diff
77 # against the empty tree. Git's empty tree is a built-in
78 # constant object name.
79 left
= '4b825dc642cb6eb9a060e54bf8d69288fbee4904'
80 if not right
and left_take_magic
:
82 difftool_args
.append(left
)
85 difftool_args
.append(right
)
88 difftool_args
.append('--')
89 difftool_args
.extend(paths
)
91 core
.fork(difftool_args
)
94 def diff_commits(parent
, a
, b
):
95 """Show a dialog for diffing two commits"""
96 dlg
= FileDiffDialog(parent
, a
=a
, b
=b
)
99 return dlg
.exec_() == QtWidgets
.QDialog
.Accepted
102 def diff_expression(parent
, expr
,
106 """Show a diff dialog for diff expressions"""
107 dlg
= FileDiffDialog(parent
,
110 focus_tree
=focus_tree
)
115 return dlg
.exec_() == QtWidgets
.QDialog
.Accepted
118 class FileDiffDialog(QtWidgets
.QDialog
):
120 def __init__(self
, parent
, a
=None, b
=None, expr
=None, title
=None,
121 hide_expr
=False, focus_tree
=False):
122 """Show files with differences and launch difftool"""
124 QtWidgets
.QDialog
.__init
__(self
, parent
)
125 self
.setAttribute(Qt
.WA_MacMetalStyle
)
129 self
.diff_expr
= expr
132 title
= N_('git-cola diff')
134 self
.setWindowTitle(title
)
135 self
.setWindowModality(QtCore
.Qt
.WindowModal
)
137 self
.expr
= completion
.GitRefLineEdit(parent
=self
)
139 self
.expr
.setText(expr
)
141 if expr
is None or hide_expr
:
144 self
.tree
= filetree
.FileTree(parent
=self
)
146 self
.diff_button
= qtutils
.create_button(text
=N_('Compare'),
149 self
.diff_all_button
= qtutils
.create_button(text
=N_('Compare All'),
151 self
.close_button
= qtutils
.close_button()
153 self
.button_layout
= qtutils
.hbox(defs
.no_margin
, defs
.spacing
,
156 self
.diff_all_button
,
160 self
.main_layout
= qtutils
.vbox(defs
.margin
, defs
.spacing
,
161 self
.expr
, self
.tree
,
163 self
.setLayout(self
.main_layout
)
165 self
.tree
.itemSelectionChanged
.connect(self
.tree_selection_changed
)
166 self
.tree
.itemDoubleClicked
.connect(self
.tree_double_clicked
)
167 self
.tree
.up
.connect(self
.focus_input
)
169 self
.expr
.textChanged
.connect(self
.text_changed
)
171 self
.expr
.activated
.connect(self
.focus_tree
)
172 self
.expr
.down
.connect(self
.focus_tree
)
173 self
.expr
.enter
.connect(self
.focus_tree
)
175 qtutils
.connect_button(self
.diff_button
, self
.diff
)
176 qtutils
.connect_button(self
.diff_all_button
,
177 lambda: self
.diff(dir_diff
=True))
178 qtutils
.connect_button(self
.close_button
, self
.close
)
180 qtutils
.add_action(self
, 'Focus Input', self
.focus_input
, hotkeys
.FOCUS
)
181 qtutils
.add_action(self
, 'Diff All', lambda: self
.diff(dir_diff
=True),
182 hotkeys
.CTRL_ENTER
, hotkeys
.CTRL_RETURN
)
183 qtutils
.add_close_action(self
)
185 self
.resize(720, 420)
191 def focus_tree(self
):
192 """Focus the files tree"""
195 def focus_input(self
):
196 """Focus the expression input"""
199 def text_changed(self
, txt
):
204 """Redo the diff when the expression changes"""
205 if self
.diff_expr
is not None:
206 self
.diff_arg
= utils
.shell_split(self
.diff_expr
)
208 self
.diff_arg
= [self
.a
]
210 self
.diff_arg
= [self
.a
, self
.b
]
211 self
.refresh_filenames()
213 def refresh_filenames(self
):
214 if self
.a
and self
.b
is None:
215 filenames
= gitcmds
.diff_index_filenames(self
.a
)
217 filenames
= gitcmds
.diff(self
.diff_arg
)
218 self
.tree
.set_filenames(filenames
, select
=True)
220 def tree_selection_changed(self
):
221 has_selection
= self
.tree
.has_selection()
222 self
.diff_button
.setEnabled(has_selection
)
223 self
.diff_all_button
.setEnabled(has_selection
)
225 def tree_double_clicked(self
, item
, column
):
226 path
= self
.tree
.filename_from_item(item
)
227 left
, right
= self
._left
_right
_args
()
228 launch(left
=left
, right
=right
, paths
=[path
])
230 def diff(self
, dir_diff
=False):
231 paths
= self
.tree
.selected_filenames()
232 left
, right
= self
._left
_right
_args
()
233 launch(left
=left
, right
=right
, paths
=paths
, dir_diff
=dir_diff
)
235 def _left_right_args(self
):
237 left
= self
.diff_arg
[0]
240 if len(self
.diff_arg
) > 1:
241 right
= self
.diff_arg
[1]