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
.i18n
import N_
12 from cola
.qtutils
import connect_button
13 from cola
.widgets
import defs
14 from cola
.widgets
import standard
17 class FileItem(QtGui
.QTreeWidgetItem
):
18 def __init__(self
, path
, icon
):
19 QtGui
.QTreeWidgetItem
.__init
__(self
, [path
])
24 def compare_branches():
25 """Launches a dialog for comparing a pair of branches"""
26 view
= CompareBranchesDialog(qtutils
.active_window())
31 class CompareBranchesDialog(standard
.Dialog
):
34 def __init__(self
, parent
):
35 standard
.Dialog
.__init
__(self
, parent
=parent
)
37 self
.BRANCH_POINT
= N_('*** Branch Point ***')
38 self
.SANDBOX
= N_('*** Sandbox ***')
39 self
.LOCAL
= N_('Local')
41 self
.remote_branches
= gitcmds
.branch_list(remote
=True)
42 self
.local_branches
= gitcmds
.branch_list(remote
=False)
44 self
.setWindowTitle(N_('Branch Diff Viewer'))
47 self
.main_layt
= QtGui
.QVBoxLayout(self
)
48 self
.main_layt
.setMargin(defs
.margin
)
49 self
.main_layt
.setSpacing(defs
.spacing
)
51 self
.splitter
= QtGui
.QSplitter(self
)
52 self
.splitter
.setOrientation(QtCore
.Qt
.Vertical
)
53 self
.splitter
.setHandleWidth(defs
.handle_width
)
55 self
.top_widget
= QtGui
.QWidget(self
.splitter
)
57 self
.top_grid_layt
= QtGui
.QGridLayout(self
.top_widget
)
58 self
.top_grid_layt
.setMargin(0)
59 self
.top_grid_layt
.setSpacing(defs
.spacing
)
61 self
.left_combo
= QtGui
.QComboBox(self
.top_widget
)
62 self
.left_combo
.addItem(N_('Local'))
63 self
.left_combo
.addItem(N_('Remote'))
64 self
.left_combo
.setCurrentIndex(0)
65 self
.top_grid_layt
.addWidget(self
.left_combo
, 0, 0, 1, 1)
67 self
.right_combo
= QtGui
.QComboBox(self
.top_widget
)
68 self
.right_combo
.addItem(N_('Local'))
69 self
.right_combo
.addItem(N_('Remote'))
70 self
.right_combo
.setCurrentIndex(1)
71 self
.top_grid_layt
.addWidget(self
.right_combo
, 0, 1, 1, 1)
73 self
.left_list
= QtGui
.QListWidget(self
.top_widget
)
74 self
.top_grid_layt
.addWidget(self
.left_list
, 1, 0, 1, 1)
76 self
.right_list
= QtGui
.QListWidget(self
.top_widget
)
77 self
.top_grid_layt
.addWidget(self
.right_list
, 1, 1, 1, 1)
79 self
.bottom_widget
= QtGui
.QWidget(self
.splitter
)
80 self
.bottom_grid_layt
= QtGui
.QGridLayout(self
.bottom_widget
)
81 self
.bottom_grid_layt
.setMargin(0)
82 self
.bottom_grid_layt
.setSpacing(defs
.button_spacing
)
84 self
.button_spacer
= QtGui
.QSpacerItem(1, 1,
85 QtGui
.QSizePolicy
.Expanding
,
86 QtGui
.QSizePolicy
.Minimum
)
87 self
.bottom_grid_layt
.addItem(self
.button_spacer
, 1, 1, 1, 1)
89 self
.button_compare
= QtGui
.QPushButton(self
.bottom_widget
)
90 self
.button_compare
.setText(N_('Compare'))
91 self
.bottom_grid_layt
.addWidget(self
.button_compare
, 1, 2, 1, 1)
93 self
.button_close
= QtGui
.QPushButton(self
.bottom_widget
)
94 self
.button_close
.setText(N_('Close'))
95 self
.bottom_grid_layt
.addWidget(self
.button_close
, 1, 3, 1, 1)
97 self
.diff_files
= standard
.TreeWidget(self
.bottom_widget
)
98 self
.diff_files
.headerItem().setText(0, N_('File Differences'))
100 self
.bottom_grid_layt
.addWidget(self
.diff_files
, 0, 0, 1, 4)
101 self
.main_layt
.addWidget(self
.splitter
)
103 connect_button(self
.button_close
, self
.accept
)
104 connect_button(self
.button_compare
, self
.compare
)
106 self
.connect(self
.diff_files
,
107 SIGNAL('itemDoubleClicked(QTreeWidgetItem*,int)'),
110 self
.connect(self
.left_combo
,
111 SIGNAL('currentIndexChanged(int)'),
112 lambda x
: self
.update_combo_boxes(left
=True))
114 self
.connect(self
.right_combo
,
115 SIGNAL('currentIndexChanged(int)'),
116 lambda x
: self
.update_combo_boxes(left
=False))
118 self
.connect(self
.left_list
,
119 SIGNAL('itemSelectionChanged()'), self
.update_diff_files
)
121 self
.connect(self
.right_list
,
122 SIGNAL('itemSelectionChanged()'), self
.update_diff_files
)
124 self
.update_combo_boxes(left
=True)
125 self
.update_combo_boxes(left
=False)
127 # Pre-select the 0th elements
128 item
= self
.left_list
.item(0)
130 self
.left_list
.setCurrentItem(item
)
131 self
.left_list
.setItemSelected(item
, True)
133 item
= self
.right_list
.item(0)
135 self
.right_list
.setCurrentItem(item
)
136 self
.right_list
.setItemSelected(item
, True)
139 left_item
= self
.left_list
.currentItem()
140 if left_item
and left_item
.isSelected():
141 left_item
= unicode(left_item
.text())
144 right_item
= self
.right_list
.currentItem()
145 if right_item
and right_item
.isSelected():
146 right_item
= unicode(right_item
.text())
149 return (left_item
, right_item
)
152 def update_diff_files(self
, *rest
):
153 """Updates the list of files whenever the selection changes"""
154 # Left and Right refer to the comparison pair (l,r)
155 left_item
, right_item
= self
.selection()
156 if (not left_item
or not right_item
or
157 left_item
== right_item
):
158 self
.set_diff_files([])
160 left_item
= self
.remote_ref(left_item
)
161 right_item
= self
.remote_ref(right_item
)
163 # If any of the selection includes sandbox then we
164 # generate the same diff, regardless. This means we don't
165 # support reverse diffs against sandbox aka worktree.
166 if self
.SANDBOX
in (left_item
, right_item
):
167 self
.use_sandbox
= True
168 if left_item
== self
.SANDBOX
:
169 self
.diff_arg
= (right_item
,)
171 self
.diff_arg
= (left_item
,)
173 self
.diff_arg
= (left_item
, right_item
)
174 self
.use_sandbox
= False
176 # start and end as in 'git diff start end'
177 self
.start
= left_item
178 self
.end
= right_item
180 if len(self
.diff_arg
) == 1:
181 files
= gitcmds
.diff_index_filenames(self
.diff_arg
[0])
183 files
= gitcmds
.diff_filenames(*self
.diff_arg
)
185 self
.set_diff_files(files
)
187 def set_diff_files(self
, files
):
189 icon
= qtutils
.icon('script.png')
190 self
.diff_files
.clear()
191 self
.diff_files
.addTopLevelItems([mk(f
, icon
) for f
in files
])
193 def remote_ref(self
, branch
):
194 """Returns the remote ref for 'git diff [local] [remote]'
196 if branch
== self
.BRANCH_POINT
:
197 # Compare against the branch point so find the merge-base
198 branch
= gitcmds
.current_branch()
199 tracked_branch
= gitcmds
.tracked_branch()
201 return git
.merge_base(branch
, tracked_branch
)
203 remote_branches
= gitcmds
.branch_list(remote
=True)
204 remote_branch
= 'origin/%s' % branch
205 if remote_branch
in remote_branches
:
206 return git
.merge_base(branch
, remote_branch
)
208 elif 'origin/master' in remote_branches
:
209 return git
.merge_base(branch
, 'origin/master')
213 # Compare against the remote branch
217 def update_combo_boxes(self
, left
=False):
218 """Update listwidgets from the combobox selection
220 Update either the left or right listwidgets
221 to reflect the available items.
224 which
= unicode(self
.left_combo
.currentText())
225 widget
= self
.left_list
227 which
= unicode(self
.right_combo
.currentText())
228 widget
= self
.right_list
231 # If we're looking at "local" stuff then provide the
232 # sandbox as a valid choice. If we're looking at
233 # "remote" stuff then also include the branch point.
234 if which
== self
.LOCAL
:
235 new_list
= ([self
.SANDBOX
]+ self
.local_branches
)
237 new_list
= ([self
.BRANCH_POINT
] + self
.remote_branches
)
240 widget
.addItems(new_list
)
242 item
= widget
.item(0)
243 widget
.setCurrentItem(item
)
244 widget
.setItemSelected(item
, True)
246 def compare(self
, *args
):
247 """Shows the diff for a specific file
249 tree_widget
= self
.diff_files
250 item
= tree_widget
.currentItem()
251 if item
and item
.isSelected():
252 self
.compare_file(item
.path
)
254 def compare_file(self
, filename
):
255 """Initiates the difftool session"""
259 arg
= (self
.start
, self
.end
)
260 difftool
.launch(arg
+ ('--', filename
))