Coverage for britney2/transaction.py: 92%
82 statements
« prev ^ index » next coverage.py v7.6.0, created at 2025-10-17 17:32 +0000
« prev ^ index » next coverage.py v7.6.0, created at 2025-10-17 17:32 +0000
1from collections.abc import Iterator
2from typing import TYPE_CHECKING, Optional, TypedDict
4if TYPE_CHECKING: 4 ↛ 5line 4 didn't jump to line 5 because the condition on line 4 was never true
5 from . import BinaryPackage, BinaryPackageId, SourcePackage, Suites
8class UndoItem(TypedDict):
9 sources: dict[str, Optional["SourcePackage"]]
10 binaries: dict[tuple[str, str], "BinaryPackageId"]
11 virtual: dict[tuple[str, str], set[tuple[str, str]] | None]
14class MigrationTransactionState:
15 def __init__(
16 self,
17 suite_info: "Suites",
18 all_binaries: dict["BinaryPackageId", "BinaryPackage"],
19 parent: Optional["MigrationTransactionState"] = None,
20 ) -> None:
21 self._suite_info = suite_info
22 self._all_binaries = all_binaries
23 self.parent_transaction = parent
24 self._is_rolled_back = False
25 self._is_committed = False
26 self._undo_items: list[tuple[UndoItem, set["BinaryPackageId"]]] = []
27 self._pending_child = False
28 if self.parent_transaction:
29 # Transactions can only support one child transaction at a time
30 assert not self.parent_transaction._pending_child
31 self.parent_transaction._pending_child = True
33 def add_undo_item(
34 self, undo: UndoItem, updated_binaries: set["BinaryPackageId"]
35 ) -> None:
36 # We do not accept any changes to this transaction while it has a child transaction
37 # (the undo code does not handle that case correctly)
38 assert not self._pending_child
39 self._assert_open_transaction()
40 self._undo_items.append((undo, updated_binaries))
42 def _assert_open_transaction(self) -> None:
43 assert not self._is_rolled_back and not self._is_committed
44 if p := self.parent_transaction:
45 p._assert_open_transaction()
47 @property
48 def undo_items(self) -> Iterator[tuple[UndoItem, set["BinaryPackageId"]]]:
49 """Only needed by a _apply_item_to_target_suite for the "hint"-hint case"""
50 yield from self._undo_items
52 def commit(self) -> None:
53 """Commit the transaction
55 After this call, it is not possible to roll these changes
56 back (except if there is a parent transaction, which can
57 still be rolled back).
58 """
59 self._assert_open_transaction()
60 self._is_committed = True
61 if self.parent_transaction:
62 self.parent_transaction._pending_child = False
63 for undo_item in self._undo_items:
64 self.parent_transaction.add_undo_item(*undo_item)
66 def rollback(self) -> None:
67 """Rollback all recorded changes by this transaction
69 The parent transaction (if any) will remain unchanged
70 """
72 self._assert_open_transaction()
73 self._is_rolled_back = True
74 lundo = self._undo_items
75 lundo.reverse()
77 all_binary_packages = self._all_binaries
78 target_suite = self._suite_info.target_suite
79 sources_t = target_suite.sources
80 binaries_t = target_suite.binaries
81 provides_t = target_suite.provides_table
83 # Historically, we have done the undo process in "4 steps"
84 # with the rule that each step must be fully completed for
85 # each undo-item before starting on the next.
86 #
87 # see commit:ef71f0e33a7c3d8ef223ec9ad5e9843777e68133 and
88 # #624716 for the issues we had when we did not do this.
89 #
90 # Today, only STEP 2 and STEP 3 are known to potentially
91 # clash. If there is a point in merging the loops/steps,
92 # then it is now feasible.
94 # STEP 1
95 # undo all the changes for sources
96 for undo, updated_binaries in lundo:
97 for k, v in undo["sources"].items():
98 if v is None:
99 del sources_t[k]
100 else:
101 sources_t[k] = v
103 # STEP 2
104 # undo all new/updated binaries
105 # Note this must be completed fully before starting STEP 3
106 # as it potentially breaks STEP 3 if the two are interleaved.
107 for _, updated_binaries in lundo:
108 for pkg_id in updated_binaries:
109 pkg_name, _, pkg_arch = pkg_id
110 try:
111 del binaries_t[pkg_arch][pkg_name]
112 except KeyError:
113 continue
115 target_suite.remove_binary(pkg_id)
117 # STEP 3
118 # undo all other binary package changes (except virtual packages)
119 for undo, updated_binaries in lundo:
120 for p in undo["binaries"]:
121 binary, arch = p
122 binaries_t_a = binaries_t[arch]
123 pkgdata = all_binary_packages[undo["binaries"][p]]
124 binaries_t_a[binary] = pkgdata
125 target_suite.add_binary(pkgdata.pkg_id)
127 # STEP 4
128 # undo all changes to virtual packages
129 for undo, _ in lundo:
130 for p, value in undo["virtual"].items():
131 provided_pkg, arch = p
132 if value is None: 132 ↛ 133line 132 didn't jump to line 133 because the condition on line 132 was never true
133 del provides_t[arch][provided_pkg]
134 else:
135 provides_t[arch][provided_pkg] = value
137 if self.parent_transaction: 137 ↛ 138line 137 didn't jump to line 138 because the condition on line 137 was never true
138 self.parent_transaction._pending_child = False
140 @property
141 def is_rolled_back(self) -> bool:
142 return self._is_rolled_back
144 @property
145 def is_committed(self) -> bool:
146 return self._is_committed