Changeset View
Changeset View
Standalone View
Standalone View
swh/loader/svn/svn.py
# Copyright (C) 2015-2021 The Software Heritage developers | # Copyright (C) 2015-2022 The Software Heritage developers | |||||||||||||||||||||||||
# See the AUTHORS file at the top-level directory of this distribution | # See the AUTHORS file at the top-level directory of this distribution | |||||||||||||||||||||||||
# License: GNU General Public License version 3, or any later version | # License: GNU General Public License version 3, or any later version | |||||||||||||||||||||||||
# See top-level LICENSE file for more information | # See top-level LICENSE file for more information | |||||||||||||||||||||||||
"""SVN client in charge of iterating over svn logs and yield commit | """SVN client in charge of iterating over svn logs and yield commit | |||||||||||||||||||||||||
representations including the hash tree/content computations per svn | representations including the hash tree/content computations per svn | |||||||||||||||||||||||||
commit. | commit. | |||||||||||||||||||||||||
Show All 13 Lines | from swh.model.model import ( | |||||||||||||||||||||||||
Content, | Content, | |||||||||||||||||||||||||
Directory, | Directory, | |||||||||||||||||||||||||
Person, | Person, | |||||||||||||||||||||||||
SkippedContent, | SkippedContent, | |||||||||||||||||||||||||
TimestampWithTimezone, | TimestampWithTimezone, | |||||||||||||||||||||||||
) | ) | |||||||||||||||||||||||||
from . import converters, ra | from . import converters, ra | |||||||||||||||||||||||||
from .utils import parse_external_definition | ||||||||||||||||||||||||||
# When log message contains empty data | # When log message contains empty data | |||||||||||||||||||||||||
DEFAULT_AUTHOR_MESSAGE = "" | DEFAULT_AUTHOR_MESSAGE = "" | |||||||||||||||||||||||||
logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | |||||||||||||||||||||||||
Show All 29 Lines | ): | |||||||||||||||||||||||||
local_name = os.path.basename(self.remote_url) | local_name = os.path.basename(self.remote_url) | |||||||||||||||||||||||||
self.local_url = os.path.join(self.local_dirname, local_name).encode("utf-8") | self.local_url = os.path.join(self.local_dirname, local_name).encode("utf-8") | |||||||||||||||||||||||||
self.uuid = self.conn.get_uuid().encode("utf-8") | self.uuid = self.conn.get_uuid().encode("utf-8") | |||||||||||||||||||||||||
self.swhreplay = ra.Replay( | self.swhreplay = ra.Replay( | |||||||||||||||||||||||||
conn=self.conn, rootpath=self.local_url, svnrepo=self | conn=self.conn, rootpath=self.local_url, svnrepo=self | |||||||||||||||||||||||||
) | ) | |||||||||||||||||||||||||
self.max_content_length = max_content_length | self.max_content_length = max_content_length | |||||||||||||||||||||||||
self.has_relative_externals = False | ||||||||||||||||||||||||||
self.replay_started = False | ||||||||||||||||||||||||||
def __str__(self): | def __str__(self): | |||||||||||||||||||||||||
return str( | return str( | |||||||||||||||||||||||||
{ | { | |||||||||||||||||||||||||
"swh-origin": self.origin_url, | "swh-origin": self.origin_url, | |||||||||||||||||||||||||
"remote_url": self.remote_url, | "remote_url": self.remote_url, | |||||||||||||||||||||||||
"local_url": self.local_url, | "local_url": self.local_url, | |||||||||||||||||||||||||
"uuid": self.uuid, | "uuid": self.uuid, | |||||||||||||||||||||||||
▲ Show 20 Lines • Show All 115 Lines • ▼ Show 20 Lines | def export_temporary(self, revision: int) -> Tuple[str, bytes]: | |||||||||||||||||||||||||
Returns: | Returns: | |||||||||||||||||||||||||
The tuple local_dirname the temporary location root | The tuple local_dirname the temporary location root | |||||||||||||||||||||||||
folder, local_url where the repository was exported. | folder, local_url where the repository was exported. | |||||||||||||||||||||||||
""" | """ | |||||||||||||||||||||||||
local_dirname = tempfile.mkdtemp( | local_dirname = tempfile.mkdtemp( | |||||||||||||||||||||||||
dir=self.local_dirname, prefix=f"check-revision-{revision}." | dir=self.local_dirname, prefix=f"check-revision-{revision}." | |||||||||||||||||||||||||
) | ) | |||||||||||||||||||||||||
local_name = os.path.basename(self.remote_url) | local_name = os.path.basename(self.remote_url) | |||||||||||||||||||||||||
local_url = os.path.join(local_dirname, local_name) | local_url = os.path.join(local_dirname, local_name) | |||||||||||||||||||||||||
url = self.remote_url | ||||||||||||||||||||||||||
# if some paths have external URLs relative to the repository URL but targeting | ||||||||||||||||||||||||||
# paths oustide it, we need to export from the origin URL as the remote URL can | ||||||||||||||||||||||||||
# target a dump mounted on the local filesystem | ||||||||||||||||||||||||||
if self.replay_started and self.has_relative_externals: | ||||||||||||||||||||||||||
# externals detected while replaying revisions | ||||||||||||||||||||||||||
url = self.origin_url | ||||||||||||||||||||||||||
elif not self.replay_started and self.remote_url.startswith("file://"): | ||||||||||||||||||||||||||
# revisions replay has not started, we need to check if svn:externals | ||||||||||||||||||||||||||
# properties are set from a checkout of the revision and if some | ||||||||||||||||||||||||||
# external URLs are relative to pick the right export URL | ||||||||||||||||||||||||||
with tempfile.TemporaryDirectory( | ||||||||||||||||||||||||||
dir=self.local_dirname, prefix=f"checkout-revision-{revision}." | ||||||||||||||||||||||||||
) as co_dirname: | ||||||||||||||||||||||||||
self.client.checkout( | ||||||||||||||||||||||||||
self.remote_url, co_dirname, revision, ignore_externals=True | ||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
# get all svn:externals properties recursively | ||||||||||||||||||||||||||
externals = self.client.propget( | ||||||||||||||||||||||||||
vlorentzUnsubmitted Not Done Inline Actions
vlorentz: | ||||||||||||||||||||||||||
"svn:externals", co_dirname, None, revision, True | ||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
self.has_relative_externals = False | ||||||||||||||||||||||||||
for path, external_defs in externals.items(): | ||||||||||||||||||||||||||
if self.has_relative_externals: | ||||||||||||||||||||||||||
break | ||||||||||||||||||||||||||
for external_def in os.fsdecode(external_defs).split("\n"): | ||||||||||||||||||||||||||
# skip empty line or comment | ||||||||||||||||||||||||||
if not external_def or external_def.startswith("#"): | ||||||||||||||||||||||||||
continue | ||||||||||||||||||||||||||
_, _, _, relative_url = parse_external_definition( | ||||||||||||||||||||||||||
external_def.rstrip("\r"), path, self.origin_url | ||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
if relative_url: | ||||||||||||||||||||||||||
self.has_relative_externals = True | ||||||||||||||||||||||||||
url = self.origin_url | ||||||||||||||||||||||||||
break | ||||||||||||||||||||||||||
self.client.export( | self.client.export( | |||||||||||||||||||||||||
self.remote_url, to=local_url, rev=revision, ignore_keywords=True | url.rstrip("/"), to=local_url, rev=revision, ignore_keywords=True, | |||||||||||||||||||||||||
) | ) | |||||||||||||||||||||||||
return local_dirname, os.fsencode(local_url) | return local_dirname, os.fsencode(local_url) | |||||||||||||||||||||||||
def swh_hash_data_per_revision( | def swh_hash_data_per_revision( | |||||||||||||||||||||||||
self, start_revision: int, end_revision: int | self, start_revision: int, end_revision: int | |||||||||||||||||||||||||
) -> Iterator[ | ) -> Iterator[ | |||||||||||||||||||||||||
Tuple[ | Tuple[ | |||||||||||||||||||||||||
int, | int, | |||||||||||||||||||||||||
Show All 20 Lines | ]: | |||||||||||||||||||||||||
- objects_per_path: Tuple of list of objects between start_revision and | - objects_per_path: Tuple of list of objects between start_revision and | |||||||||||||||||||||||||
end_revision | end_revision | |||||||||||||||||||||||||
- complete Directory representation | - complete Directory representation | |||||||||||||||||||||||||
""" | """ | |||||||||||||||||||||||||
# even in incremental loading mode, we need to replay the whole set of | # even in incremental loading mode, we need to replay the whole set of | |||||||||||||||||||||||||
# path modifications from first revision to restore possible file states induced | # path modifications from first revision to restore possible file states induced | |||||||||||||||||||||||||
# by setting svn properties on those files (end of line style for instance) | # by setting svn properties on those files (end of line style for instance) | |||||||||||||||||||||||||
self.replay_started = True | ||||||||||||||||||||||||||
first_revision = 1 if start_revision else 0 # handle empty repository edge case | first_revision = 1 if start_revision else 0 # handle empty repository edge case | |||||||||||||||||||||||||
for commit in self.logs(first_revision, end_revision): | for commit in self.logs(first_revision, end_revision): | |||||||||||||||||||||||||
rev = commit["rev"] | rev = commit["rev"] | |||||||||||||||||||||||||
objects = self.swhreplay.compute_objects(rev) | objects = self.swhreplay.compute_objects(rev) | |||||||||||||||||||||||||
if rev == end_revision: | if rev == end_revision: | |||||||||||||||||||||||||
nextrev = None | nextrev = None | |||||||||||||||||||||||||
else: | else: | |||||||||||||||||||||||||
▲ Show 20 Lines • Show All 44 Lines • Show Last 20 Lines |