1 """Provides dialogs for comparing branches and commits."""
3 from PyQt4
import QtCore
4 from PyQt4
import QtGui
5 from PyQt4
.QtCore
import SIGNAL
7 from cola
import qtutils
8 from cola
import difftool
9 from cola
import gitcmds
10 from cola
.i18n
import N_
11 from cola
.qtutils
import connect_button
12 from cola
.widgets
import defs
13 from cola
.widgets
import standard
16 class FileItem(QtGui
.QTreeWidgetItem
):
17 def __init__(self
, path
, icon
):
18 QtGui
.QTreeWidgetItem
.__init
__(self
, [path
])
23 def compare_branches():
24 """Launches a dialog for comparing a pair of branches"""
25 view
= CompareBranchesDialog(qtutils
.active_window())
30 class CompareBranchesDialog(standard
.Dialog
):
33 def __init__(self
, parent
):
34 standard
.Dialog
.__init
__(self
, parent
=parent
)
36 self
.BRANCH_POINT
= N_('*** Branch Point ***')
37 self
.SANDBOX
= N_('*** Sandbox ***')
38 self
.LOCAL
= N_('Local')
40 self
.remote_branches
= gitcmds
.branch_list(remote
=True)
41 self
.local_branches
= gitcmds
.branch_list(remote
=False)
43 self
.setWindowTitle(N_('Branch Diff Viewer'))
46 self
.main_layt
= QtGui
.QVBoxLayout(self
)
47 self
.main_layt
.setMargin(defs
.margin
)
48 self
.main_layt
.setSpacing(defs
.spacing
)
50 self
.splitter
= QtGui
.QSplitter(self
)
51 self
.splitter
.setOrientation(QtCore
.Qt
.Vertical
)
52 self
.splitter
.setHandleWidth(defs
.handle_width
)
54 self
.top_widget
= QtGui
.QWidget(self
.splitter
)
56 self
.top_grid_layt
= QtGui
.QGridLayout(self
.top_widget
)
57 self
.top_grid_layt
.setMargin(0)
58 self
.top_grid_layt
.setSpacing(defs
.spacing
)
60 self
.left_combo
= QtGui
.QComboBox(self
.top_widget
)
61 self
.left_combo
.addItem(N_('Local'))
62 self
.left_combo
.addItem(N_('Remote'))
63 self
.left_combo
.setCurrentIndex(0)
64 self
.top_grid_layt
.addWidget(self
.left_combo
, 0, 0, 1, 1)
66 self
.right_combo
= QtGui
.QComboBox(self
.top_widget
)
67 self
.right_combo
.addItem(N_('Local'))
68 self
.right_combo
.addItem(N_('Remote'))
69 self
.right_combo
.setCurrentIndex(1)
70 self
.top_grid_layt
.addWidget(self
.right_combo
, 0, 1, 1, 1)
72 self
.left_list
= QtGui
.QListWidget(self
.top_widget
)
73 self
.top_grid_layt
.addWidget(self
.left_list
, 1, 0, 1, 1)
75 self
.right_list
= QtGui
.QListWidget(self
.top_widget
)
76 self
.top_grid_layt
.addWidget(self
.right_list
, 1, 1, 1, 1)
78 self
.bottom_widget
= QtGui
.QWidget(self
.splitter
)
79 self
.bottom_grid_layt
= QtGui
.QGridLayout(self
.bottom_widget
)
80 self
.bottom_grid_layt
.setMargin(0)
81 self
.bottom_grid_layt
.setSpacing(defs
.button_spacing
)
83 self
.button_spacer
= QtGui
.QSpacerItem(1, 1,
84 QtGui
.QSizePolicy
.Expanding
,
85 QtGui
.QSizePolicy
.Minimum
)
86 self
.bottom_grid_layt
.addItem(self
.button_spacer
, 1, 1, 1, 1)
88 self
.button_compare
= QtGui
.QPushButton(self
.bottom_widget
)
89 self
.button_compare
.setText(N_('Compare'))
90 self
.bottom_grid_layt
.addWidget(self
.button_compare
, 1, 2, 1, 1)
92 self
.button_close
= QtGui
.QPushButton(self
.bottom_widget
)
93 self
.button_close
.setText(N_('Close'))
94 self
.bottom_grid_layt
.addWidget(self
.button_close
, 1, 3, 1, 1)
96 self
.diff_files
= standard
.TreeWidget(self
.bottom_widget
)
97 self
.diff_files
.headerItem().setText(0, N_('File Differences'))
99 self
.bottom_grid_layt
.addWidget(self
.diff_files
, 0, 0, 1, 4)
100 self
.main_layt
.addWidget(self
.splitter
)
102 connect_button(self
.button_close
, self
.accept
)
103 connect_button(self
.button_compare
, self
.compare
)
105 self
.connect(self
.diff_files
,
106 SIGNAL('itemDoubleClicked(QTreeWidgetItem*,int)'),
109 self
.connect(self
.left_combo
,
110 SIGNAL('currentIndexChanged(int)'),
111 lambda x
: self
.update_combo_boxes(left
=True))
113 self
.connect(self
.right_combo
,
114 SIGNAL('currentIndexChanged(int)'),
115 lambda x
: self
.update_combo_boxes(left
=False))
117 self
.connect(self
.left_list
,
118 SIGNAL('itemSelectionChanged()'), self
.update_diff_files
)
120 self
.connect(self
.right_list
,
121 SIGNAL('itemSelectionChanged()'), self
.update_diff_files
)
123 self
.update_combo_boxes(left
=True)
124 self
.update_combo_boxes(left
=False)
126 # Pre-select the 0th elements
127 item
= self
.left_list
.item(0)
129 self
.left_list
.setCurrentItem(item
)
130 self
.left_list
.setItemSelected(item
, True)
132 item
= self
.right_list
.item(0)
134 self
.right_list
.setCurrentItem(item
)
135 self
.right_list
.setItemSelected(item
, True)
138 left_item
= self
.left_list
.currentItem()
139 if left_item
and left_item
.isSelected():
140 left_item
= unicode(left_item
.text())
143 right_item
= self
.right_list
.currentItem()
144 if right_item
and right_item
.isSelected():
145 right_item
= unicode(right_item
.text())
148 return (left_item
, right_item
)
151 def update_diff_files(self
, *rest
):
152 """Updates the list of files whenever the selection changes"""
153 # Left and Right refer to the comparison pair (l,r)
154 left_item
, right_item
= self
.selection()
155 if (not left_item
or not right_item
or
156 left_item
== right_item
):
157 self
.set_diff_files([])
159 left_item
= self
.remote_ref(left_item
)
160 right_item
= self
.remote_ref(right_item
)
162 # If any of the selection includes sandbox then we
163 # generate the same diff, regardless. This means we don't
164 # support reverse diffs against sandbox aka worktree.
165 if self
.SANDBOX
in (left_item
, right_item
):
166 self
.use_sandbox
= True
167 if left_item
== self
.SANDBOX
:
168 self
.diff_arg
= (right_item
,)
170 self
.diff_arg
= (left_item
,)
172 self
.diff_arg
= (left_item
, right_item
)
173 self
.use_sandbox
= False
175 # start and end as in 'git diff start end'
176 self
.start
= left_item
177 self
.end
= right_item
179 if len(self
.diff_arg
) == 1:
180 files
= gitcmds
.diff_index_filenames(self
.diff_arg
[0])
182 files
= gitcmds
.diff_filenames(*self
.diff_arg
)
184 self
.set_diff_files(files
)
186 def set_diff_files(self
, files
):
188 icon
= qtutils
.icon('script.png')
189 self
.diff_files
.clear()
190 self
.diff_files
.addTopLevelItems([mk(f
, icon
) for f
in files
])
192 def remote_ref(self
, branch
):
193 """Returns the remote ref for 'git diff [local] [remote]'
195 if branch
== self
.BRANCH_POINT
:
196 # Compare against the branch point so find the merge-base
197 branch
= gitcmds
.current_branch()
198 tracked_branch
= gitcmds
.tracked_branch()
200 return gitcmds
.merge_base(branch
, tracked_branch
)
202 remote_branches
= gitcmds
.branch_list(remote
=True)
203 remote_branch
= 'origin/%s' % branch
204 if remote_branch
in remote_branches
:
205 return gitcmds
.merge_base(branch
, remote_branch
)
207 elif 'origin/master' in remote_branches
:
208 return gitcmds
.merge_base(branch
, 'origin/master')
212 # Compare against the remote branch
216 def update_combo_boxes(self
, left
=False):
217 """Update listwidgets from the combobox selection
219 Update either the left or right listwidgets
220 to reflect the available items.
223 which
= unicode(self
.left_combo
.currentText())
224 widget
= self
.left_list
226 which
= unicode(self
.right_combo
.currentText())
227 widget
= self
.right_list
230 # If we're looking at "local" stuff then provide the
231 # sandbox as a valid choice. If we're looking at
232 # "remote" stuff then also include the branch point.
233 if which
== self
.LOCAL
:
234 new_list
= ([self
.SANDBOX
]+ self
.local_branches
)
236 new_list
= ([self
.BRANCH_POINT
] + self
.remote_branches
)
239 widget
.addItems(new_list
)
241 item
= widget
.item(0)
242 widget
.setCurrentItem(item
)
243 widget
.setItemSelected(item
, True)
245 def compare(self
, *args
):
246 """Shows the diff for a specific file
248 tree_widget
= self
.diff_files
249 item
= tree_widget
.currentItem()
250 if item
and item
.isSelected():
251 self
.compare_file(item
.path
)
253 def compare_file(self
, filename
):
254 """Initiates the difftool session"""
258 arg
= (self
.start
, self
.end
)
259 difftool
.launch(arg
+ ('--', filename
))