Skip to content
Merged
2 changes: 1 addition & 1 deletion CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Release 0.12.0 (unreleased)
====================================

* Under development...
* Internal refactoring: introduce superproject & subproject (#896)

Release 0.11.0 (released 2026-01-03)
====================================
Expand Down
18 changes: 9 additions & 9 deletions dfetch/commands/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,11 @@
import os

import dfetch.commands.command
import dfetch.manifest.manifest
import dfetch.manifest.validate
import dfetch.project
from dfetch.commands.common import check_child_manifests, files_to_ignore
from dfetch.commands.common import check_child_manifests
from dfetch.log import get_logger
from dfetch.manifest.manifest import Manifest
from dfetch.project.superproject import SuperProject
from dfetch.reporting.check.code_climate_reporter import CodeClimateReporter
from dfetch.reporting.check.jenkins_reporter import JenkinsReporter
from dfetch.reporting.check.reporter import CheckReporter
Expand Down Expand Up @@ -91,20 +90,21 @@ def create_menu(subparsers: dfetch.commands.command.SubparserActionType) -> None

def __call__(self, args: argparse.Namespace) -> None:
"""Perform the check."""
manifest = dfetch.manifest.manifest.get_manifest()
reporters = self._get_reporters(args, manifest)
superproject = SuperProject()
reporters = self._get_reporters(args, superproject.manifest)

with in_directory(os.path.dirname(manifest.path)):
with in_directory(superproject.root_directory):
exceptions: list[str] = []
for project in manifest.selected_projects(args.projects):
for project in superproject.manifest.selected_projects(args.projects):
with catch_runtime_exceptions(exceptions) as exceptions:
dfetch.project.make(project).check_for_update(
reporters, files_to_ignore=files_to_ignore(project.destination)
reporters,
files_to_ignore=superproject.ignored_files(project.destination),
)

if not args.no_recommendations and os.path.isdir(project.destination):
with in_directory(project.destination):
check_child_manifests(manifest, project)
check_child_manifests(superproject.manifest, project)

for reporter in reporters:
reporter.dump_to_file()
Expand Down
14 changes: 0 additions & 14 deletions dfetch/commands/common.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
"""Module for common command operations."""

import os
from collections.abc import Sequence

import yaml

from dfetch.log import get_logger
from dfetch.manifest.manifest import Manifest, get_childmanifests
from dfetch.manifest.project import ProjectEntry
from dfetch.project.svn import SvnRepo
from dfetch.vcs.git import GitLocalRepo

logger = get_logger(__name__)

Expand Down Expand Up @@ -66,14 +63,3 @@ def _make_recommendation(
for line in recommendation_json.splitlines():
logger.warning(line)
logger.warning("")


def files_to_ignore(path: str) -> Sequence[str]:
"""Return a list of files that can be ignored in a given path."""
if GitLocalRepo().is_git():
ignore_list = GitLocalRepo.ignored_files(path)
elif SvnRepo.check_path():
ignore_list = SvnRepo.ignored_files(path)
else:
ignore_list = []
return ignore_list
117 changes: 41 additions & 76 deletions dfetch/commands/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,19 +95,12 @@

import argparse
import os
import pathlib

import dfetch.commands.command
import dfetch.manifest.manifest
import dfetch.manifest.validate
import dfetch.project
from dfetch.log import get_logger
from dfetch.manifest.project import ProjectEntry
from dfetch.project.git import GitRepo
from dfetch.project.metadata import Metadata
from dfetch.project.svn import SvnRepo
from dfetch.project.vcs import VCS
from dfetch.project.superproject import SuperProject
from dfetch.util.util import catch_runtime_exceptions, in_directory
from dfetch.vcs.git import GitLocalRepo

logger = get_logger(__name__)

Expand Down Expand Up @@ -142,83 +135,55 @@ def create_menu(subparsers: dfetch.commands.command.SubparserActionType) -> None

def __call__(self, args: argparse.Namespace) -> None:
"""Perform the diff."""
manifest = dfetch.manifest.manifest.get_manifest()
revs = [r for r in args.revs.strip(":").split(":", maxsplit=1) if r]
superproject = SuperProject()
old_rev, new_rev = self._parse_revs(args.revs)

with in_directory(os.path.dirname(manifest.path)):
with in_directory(superproject.root_directory):
exceptions: list[str] = []
projects = manifest.selected_projects(args.projects)
projects = superproject.manifest.selected_projects(args.projects)
if not projects:
raise RuntimeError(
f"No (such) project found! {', '.join(args.projects)}"
)
for project in projects:
patch_name = f"{project.name}.patch"
with catch_runtime_exceptions(exceptions) as exceptions:
repo = _get_repo(manifest.path, project)
patch = _diff_from_repo(repo, project, revs)

_dump_patch(manifest.path, revs, project, patch_name, patch)
if not os.path.exists(project.destination):
raise RuntimeError(
"You cannot generate a diff of a project that was never fetched"
)
subproject = superproject.get_sub_project(project)

if subproject is None:
raise RuntimeError(
"Can only create patch if your project is an SVN or Git repo",
)
old_rev = old_rev or subproject.metadata_revision()
patch = subproject.diff(old_rev, new_rev)

msg = self._rev_msg(old_rev, new_rev)
if patch:
patch_path = pathlib.Path(f"{project.name}.patch")
logger.print_info_line(
project.name,
f"Generating patch {patch_path} {msg} in {superproject.root_directory}",
)
patch_path.write_text(patch, encoding="UTF-8")
else:
logger.print_info_line(project.name, f"No diffs found {msg}")

if exceptions:
raise RuntimeError("\n".join(exceptions))

@staticmethod
def _parse_revs(revs_arg: str) -> tuple[str, str]:
revs = [r for r in revs_arg.strip(":").split(":", maxsplit=1) if r]

def _get_repo(path: str, project: ProjectEntry) -> VCS:
"""Get the repo type from the project."""
if not os.path.exists(project.destination):
raise RuntimeError(
"You cannot generate a diff of a project that was never fetched"
)
main_project_dir = os.path.dirname(path)
if GitLocalRepo(main_project_dir).is_git():
return GitRepo(project)
if SvnRepo.check_path(main_project_dir):
return SvnRepo(project)

raise RuntimeError(
"Can only create patch in SVN or Git repo",
)


def _diff_from_repo(repo: VCS, project: ProjectEntry, revs: list[str]) -> str:
"""Generate a relative diff for a svn repo."""
if len(revs) > 2:
raise RuntimeError(f"Too many revisions given! {revs}")

if not revs:
revs.append(repo.metadata_revision())
if not revs[-1]:
raise RuntimeError(
"When not providing any commits, dfetch starts from"
f" the last commit to {Metadata.FILENAME} in {project.destination}."
" Please either commit this, or specify a revision to start from with --revs"
)

if len(revs) == 1:
revs.append("")

return repo.get_diff(
old_revision=revs[0], new_revision=revs[1], ignore=(Metadata.FILENAME,)
)


def _dump_patch(
path: str, revs: list[str], project: ProjectEntry, patch_name: str, patch: str
) -> None:
"""Dump the patch to a file."""
if patch:
rev_range = f"from {revs[0]} to {revs[1]}" if revs[1] else f"since {revs[0]}"
logger.print_info_line(
project.name,
f"Generating patch {patch_name} {rev_range} in {os.path.dirname(path)}",
)
with open(patch_name, "w", encoding="UTF-8") as patch_file:
patch_file.write(patch)
else:
if revs[1]:
msg = f"No diffs found from {revs[0]} to {revs[1]}"
else:
msg = f"No diffs found since {revs[0]}"

logger.print_info_line(project.name, msg)
if len(revs) == 0:
return "", ""
if len(revs) == 1:
return revs[0], ""
return revs[0], revs[1]

@staticmethod
def _rev_msg(old_rev: str, new_rev: str) -> str:
return f"from {old_rev} to {new_rev}" if new_rev else f"since {old_rev}"
4 changes: 2 additions & 2 deletions dfetch/commands/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@ def create_menu(subparsers: dfetch.commands.command.SubparserActionType) -> None
def __call__(self, _: argparse.Namespace) -> None:
"""Perform listing the environment."""
logger.print_info_line("platform", f"{platform.system()} {platform.release()}")
for vcs in SUPPORTED_PROJECT_TYPES:
vcs.list_tool_info()
for project_type in SUPPORTED_PROJECT_TYPES:
project_type.list_tool_info()
15 changes: 10 additions & 5 deletions dfetch/commands/freeze.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@
import dfetch.project
from dfetch import DEFAULT_MANIFEST_NAME
from dfetch.log import get_logger
from dfetch.manifest.manifest import Manifest, get_manifest
from dfetch.manifest.manifest import Manifest
from dfetch.manifest.project import ProjectEntry
from dfetch.project.superproject import SuperProject
from dfetch.util.util import catch_runtime_exceptions, in_directory

logger = get_logger(__name__)
Expand All @@ -69,13 +70,13 @@ def __call__(self, args: argparse.Namespace) -> None:
"""Perform the freeze."""
del args # unused

manifest = get_manifest()
superproject = SuperProject()

exceptions: list[str] = []
projects: list[ProjectEntry] = []

with in_directory(os.path.dirname(manifest.path)):
for project in manifest.projects:
with in_directory(superproject.root_directory):
for project in superproject.manifest.projects:
with catch_runtime_exceptions(exceptions) as exceptions:
on_disk_version = dfetch.project.make(project).on_disk_version()

Expand All @@ -98,7 +99,11 @@ def __call__(self, args: argparse.Namespace) -> None:
projects.append(project)

manifest = Manifest(
{"version": "0.0", "remotes": manifest.remotes, "projects": projects}
{
"version": "0.0",
"remotes": superproject.manifest.remotes,
"projects": projects,
}
)

shutil.move(DEFAULT_MANIFEST_NAME, DEFAULT_MANIFEST_NAME + ".backup")
Expand Down
6 changes: 3 additions & 3 deletions dfetch/commands/import_.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,8 @@
from dfetch.manifest.manifest import Manifest
from dfetch.manifest.project import ProjectEntry
from dfetch.manifest.remote import Remote
from dfetch.project.svn import SvnRepo
from dfetch.vcs.git import GitLocalRepo
from dfetch.vcs.svn import SvnRepo

logger = get_logger(__name__)

Expand Down Expand Up @@ -139,7 +139,7 @@ def _import_projects() -> Sequence[ProjectEntry]:
"""Find out what type of VCS is used and import projects."""
if GitLocalRepo().is_git():
projects = _import_from_git()
elif SvnRepo.check_path():
elif SvnRepo().is_svn():
projects = _import_from_svn()
else:
raise RuntimeError(
Expand All @@ -152,7 +152,7 @@ def _import_projects() -> Sequence[ProjectEntry]:
def _import_from_svn() -> Sequence[ProjectEntry]:
projects: list[ProjectEntry] = []

for external in SvnRepo.externals():
for external in SvnRepo(os.getcwd()).externals():
projects.append(
ProjectEntry(
{
Expand Down
13 changes: 7 additions & 6 deletions dfetch/commands/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
from dfetch.log import get_logger
from dfetch.manifest.project import ProjectEntry
from dfetch.project.metadata import Metadata
from dfetch.project.vcs import VCS
from dfetch.project.subproject import SubProject
from dfetch.project.superproject import SuperProject
from dfetch.reporting import REPORTERS, ReportTypes
from dfetch.util.license import License, guess_license_in_file

Expand Down Expand Up @@ -62,12 +63,12 @@ def create_menu(subparsers: dfetch.commands.command.SubparserActionType) -> None

def __call__(self, args: argparse.Namespace) -> None:
"""Generate the report."""
manifest = dfetch.manifest.manifest.get_manifest()
superproject = SuperProject()

with dfetch.util.util.in_directory(os.path.dirname(manifest.path)):
reporter = REPORTERS[args.type](manifest)
with dfetch.util.util.in_directory(superproject.root_directory):
reporter = REPORTERS[args.type](superproject.manifest)

for project in manifest.selected_projects(args.projects):
for project in superproject.manifest.selected_projects(args.projects):
determined_licenses = self._determine_licenses(project)
version = self._determine_version(project)
reporter.add_project(
Expand All @@ -89,7 +90,7 @@ def _determine_licenses(project: ProjectEntry) -> list[License]:
license_files = []
with dfetch.util.util.in_directory(project.destination):

for license_file in filter(VCS.is_license_file, glob.glob("*")):
for license_file in filter(SubProject.is_license_file, glob.glob("*")):
logger.debug(f"Found license file {license_file} for {project.name}")
guessed_license = guess_license_in_file(license_file)

Expand Down
16 changes: 9 additions & 7 deletions dfetch/commands/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@
import dfetch.manifest.validate
import dfetch.project.git
import dfetch.project.svn
from dfetch.commands.common import check_child_manifests, files_to_ignore
from dfetch.commands.common import check_child_manifests
from dfetch.log import get_logger
from dfetch.project.superproject import SuperProject
from dfetch.util.util import catch_runtime_exceptions, in_directory

logger = get_logger(__name__)
Expand Down Expand Up @@ -76,26 +77,27 @@ def create_menu(subparsers: dfetch.commands.command.SubparserActionType) -> None

def __call__(self, args: argparse.Namespace) -> None:
"""Perform the update."""
manifest = dfetch.manifest.manifest.get_manifest()
superproject = SuperProject()

exceptions: list[str] = []
destinations: list[str] = [
os.path.realpath(project.destination) for project in manifest.projects
os.path.realpath(project.destination)
for project in superproject.manifest.projects
]
with in_directory(os.path.dirname(manifest.path)):
for project in manifest.selected_projects(args.projects):
with in_directory(superproject.root_directory):
for project in superproject.manifest.selected_projects(args.projects):
with catch_runtime_exceptions(exceptions) as exceptions:
self._check_destination(project, destinations)
dfetch.project.make(project).update(
force=args.force,
files_to_ignore=files_to_ignore(project.destination),
files_to_ignore=superproject.ignored_files(project.destination),
)

if not args.no_recommendations and os.path.isdir(
project.destination
):
with in_directory(project.destination):
check_child_manifests(manifest, project)
check_child_manifests(superproject.manifest, project)

if exceptions:
raise RuntimeError("\n".join(exceptions))
Expand Down
Loading
Loading