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
.git
import git
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
):
32 BRANCH_POINT
= '*** Branch Point ***'
33 SANDBOX
= '*** Sandbox ***'
36 def __init__(self
, parent
):
37 standard
.Dialog
.__init
__(self
, parent
=parent
)
38 self
.remote_branches
= gitcmds
.branch_list(remote
=True)
39 self
.local_branches
= gitcmds
.branch_list(remote
=False)
41 self
.setWindowTitle(self
.tr('Branch Diff Viewer'))
44 self
.main_layt
= QtGui
.QVBoxLayout(self
)
45 self
.main_layt
.setMargin(defs
.margin
)
46 self
.main_layt
.setSpacing(defs
.spacing
)
48 self
.splitter
= QtGui
.QSplitter(self
)
49 self
.splitter
.setOrientation(QtCore
.Qt
.Vertical
)
50 self
.splitter
.setHandleWidth(defs
.handle_width
)
52 self
.top_widget
= QtGui
.QWidget(self
.splitter
)
54 self
.top_grid_layt
= QtGui
.QGridLayout(self
.top_widget
)
55 self
.top_grid_layt
.setMargin(0)
56 self
.top_grid_layt
.setSpacing(defs
.spacing
)
58 self
.left_combo
= QtGui
.QComboBox(self
.top_widget
)
59 self
.left_combo
.addItem(self
.tr('Local'))
60 self
.left_combo
.addItem(self
.tr('Remote'))
61 self
.left_combo
.setCurrentIndex(0)
62 self
.top_grid_layt
.addWidget(self
.left_combo
, 0, 0, 1, 1)
64 self
.right_combo
= QtGui
.QComboBox(self
.top_widget
)
65 self
.right_combo
.addItem(self
.tr('Local'))
66 self
.right_combo
.addItem(self
.tr('Remote'))
67 self
.right_combo
.setCurrentIndex(1)
68 self
.top_grid_layt
.addWidget(self
.right_combo
, 0, 1, 1, 1)
70 self
.left_list
= QtGui
.QListWidget(self
.top_widget
)
71 self
.top_grid_layt
.addWidget(self
.left_list
, 1, 0, 1, 1)
73 self
.right_list
= QtGui
.QListWidget(self
.top_widget
)
74 self
.top_grid_layt
.addWidget(self
.right_list
, 1, 1, 1, 1)
76 self
.bottom_widget
= QtGui
.QWidget(self
.splitter
)
77 self
.bottom_grid_layt
= QtGui
.QGridLayout(self
.bottom_widget
)
78 self
.bottom_grid_layt
.setMargin(0)
79 self
.bottom_grid_layt
.setSpacing(defs
.button_spacing
)
81 self
.button_spacer
= QtGui
.QSpacerItem(1, 1,
82 QtGui
.QSizePolicy
.Expanding
,
83 QtGui
.QSizePolicy
.Minimum
)
84 self
.bottom_grid_layt
.addItem(self
.button_spacer
, 1, 1, 1, 1)
86 self
.button_compare
= QtGui
.QPushButton(self
.bottom_widget
)
87 self
.button_compare
.setText(self
.tr('Compare'))
88 self
.bottom_grid_layt
.addWidget(self
.button_compare
, 1, 2, 1, 1)
90 self
.button_close
= QtGui
.QPushButton(self
.bottom_widget
)
91 self
.button_close
.setText(self
.tr('Close'))
92 self
.bottom_grid_layt
.addWidget(self
.button_close
, 1, 3, 1, 1)
94 self
.diff_files
= QtGui
.QTreeWidget(self
.bottom_widget
)
95 self
.diff_files
.headerItem().setText(0, self
.tr('File Differences'))
96 self
.diff_files
.setRootIsDecorated(False)
98 self
.bottom_grid_layt
.addWidget(self
.diff_files
, 0, 0, 1, 4)
99 self
.main_layt
.addWidget(self
.splitter
)
101 connect_button(self
.button_close
, self
.accept
)
102 connect_button(self
.button_compare
, self
.compare
)
104 self
.connect(self
.diff_files
,
105 SIGNAL('itemDoubleClicked(QTreeWidgetItem*,int)'),
108 self
.connect(self
.left_combo
,
109 SIGNAL('currentIndexChanged(int)'),
110 lambda x
: self
.update_combo_boxes(left
=True))
112 self
.connect(self
.right_combo
,
113 SIGNAL('currentIndexChanged(int)'),
114 lambda x
: self
.update_combo_boxes(left
=False))
116 self
.connect(self
.left_list
,
117 SIGNAL('itemSelectionChanged()'), self
.update_diff_files
)
119 self
.connect(self
.right_list
,
120 SIGNAL('itemSelectionChanged()'), self
.update_diff_files
)
122 self
.update_combo_boxes(left
=True)
123 self
.update_combo_boxes(left
=False)
125 # Pre-select the 0th elements
126 item
= self
.left_list
.item(0)
128 self
.left_list
.setCurrentItem(item
)
129 self
.left_list
.setItemSelected(item
, True)
131 item
= self
.right_list
.item(0)
133 self
.right_list
.setCurrentItem(item
)
134 self
.right_list
.setItemSelected(item
, True)
137 left_item
= self
.left_list
.currentItem()
138 if left_item
and left_item
.isSelected():
139 left_item
= unicode(left_item
.text())
142 right_item
= self
.right_list
.currentItem()
143 if right_item
and right_item
.isSelected():
144 right_item
= unicode(right_item
.text())
147 return (left_item
, right_item
)
150 def update_diff_files(self
, *rest
):
151 """Updates the list of files whenever the selection changes"""
152 # Left and Right refer to the comparison pair (l,r)
153 left_item
, right_item
= self
.selection()
154 if (not left_item
or not right_item
or
155 left_item
== right_item
):
156 self
.set_diff_files([])
158 left_item
= self
.remote_ref(left_item
)
159 right_item
= self
.remote_ref(right_item
)
161 # If any of the selection includes sandbox then we
162 # generate the same diff, regardless. This means we don't
163 # support reverse diffs against sandbox aka worktree.
164 if self
.SANDBOX
in (left_item
, right_item
):
165 self
.use_sandbox
= True
166 if left_item
== self
.SANDBOX
:
167 self
.diff_arg
= (right_item
,)
169 self
.diff_arg
= (left_item
,)
171 self
.diff_arg
= (left_item
, right_item
)
172 self
.use_sandbox
= False
174 # start and end as in 'git diff start end'
175 self
.start
= left_item
176 self
.end
= right_item
178 if len(self
.diff_arg
) == 1:
179 files
= gitcmds
.diff_index_filenames(self
.diff_arg
[0])
181 files
= gitcmds
.diff_filenames(*self
.diff_arg
)
183 self
.set_diff_files(files
)
185 def set_diff_files(self
, files
):
187 icon
= qtutils
.icon('script.png')
188 self
.diff_files
.clear()
189 self
.diff_files
.addTopLevelItems([mk(f
, icon
) for f
in files
])
191 def remote_ref(self
, branch
):
192 """Returns the remote ref for 'git diff [local] [remote]'
194 if branch
== self
.BRANCH_POINT
:
195 # Compare against the branch point so find the merge-base
196 branch
= gitcmds
.current_branch()
197 tracked_branch
= gitcmds
.tracked_branch()
199 return git
.merge_base(branch
, tracked_branch
)
201 remote_branches
= gitcmds
.branch_list(remote
=True)
202 remote_branch
= 'origin/%s' % branch
203 if remote_branch
in remote_branches
:
204 return git
.merge_base(branch
, remote_branch
)
206 elif 'origin/master' in remote_branches
:
207 return git
.merge_base(branch
, 'origin/master')
211 # Compare against the remote branch
215 def update_combo_boxes(self
, left
=False):
216 """Update listwidgets from the combobox selection
218 Update either the left or right listwidgets
219 to reflect the available items.
222 which
= unicode(self
.left_combo
.currentText())
223 widget
= self
.left_list
225 which
= unicode(self
.right_combo
.currentText())
226 widget
= self
.right_list
229 # If we're looking at "local" stuff then provide the
230 # sandbox as a valid choice. If we're looking at
231 # "remote" stuff then also include the branch point.
232 if which
== self
.LOCAL
:
233 new_list
= ([self
.SANDBOX
]+ self
.local_branches
)
235 new_list
= ([self
.BRANCH_POINT
] + self
.remote_branches
)
238 widget
.addItems(new_list
)
240 item
= widget
.item(0)
241 widget
.setCurrentItem(item
)
242 widget
.setItemSelected(item
, True)
244 def compare(self
, *args
):
245 """Shows the diff for a specific file
247 tree_widget
= self
.diff_files
248 item
= tree_widget
.currentItem()
249 if item
and item
.isSelected():
250 self
.compare_file(item
.path
)
252 def compare_file(self
, filename
):
253 """Initiates the difftool session"""
257 arg
= (self
.start
, self
.end
)
258 difftool
.launch(arg
+ ('--', filename
))