FreeCAD
245 строк · 9.9 Кб
1# SPDX-License-Identifier: LGPL-2.1-or-later
2# ***************************************************************************
3# * *
4# * Copyright (c) 2022-2023 FreeCAD Project Association *
5# * *
6# * This file is part of FreeCAD. *
7# * *
8# * FreeCAD is free software: you can redistribute it and/or modify it *
9# * under the terms of the GNU Lesser General Public License as *
10# * published by the Free Software Foundation, either version 2.1 of the *
11# * License, or (at your option) any later version. *
12# * *
13# * FreeCAD is distributed in the hope that it will be useful, but *
14# * WITHOUT ANY WARRANTY; without even the implied warranty of *
15# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
16# * Lesser General Public License for more details. *
17# * *
18# * You should have received a copy of the GNU Lesser General Public *
19# * License along with FreeCAD. If not, see *
20# * <https://www.gnu.org/licenses/>. *
21# * *
22# ***************************************************************************
23
24import os
25
26import FreeCAD
27import FreeCADGui
28from addonmanager_git import initialize_git
29
30from PySide import QtWidgets, QtCore
31
32translate = FreeCAD.Qt.translate
33
34
35class ChangeBranchDialog(QtWidgets.QWidget):
36
37branch_changed = QtCore.Signal(str)
38
39def __init__(self, path: str, parent=None):
40super().__init__(parent)
41
42self.ui = FreeCADGui.PySideUic.loadUi(
43os.path.join(os.path.dirname(__file__), "change_branch.ui")
44)
45
46self.item_filter = ChangeBranchDialogFilter()
47self.ui.tableView.setModel(self.item_filter)
48
49self.item_model = ChangeBranchDialogModel(path, self)
50self.item_filter.setSourceModel(self.item_model)
51self.ui.tableView.sortByColumn(
522, QtCore.Qt.DescendingOrder
53) # Default to sorting by remote last-changed date
54
55# Figure out what row gets selected:
56git_manager = initialize_git()
57row = 0
58current_ref = git_manager.current_branch(path)
59selection_model = self.ui.tableView.selectionModel()
60for ref in self.item_model.branches:
61if ref["ref_name"] == current_ref:
62index = self.item_filter.mapFromSource(self.item_model.index(row, 0))
63selection_model.select(index, QtCore.QItemSelectionModel.ClearAndSelect)
64selection_model.select(index.siblingAtColumn(1), QtCore.QItemSelectionModel.Select)
65selection_model.select(index.siblingAtColumn(2), QtCore.QItemSelectionModel.Select)
66break
67row += 1
68
69# Make sure the column widths are OK:
70header = self.ui.tableView.horizontalHeader()
71header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
72
73def exec(self):
74if self.ui.exec() == QtWidgets.QDialog.Accepted:
75
76selection = self.ui.tableView.selectedIndexes()
77index = self.item_filter.mapToSource(selection[0])
78ref = self.item_model.data(index, ChangeBranchDialogModel.RefAccessRole)
79
80if ref["ref_name"] == self.item_model.current_branch:
81# This is the one we are already on... just return
82return
83
84result = QtWidgets.QMessageBox.critical(
85self,
86translate("AddonsInstaller", "DANGER: Developer feature"),
87translate(
88"AddonsInstaller",
89"DANGER: Switching branches is intended for developers and beta testers, "
90"and may result in broken, non-backwards compatible documents, instability, "
91"crashes, and/or the premature heat death of the universe. Are you sure you "
92"want to continue?",
93),
94QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Cancel,
95QtWidgets.QMessageBox.Cancel,
96)
97if result == QtWidgets.QMessageBox.Cancel:
98return
99if self.item_model.dirty:
100result = QtWidgets.QMessageBox.critical(
101self,
102translate("AddonsInstaller", "There are local changes"),
103translate(
104"AddonsInstaller",
105"WARNING: This repo has uncommitted local changes. Are you sure you want "
106"to change branches (bringing the changes with you)?",
107),
108QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.Cancel,
109QtWidgets.QMessageBox.Cancel,
110)
111if result == QtWidgets.QMessageBox.Cancel:
112return
113
114gm = initialize_git()
115remote_name = ref["ref_name"]
116_, _, local_name = ref["ref_name"].rpartition("/")
117if ref["upstream"]:
118gm.checkout(self.item_model.path, remote_name)
119else:
120gm.checkout(self.item_model.path, remote_name, args=["-b", local_name])
121self.branch_changed.emit(local_name)
122
123
124class ChangeBranchDialogModel(QtCore.QAbstractTableModel):
125
126branches = []
127DataSortRole = QtCore.Qt.UserRole
128RefAccessRole = QtCore.Qt.UserRole + 1
129
130def __init__(self, path: str, parent=None) -> None:
131super().__init__(parent)
132
133gm = initialize_git()
134self.path = path
135self.branches = gm.get_branches_with_info(path)
136self.current_branch = gm.current_branch(path)
137self.dirty = gm.dirty(path)
138self._remove_tracking_duplicates()
139
140def rowCount(self, parent: QtCore.QModelIndex = QtCore.QModelIndex()) -> int:
141if parent.isValid():
142return 0
143return len(self.branches)
144
145def columnCount(self, parent: QtCore.QModelIndex = QtCore.QModelIndex()) -> int:
146if parent.isValid():
147return 0
148return 3 # Local name, remote name, date
149
150def data(self, index: QtCore.QModelIndex, role: int = QtCore.Qt.DisplayRole):
151if not index.isValid():
152return None
153row = index.row()
154column = index.column()
155if role == QtCore.Qt.ToolTipRole:
156tooltip = self.branches[row]["author"] + ": " + self.branches[row]["subject"]
157return tooltip
158elif role == QtCore.Qt.DisplayRole:
159dd = self.branches[row]
160if column == 2:
161if dd["date"] is not None:
162q_date = QtCore.QDateTime.fromString(
163dd["date"], QtCore.Qt.DateFormat.RFC2822Date
164)
165return QtCore.QLocale().toString(q_date, QtCore.QLocale.ShortFormat)
166return None
167elif column == 0:
168return dd["ref_name"]
169elif column == 1:
170return dd["upstream"]
171else:
172return None
173elif role == ChangeBranchDialogModel.DataSortRole:
174if column == 2:
175if self.branches[row]["date"] is not None:
176q_date = QtCore.QDateTime.fromString(
177self.branches[row]["date"], QtCore.Qt.DateFormat.RFC2822Date
178)
179return q_date
180return None
181elif column == 0:
182return self.branches[row]["ref_name"]
183elif column == 1:
184return self.branches[row]["upstream"]
185else:
186return None
187elif role == ChangeBranchDialogModel.RefAccessRole:
188return self.branches[row]
189
190def headerData(
191self,
192section: int,
193orientation: QtCore.Qt.Orientation,
194role: int = QtCore.Qt.DisplayRole,
195):
196if orientation == QtCore.Qt.Vertical:
197return None
198if role != QtCore.Qt.DisplayRole:
199return None
200if section == 0:
201return translate(
202"AddonsInstaller",
203"Local",
204"Table header for local git ref name",
205)
206if section == 1:
207return translate(
208"AddonsInstaller",
209"Remote tracking",
210"Table header for git remote tracking branch name",
211)
212elif section == 2:
213return translate(
214"AddonsInstaller",
215"Last Updated",
216"Table header for git update date",
217)
218else:
219return None
220
221def _remove_tracking_duplicates(self):
222remote_tracking_branches = []
223branches_to_keep = []
224for branch in self.branches:
225if branch["upstream"]:
226remote_tracking_branches.append(branch["upstream"])
227for branch in self.branches:
228if (
229"HEAD" not in branch["ref_name"]
230and branch["ref_name"] not in remote_tracking_branches
231):
232branches_to_keep.append(branch)
233self.branches = branches_to_keep
234
235
236class ChangeBranchDialogFilter(QtCore.QSortFilterProxyModel):
237def lessThan(self, left: QtCore.QModelIndex, right: QtCore.QModelIndex):
238leftData = self.sourceModel().data(left, ChangeBranchDialogModel.DataSortRole)
239rightData = self.sourceModel().data(right, ChangeBranchDialogModel.DataSortRole)
240if leftData is None or rightData is None:
241if rightData is not None:
242return True
243else:
244return False
245return leftData < rightData
246