revision.py 61 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728
  1. from __future__ import annotations
  2. import collections
  3. import re
  4. from typing import Any
  5. from typing import Callable
  6. from typing import cast
  7. from typing import Collection
  8. from typing import Deque
  9. from typing import Dict
  10. from typing import FrozenSet
  11. from typing import Iterable
  12. from typing import Iterator
  13. from typing import List
  14. from typing import Optional
  15. from typing import overload
  16. from typing import Protocol
  17. from typing import Sequence
  18. from typing import Set
  19. from typing import Tuple
  20. from typing import TYPE_CHECKING
  21. from typing import TypeVar
  22. from typing import Union
  23. from sqlalchemy import util as sqlautil
  24. from .. import util
  25. from ..util import not_none
  26. if TYPE_CHECKING:
  27. from typing import Literal
  28. _RevIdType = Union[str, List[str], Tuple[str, ...]]
  29. _GetRevArg = Union[
  30. str,
  31. Iterable[Optional[str]],
  32. Iterable[str],
  33. ]
  34. _RevisionIdentifierType = Union[str, Tuple[str, ...], None]
  35. _RevisionOrStr = Union["Revision", str]
  36. _RevisionOrBase = Union["Revision", "Literal['base']"]
  37. _InterimRevisionMapType = Dict[str, "Revision"]
  38. _RevisionMapType = Dict[Union[None, str, Tuple[()]], Optional["Revision"]]
  39. _T = TypeVar("_T")
  40. _TR = TypeVar("_TR", bound=Optional[_RevisionOrStr])
  41. _relative_destination = re.compile(r"(?:(.+?)@)?(\w+)?((?:\+|-)\d+)")
  42. _revision_illegal_chars = ["@", "-", "+"]
  43. class _CollectRevisionsProtocol(Protocol):
  44. def __call__(
  45. self,
  46. upper: _RevisionIdentifierType,
  47. lower: _RevisionIdentifierType,
  48. inclusive: bool,
  49. implicit_base: bool,
  50. assert_relative_length: bool,
  51. ) -> Tuple[Set[Revision], Tuple[Optional[_RevisionOrBase], ...]]: ...
  52. class RevisionError(Exception):
  53. pass
  54. class RangeNotAncestorError(RevisionError):
  55. def __init__(
  56. self, lower: _RevisionIdentifierType, upper: _RevisionIdentifierType
  57. ) -> None:
  58. self.lower = lower
  59. self.upper = upper
  60. super().__init__(
  61. "Revision %s is not an ancestor of revision %s"
  62. % (lower or "base", upper or "base")
  63. )
  64. class MultipleHeads(RevisionError):
  65. def __init__(self, heads: Sequence[str], argument: Optional[str]) -> None:
  66. self.heads = heads
  67. self.argument = argument
  68. super().__init__(
  69. "Multiple heads are present for given argument '%s'; "
  70. "%s" % (argument, ", ".join(heads))
  71. )
  72. class ResolutionError(RevisionError):
  73. def __init__(self, message: str, argument: str) -> None:
  74. super().__init__(message)
  75. self.argument = argument
  76. class CycleDetected(RevisionError):
  77. kind = "Cycle"
  78. def __init__(self, revisions: Sequence[str]) -> None:
  79. self.revisions = revisions
  80. super().__init__(
  81. "%s is detected in revisions (%s)"
  82. % (self.kind, ", ".join(revisions))
  83. )
  84. class DependencyCycleDetected(CycleDetected):
  85. kind = "Dependency cycle"
  86. def __init__(self, revisions: Sequence[str]) -> None:
  87. super().__init__(revisions)
  88. class LoopDetected(CycleDetected):
  89. kind = "Self-loop"
  90. def __init__(self, revision: str) -> None:
  91. super().__init__([revision])
  92. class DependencyLoopDetected(DependencyCycleDetected, LoopDetected):
  93. kind = "Dependency self-loop"
  94. def __init__(self, revision: Sequence[str]) -> None:
  95. super().__init__(revision)
  96. class RevisionMap:
  97. """Maintains a map of :class:`.Revision` objects.
  98. :class:`.RevisionMap` is used by :class:`.ScriptDirectory` to maintain
  99. and traverse the collection of :class:`.Script` objects, which are
  100. themselves instances of :class:`.Revision`.
  101. """
  102. def __init__(self, generator: Callable[[], Iterable[Revision]]) -> None:
  103. """Construct a new :class:`.RevisionMap`.
  104. :param generator: a zero-arg callable that will generate an iterable
  105. of :class:`.Revision` instances to be used. These are typically
  106. :class:`.Script` subclasses within regular Alembic use.
  107. """
  108. self._generator = generator
  109. @util.memoized_property
  110. def heads(self) -> Tuple[str, ...]:
  111. """All "head" revisions as strings.
  112. This is normally a tuple of length one,
  113. unless unmerged branches are present.
  114. :return: a tuple of string revision numbers.
  115. """
  116. self._revision_map
  117. return self.heads
  118. @util.memoized_property
  119. def bases(self) -> Tuple[str, ...]:
  120. """All "base" revisions as strings.
  121. These are revisions that have a ``down_revision`` of None,
  122. or empty tuple.
  123. :return: a tuple of string revision numbers.
  124. """
  125. self._revision_map
  126. return self.bases
  127. @util.memoized_property
  128. def _real_heads(self) -> Tuple[str, ...]:
  129. """All "real" head revisions as strings.
  130. :return: a tuple of string revision numbers.
  131. """
  132. self._revision_map
  133. return self._real_heads
  134. @util.memoized_property
  135. def _real_bases(self) -> Tuple[str, ...]:
  136. """All "real" base revisions as strings.
  137. :return: a tuple of string revision numbers.
  138. """
  139. self._revision_map
  140. return self._real_bases
  141. @util.memoized_property
  142. def _revision_map(self) -> _RevisionMapType:
  143. """memoized attribute, initializes the revision map from the
  144. initial collection.
  145. """
  146. # Ordering required for some tests to pass (but not required in
  147. # general)
  148. map_: _InterimRevisionMapType = sqlautil.OrderedDict()
  149. heads: Set[Revision] = sqlautil.OrderedSet()
  150. _real_heads: Set[Revision] = sqlautil.OrderedSet()
  151. bases: Tuple[Revision, ...] = ()
  152. _real_bases: Tuple[Revision, ...] = ()
  153. has_branch_labels = set()
  154. all_revisions = set()
  155. for revision in self._generator():
  156. all_revisions.add(revision)
  157. if revision.revision in map_:
  158. util.warn(
  159. "Revision %s is present more than once" % revision.revision
  160. )
  161. map_[revision.revision] = revision
  162. if revision.branch_labels:
  163. has_branch_labels.add(revision)
  164. heads.add(revision)
  165. _real_heads.add(revision)
  166. if revision.is_base:
  167. bases += (revision,)
  168. if revision._is_real_base:
  169. _real_bases += (revision,)
  170. # add the branch_labels to the map_. We'll need these
  171. # to resolve the dependencies.
  172. rev_map = map_.copy()
  173. self._map_branch_labels(
  174. has_branch_labels, cast(_RevisionMapType, map_)
  175. )
  176. # resolve dependency names from branch labels and symbolic
  177. # names
  178. self._add_depends_on(all_revisions, cast(_RevisionMapType, map_))
  179. for rev in map_.values():
  180. for downrev in rev._all_down_revisions:
  181. if downrev not in map_:
  182. util.warn(
  183. "Revision %s referenced from %s is not present"
  184. % (downrev, rev)
  185. )
  186. down_revision = map_[downrev]
  187. down_revision.add_nextrev(rev)
  188. if downrev in rev._versioned_down_revisions:
  189. heads.discard(down_revision)
  190. _real_heads.discard(down_revision)
  191. # once the map has downrevisions populated, the dependencies
  192. # can be further refined to include only those which are not
  193. # already ancestors
  194. self._normalize_depends_on(all_revisions, cast(_RevisionMapType, map_))
  195. self._detect_cycles(rev_map, heads, bases, _real_heads, _real_bases)
  196. revision_map: _RevisionMapType = dict(map_.items())
  197. revision_map[None] = revision_map[()] = None
  198. self.heads = tuple(rev.revision for rev in heads)
  199. self._real_heads = tuple(rev.revision for rev in _real_heads)
  200. self.bases = tuple(rev.revision for rev in bases)
  201. self._real_bases = tuple(rev.revision for rev in _real_bases)
  202. self._add_branches(has_branch_labels, revision_map)
  203. return revision_map
  204. def _detect_cycles(
  205. self,
  206. rev_map: _InterimRevisionMapType,
  207. heads: Set[Revision],
  208. bases: Tuple[Revision, ...],
  209. _real_heads: Set[Revision],
  210. _real_bases: Tuple[Revision, ...],
  211. ) -> None:
  212. if not rev_map:
  213. return
  214. if not heads or not bases:
  215. raise CycleDetected(list(rev_map))
  216. total_space = {
  217. rev.revision
  218. for rev in self._iterate_related_revisions(
  219. lambda r: r._versioned_down_revisions,
  220. heads,
  221. map_=cast(_RevisionMapType, rev_map),
  222. )
  223. }.intersection(
  224. rev.revision
  225. for rev in self._iterate_related_revisions(
  226. lambda r: r.nextrev,
  227. bases,
  228. map_=cast(_RevisionMapType, rev_map),
  229. )
  230. )
  231. deleted_revs = set(rev_map.keys()) - total_space
  232. if deleted_revs:
  233. raise CycleDetected(sorted(deleted_revs))
  234. if not _real_heads or not _real_bases:
  235. raise DependencyCycleDetected(list(rev_map))
  236. total_space = {
  237. rev.revision
  238. for rev in self._iterate_related_revisions(
  239. lambda r: r._all_down_revisions,
  240. _real_heads,
  241. map_=cast(_RevisionMapType, rev_map),
  242. )
  243. }.intersection(
  244. rev.revision
  245. for rev in self._iterate_related_revisions(
  246. lambda r: r._all_nextrev,
  247. _real_bases,
  248. map_=cast(_RevisionMapType, rev_map),
  249. )
  250. )
  251. deleted_revs = set(rev_map.keys()) - total_space
  252. if deleted_revs:
  253. raise DependencyCycleDetected(sorted(deleted_revs))
  254. def _map_branch_labels(
  255. self, revisions: Collection[Revision], map_: _RevisionMapType
  256. ) -> None:
  257. for revision in revisions:
  258. if revision.branch_labels:
  259. assert revision._orig_branch_labels is not None
  260. for branch_label in revision._orig_branch_labels:
  261. if branch_label in map_:
  262. map_rev = map_[branch_label]
  263. assert map_rev is not None
  264. raise RevisionError(
  265. "Branch name '%s' in revision %s already "
  266. "used by revision %s"
  267. % (
  268. branch_label,
  269. revision.revision,
  270. map_rev.revision,
  271. )
  272. )
  273. map_[branch_label] = revision
  274. def _add_branches(
  275. self, revisions: Collection[Revision], map_: _RevisionMapType
  276. ) -> None:
  277. for revision in revisions:
  278. if revision.branch_labels:
  279. revision.branch_labels.update(revision.branch_labels)
  280. for node in self._get_descendant_nodes(
  281. [revision], map_, include_dependencies=False
  282. ):
  283. node.branch_labels.update(revision.branch_labels)
  284. parent = node
  285. while (
  286. parent
  287. and not parent._is_real_branch_point
  288. and not parent.is_merge_point
  289. ):
  290. parent.branch_labels.update(revision.branch_labels)
  291. if parent.down_revision:
  292. parent = map_[parent.down_revision]
  293. else:
  294. break
  295. def _add_depends_on(
  296. self, revisions: Collection[Revision], map_: _RevisionMapType
  297. ) -> None:
  298. """Resolve the 'dependencies' for each revision in a collection
  299. in terms of actual revision ids, as opposed to branch labels or other
  300. symbolic names.
  301. The collection is then assigned to the _resolved_dependencies
  302. attribute on each revision object.
  303. """
  304. for revision in revisions:
  305. if revision.dependencies:
  306. deps = [
  307. map_[dep] for dep in util.to_tuple(revision.dependencies)
  308. ]
  309. revision._resolved_dependencies = tuple(
  310. [d.revision for d in deps if d is not None]
  311. )
  312. else:
  313. revision._resolved_dependencies = ()
  314. def _normalize_depends_on(
  315. self, revisions: Collection[Revision], map_: _RevisionMapType
  316. ) -> None:
  317. """Create a collection of "dependencies" that omits dependencies
  318. that are already ancestor nodes for each revision in a given
  319. collection.
  320. This builds upon the _resolved_dependencies collection created in the
  321. _add_depends_on() method, looking in the fully populated revision map
  322. for ancestors, and omitting them as the _resolved_dependencies
  323. collection as it is copied to a new collection. The new collection is
  324. then assigned to the _normalized_resolved_dependencies attribute on
  325. each revision object.
  326. The collection is then used to determine the immediate "down revision"
  327. identifiers for this revision.
  328. """
  329. for revision in revisions:
  330. if revision._resolved_dependencies:
  331. normalized_resolved = set(revision._resolved_dependencies)
  332. for rev in self._get_ancestor_nodes(
  333. [revision],
  334. include_dependencies=False,
  335. map_=map_,
  336. ):
  337. if rev is revision:
  338. continue
  339. elif rev._resolved_dependencies:
  340. normalized_resolved.difference_update(
  341. rev._resolved_dependencies
  342. )
  343. revision._normalized_resolved_dependencies = tuple(
  344. normalized_resolved
  345. )
  346. else:
  347. revision._normalized_resolved_dependencies = ()
  348. def add_revision(self, revision: Revision, _replace: bool = False) -> None:
  349. """add a single revision to an existing map.
  350. This method is for single-revision use cases, it's not
  351. appropriate for fully populating an entire revision map.
  352. """
  353. map_ = self._revision_map
  354. if not _replace and revision.revision in map_:
  355. util.warn(
  356. "Revision %s is present more than once" % revision.revision
  357. )
  358. elif _replace and revision.revision not in map_:
  359. raise Exception("revision %s not in map" % revision.revision)
  360. map_[revision.revision] = revision
  361. revisions = [revision]
  362. self._add_branches(revisions, map_)
  363. self._map_branch_labels(revisions, map_)
  364. self._add_depends_on(revisions, map_)
  365. if revision.is_base:
  366. self.bases += (revision.revision,)
  367. if revision._is_real_base:
  368. self._real_bases += (revision.revision,)
  369. for downrev in revision._all_down_revisions:
  370. if downrev not in map_:
  371. util.warn(
  372. "Revision %s referenced from %s is not present"
  373. % (downrev, revision)
  374. )
  375. not_none(map_[downrev]).add_nextrev(revision)
  376. self._normalize_depends_on(revisions, map_)
  377. if revision._is_real_head:
  378. self._real_heads = tuple(
  379. head
  380. for head in self._real_heads
  381. if head
  382. not in set(revision._all_down_revisions).union(
  383. [revision.revision]
  384. )
  385. ) + (revision.revision,)
  386. if revision.is_head:
  387. self.heads = tuple(
  388. head
  389. for head in self.heads
  390. if head
  391. not in set(revision._versioned_down_revisions).union(
  392. [revision.revision]
  393. )
  394. ) + (revision.revision,)
  395. def get_current_head(
  396. self, branch_label: Optional[str] = None
  397. ) -> Optional[str]:
  398. """Return the current head revision.
  399. If the script directory has multiple heads
  400. due to branching, an error is raised;
  401. :meth:`.ScriptDirectory.get_heads` should be
  402. preferred.
  403. :param branch_label: optional branch name which will limit the
  404. heads considered to those which include that branch_label.
  405. :return: a string revision number.
  406. .. seealso::
  407. :meth:`.ScriptDirectory.get_heads`
  408. """
  409. current_heads: Sequence[str] = self.heads
  410. if branch_label:
  411. current_heads = self.filter_for_lineage(
  412. current_heads, branch_label
  413. )
  414. if len(current_heads) > 1:
  415. raise MultipleHeads(
  416. current_heads,
  417. "%s@head" % branch_label if branch_label else "head",
  418. )
  419. if current_heads:
  420. return current_heads[0]
  421. else:
  422. return None
  423. def _get_base_revisions(self, identifier: str) -> Tuple[str, ...]:
  424. return self.filter_for_lineage(self.bases, identifier)
  425. def get_revisions(
  426. self, id_: Optional[_GetRevArg]
  427. ) -> Tuple[Optional[_RevisionOrBase], ...]:
  428. """Return the :class:`.Revision` instances with the given rev id
  429. or identifiers.
  430. May be given a single identifier, a sequence of identifiers, or the
  431. special symbols "head" or "base". The result is a tuple of one
  432. or more identifiers, or an empty tuple in the case of "base".
  433. In the cases where 'head', 'heads' is requested and the
  434. revision map is empty, returns an empty tuple.
  435. Supports partial identifiers, where the given identifier
  436. is matched against all identifiers that start with the given
  437. characters; if there is exactly one match, that determines the
  438. full revision.
  439. """
  440. if isinstance(id_, (list, tuple, set, frozenset)):
  441. return sum([self.get_revisions(id_elem) for id_elem in id_], ())
  442. else:
  443. resolved_id, branch_label = self._resolve_revision_number(id_)
  444. if len(resolved_id) == 1:
  445. try:
  446. rint = int(resolved_id[0])
  447. if rint < 0:
  448. # branch@-n -> walk down from heads
  449. select_heads = self.get_revisions("heads")
  450. if branch_label is not None:
  451. select_heads = tuple(
  452. head
  453. for head in select_heads
  454. if branch_label
  455. in is_revision(head).branch_labels
  456. )
  457. return tuple(
  458. self._walk(head, steps=rint)
  459. for head in select_heads
  460. )
  461. except ValueError:
  462. # couldn't resolve as integer
  463. pass
  464. return tuple(
  465. self._revision_for_ident(rev_id, branch_label)
  466. for rev_id in resolved_id
  467. )
  468. def get_revision(self, id_: Optional[str]) -> Optional[Revision]:
  469. """Return the :class:`.Revision` instance with the given rev id.
  470. If a symbolic name such as "head" or "base" is given, resolves
  471. the identifier into the current head or base revision. If the symbolic
  472. name refers to multiples, :class:`.MultipleHeads` is raised.
  473. Supports partial identifiers, where the given identifier
  474. is matched against all identifiers that start with the given
  475. characters; if there is exactly one match, that determines the
  476. full revision.
  477. """
  478. resolved_id, branch_label = self._resolve_revision_number(id_)
  479. if len(resolved_id) > 1:
  480. raise MultipleHeads(resolved_id, id_)
  481. resolved: Union[str, Tuple[()]] = resolved_id[0] if resolved_id else ()
  482. return self._revision_for_ident(resolved, branch_label)
  483. def _resolve_branch(self, branch_label: str) -> Optional[Revision]:
  484. try:
  485. branch_rev = self._revision_map[branch_label]
  486. except KeyError:
  487. try:
  488. nonbranch_rev = self._revision_for_ident(branch_label)
  489. except ResolutionError as re:
  490. raise ResolutionError(
  491. "No such branch: '%s'" % branch_label, branch_label
  492. ) from re
  493. else:
  494. return nonbranch_rev
  495. else:
  496. return branch_rev
  497. def _revision_for_ident(
  498. self,
  499. resolved_id: Union[str, Tuple[()], None],
  500. check_branch: Optional[str] = None,
  501. ) -> Optional[Revision]:
  502. branch_rev: Optional[Revision]
  503. if check_branch:
  504. branch_rev = self._resolve_branch(check_branch)
  505. else:
  506. branch_rev = None
  507. revision: Union[Optional[Revision], Literal[False]]
  508. try:
  509. revision = self._revision_map[resolved_id]
  510. except KeyError:
  511. # break out to avoid misleading py3k stack traces
  512. revision = False
  513. revs: Sequence[str]
  514. if revision is False:
  515. assert resolved_id
  516. # do a partial lookup
  517. revs = [
  518. x
  519. for x in self._revision_map
  520. if x and len(x) > 3 and x.startswith(resolved_id)
  521. ]
  522. if branch_rev:
  523. revs = self.filter_for_lineage(revs, check_branch)
  524. if not revs:
  525. raise ResolutionError(
  526. "No such revision or branch '%s'%s"
  527. % (
  528. resolved_id,
  529. (
  530. "; please ensure at least four characters are "
  531. "present for partial revision identifier matches"
  532. if len(resolved_id) < 4
  533. else ""
  534. ),
  535. ),
  536. resolved_id,
  537. )
  538. elif len(revs) > 1:
  539. raise ResolutionError(
  540. "Multiple revisions start "
  541. "with '%s': %s..."
  542. % (resolved_id, ", ".join("'%s'" % r for r in revs[0:3])),
  543. resolved_id,
  544. )
  545. else:
  546. revision = self._revision_map[revs[0]]
  547. if check_branch and revision is not None:
  548. assert branch_rev is not None
  549. assert resolved_id
  550. if not self._shares_lineage(
  551. revision.revision, branch_rev.revision
  552. ):
  553. raise ResolutionError(
  554. "Revision %s is not a member of branch '%s'"
  555. % (revision.revision, check_branch),
  556. resolved_id,
  557. )
  558. return revision
  559. def _filter_into_branch_heads(
  560. self, targets: Iterable[Optional[_RevisionOrBase]]
  561. ) -> Set[Optional[_RevisionOrBase]]:
  562. targets = set(targets)
  563. for rev in list(targets):
  564. assert rev
  565. if targets.intersection(
  566. self._get_descendant_nodes([rev], include_dependencies=False)
  567. ).difference([rev]):
  568. targets.discard(rev)
  569. return targets
  570. def filter_for_lineage(
  571. self,
  572. targets: Iterable[_TR],
  573. check_against: Optional[str],
  574. include_dependencies: bool = False,
  575. ) -> Tuple[_TR, ...]:
  576. id_, branch_label = self._resolve_revision_number(check_against)
  577. shares = []
  578. if branch_label:
  579. shares.append(branch_label)
  580. if id_:
  581. shares.extend(id_)
  582. return tuple(
  583. tg
  584. for tg in targets
  585. if self._shares_lineage(
  586. tg, shares, include_dependencies=include_dependencies
  587. )
  588. )
  589. def _shares_lineage(
  590. self,
  591. target: Optional[_RevisionOrStr],
  592. test_against_revs: Sequence[_RevisionOrStr],
  593. include_dependencies: bool = False,
  594. ) -> bool:
  595. if not test_against_revs:
  596. return True
  597. if not isinstance(target, Revision):
  598. resolved_target = not_none(self._revision_for_ident(target))
  599. else:
  600. resolved_target = target
  601. resolved_test_against_revs = [
  602. (
  603. self._revision_for_ident(test_against_rev)
  604. if not isinstance(test_against_rev, Revision)
  605. else test_against_rev
  606. )
  607. for test_against_rev in util.to_tuple(
  608. test_against_revs, default=()
  609. )
  610. ]
  611. return bool(
  612. set(
  613. self._get_descendant_nodes(
  614. [resolved_target],
  615. include_dependencies=include_dependencies,
  616. )
  617. )
  618. .union(
  619. self._get_ancestor_nodes(
  620. [resolved_target],
  621. include_dependencies=include_dependencies,
  622. )
  623. )
  624. .intersection(resolved_test_against_revs)
  625. )
  626. def _resolve_revision_number(
  627. self, id_: Optional[_GetRevArg]
  628. ) -> Tuple[Tuple[str, ...], Optional[str]]:
  629. branch_label: Optional[str]
  630. if isinstance(id_, str) and "@" in id_:
  631. branch_label, id_ = id_.split("@", 1)
  632. elif id_ is not None and (
  633. (isinstance(id_, tuple) and id_ and not isinstance(id_[0], str))
  634. or not isinstance(id_, (str, tuple))
  635. ):
  636. raise RevisionError(
  637. "revision identifier %r is not a string; ensure database "
  638. "driver settings are correct" % (id_,)
  639. )
  640. else:
  641. branch_label = None
  642. # ensure map is loaded
  643. self._revision_map
  644. if id_ == "heads":
  645. if branch_label:
  646. return (
  647. self.filter_for_lineage(self.heads, branch_label),
  648. branch_label,
  649. )
  650. else:
  651. return self._real_heads, branch_label
  652. elif id_ == "head":
  653. current_head = self.get_current_head(branch_label)
  654. if current_head:
  655. return (current_head,), branch_label
  656. else:
  657. return (), branch_label
  658. elif id_ == "base" or id_ is None:
  659. return (), branch_label
  660. else:
  661. return util.to_tuple(id_, default=None), branch_label
  662. def iterate_revisions(
  663. self,
  664. upper: _RevisionIdentifierType,
  665. lower: _RevisionIdentifierType,
  666. implicit_base: bool = False,
  667. inclusive: bool = False,
  668. assert_relative_length: bool = True,
  669. select_for_downgrade: bool = False,
  670. ) -> Iterator[Revision]:
  671. """Iterate through script revisions, starting at the given
  672. upper revision identifier and ending at the lower.
  673. The traversal uses strictly the `down_revision`
  674. marker inside each migration script, so
  675. it is a requirement that upper >= lower,
  676. else you'll get nothing back.
  677. The iterator yields :class:`.Revision` objects.
  678. """
  679. fn: _CollectRevisionsProtocol
  680. if select_for_downgrade:
  681. fn = self._collect_downgrade_revisions
  682. else:
  683. fn = self._collect_upgrade_revisions
  684. revisions, heads = fn(
  685. upper,
  686. lower,
  687. inclusive=inclusive,
  688. implicit_base=implicit_base,
  689. assert_relative_length=assert_relative_length,
  690. )
  691. for node in self._topological_sort(revisions, heads):
  692. yield not_none(self.get_revision(node))
  693. def _get_descendant_nodes(
  694. self,
  695. targets: Collection[Optional[_RevisionOrBase]],
  696. map_: Optional[_RevisionMapType] = None,
  697. check: bool = False,
  698. omit_immediate_dependencies: bool = False,
  699. include_dependencies: bool = True,
  700. ) -> Iterator[Any]:
  701. if omit_immediate_dependencies:
  702. def fn(rev: Revision) -> Iterable[str]:
  703. if rev not in targets:
  704. return rev._all_nextrev
  705. else:
  706. return rev.nextrev
  707. elif include_dependencies:
  708. def fn(rev: Revision) -> Iterable[str]:
  709. return rev._all_nextrev
  710. else:
  711. def fn(rev: Revision) -> Iterable[str]:
  712. return rev.nextrev
  713. return self._iterate_related_revisions(
  714. fn, targets, map_=map_, check=check
  715. )
  716. def _get_ancestor_nodes(
  717. self,
  718. targets: Collection[Optional[_RevisionOrBase]],
  719. map_: Optional[_RevisionMapType] = None,
  720. check: bool = False,
  721. include_dependencies: bool = True,
  722. ) -> Iterator[Revision]:
  723. if include_dependencies:
  724. def fn(rev: Revision) -> Iterable[str]:
  725. return rev._normalized_down_revisions
  726. else:
  727. def fn(rev: Revision) -> Iterable[str]:
  728. return rev._versioned_down_revisions
  729. return self._iterate_related_revisions(
  730. fn, targets, map_=map_, check=check
  731. )
  732. def _iterate_related_revisions(
  733. self,
  734. fn: Callable[[Revision], Iterable[str]],
  735. targets: Collection[Optional[_RevisionOrBase]],
  736. map_: Optional[_RevisionMapType],
  737. check: bool = False,
  738. ) -> Iterator[Revision]:
  739. if map_ is None:
  740. map_ = self._revision_map
  741. seen = set()
  742. todo: Deque[Revision] = collections.deque()
  743. for target_for in targets:
  744. target = is_revision(target_for)
  745. todo.append(target)
  746. if check:
  747. per_target = set()
  748. while todo:
  749. rev = todo.pop()
  750. if check:
  751. per_target.add(rev)
  752. if rev in seen:
  753. continue
  754. seen.add(rev)
  755. # Check for map errors before collecting.
  756. for rev_id in fn(rev):
  757. next_rev = map_[rev_id]
  758. assert next_rev is not None
  759. if next_rev.revision != rev_id:
  760. raise RevisionError(
  761. "Dependency resolution failed; broken map"
  762. )
  763. todo.append(next_rev)
  764. yield rev
  765. if check:
  766. overlaps = per_target.intersection(targets).difference(
  767. [target]
  768. )
  769. if overlaps:
  770. raise RevisionError(
  771. "Requested revision %s overlaps with "
  772. "other requested revisions %s"
  773. % (
  774. target.revision,
  775. ", ".join(r.revision for r in overlaps),
  776. )
  777. )
  778. def _topological_sort(
  779. self,
  780. revisions: Collection[Revision],
  781. heads: Any,
  782. ) -> List[str]:
  783. """Yield revision ids of a collection of Revision objects in
  784. topological sorted order (i.e. revisions always come after their
  785. down_revisions and dependencies). Uses the order of keys in
  786. _revision_map to sort.
  787. """
  788. id_to_rev = self._revision_map
  789. def get_ancestors(rev_id: str) -> Set[str]:
  790. return {
  791. r.revision
  792. for r in self._get_ancestor_nodes([id_to_rev[rev_id]])
  793. }
  794. todo = {d.revision for d in revisions}
  795. # Use revision map (ordered dict) key order to pre-sort.
  796. inserted_order = list(self._revision_map)
  797. current_heads = list(
  798. sorted(
  799. {d.revision for d in heads if d.revision in todo},
  800. key=inserted_order.index,
  801. )
  802. )
  803. ancestors_by_idx = [get_ancestors(rev_id) for rev_id in current_heads]
  804. output = []
  805. current_candidate_idx = 0
  806. while current_heads:
  807. candidate = current_heads[current_candidate_idx]
  808. for check_head_index, ancestors in enumerate(ancestors_by_idx):
  809. # scan all the heads. see if we can continue walking
  810. # down the current branch indicated by current_candidate_idx.
  811. if (
  812. check_head_index != current_candidate_idx
  813. and candidate in ancestors
  814. ):
  815. current_candidate_idx = check_head_index
  816. # nope, another head is dependent on us, they have
  817. # to be traversed first
  818. break
  819. else:
  820. # yup, we can emit
  821. if candidate in todo:
  822. output.append(candidate)
  823. todo.remove(candidate)
  824. # now update the heads with our ancestors.
  825. candidate_rev = id_to_rev[candidate]
  826. assert candidate_rev is not None
  827. heads_to_add = [
  828. r
  829. for r in candidate_rev._normalized_down_revisions
  830. if r in todo and r not in current_heads
  831. ]
  832. if not heads_to_add:
  833. # no ancestors, so remove this head from the list
  834. del current_heads[current_candidate_idx]
  835. del ancestors_by_idx[current_candidate_idx]
  836. current_candidate_idx = max(current_candidate_idx - 1, 0)
  837. else:
  838. if (
  839. not candidate_rev._normalized_resolved_dependencies
  840. and len(candidate_rev._versioned_down_revisions) == 1
  841. ):
  842. current_heads[current_candidate_idx] = heads_to_add[0]
  843. # for plain movement down a revision line without
  844. # any mergepoints, branchpoints, or deps, we
  845. # can update the ancestors collection directly
  846. # by popping out the candidate we just emitted
  847. ancestors_by_idx[current_candidate_idx].discard(
  848. candidate
  849. )
  850. else:
  851. # otherwise recalculate it again, things get
  852. # complicated otherwise. This can possibly be
  853. # improved to not run the whole ancestor thing
  854. # each time but it was getting complicated
  855. current_heads[current_candidate_idx] = heads_to_add[0]
  856. current_heads.extend(heads_to_add[1:])
  857. ancestors_by_idx[current_candidate_idx] = (
  858. get_ancestors(heads_to_add[0])
  859. )
  860. ancestors_by_idx.extend(
  861. get_ancestors(head) for head in heads_to_add[1:]
  862. )
  863. assert not todo
  864. return output
  865. def _walk(
  866. self,
  867. start: Optional[Union[str, Revision]],
  868. steps: int,
  869. branch_label: Optional[str] = None,
  870. no_overwalk: bool = True,
  871. ) -> Optional[_RevisionOrBase]:
  872. """
  873. Walk the requested number of :steps up (steps > 0) or down (steps < 0)
  874. the revision tree.
  875. :branch_label is used to select branches only when walking up.
  876. If the walk goes past the boundaries of the tree and :no_overwalk is
  877. True, None is returned, otherwise the walk terminates early.
  878. A RevisionError is raised if there is no unambiguous revision to
  879. walk to.
  880. """
  881. initial: Optional[_RevisionOrBase]
  882. if isinstance(start, str):
  883. initial = self.get_revision(start)
  884. else:
  885. initial = start
  886. children: Sequence[Optional[_RevisionOrBase]]
  887. for _ in range(abs(steps)):
  888. if steps > 0:
  889. assert initial != "base" # type: ignore[comparison-overlap]
  890. # Walk up
  891. walk_up = [
  892. is_revision(rev)
  893. for rev in self.get_revisions(
  894. self.bases if initial is None else initial.nextrev
  895. )
  896. ]
  897. if branch_label:
  898. children = self.filter_for_lineage(walk_up, branch_label)
  899. else:
  900. children = walk_up
  901. else:
  902. # Walk down
  903. if initial == "base": # type: ignore[comparison-overlap]
  904. children = ()
  905. else:
  906. children = self.get_revisions(
  907. self.heads
  908. if initial is None
  909. else initial.down_revision
  910. )
  911. if not children:
  912. children = ("base",)
  913. if not children:
  914. # This will return an invalid result if no_overwalk, otherwise
  915. # further steps will stay where we are.
  916. ret = None if no_overwalk else initial
  917. return ret
  918. elif len(children) > 1:
  919. raise RevisionError("Ambiguous walk")
  920. initial = children[0]
  921. return initial
  922. def _parse_downgrade_target(
  923. self,
  924. current_revisions: _RevisionIdentifierType,
  925. target: _RevisionIdentifierType,
  926. assert_relative_length: bool,
  927. ) -> Tuple[Optional[str], Optional[_RevisionOrBase]]:
  928. """
  929. Parse downgrade command syntax :target to retrieve the target revision
  930. and branch label (if any) given the :current_revisions stamp of the
  931. database.
  932. Returns a tuple (branch_label, target_revision) where branch_label
  933. is a string from the command specifying the branch to consider (or
  934. None if no branch given), and target_revision is a Revision object
  935. which the command refers to. target_revisions is None if the command
  936. refers to 'base'. The target may be specified in absolute form, or
  937. relative to :current_revisions.
  938. """
  939. if target is None:
  940. return None, None
  941. assert isinstance(
  942. target, str
  943. ), "Expected downgrade target in string form"
  944. match = _relative_destination.match(target)
  945. if match:
  946. branch_label, symbol, relative = match.groups()
  947. rel_int = int(relative)
  948. if rel_int >= 0:
  949. if symbol is None:
  950. # Downgrading to current + n is not valid.
  951. raise RevisionError(
  952. "Relative revision %s didn't "
  953. "produce %d migrations" % (relative, abs(rel_int))
  954. )
  955. # Find target revision relative to given symbol.
  956. rev = self._walk(
  957. symbol,
  958. rel_int,
  959. branch_label,
  960. no_overwalk=assert_relative_length,
  961. )
  962. if rev is None:
  963. raise RevisionError("Walked too far")
  964. return branch_label, rev
  965. else:
  966. relative_revision = symbol is None
  967. if relative_revision:
  968. # Find target revision relative to current state.
  969. if branch_label:
  970. cr_tuple = util.to_tuple(current_revisions)
  971. symbol_list: Sequence[str]
  972. symbol_list = self.filter_for_lineage(
  973. cr_tuple, branch_label
  974. )
  975. if not symbol_list:
  976. # check the case where there are multiple branches
  977. # but there is currently a single heads, since all
  978. # other branch heads are dependent of the current
  979. # single heads.
  980. all_current = cast(
  981. Set[Revision], self._get_all_current(cr_tuple)
  982. )
  983. sl_all_current = self.filter_for_lineage(
  984. all_current, branch_label
  985. )
  986. symbol_list = [
  987. r.revision if r else r # type: ignore[misc]
  988. for r in sl_all_current
  989. ]
  990. assert len(symbol_list) == 1
  991. symbol = symbol_list[0]
  992. else:
  993. current_revisions = util.to_tuple(current_revisions)
  994. if not current_revisions:
  995. raise RevisionError(
  996. "Relative revision %s didn't "
  997. "produce %d migrations"
  998. % (relative, abs(rel_int))
  999. )
  1000. # Have to check uniques here for duplicate rows test.
  1001. if len(set(current_revisions)) > 1:
  1002. util.warn(
  1003. "downgrade -1 from multiple heads is "
  1004. "ambiguous; "
  1005. "this usage will be disallowed in a future "
  1006. "release."
  1007. )
  1008. symbol = current_revisions[0]
  1009. # Restrict iteration to just the selected branch when
  1010. # ambiguous branches are involved.
  1011. branch_label = symbol
  1012. # Walk down the tree to find downgrade target.
  1013. rev = self._walk(
  1014. start=(
  1015. self.get_revision(symbol)
  1016. if branch_label is None
  1017. else self.get_revision(
  1018. "%s@%s" % (branch_label, symbol)
  1019. )
  1020. ),
  1021. steps=rel_int,
  1022. no_overwalk=assert_relative_length,
  1023. )
  1024. if rev is None:
  1025. if relative_revision:
  1026. raise RevisionError(
  1027. "Relative revision %s didn't "
  1028. "produce %d migrations" % (relative, abs(rel_int))
  1029. )
  1030. else:
  1031. raise RevisionError("Walked too far")
  1032. return branch_label, rev
  1033. # No relative destination given, revision specified is absolute.
  1034. branch_label, _, symbol = target.rpartition("@")
  1035. if not branch_label:
  1036. branch_label = None
  1037. return branch_label, self.get_revision(symbol)
  1038. def _parse_upgrade_target(
  1039. self,
  1040. current_revisions: _RevisionIdentifierType,
  1041. target: _RevisionIdentifierType,
  1042. assert_relative_length: bool,
  1043. ) -> Tuple[Optional[_RevisionOrBase], ...]:
  1044. """
  1045. Parse upgrade command syntax :target to retrieve the target revision
  1046. and given the :current_revisions stamp of the database.
  1047. Returns a tuple of Revision objects which should be iterated/upgraded
  1048. to. The target may be specified in absolute form, or relative to
  1049. :current_revisions.
  1050. """
  1051. if isinstance(target, str):
  1052. match = _relative_destination.match(target)
  1053. else:
  1054. match = None
  1055. if not match:
  1056. # No relative destination, target is absolute.
  1057. return self.get_revisions(target)
  1058. current_revisions_tup: Union[str, Tuple[Optional[str], ...], None]
  1059. current_revisions_tup = util.to_tuple(current_revisions)
  1060. branch_label, symbol, relative_str = match.groups()
  1061. relative = int(relative_str)
  1062. if relative > 0:
  1063. if symbol is None:
  1064. if not current_revisions_tup:
  1065. current_revisions_tup = (None,)
  1066. # Try to filter to a single target (avoid ambiguous branches).
  1067. start_revs = current_revisions_tup
  1068. if branch_label:
  1069. start_revs = self.filter_for_lineage(
  1070. self.get_revisions(current_revisions_tup), # type: ignore[arg-type] # noqa: E501
  1071. branch_label,
  1072. )
  1073. if not start_revs:
  1074. # The requested branch is not a head, so we need to
  1075. # backtrack to find a branchpoint.
  1076. active_on_branch = self.filter_for_lineage(
  1077. self._get_ancestor_nodes(
  1078. self.get_revisions(current_revisions_tup)
  1079. ),
  1080. branch_label,
  1081. )
  1082. # Find the tips of this set of revisions (revisions
  1083. # without children within the set).
  1084. start_revs = tuple(
  1085. {rev.revision for rev in active_on_branch}
  1086. - {
  1087. down
  1088. for rev in active_on_branch
  1089. for down in rev._normalized_down_revisions
  1090. }
  1091. )
  1092. if not start_revs:
  1093. # We must need to go right back to base to find
  1094. # a starting point for this branch.
  1095. start_revs = (None,)
  1096. if len(start_revs) > 1:
  1097. raise RevisionError(
  1098. "Ambiguous upgrade from multiple current revisions"
  1099. )
  1100. # Walk up from unique target revision.
  1101. rev = self._walk(
  1102. start=start_revs[0],
  1103. steps=relative,
  1104. branch_label=branch_label,
  1105. no_overwalk=assert_relative_length,
  1106. )
  1107. if rev is None:
  1108. raise RevisionError(
  1109. "Relative revision %s didn't "
  1110. "produce %d migrations" % (relative_str, abs(relative))
  1111. )
  1112. return (rev,)
  1113. else:
  1114. # Walk is relative to a given revision, not the current state.
  1115. return (
  1116. self._walk(
  1117. start=self.get_revision(symbol),
  1118. steps=relative,
  1119. branch_label=branch_label,
  1120. no_overwalk=assert_relative_length,
  1121. ),
  1122. )
  1123. else:
  1124. if symbol is None:
  1125. # Upgrading to current - n is not valid.
  1126. raise RevisionError(
  1127. "Relative revision %s didn't "
  1128. "produce %d migrations" % (relative, abs(relative))
  1129. )
  1130. return (
  1131. self._walk(
  1132. start=(
  1133. self.get_revision(symbol)
  1134. if branch_label is None
  1135. else self.get_revision(
  1136. "%s@%s" % (branch_label, symbol)
  1137. )
  1138. ),
  1139. steps=relative,
  1140. no_overwalk=assert_relative_length,
  1141. ),
  1142. )
  1143. def _collect_downgrade_revisions(
  1144. self,
  1145. upper: _RevisionIdentifierType,
  1146. lower: _RevisionIdentifierType,
  1147. inclusive: bool,
  1148. implicit_base: bool,
  1149. assert_relative_length: bool,
  1150. ) -> Tuple[Set[Revision], Tuple[Optional[_RevisionOrBase], ...]]:
  1151. """
  1152. Compute the set of current revisions specified by :upper, and the
  1153. downgrade target specified by :target. Return all dependents of target
  1154. which are currently active.
  1155. :inclusive=True includes the target revision in the set
  1156. """
  1157. branch_label, target_revision = self._parse_downgrade_target(
  1158. current_revisions=upper,
  1159. target=lower,
  1160. assert_relative_length=assert_relative_length,
  1161. )
  1162. if target_revision == "base":
  1163. target_revision = None
  1164. assert target_revision is None or isinstance(target_revision, Revision)
  1165. roots: List[Revision]
  1166. # Find candidates to drop.
  1167. if target_revision is None:
  1168. # Downgrading back to base: find all tree roots.
  1169. roots = [
  1170. rev
  1171. for rev in self._revision_map.values()
  1172. if rev is not None and rev.down_revision is None
  1173. ]
  1174. elif inclusive:
  1175. # inclusive implies target revision should also be dropped
  1176. roots = [target_revision]
  1177. else:
  1178. # Downgrading to fixed target: find all direct children.
  1179. roots = [
  1180. is_revision(rev)
  1181. for rev in self.get_revisions(target_revision.nextrev)
  1182. ]
  1183. if branch_label and len(roots) > 1:
  1184. # Need to filter roots.
  1185. ancestors = {
  1186. rev.revision
  1187. for rev in self._get_ancestor_nodes(
  1188. [self._resolve_branch(branch_label)],
  1189. include_dependencies=False,
  1190. )
  1191. }
  1192. # Intersection gives the root revisions we are trying to
  1193. # rollback with the downgrade.
  1194. roots = [
  1195. is_revision(rev)
  1196. for rev in self.get_revisions(
  1197. {rev.revision for rev in roots}.intersection(ancestors)
  1198. )
  1199. ]
  1200. # Ensure we didn't throw everything away when filtering branches.
  1201. if len(roots) == 0:
  1202. raise RevisionError(
  1203. "Not a valid downgrade target from current heads"
  1204. )
  1205. heads = self.get_revisions(upper)
  1206. # Aim is to drop :branch_revision; to do so we also need to drop its
  1207. # descendents and anything dependent on it.
  1208. downgrade_revisions = set(
  1209. self._get_descendant_nodes(
  1210. roots,
  1211. include_dependencies=True,
  1212. omit_immediate_dependencies=False,
  1213. )
  1214. )
  1215. active_revisions = set(
  1216. self._get_ancestor_nodes(heads, include_dependencies=True)
  1217. )
  1218. # Emit revisions to drop in reverse topological sorted order.
  1219. downgrade_revisions.intersection_update(active_revisions)
  1220. if implicit_base:
  1221. # Wind other branches back to base.
  1222. downgrade_revisions.update(
  1223. active_revisions.difference(self._get_ancestor_nodes(roots))
  1224. )
  1225. if (
  1226. target_revision is not None
  1227. and not downgrade_revisions
  1228. and target_revision not in heads
  1229. ):
  1230. # Empty intersection: target revs are not present.
  1231. raise RangeNotAncestorError("Nothing to drop", upper)
  1232. return downgrade_revisions, heads
  1233. def _collect_upgrade_revisions(
  1234. self,
  1235. upper: _RevisionIdentifierType,
  1236. lower: _RevisionIdentifierType,
  1237. inclusive: bool,
  1238. implicit_base: bool,
  1239. assert_relative_length: bool,
  1240. ) -> Tuple[Set[Revision], Tuple[Revision, ...]]:
  1241. """
  1242. Compute the set of required revisions specified by :upper, and the
  1243. current set of active revisions specified by :lower. Find the
  1244. difference between the two to compute the required upgrades.
  1245. :inclusive=True includes the current/lower revisions in the set
  1246. :implicit_base=False only returns revisions which are downstream
  1247. of the current/lower revisions. Dependencies from branches with
  1248. different bases will not be included.
  1249. """
  1250. targets: Collection[Revision] = [
  1251. is_revision(rev)
  1252. for rev in self._parse_upgrade_target(
  1253. current_revisions=lower,
  1254. target=upper,
  1255. assert_relative_length=assert_relative_length,
  1256. )
  1257. ]
  1258. # assert type(targets) is tuple, "targets should be a tuple"
  1259. # Handled named bases (e.g. branch@... -> heads should only produce
  1260. # targets on the given branch)
  1261. if isinstance(lower, str) and "@" in lower:
  1262. branch, _, _ = lower.partition("@")
  1263. branch_rev = self.get_revision(branch)
  1264. if branch_rev is not None and branch_rev.revision == branch:
  1265. # A revision was used as a label; get its branch instead
  1266. assert len(branch_rev.branch_labels) == 1
  1267. branch = next(iter(branch_rev.branch_labels))
  1268. targets = {
  1269. need for need in targets if branch in need.branch_labels
  1270. }
  1271. required_node_set = set(
  1272. self._get_ancestor_nodes(
  1273. targets, check=True, include_dependencies=True
  1274. )
  1275. ).union(targets)
  1276. current_revisions = self.get_revisions(lower)
  1277. if not implicit_base and any(
  1278. rev not in required_node_set
  1279. for rev in current_revisions
  1280. if rev is not None
  1281. ):
  1282. raise RangeNotAncestorError(lower, upper)
  1283. assert (
  1284. type(current_revisions) is tuple
  1285. ), "current_revisions should be a tuple"
  1286. # Special case where lower = a relative value (get_revisions can't
  1287. # find it)
  1288. if current_revisions and current_revisions[0] is None:
  1289. _, rev = self._parse_downgrade_target(
  1290. current_revisions=upper,
  1291. target=lower,
  1292. assert_relative_length=assert_relative_length,
  1293. )
  1294. assert rev
  1295. if rev == "base":
  1296. current_revisions = tuple()
  1297. lower = None
  1298. else:
  1299. current_revisions = (rev,)
  1300. lower = rev.revision
  1301. current_node_set = set(
  1302. self._get_ancestor_nodes(
  1303. current_revisions, check=True, include_dependencies=True
  1304. )
  1305. ).union(current_revisions)
  1306. needs = required_node_set.difference(current_node_set)
  1307. # Include the lower revision (=current_revisions?) in the iteration
  1308. if inclusive:
  1309. needs.update(is_revision(rev) for rev in self.get_revisions(lower))
  1310. # By default, base is implicit as we want all dependencies returned.
  1311. # Base is also implicit if lower = base
  1312. # implicit_base=False -> only return direct downstreams of
  1313. # current_revisions
  1314. if current_revisions and not implicit_base:
  1315. lower_descendents = self._get_descendant_nodes(
  1316. [is_revision(rev) for rev in current_revisions],
  1317. check=True,
  1318. include_dependencies=False,
  1319. )
  1320. needs.intersection_update(lower_descendents)
  1321. return needs, tuple(targets)
  1322. def _get_all_current(
  1323. self, id_: Tuple[str, ...]
  1324. ) -> Set[Optional[_RevisionOrBase]]:
  1325. top_revs: Set[Optional[_RevisionOrBase]]
  1326. top_revs = set(self.get_revisions(id_))
  1327. top_revs.update(
  1328. self._get_ancestor_nodes(list(top_revs), include_dependencies=True)
  1329. )
  1330. return self._filter_into_branch_heads(top_revs)
  1331. class Revision:
  1332. """Base class for revisioned objects.
  1333. The :class:`.Revision` class is the base of the more public-facing
  1334. :class:`.Script` object, which represents a migration script.
  1335. The mechanics of revision management and traversal are encapsulated
  1336. within :class:`.Revision`, while :class:`.Script` applies this logic
  1337. to Python files in a version directory.
  1338. """
  1339. nextrev: FrozenSet[str] = frozenset()
  1340. """following revisions, based on down_revision only."""
  1341. _all_nextrev: FrozenSet[str] = frozenset()
  1342. revision: str = None # type: ignore[assignment]
  1343. """The string revision number."""
  1344. down_revision: Optional[_RevIdType] = None
  1345. """The ``down_revision`` identifier(s) within the migration script.
  1346. Note that the total set of "down" revisions is
  1347. down_revision + dependencies.
  1348. """
  1349. dependencies: Optional[_RevIdType] = None
  1350. """Additional revisions which this revision is dependent on.
  1351. From a migration standpoint, these dependencies are added to the
  1352. down_revision to form the full iteration. However, the separation
  1353. of down_revision from "dependencies" is to assist in navigating
  1354. a history that contains many branches, typically a multi-root scenario.
  1355. """
  1356. branch_labels: Set[str] = None # type: ignore[assignment]
  1357. """Optional string/tuple of symbolic names to apply to this
  1358. revision's branch"""
  1359. _resolved_dependencies: Tuple[str, ...]
  1360. _normalized_resolved_dependencies: Tuple[str, ...]
  1361. @classmethod
  1362. def verify_rev_id(cls, revision: str) -> None:
  1363. illegal_chars = set(revision).intersection(_revision_illegal_chars)
  1364. if illegal_chars:
  1365. raise RevisionError(
  1366. "Character(s) '%s' not allowed in revision identifier '%s'"
  1367. % (", ".join(sorted(illegal_chars)), revision)
  1368. )
  1369. def __init__(
  1370. self,
  1371. revision: str,
  1372. down_revision: Optional[Union[str, Tuple[str, ...]]],
  1373. dependencies: Optional[Union[str, Tuple[str, ...]]] = None,
  1374. branch_labels: Optional[Union[str, Tuple[str, ...]]] = None,
  1375. ) -> None:
  1376. if down_revision and revision in util.to_tuple(down_revision):
  1377. raise LoopDetected(revision)
  1378. elif dependencies is not None and revision in util.to_tuple(
  1379. dependencies
  1380. ):
  1381. raise DependencyLoopDetected(revision)
  1382. self.verify_rev_id(revision)
  1383. self.revision = revision
  1384. self.down_revision = tuple_rev_as_scalar(util.to_tuple(down_revision))
  1385. self.dependencies = tuple_rev_as_scalar(util.to_tuple(dependencies))
  1386. self._orig_branch_labels = util.to_tuple(branch_labels, default=())
  1387. self.branch_labels = set(self._orig_branch_labels)
  1388. def __repr__(self) -> str:
  1389. args = [repr(self.revision), repr(self.down_revision)]
  1390. if self.dependencies:
  1391. args.append("dependencies=%r" % (self.dependencies,))
  1392. if self.branch_labels:
  1393. args.append("branch_labels=%r" % (self.branch_labels,))
  1394. return "%s(%s)" % (self.__class__.__name__, ", ".join(args))
  1395. def add_nextrev(self, revision: Revision) -> None:
  1396. self._all_nextrev = self._all_nextrev.union([revision.revision])
  1397. if self.revision in revision._versioned_down_revisions:
  1398. self.nextrev = self.nextrev.union([revision.revision])
  1399. @property
  1400. def _all_down_revisions(self) -> Tuple[str, ...]:
  1401. return util.dedupe_tuple(
  1402. util.to_tuple(self.down_revision, default=())
  1403. + self._resolved_dependencies
  1404. )
  1405. @property
  1406. def _normalized_down_revisions(self) -> Tuple[str, ...]:
  1407. """return immediate down revisions for a rev, omitting dependencies
  1408. that are still dependencies of ancestors.
  1409. """
  1410. return util.dedupe_tuple(
  1411. util.to_tuple(self.down_revision, default=())
  1412. + self._normalized_resolved_dependencies
  1413. )
  1414. @property
  1415. def _versioned_down_revisions(self) -> Tuple[str, ...]:
  1416. return util.to_tuple(self.down_revision, default=())
  1417. @property
  1418. def is_head(self) -> bool:
  1419. """Return True if this :class:`.Revision` is a 'head' revision.
  1420. This is determined based on whether any other :class:`.Script`
  1421. within the :class:`.ScriptDirectory` refers to this
  1422. :class:`.Script`. Multiple heads can be present.
  1423. """
  1424. return not bool(self.nextrev)
  1425. @property
  1426. def _is_real_head(self) -> bool:
  1427. return not bool(self._all_nextrev)
  1428. @property
  1429. def is_base(self) -> bool:
  1430. """Return True if this :class:`.Revision` is a 'base' revision."""
  1431. return self.down_revision is None
  1432. @property
  1433. def _is_real_base(self) -> bool:
  1434. """Return True if this :class:`.Revision` is a "real" base revision,
  1435. e.g. that it has no dependencies either."""
  1436. # we use self.dependencies here because this is called up
  1437. # in initialization where _real_dependencies isn't set up
  1438. # yet
  1439. return self.down_revision is None and self.dependencies is None
  1440. @property
  1441. def is_branch_point(self) -> bool:
  1442. """Return True if this :class:`.Script` is a branch point.
  1443. A branchpoint is defined as a :class:`.Script` which is referred
  1444. to by more than one succeeding :class:`.Script`, that is more
  1445. than one :class:`.Script` has a `down_revision` identifier pointing
  1446. here.
  1447. """
  1448. return len(self.nextrev) > 1
  1449. @property
  1450. def _is_real_branch_point(self) -> bool:
  1451. """Return True if this :class:`.Script` is a 'real' branch point,
  1452. taking into account dependencies as well.
  1453. """
  1454. return len(self._all_nextrev) > 1
  1455. @property
  1456. def is_merge_point(self) -> bool:
  1457. """Return True if this :class:`.Script` is a merge point."""
  1458. return len(self._versioned_down_revisions) > 1
  1459. @overload
  1460. def tuple_rev_as_scalar(rev: None) -> None: ...
  1461. @overload
  1462. def tuple_rev_as_scalar(
  1463. rev: Union[Tuple[_T, ...], List[_T]],
  1464. ) -> Union[_T, Tuple[_T, ...], List[_T]]: ...
  1465. def tuple_rev_as_scalar(
  1466. rev: Optional[Sequence[_T]],
  1467. ) -> Union[_T, Sequence[_T], None]:
  1468. if not rev:
  1469. return None
  1470. elif len(rev) == 1:
  1471. return rev[0]
  1472. else:
  1473. return rev
  1474. def is_revision(rev: Any) -> Revision:
  1475. assert isinstance(rev, Revision)
  1476. return rev