| Server IP : 74.208.236.52 / Your IP : 216.73.217.98 Web Server : Apache System : Linux infong527 4.4.400-icpu-115 #2 SMP Mon Jul 6 08:15:05 UTC 2026 x86_64 User : u44043973 ( 5404757) PHP Version : 8.4.24 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /proc/thread-self/root/lib/python3/dist-packages/breezy/ |
Upload File : |
# Copyright (C) 2005-2011 Canonical Ltd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
# TODO: Some kind of command-line display of revision properties:
# perhaps show them in log -v and allow them as options to the commit command.
__docformat__ = "google"
from typing import Optional
from . import errors, osutils
RevisionID = bytes
NULL_REVISION = b"null:"
CURRENT_REVISION = b"current:"
class Revision:
"""Single revision on a branch.
Revisions may know their revision_hash, but only once they've been
written out. This is not stored because you cannot write the hash
into the file it describes.
Attributes:
parent_ids: List of parent revision_ids
properties:
Dictionary of revision properties. These are attached to the
revision as extra metadata. The name must be a single
word; the value can be an arbitrary string.
"""
parent_ids: list[RevisionID]
revision_id: RevisionID
parent_sha1s: list[str]
committer: Optional[str]
message: str
properties: dict[str, bytes]
inventory_sha1: str
timestamp: float
timezone: int
def __init__(self, revision_id: RevisionID, properties=None, **args) -> None:
self.revision_id = revision_id
if properties is None:
self.properties = {}
else:
self.properties = properties
self._check_properties()
self.committer = None
self.parent_ids = []
self.parent_sha1s = []
# Not used anymore - legacy from for 4.
self.__dict__.update(args)
def __repr__(self):
return "<Revision id {}>".format(self.revision_id)
def datetime(self):
import datetime
# TODO: Handle timezone.
return datetime.datetime.fromtimestamp(self.timestamp)
def __eq__(self, other):
if not isinstance(other, Revision):
return False
return (
self.inventory_sha1 == other.inventory_sha1
and self.revision_id == other.revision_id
and self.timestamp == other.timestamp
and self.message == other.message
and self.timezone == other.timezone
and self.committer == other.committer
and self.properties == other.properties
and self.parent_ids == other.parent_ids
)
def __ne__(self, other):
return not self.__eq__(other)
def _check_properties(self):
"""Verify that all revision properties are OK."""
for name, value in self.properties.items():
# GZ 2017-06-10: What sort of string are properties exactly?
not_text = not isinstance(name, str)
if not_text or osutils.contains_whitespace(name):
raise ValueError("invalid property name {!r}".format(name))
if not isinstance(value, (str, bytes)):
raise ValueError(
"invalid property value {!r} for {!r}".format(value, name)
)
def get_history(self, repository):
"""Return the canonical line-of-history for this revision.
If ghosts are present this may differ in result from a ghost-free
repository.
"""
current_revision = self
reversed_result = []
while current_revision is not None:
reversed_result.append(current_revision.revision_id)
if not len(current_revision.parent_ids):
reversed_result.append(None)
current_revision = None
else:
next_revision_id = current_revision.parent_ids[0]
current_revision = repository.get_revision(next_revision_id)
reversed_result.reverse()
return reversed_result
def get_summary(self):
"""Get the first line of the log message for this revision.
Return an empty string if message is None.
"""
if self.message:
return self.message.lstrip().split("\n", 1)[0]
else:
return ""
def get_apparent_authors(self):
"""Return the apparent authors of this revision.
If the revision properties contain the names of the authors,
return them. Otherwise return the committer name.
The return value will be a list containing at least one element.
"""
authors = self.properties.get("authors", None)
if authors is None:
author = self.properties.get("author", self.committer)
if author is None:
return []
return [author]
else:
return authors.split("\n")
def iter_bugs(self):
"""Iterate over the bugs associated with this revision."""
bug_property = self.properties.get("bugs", None)
if bug_property is None:
return iter([])
from . import bugtracker
return bugtracker.decode_bug_urls(bug_property)
def iter_ancestors(
revision_id: RevisionID, revision_source, only_present: bool = False
):
ancestors = [revision_id]
distance = 0
while len(ancestors) > 0:
new_ancestors: list[bytes] = []
for ancestor in ancestors:
if not only_present:
yield ancestor, distance
try:
revision = revision_source.get_revision(ancestor)
except errors.NoSuchRevision as e:
if e.revision == revision_id:
raise
else:
continue
if only_present:
yield ancestor, distance
new_ancestors.extend(revision.parent_ids)
ancestors = new_ancestors
distance += 1
def find_present_ancestors(
revision_id: RevisionID, revision_source
) -> dict[RevisionID, tuple[int, int]]:
"""Return the ancestors of a revision present in a branch.
It's possible that a branch won't have the complete ancestry of
one of its revisions.
"""
found_ancestors: dict[RevisionID, tuple[int, int]] = {}
anc_iter = enumerate(
iter_ancestors(revision_id, revision_source, only_present=True)
)
for anc_order, (anc_id, anc_distance) in anc_iter:
if anc_id not in found_ancestors:
found_ancestors[anc_id] = (anc_order, anc_distance)
return found_ancestors
def __get_closest(intersection):
intersection.sort()
matches = []
for entry in intersection:
if entry[0] == intersection[0][0]:
matches.append(entry[2])
return matches
def is_reserved_id(revision_id: RevisionID) -> bool:
"""Determine whether a revision id is reserved.
Returns:
True if the revision is reserved, False otherwise
"""
return isinstance(revision_id, bytes) and revision_id.endswith(b":")
def check_not_reserved_id(revision_id: RevisionID) -> None:
"""Raise ReservedId if the supplied revision_id is reserved."""
if is_reserved_id(revision_id):
raise errors.ReservedId(revision_id)
def is_null(revision_id: RevisionID) -> bool:
if revision_id is None:
raise ValueError(
"NULL_REVISION should be used for the null revision instead of None."
)
return revision_id == NULL_REVISION