diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index e199302e..5810de03 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -11,6 +11,9 @@ Release 0.12.0 (unreleased)
* Skip patches outside manifest dir (#942)
* Make patch path in metadata platform independent (#937)
* Fix extra newlines in patch for new files (#945)
+* Replace colored-logs and Halo with Rich (#960)
+* Respect `NO_COLOR `_ (#960)
+* Group logging under a project name header (#960)
Release 0.11.0 (released 2026-01-03)
====================================
diff --git a/dfetch/__main__.py b/dfetch/__main__.py
index 0cc82c8b..312610d4 100644
--- a/dfetch/__main__.py
+++ b/dfetch/__main__.py
@@ -6,6 +6,9 @@
import argparse
import sys
from collections.abc import Sequence
+from typing import Optional
+
+from rich.console import Console
import dfetch.commands.check
import dfetch.commands.diff
@@ -18,8 +21,7 @@
import dfetch.commands.validate
import dfetch.log
import dfetch.util.cmdline
-
-logger = dfetch.log.setup_root(__name__)
+from dfetch.log import DLogger
class DfetchFatalException(Exception):
@@ -34,6 +36,9 @@ def create_parser() -> argparse.ArgumentParser:
parser.add_argument(
"--verbose", "-v", action="store_true", help="Increase verbosity"
)
+ parser.add_argument(
+ "--no-color", action="store_true", help="Disable colored output"
+ )
parser.set_defaults(func=_help)
subparsers = parser.add_subparsers(help="commands")
@@ -50,16 +55,21 @@ def create_parser() -> argparse.ArgumentParser:
return parser
-def _help(args: argparse.Namespace) -> None:
- """Show the help."""
- raise RuntimeError("Select a function")
+def _help(_: argparse.Namespace) -> None:
+ """Show help if no subcommand was selected."""
+ parser = create_parser()
+ parser.print_help()
-def run(argv: Sequence[str]) -> None:
+def run(argv: Sequence[str], console: Optional[Console] = None) -> None:
"""Start dfetch."""
- logger.print_title()
args = create_parser().parse_args(argv)
+ console = console or dfetch.log.make_console(no_color=args.no_color)
+ logger: DLogger = dfetch.log.setup_root(__name__, console=console)
+
+ logger.print_title()
+
if args.verbose:
dfetch.log.increase_verbosity()
diff --git a/dfetch/commands/common.py b/dfetch/commands/common.py
index 5ccd2b58..9f3878cf 100644
--- a/dfetch/commands/common.py
+++ b/dfetch/commands/common.py
@@ -44,15 +44,15 @@ def _make_recommendation(
recommendations (List[ProjectEntry]): List of recommendations
childmanifest_path (str): Path to the source of recommendations
"""
- logger.warning(
- "\n".join(
+ logger.print_warning_line(
+ project.name,
+ " ".join(
[
- "",
f'"{project.name}" depends on the following project(s) '
"which are not part of your manifest:",
f"(found in {childmanifest_path})",
]
- )
+ ),
)
recommendation_json = yaml.dump(
diff --git a/dfetch/commands/environment.py b/dfetch/commands/environment.py
index 99109baf..7ce1e63a 100644
--- a/dfetch/commands/environment.py
+++ b/dfetch/commands/environment.py
@@ -23,6 +23,8 @@ 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()}")
+ logger.print_report_line(
+ "platform", f"{platform.system()} {platform.release()}"
+ )
for project_type in SUPPORTED_PROJECT_TYPES:
project_type.list_tool_info()
diff --git a/dfetch/commands/validate.py b/dfetch/commands/validate.py
index 7b3a992a..4845f23a 100644
--- a/dfetch/commands/validate.py
+++ b/dfetch/commands/validate.py
@@ -35,4 +35,4 @@ def __call__(self, args: argparse.Namespace) -> None:
manifest_path = find_manifest()
parse(manifest_path)
manifest_path = os.path.relpath(manifest_path, os.getcwd())
- logger.print_info_line(manifest_path, "valid")
+ logger.print_report_line(manifest_path, "valid")
diff --git a/dfetch/log.py b/dfetch/log.py
index 5b67838e..274d42f9 100644
--- a/dfetch/log.py
+++ b/dfetch/log.py
@@ -1,63 +1,185 @@
"""Logging related items."""
import logging
-from typing import cast
+import os
+import sys
+from contextlib import nullcontext
+from typing import Any, Optional, Union, cast
-import coloredlogs
-from colorama import Fore
+from rich.console import Console
+from rich.highlighter import NullHighlighter
+from rich.logging import RichHandler
+from rich.status import Status
from dfetch import __version__
+def make_console(no_color: bool = False) -> Console:
+ """Create a Rich Console with proper color handling."""
+ return Console(
+ no_color=no_color
+ or os.getenv("NO_COLOR") is not None
+ or not sys.stdout.isatty()
+ )
+
+
+def configure_root_logger(console: Optional[Console] = None) -> None:
+ """Configure the root logger with RichHandler using the provided Console."""
+ console = console or make_console()
+
+ handler = RichHandler(
+ console=console,
+ show_time=False,
+ show_path=False,
+ show_level=False,
+ markup=True,
+ rich_tracebacks=True,
+ highlighter=NullHighlighter(),
+ )
+
+ logging.basicConfig(
+ level=logging.INFO,
+ format="%(message)s",
+ handlers=[handler],
+ force=True,
+ )
+
+
class DLogger(logging.Logger):
"""Logging class extended with specific log items for dfetch."""
+ _printed_projects: set[str] = set()
+
+ def print_report_line(self, name: str, info: str) -> None:
+ """Print a line for a report."""
+ self.info(
+ f" [bold][bright_green]{name:20s}:[/bright_green][blue] {info}[/blue][/bold]"
+ )
+
def print_info_line(self, name: str, info: str) -> None:
- """Print a line of info."""
- self.info(f" {Fore.GREEN}{name:20s}:{Fore.BLUE} {info}")
+ """Print a line of info, only printing the project name once."""
+ if name not in DLogger._printed_projects:
+ self.info(f" [bold][bright_green]{name}:[/bright_green][/bold]")
+ DLogger._printed_projects.add(name)
+
+ self.info(f" [bold blue]> {info}[/bold blue]")
def print_warning_line(self, name: str, info: str) -> None:
- """Print a line of info."""
- self.info(f" {Fore.GREEN}{name:20s}:{Fore.YELLOW} {info}")
+ """Print a warning line: green name, yellow value."""
+ if name not in DLogger._printed_projects:
+ self.info(f" [bold][bright_green]{name}:[/bright_green][/bold]")
+ DLogger._printed_projects.add(name)
+
+ self.info(f" [bold bright_yellow]> {info}[/bold bright_yellow]")
def print_title(self) -> None:
"""Print the DFetch tool title and version."""
- self.info(f"{Fore.BLUE}Dfetch ({__version__})")
+ self.info(f"[bold blue]Dfetch ({__version__})[/bold blue]")
def print_info_field(self, field_name: str, field: str) -> None:
"""Print a field with corresponding value."""
- self.print_info_line(field_name, field if field else "")
-
-
-def setup_root(name: str) -> DLogger:
- """Create the root logger."""
- logger = get_logger(name)
-
- msg_format = "%(message)s"
-
- level_style = {
- "critical": {"color": "magenta", "bright": True, "bold": True},
- "debug": {"color": "green", "bright": True, "bold": True},
- "error": {"color": "red", "bright": True, "bold": True},
- "info": {"color": 4, "bright": True, "bold": True},
- "notice": {"color": "magenta", "bright": True, "bold": True},
- "spam": {"color": "green", "faint": True},
- "success": {"color": "green", "bright": True, "bold": True},
- "verbose": {"color": "blue", "bright": True, "bold": True},
- "warning": {"color": "yellow", "bright": True, "bold": True},
- }
-
- coloredlogs.install(fmt=msg_format, level_styles=level_style, level="INFO")
-
- return logger
+ self.print_report_line(field_name, field if field else "")
+
+ def warning(self, msg: object, *args: Any, **kwargs: Any) -> None:
+ """Log warning."""
+ super().warning(
+ f" [bold bright_yellow]{msg}[/bold bright_yellow]", *args, **kwargs
+ )
+
+ def error(self, msg: object, *args: Any, **kwargs: Any) -> None:
+ """Log error."""
+ super().error(f"[red]{msg}[/red]", *args, **kwargs)
+
+ def status(
+ self, message: str, spinner: str = "dots", enabled: bool = True
+ ) -> Union[Status, nullcontext[None]]:
+ """Show status message with spinner if enabled."""
+ rich_console = None
+ logger: Optional[logging.Logger] = self
+ while logger:
+ for handler in getattr(logger, "handlers", []):
+ if isinstance(handler, RichHandler):
+ rich_console = handler.console
+ break
+ if rich_console or not getattr(logger, "parent", None):
+ break
+ logger = logger.parent
+
+ if not rich_console or not enabled:
+ return nullcontext(None)
+
+ return Status(
+ f"[bold bright_blue]> {message}[/bold bright_blue]",
+ spinner=spinner,
+ console=rich_console,
+ )
+
+ @classmethod
+ def reset_projects(cls) -> None:
+ """Clear the record of printed project names."""
+ cls._printed_projects.clear()
+
+
+class IndentFilter(logging.Filter): # pylint: disable=too-few-public-methods
+ """Adds indentation to all log messages that pass through this filter."""
+
+ def __init__(self, prefix: str = " "):
+ """Initialize the IndentFilter with a prefix."""
+ super().__init__()
+ self.prefix = prefix
+
+ def filter(self, record: logging.LogRecord) -> bool:
+ """Add indentation to the log record message."""
+ color = "blue" if record.levelno < logging.WARNING else "yellow"
+
+ record.msg = f"{self.prefix}[{color}]{record.msg}[/{color}]"
+ return True
+
+
+def setup_root(name: str, console: Optional[Console] = None) -> DLogger:
+ """Create and return the root logger."""
+ logging.setLoggerClass(DLogger)
+ configure_root_logger(console)
+ logger = logging.getLogger(name)
+ return cast(DLogger, logger)
def increase_verbosity() -> None:
- """Increase the verbosity of the logger."""
- coloredlogs.increase_verbosity()
-
-
-def get_logger(name: str) -> DLogger:
- """Get logger for a module."""
+ """Increase verbosity of the root logger."""
+ levels = [
+ logging.CRITICAL,
+ logging.ERROR,
+ logging.WARNING,
+ logging.INFO,
+ logging.DEBUG,
+ ]
+ logger_ = logging.getLogger()
+ current_level = logger_.getEffectiveLevel()
+ try:
+ idx = levels.index(current_level)
+ if idx < len(levels) - 1:
+ new_level = levels[idx + 1]
+ else:
+ new_level = levels[-1]
+ except ValueError:
+ new_level = logging.DEBUG
+ logger_.setLevel(new_level)
+
+
+def get_logger(name: str, console: Optional[Console] = None) -> DLogger:
+ """Get logger for a module, optionally configuring console colors."""
logging.setLoggerClass(DLogger)
- return cast(DLogger, logging.getLogger(name))
+ logger = logging.getLogger(name)
+ logger.propagate = True
+ if console:
+ configure_root_logger(console)
+ return cast(DLogger, logger)
+
+
+def configure_external_logger(name: str, level: int = logging.INFO) -> None:
+ """Configure an external logger from a third party package."""
+ logger = logging.getLogger(name)
+ logger.setLevel(level)
+ logger.propagate = True
+ logger.handlers.clear()
+ logger.addFilter(IndentFilter())
diff --git a/dfetch/project/subproject.py b/dfetch/project/subproject.py
index 837b84bb..c8bc7cc3 100644
--- a/dfetch/project/subproject.py
+++ b/dfetch/project/subproject.py
@@ -7,8 +7,6 @@
from collections.abc import Sequence
from typing import Optional
-from halo import Halo
-
from dfetch.log import get_logger
from dfetch.manifest.project import ProjectEntry
from dfetch.manifest.version import Version
@@ -119,11 +117,8 @@ def update(
logger.debug(f"Clearing destination {self.local_path}")
safe_rm(self.local_path)
- with Halo(
- text=f"Fetching {self.__project.name} {to_fetch}",
- spinner="dots",
- text_color="green",
- enabled=self._show_animations,
+ with logger.status(
+ f"Fetching {self.__project.name} {to_fetch}", enabled=self._show_animations
):
actually_fetched = self._fetch_impl(to_fetch)
self._log_project(f"Fetched {actually_fetched}")
@@ -159,8 +154,8 @@ def _apply_patches(self) -> list[str]:
normalized_patch_path = str(relative_patch_path.as_posix())
- apply_patch(normalized_patch_path, root=self.local_path)
- self._log_project(f'Applied patch "{normalized_patch_path}"')
+ self._log_project(f'Applying patch "{normalized_patch_path}"')
+ apply_patch(logger, normalized_patch_path, root=self.local_path)
applied_patches.append(normalized_patch_path)
return applied_patches
@@ -169,11 +164,8 @@ def check_for_update(
) -> None:
"""Check if there is an update available."""
on_disk_version = self.on_disk_version()
- with Halo(
- text=f"Checking {self.__project.name}",
- spinner="dots",
- text_color="green",
- enabled=self._show_animations,
+ with logger.status(
+ f"Checking {self.__project.name}", enabled=self._show_animations
):
latest_version = self._check_for_newer_version()
@@ -229,7 +221,7 @@ def _log_project(self, msg: str) -> None:
@staticmethod
def _log_tool(name: str, msg: str) -> None:
- logger.print_info_line(name, msg.strip())
+ logger.print_report_line(name, msg.strip())
@property
def local_path(self) -> str:
@@ -299,9 +291,10 @@ def on_disk_version(self) -> Optional[Version]:
try:
return Metadata.from_file(self.__metadata.path).version
except TypeError:
- logger.warning(
+ logger.print_warning_line(
+ self.__project.name,
f"{pathlib.Path(self.__metadata.path).relative_to(os.getcwd()).as_posix()}"
- " is an invalid metadata file, not checking on disk version!"
+ " is an invalid metadata file, not checking on disk version!",
)
return None
@@ -317,9 +310,10 @@ def _on_disk_hash(self) -> Optional[str]:
try:
return Metadata.from_file(self.__metadata.path).hash
except TypeError:
- logger.warning(
+ logger.print_warning_line(
+ self.__project.name,
f"{pathlib.Path(self.__metadata.path).relative_to(os.getcwd()).as_posix()}"
- " is an invalid metadata file, not checking local hash!"
+ " is an invalid metadata file, not checking local hash!",
)
return None
diff --git a/dfetch/vcs/patch.py b/dfetch/vcs/patch.py
index a70e85d9..02b13f49 100644
--- a/dfetch/vcs/patch.py
+++ b/dfetch/vcs/patch.py
@@ -8,9 +8,9 @@
import patch_ng
-from dfetch.log import get_logger
+from dfetch.log import DLogger, configure_external_logger
-logger = get_logger(__name__)
+configure_external_logger("patch_ng")
def _git_mode(path: Path) -> str:
@@ -59,7 +59,11 @@ def dump_patch(patch_set: patch_ng.PatchSet) -> str:
return "\n".join(patch_lines) + "\n" if patch_lines else ""
-def apply_patch(patch_path: str, root: str = ".") -> None:
+def apply_patch(
+ logger: DLogger,
+ patch_path: str,
+ root: str = ".",
+) -> None:
"""Apply the specified patch relative to the root."""
patch_set = patch_ng.fromfile(patch_path)
diff --git a/doc/_ext/sphinxcontrib_asciinema/.dfetch_data.yaml b/doc/_ext/sphinxcontrib_asciinema/.dfetch_data.yaml
index 5c941f3c..49a36e79 100644
--- a/doc/_ext/sphinxcontrib_asciinema/.dfetch_data.yaml
+++ b/doc/_ext/sphinxcontrib_asciinema/.dfetch_data.yaml
@@ -2,8 +2,8 @@
# For more info see https://dfetch.rtfd.io/en/latest/getting_started.html
dfetch:
branch: master
- hash: dcd1473e1a3ca613b804e3e51e7ee342
- last_fetch: 07/01/2026, 21:38:48
+ hash: 5b0a3a18e1e83d363f9eb0ac4b3fca17
+ last_fetch: 26/01/2026, 23:40:59
patch:
- doc/_ext/patches/001-autoformat-sphinxcontrib.asciinema.patch
- doc/_ext/patches/002-fix-options-sphinxcontrib.asciinema.patch
diff --git a/doc/legal.rst b/doc/legal.rst
index 5e36a740..f7ed3a1d 100644
--- a/doc/legal.rst
+++ b/doc/legal.rst
@@ -77,34 +77,33 @@ We use `PyYAML`_ for parsing manifests (which are YAML). This uses the MIT licen
.. _`PyYAML`: https://pyyaml.org/
-python-coloredlogs
-~~~~~~~~~~~~~~~~~~
-`Colored logs`_ is used for the colored text output.
+Rich
+~~~~
+`Rich`_ is used for the colored text output.
::
- Copyright (c) 2020 Peter Odding
+ Copyright (c) 2020 Will McGugan
- Permission is hereby granted, free of charge, to any person obtaining
- a copy of this software and associated documentation files (the
- "Software"), to deal in the Software without restriction, including
- without limitation the rights to use, copy, modify, merge, publish,
- distribute, sublicense, and/or sell copies of the Software, and to
- permit persons to whom the Software is furnished to do so, subject to
- the following conditions:
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
- The above copyright notice and this permission notice shall be
- included in all copies or substantial portions of the Software.
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
-.. _`Colored logs`: https://coloredlogs.readthedocs.io/en/latest/
+.. _`Rich`: https://rich.readthedocs.io/en/latest/
pykwalify
~~~~~~~~~
@@ -137,41 +136,7 @@ pykwalify
.. _`pykwalify`: https://github.com/Grokzen/pykwalify
-Colorama
-~~~~~~~~
-`colorama`_ is also used for the colored text output.
-
-::
-
- Copyright (c) 2010 Jonathan Hartley
- All rights reserved.
-
- Redistribution and use in source and binary forms, with or without
- modification, are permitted provided that the following conditions are met:
-
- * Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
- * Neither the name of the copyright holders, nor those of its contributors
- may be used to endorse or promote products derived from this software without
- specific prior written permission.
-
- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-.. _`colorama`: https://github.com/tartley/colorama
Typing-extensions
@@ -431,37 +396,6 @@ cyclonedx-python-lib
.. _`cyclonedx-python-lib`: https://github.com/CycloneDX/cyclonedx-python-lib/
-Halo
-~~~~
-`Halo`_ is used to show a nice spinner during long-running operations.
-
-::
-
- MIT License
-
- Copyright (c) 2017 Manraj Singh
-
- Permission is hereby granted, free of charge, to any person obtaining a copy
- of this software and associated documentation files (the "Software"), to deal
- in the Software without restriction, including without limitation the rights
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- copies of the Software, and to permit persons to whom the Software is
- furnished to do so, subject to the following conditions:
-
- The above copyright notice and this permission notice shall be included in all
- copies or substantial portions of the Software.
-
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- SOFTWARE.
-
-
-.. _`Halo`: https://github.com/manrajgrover/halo/
-
Sarif-om
~~~~~~~~
`Sarif-om`_ is used for generating reports in Sarif format for Github.
diff --git a/features/check-git-repo.feature b/features/check-git-repo.feature
index 0d03930c..25a24d97 100644
--- a/features/check-git-repo.feature
+++ b/features/check-git-repo.feature
@@ -27,8 +27,10 @@ Feature: Checking dependencies from a git repository
Then the output shows
"""
Dfetch (0.11.0)
- ext/test-repo-rev-only: wanted (e1fda19a57b873eb8e6ae37780594cbb77b70f1a), available (e1fda19a57b873eb8e6ae37780594cbb77b70f1a)
- ext/test-rev-and-branch: wanted (main - 8df389d0524863b85f484f15a91c5f2c40aefda1), available (main - e1fda19a57b873eb8e6ae37780594cbb77b70f1a)
+ ext/test-repo-rev-only:
+ > wanted (e1fda19a57b873eb8e6ae37780594cbb77b70f1a), available (e1fda19a57b873eb8e6ae37780594cbb77b70f1a)
+ ext/test-rev-and-branch:
+ > wanted (main - 8df389d0524863b85f484f15a91c5f2c40aefda1), available (main - e1fda19a57b873eb8e6ae37780594cbb77b70f1a)
"""
Scenario: A newer tag is available than in manifest
@@ -51,7 +53,8 @@ Feature: Checking dependencies from a git repository
Then the output shows
"""
Dfetch (0.11.0)
- ext/test-repo-tag-v1: wanted (v1), available (v2.0)
+ ext/test-repo-tag-v1:
+ > wanted (v1), available (v2.0)
"""
Scenario: Check is done after an update
@@ -80,8 +83,10 @@ Feature: Checking dependencies from a git repository
Then the output shows
"""
Dfetch (0.11.0)
- ext/test-repo-rev-only: up-to-date (e1fda19a57b873eb8e6ae37780594cbb77b70f1a)
- ext/test-rev-and-branch: wanted & current (main - 8df389d0524863b85f484f15a91c5f2c40aefda1), available (main - e1fda19a57b873eb8e6ae37780594cbb77b70f1a)
+ ext/test-repo-rev-only:
+ > up-to-date (e1fda19a57b873eb8e6ae37780594cbb77b70f1a)
+ ext/test-rev-and-branch:
+ > wanted & current (main - 8df389d0524863b85f484f15a91c5f2c40aefda1), available (main - e1fda19a57b873eb8e6ae37780594cbb77b70f1a)
"""
Scenario: Tag is updated in manifest
@@ -112,7 +117,8 @@ Feature: Checking dependencies from a git repository
Then the output shows
"""
Dfetch (0.11.0)
- ext/test-repo-tag : wanted (v2.0), current (v1), available (v2.0)
+ ext/test-repo-tag:
+ > wanted (v2.0), current (v1), available (v2.0)
"""
Scenario: A local change is reported
@@ -133,8 +139,9 @@ Feature: Checking dependencies from a git repository
Then the output shows
"""
Dfetch (0.11.0)
- SomeProject : Local changes were detected, please generate a patch using 'dfetch diff SomeProject' and add it to your manifest using 'patch:'. Alternatively overwrite the local changes with 'dfetch update --force SomeProject'
- SomeProject : up-to-date (master - 90be799b58b10971691715bdc751fbe5237848a0)
+ SomeProject:
+ > Local changes were detected, please generate a patch using 'dfetch diff SomeProject' and add it to your manifest using 'patch:'. Alternatively overwrite the local changes with 'dfetch update --force SomeProject'
+ > up-to-date (master - 90be799b58b10971691715bdc751fbe5237848a0)
"""
Scenario: Change to ignored files are not reported
@@ -153,7 +160,8 @@ Feature: Checking dependencies from a git repository
Then the output shows
"""
Dfetch (0.11.0)
- SomeProject : up-to-date (master - 90be799b58b10971691715bdc751fbe5237848a0)
+ SomeProject:
+ > up-to-date (master - 90be799b58b10971691715bdc751fbe5237848a0)
"""
Scenario: A non-existent remote is reported
@@ -201,9 +209,12 @@ Feature: Checking dependencies from a git repository
Then the output shows
"""
Dfetch (0.11.0)
- SomeProjectMissingTag: wanted (i-dont-exist), but not available at the upstream.
- SomeProjectNonExistentBranch: wanted (i-dont-exist), but not available at the upstream.
- SomeProjectNonExistentRevision: wanted (0123112321234123512361236123712381239123), but not available at the upstream.
+ SomeProjectMissingTag:
+ > wanted (i-dont-exist), but not available at the upstream.
+ SomeProjectNonExistentBranch:
+ > wanted (i-dont-exist), but not available at the upstream.
+ SomeProjectNonExistentRevision:
+ > wanted (0123112321234123512361236123712381239123), but not available at the upstream.
"""
Scenario: Credentials required for remote
diff --git a/features/check-specific-projects.feature b/features/check-specific-projects.feature
index 79ae9a2d..6d69b63e 100644
--- a/features/check-specific-projects.feature
+++ b/features/check-specific-projects.feature
@@ -28,5 +28,6 @@ Feature: Checking specific projects
Then the output shows
"""
Dfetch (0.11.0)
- ext/test-rev-and-branch: wanted (main - 8df389d0524863b85f484f15a91c5f2c40aefda1), available (main - e1fda19a57b873eb8e6ae37780594cbb77b70f1a)
+ ext/test-rev-and-branch:
+ > wanted (main - 8df389d0524863b85f484f15a91c5f2c40aefda1), available (main - e1fda19a57b873eb8e6ae37780594cbb77b70f1a)
"""
diff --git a/features/check-svn-repo.feature b/features/check-svn-repo.feature
index 8553309a..d864cf40 100644
--- a/features/check-svn-repo.feature
+++ b/features/check-svn-repo.feature
@@ -30,8 +30,10 @@ Feature: Checking dependencies from a svn repository
Then the output shows
"""
Dfetch (0.11.0)
- cunit-svn-rev-only : wanted (170), available (trunk - 170)
- cunit-svn-rev-and-branch: wanted (mingw64 - 156), available (mingw64 - 170)
+ cunit-svn-rev-only:
+ > wanted (170), available (trunk - 170)
+ cunit-svn-rev-and-branch:
+ > wanted (mingw64 - 156), available (mingw64 - 170)
"""
Scenario: A newer tag is available than in manifest
@@ -55,7 +57,8 @@ Feature: Checking dependencies from a svn repository
Then the output shows
"""
Dfetch (0.11.0)
- cutter-svn-tag : wanted (1.1.7), available (1.1.8)
+ cutter-svn-tag:
+ > wanted (1.1.7), available (1.1.8)
"""
Scenario: Check is done after an update
@@ -92,9 +95,12 @@ Feature: Checking dependencies from a svn repository
Then the output shows
"""
Dfetch (0.11.0)
- cunit-svn-rev-only : wanted (169), current (trunk - 169), available (trunk - 170)
- cunit-svn-rev-and-branch: wanted & current (mingw64 - 156), available (mingw64 - 170)
- ext/test-non-standard-svn: wanted (latest), current (1), available (1)
+ cunit-svn-rev-only:
+ > wanted (169), current (trunk - 169), available (trunk - 170)
+ cunit-svn-rev-and-branch:
+ > wanted & current (mingw64 - 156), available (mingw64 - 170)
+ ext/test-non-standard-svn:
+ > wanted (latest), current (1), available (1)
"""
Scenario: A non-standard SVN repository can be checked
@@ -113,7 +119,8 @@ Feature: Checking dependencies from a svn repository
Then the output shows
"""
Dfetch (0.11.0)
- SomeProject : wanted (latest), current (1), available (1)
+ SomeProject:
+ > wanted (latest), current (1), available (1)
"""
Scenario: A non-existent remote is reported
@@ -156,7 +163,8 @@ Feature: Checking dependencies from a svn repository
Then the output shows
"""
Dfetch (0.11.0)
- cutter-svn-tag : wanted (non-existent-tag), but not available at the upstream.
+ cutter-svn-tag:
+ > wanted (non-existent-tag), but not available at the upstream.
"""
Scenario: Change to ignored files are not reported
@@ -176,5 +184,6 @@ Feature: Checking dependencies from a svn repository
Then the output shows
"""
Dfetch (0.11.0)
- SomeProject : up-to-date (v1)
+ SomeProject:
+ > up-to-date (v1)
"""
diff --git a/features/checked-project-has-dependencies.feature b/features/checked-project-has-dependencies.feature
index 8b8f59de..6c6dbcdd 100644
--- a/features/checked-project-has-dependencies.feature
+++ b/features/checked-project-has-dependencies.feature
@@ -30,7 +30,8 @@ Feature: Check for dependencies in projects
Then the output shows
"""
Dfetch (0.11.0)
- SomeProject : wanted (v1), available (v1)
+ SomeProject:
+ > wanted (v1), available (v1)
"""
Scenario: A recommendation is done due to a missing dependency
@@ -67,7 +68,8 @@ Feature: Check for dependencies in projects
"""
Dfetch (0.11.0)
Multiple manifests found, using dfetch.yaml
- SomeProject : up-to-date (v1)
+ SomeProject:
+ > up-to-date (v1)
"SomeProject" depends on the following project(s) which are not part of your manifest:
(found in third-party/SomeProject/dfetch.yaml)
@@ -110,6 +112,8 @@ Feature: Check for dependencies in projects
"""
Dfetch (0.11.0)
Multiple manifests found, using dfetch.yaml
- SomeProject : up-to-date (v1)
- SomeOtherProject : up-to-date (v1)
+ SomeProject:
+ > up-to-date (v1)
+ SomeOtherProject:
+ > up-to-date (v1)
"""
diff --git a/features/diff-in-git.feature b/features/diff-in-git.feature
index 6941f660..3c4a85ac 100644
--- a/features/diff-in-git.feature
+++ b/features/diff-in-git.feature
@@ -66,7 +66,8 @@ Feature: Diff in git
Then the output shows
"""
Dfetch (0.11.0)
- SomeProject : No diffs found since 59efb91396fd369eb113b43382783294dc8ed6d2
+ SomeProject:
+ > No diffs found since 59efb91396fd369eb113b43382783294dc8ed6d2
"""
Scenario: Diff is generated on uncommitted changes
@@ -92,5 +93,6 @@ Feature: Diff in git
Then the output shows
"""
Dfetch (0.11.0)
- SomeProject : No diffs found since 59efb91396fd369eb113b43382783294dc8ed6d2
+ SomeProject:
+ > No diffs found since 59efb91396fd369eb113b43382783294dc8ed6d2
"""
diff --git a/features/diff-in-svn.feature b/features/diff-in-svn.feature
index eceba4e0..ca686f4c 100644
--- a/features/diff-in-svn.feature
+++ b/features/diff-in-svn.feature
@@ -63,7 +63,8 @@ Feature: Diff in svn
Then the output shows
"""
Dfetch (0.11.0)
- SomeProject : No diffs found since 1
+ SomeProject:
+ > No diffs found since 1
"""
Scenario: A patch file is generated on uncommitted changes
@@ -89,5 +90,6 @@ Feature: Diff in svn
Then the output shows
"""
Dfetch (0.11.0)
- SomeProject : No diffs found since 1
+ SomeProject:
+ > No diffs found since 1
"""
diff --git a/features/environment.py b/features/environment.py
index 30291015..10d083e9 100644
--- a/features/environment.py
+++ b/features/environment.py
@@ -4,6 +4,7 @@
import tempfile
from behave import fixture, use_fixture
+from rich.console import Console
from dfetch.util.util import safe_rmtree
@@ -28,6 +29,12 @@ def before_scenario(context, _):
"""Hook called before scenario is executed."""
use_fixture(tmpdir, context)
+ context.console = Console(
+ record=True,
+ force_terminal=True,
+ width=1024,
+ )
+
def before_all(context):
"""Hook called before first test is run."""
diff --git a/features/fetch-checks-destination.feature b/features/fetch-checks-destination.feature
index e177ad20..0443eb38 100644
--- a/features/fetch-checks-destination.feature
+++ b/features/fetch-checks-destination.feature
@@ -21,7 +21,8 @@ Feature: Fetch checks destinations
Then the output shows
"""
Dfetch (0.11.0)
- ext/test-repo-tag : Skipping, path "." is not allowed as destination.
+ ext/test-repo-tag:
+ > Skipping, path "." is not allowed as destination.
Destination must be in a valid subfolder. "." is not valid!
"""
@@ -42,6 +43,7 @@ Feature: Fetch checks destinations
Then the output shows
"""
Dfetch (0.11.0)
- ext/test-repo-tag : Skipping, path "../../some-higher-folder" is outside manifest directory tree.
+ ext/test-repo-tag:
+ > Skipping, path "../../some-higher-folder" is outside manifest directory tree.
Destination must be in the manifests folder or a subfolder. "../../some-higher-folder" is outside this tree!
"""
diff --git a/features/fetch-file-pattern-git.feature b/features/fetch-file-pattern-git.feature
index 08f689a9..8c56c72c 100644
--- a/features/fetch-file-pattern-git.feature
+++ b/features/fetch-file-pattern-git.feature
@@ -23,7 +23,8 @@ Feature: Fetch file pattern from git repo
Then the output shows
"""
Dfetch (0.11.0)
- SomeProjectWithAnInterestingFile: Fetched v1
+ SomeProjectWithAnInterestingFile:
+ > Fetched v1
"""
Then 'MyProject' looks like:
"""
diff --git a/features/fetch-file-pattern-svn.feature b/features/fetch-file-pattern-svn.feature
index 08453972..0db7c793 100644
--- a/features/fetch-file-pattern-svn.feature
+++ b/features/fetch-file-pattern-svn.feature
@@ -22,7 +22,8 @@ Feature: Fetch file pattern from svn repo
Then the output shows
"""
Dfetch (0.11.0)
- SomeProjectWithAnInterestingFile: Fetched trunk - 1
+ SomeProjectWithAnInterestingFile:
+ > Fetched trunk - 1
"""
Then 'MyProject' looks like:
"""
diff --git a/features/fetch-git-repo.feature b/features/fetch-git-repo.feature
index b6b54612..adfb6e0c 100644
--- a/features/fetch-git-repo.feature
+++ b/features/fetch-git-repo.feature
@@ -64,7 +64,8 @@ Feature: Fetching dependencies from a git repository
Then the output shows
"""
Dfetch (0.11.0)
- ext/test-repo-tag : Fetched v2.0
+ ext/test-repo-tag:
+ > Fetched v2.0
"""
Scenario: Version check ignored when force flag is given
@@ -84,5 +85,6 @@ Feature: Fetching dependencies from a git repository
Then the output shows
"""
Dfetch (0.11.0)
- ext/test-repo-tag : Fetched v1
+ ext/test-repo-tag:
+ > Fetched v1
"""
diff --git a/features/fetch-single-file-git.feature b/features/fetch-single-file-git.feature
index c6434cce..13cc994a 100644
--- a/features/fetch-single-file-git.feature
+++ b/features/fetch-single-file-git.feature
@@ -22,7 +22,8 @@ Feature: Fetch single file from git repo
Then the output shows
"""
Dfetch (0.11.0)
- SomeProjectWithAnInterestingFile: Fetched v1
+ SomeProjectWithAnInterestingFile:
+ > Fetched v1
"""
Then 'MyProject' looks like:
"""
@@ -53,7 +54,8 @@ Feature: Fetch single file from git repo
Then the output shows
"""
Dfetch (0.11.0)
- SomeProjectWithAnInterestingFile: Fetched v1
+ SomeProjectWithAnInterestingFile:
+ > Fetched v1
"""
Then 'MyProject' looks like:
"""
diff --git a/features/fetch-single-file-svn.feature b/features/fetch-single-file-svn.feature
index 1b530ffc..2d471a4a 100644
--- a/features/fetch-single-file-svn.feature
+++ b/features/fetch-single-file-svn.feature
@@ -22,7 +22,8 @@ Feature: Fetch single file from svn repo
Then the output shows
"""
Dfetch (0.11.0)
- SomeProjectWithAnInterestingFile: Fetched trunk - 1
+ SomeProjectWithAnInterestingFile:
+ > Fetched trunk - 1
"""
And 'MyProject' looks like:
"""
diff --git a/features/fetch-with-ignore-git.feature b/features/fetch-with-ignore-git.feature
index d29404ea..0515be32 100644
--- a/features/fetch-with-ignore-git.feature
+++ b/features/fetch-with-ignore-git.feature
@@ -29,7 +29,8 @@ Feature: Fetch with ignore in git
Then the output shows
"""
Dfetch (0.11.0)
- SomeInterestingProject: Fetched v1
+ SomeInterestingProject:
+ > Fetched v1
"""
Then 'MyProject' looks like:
"""
@@ -58,7 +59,8 @@ Feature: Fetch with ignore in git
Then the output shows
"""
Dfetch (0.11.0)
- SomeInterestingProject: Fetched v1
+ SomeInterestingProject:
+ > Fetched v1
"""
Then 'MyProject' looks like:
"""
@@ -90,7 +92,8 @@ Feature: Fetch with ignore in git
Then the output shows
"""
Dfetch (0.11.0)
- SomeInterestingProject: Fetched v1
+ SomeInterestingProject:
+ > Fetched v1
"""
Then 'MyProject' looks like:
"""
diff --git a/features/fetch-with-ignore-svn.feature b/features/fetch-with-ignore-svn.feature
index 424ef128..e1104d29 100644
--- a/features/fetch-with-ignore-svn.feature
+++ b/features/fetch-with-ignore-svn.feature
@@ -28,7 +28,8 @@ Feature: Fetch with ignore in svn
Then the output shows
"""
Dfetch (0.11.0)
- SomeInterestingProject: Fetched trunk - 1
+ SomeInterestingProject:
+ > Fetched trunk - 1
"""
Then 'MyProject' looks like:
"""
@@ -56,7 +57,8 @@ Feature: Fetch with ignore in svn
Then the output shows
"""
Dfetch (0.11.0)
- SomeInterestingProject: Fetched trunk - 1
+ SomeInterestingProject:
+ > Fetched trunk - 1
"""
Then 'MyProject' looks like:
"""
@@ -87,7 +89,8 @@ Feature: Fetch with ignore in svn
Then the output shows
"""
Dfetch (0.11.0)
- SomeInterestingProject: Fetched trunk - 1
+ SomeInterestingProject:
+ > Fetched trunk - 1
"""
Then 'MyProject' looks like:
"""
diff --git a/features/guard-against-overwriting-git.feature b/features/guard-against-overwriting-git.feature
index 979b61d2..7f147343 100644
--- a/features/guard-against-overwriting-git.feature
+++ b/features/guard-against-overwriting-git.feature
@@ -24,7 +24,8 @@ Feature: Guard against overwriting in git
Then the output shows
"""
Dfetch (0.11.0)
- SomeProject : skipped - local changes after last update (use --force to overwrite)
+ SomeProject:
+ > skipped - local changes after last update (use --force to overwrite)
"""
Scenario: Force flag overrides local changes check
@@ -33,7 +34,8 @@ Feature: Guard against overwriting in git
Then the output shows
"""
Dfetch (0.11.0)
- SomeProject : Fetched v2
+ SomeProject:
+ > Fetched v2
"""
Scenario: Ignored files are overwritten
@@ -43,5 +45,6 @@ Feature: Guard against overwriting in git
Then the output shows
"""
Dfetch (0.11.0)
- SomeProject : Fetched v2
+ SomeProject:
+ > Fetched v2
"""
diff --git a/features/guard-against-overwriting-svn.feature b/features/guard-against-overwriting-svn.feature
index f0b1393e..74f4992a 100644
--- a/features/guard-against-overwriting-svn.feature
+++ b/features/guard-against-overwriting-svn.feature
@@ -24,7 +24,8 @@ Feature: Guard against overwriting in svn
Then the output shows
"""
Dfetch (0.11.0)
- SomeProject : skipped - local changes after last update (use --force to overwrite)
+ SomeProject:
+ > skipped - local changes after last update (use --force to overwrite)
"""
Scenario: Force flag overrides local changes check
@@ -33,7 +34,8 @@ Feature: Guard against overwriting in svn
Then the output shows
"""
Dfetch (0.11.0)
- SomeProject : Fetched v2
+ SomeProject:
+ > Fetched v2
"""
Scenario: Ignored files are overwritten
@@ -43,5 +45,6 @@ Feature: Guard against overwriting in svn
Then the output shows
"""
Dfetch (0.11.0)
- SomeProject : Fetched v2
+ SomeProject:
+ > Fetched v2
"""
diff --git a/features/handle-invalid-metadata.feature b/features/handle-invalid-metadata.feature
index fae2e679..180bf0db 100644
--- a/features/handle-invalid-metadata.feature
+++ b/features/handle-invalid-metadata.feature
@@ -23,7 +23,8 @@ Feature: Handle invalid metadata files
Then the output shows
"""
Dfetch (0.11.0)
- ext/test-repo-tag/.dfetch_data.yaml is an invalid metadata file, not checking on disk version!
- ext/test-repo-tag/.dfetch_data.yaml is an invalid metadata file, not checking local hash!
- ext/test-repo-tag : Fetched v1
+ ext/test-repo-tag:
+ > ext/test-repo-tag/.dfetch_data.yaml is an invalid metadata file, not checking on disk version!
+ > ext/test-repo-tag/.dfetch_data.yaml is an invalid metadata file, not checking local hash!
+ > Fetched v1
"""
diff --git a/features/journey-basic-patching.feature b/features/journey-basic-patching.feature
index 10e9e9e8..febfd4e4 100644
--- a/features/journey-basic-patching.feature
+++ b/features/journey-basic-patching.feature
@@ -56,6 +56,8 @@ Feature: Basic patch journey
Then the output shows
"""
Dfetch (0.11.0)
- test-repo : Fetched v1
- test-repo : Applied patch "test-repo.patch"
+ test-repo:
+ > Fetched v1
+ test-repo:
+ > Applied patch "test-repo.patch"
"""
diff --git a/features/journey-basic-usage.feature b/features/journey-basic-usage.feature
index 713cee27..fe3154f7 100644
--- a/features/journey-basic-usage.feature
+++ b/features/journey-basic-usage.feature
@@ -29,7 +29,8 @@ Feature: Basic usage journey
Then the output shows
"""
Dfetch (0.11.0)
- ext/test-repo-tag : wanted & current (v1), available (v2.0)
+ ext/test-repo-tag:
+ > wanted & current (v1), available (v2.0)
"""
When the manifest 'dfetch.yaml' is changed to
"""
diff --git a/features/keep-license-in-project.feature b/features/keep-license-in-project.feature
index 7e109067..6359131c 100644
--- a/features/keep-license-in-project.feature
+++ b/features/keep-license-in-project.feature
@@ -25,11 +25,6 @@ Feature: Keep license in project
| SomeOtherFolder/SomeOtherFile.txt |
| SomeSubFolder/LICENSE |
When I run "dfetch update"
- Then the output shows
- """
- Dfetch (0.11.0)
- SomeProjectWithLicense: Fetched v1
- """
Then 'MyProject' looks like:
"""
MyProject/
@@ -58,11 +53,6 @@ Feature: Keep license in project
| SomeFolder/SomeFile.txt |
| SomeOtherFolder/SomeOtherFile.txt |
When I run "dfetch update"
- Then the output shows
- """
- Dfetch (0.11.0)
- SomeProjectWithLicense: Fetched trunk - 1
- """
Then 'MyProject' looks like:
"""
MyProject/
@@ -90,11 +80,6 @@ Feature: Keep license in project
| SomeFolder/SomeFile.txt |
| SomeOtherFolder/SomeOtherFile.txt |
When I run "dfetch update"
- Then the output shows
- """
- Dfetch (0.11.0)
- SomeProjectWithLicense: Fetched trunk - 1
- """
Then 'MyProject' looks like:
"""
MyProject/
diff --git a/features/patch-after-fetch-git.feature b/features/patch-after-fetch-git.feature
index 81a1fbdb..e57ffc6c 100644
--- a/features/patch-after-fetch-git.feature
+++ b/features/patch-after-fetch-git.feature
@@ -68,8 +68,10 @@ Feature: Patch after fetching from git repo
Then the output shows
"""
Dfetch (0.11.0)
- ext/test-repo-tag : Fetched v2.0
- source/target file does not exist:
+ ext/test-repo-tag:
+ > Fetched v2.0
+ > Applying patch "diff.patch"
+ source/target file does not exist:
--- b'README1.md'
+++ b'README1.md'
Applying patch "diff.patch" failed
@@ -124,11 +126,12 @@ Feature: Patch after fetching from git repo
And the output shows
"""
Dfetch (0.11.0)
- ext/test-repo-tag : Fetched v2.0
- successfully patched 1/1: b'README.md'
- ext/test-repo-tag : Applied patch "001-diff.patch"
- successfully patched 1/1: b'README.md'
- ext/test-repo-tag : Applied patch "002-diff.patch"
+ ext/test-repo-tag:
+ > Fetched v2.0
+ > Applying patch "001-diff.patch"
+ successfully patched 1/1: b'README.md'
+ > Applying patch "002-diff.patch"
+ successfully patched 1/1: b'README.md'
"""
Scenario: Fallback to other file encodings if patch file is not UTF-8 encoded
@@ -167,11 +170,12 @@ Feature: Patch after fetching from git repo
And the output shows
"""
Dfetch (0.11.0)
- ext/test-repo-tag : Fetched v2.0
- error: no patch data found!
- After retrying found that patch-file "diff.patch" is not UTF-8 encoded, consider saving it with UTF-8 encoding.
- successfully patched 1/1: b'README.md'
- ext/test-repo-tag : Applied patch "diff.patch"
+ ext/test-repo-tag:
+ > Fetched v2.0
+ > Applying patch "diff.patch"
+ error: no patch data found!
+ After retrying found that patch-file "diff.patch" is not UTF-8 encoded, consider saving it with UTF-8 encoding.
+ successfully patched 1/1: b'README.md'
"""
Scenario: Patch files are outside manifest dir
@@ -194,6 +198,7 @@ Feature: Patch after fetching from git repo
Then the output shows
"""
Dfetch (0.11.0)
- ext/test-repo-tag : Fetched v2.0
- ext/test-repo-tag : Skipping patch "../diff.patch" which is outside /some/path.
+ ext/test-repo-tag:
+ > Fetched v2.0
+ > Skipping patch "../diff.patch" which is outside /some/path.
"""
diff --git a/features/steps/generic_steps.py b/features/steps/generic_steps.py
index 24737fee..4c3c2d0e 100644
--- a/features/steps/generic_steps.py
+++ b/features/steps/generic_steps.py
@@ -8,6 +8,7 @@
import os
import pathlib
import re
+from contextlib import contextmanager
from itertools import zip_longest
from typing import Iterable, List, Optional, Pattern, Tuple, Union
@@ -15,9 +16,10 @@
from behave.runner import Context
from dfetch.__main__ import DfetchFatalException, run
+from dfetch.log import DLogger
from dfetch.util.util import in_directory
-ansi_escape = re.compile(r"\x1b(?:[@A-Z\\-_]|\[[0-9:;<=>?]*[ -/]*[@-~])")
+ansi_escape = re.compile(r"\[/?[a-z\_ ]+\]")
dfetch_title = re.compile(r"Dfetch \(\d+.\d+.\d+\)")
timestamp = re.compile(r"\d+\/\d+\/\d+, \d+:\d+:\d+")
git_hash = re.compile(r"(\s?)[a-f0-9]{40}(\s?)")
@@ -28,23 +30,40 @@
abs_path = re.compile(r"/tmp/[\w_]+")
+@contextmanager
+def temporary_env(key: str, value: str):
+ """Temporarily set an environment variable inside a context."""
+ old_value = os.environ.get(key)
+ os.environ[key] = value
+ try:
+ yield
+ finally:
+ if old_value is None:
+ del os.environ[key]
+ else:
+ os.environ[key] = old_value
+
+
def remote_server_path(context):
"""Get the path to the remote dir."""
return "/".join(context.remotes_dir_path.split(os.sep))
def call_command(context: Context, args: list[str], path: Optional[str] = ".") -> None:
- length_at_start = len(context.captured.output)
- with in_directory(path or "."):
- try:
- run(args)
- context.cmd_returncode = 0
- except DfetchFatalException:
- context.cmd_returncode = 1
- # Remove the color code + title
- context.cmd_output = dfetch_title.sub(
- "", ansi_escape.sub("", context.captured.output[length_at_start:].strip("\n"))
- )
+ before = context.console.export_text()
+
+ DLogger.reset_projects()
+
+ with temporary_env("CI", "true"):
+ with in_directory(path or "."):
+ try:
+ run(args, context.console)
+ context.cmd_returncode = 0
+ except DfetchFatalException:
+ context.cmd_returncode = 1
+
+ after = context.console.export_text()
+ context.cmd_output = after[len(before) :].strip("\n")
def check_file(path, content):
@@ -81,7 +100,7 @@ def check_content(
):
expected = multisub(
patterns=[
- (git_hash, r"\1[commit hash]\2"),
+ (git_hash, r"\1[commit-hash]\2"),
(iso_timestamp, "[timestamp]"),
(urn_uuid, "[urn-uuid]"),
(bom_ref, "[bom-ref]"),
@@ -91,7 +110,7 @@ def check_content(
actual = multisub(
patterns=[
- (git_hash, r"\1[commit hash]\2"),
+ (git_hash, r"\1[commit-hash]\2"),
(iso_timestamp, "[timestamp]"),
(urn_uuid, "[urn-uuid]"),
(bom_ref, "[bom-ref]"),
@@ -155,6 +174,12 @@ def list_dir(path):
return result
+def normalize_lines(text: str) -> list[str]:
+ """Normalize text for diffing."""
+ lines = text.splitlines()
+ return [line.rstrip() for line in lines if line.strip() != ""]
+
+
def check_output(context, line_count=None):
"""Check command output against expected text.
@@ -164,9 +189,9 @@ def check_output(context, line_count=None):
"""
expected_text = multisub(
patterns=[
- (git_hash, r"\1[commit hash]\2"),
+ (git_hash, r"\1[commit-hash]\2"),
(timestamp, "[timestamp]"),
- (dfetch_title, ""),
+ (ansi_escape, ""),
(svn_error, "svn: EXXXXXX: "),
],
text=context.text,
@@ -174,7 +199,7 @@ def check_output(context, line_count=None):
actual_text = multisub(
patterns=[
- (git_hash, r"\1[commit hash]\2"),
+ (git_hash, r"\1[commit-hash]\2"),
(timestamp, "[timestamp]"),
(ansi_escape, ""),
(
@@ -187,8 +212,10 @@ def check_output(context, line_count=None):
text=context.cmd_output,
)
- actual_lines = actual_text.splitlines()[:line_count]
- diff = difflib.ndiff(actual_lines, expected_text.splitlines())
+ actual_lines = normalize_lines(actual_text)[:line_count]
+ expected_lines = normalize_lines(expected_text)
+
+ diff = difflib.ndiff(actual_lines, expected_lines)
diffs = [x for x in diff if x[0] in ("+", "-")]
if diffs:
diff --git a/features/updated-project-has-dependencies.feature b/features/updated-project-has-dependencies.feature
index 77e7c456..67b7a28c 100644
--- a/features/updated-project-has-dependencies.feature
+++ b/features/updated-project-has-dependencies.feature
@@ -4,6 +4,7 @@ Feature: Updated project has dependencies
manifest. *Dfetch* should recommend the user to add these dependencies to
the manifest.
+ @wip
Scenario: Git projects are specified in the manifest
Given the manifest 'dfetch.yaml' in MyProject
"""
@@ -41,19 +42,18 @@ Feature: Updated project has dependencies
Then the output shows
"""
Dfetch (0.11.0)
- SomeProjectWithChild: Fetched v1
+ SomeProjectWithChild:
+ > Fetched v1
+ > "SomeProjectWithChild" depends on the following project(s) which are not part of your manifest: (found in third-party/SomeProjectWithChild/dfetch.yaml)
+ - name: SomeOtherProject
+ url: some-remote-server/SomeOtherProject.git
+ tag: v1
+ - name: ext/test-repo-tag-v1
+ url: https://github.com/dfetch-org/test-repo
+ tag: v1
- "SomeProjectWithChild" depends on the following project(s) which are not part of your manifest:
- (found in third-party/SomeProjectWithChild/dfetch.yaml)
-
- - name: SomeOtherProject
- url: some-remote-server/SomeOtherProject.git
- tag: v1
- - name: ext/test-repo-tag-v1
- url: https://github.com/dfetch-org/test-repo
- tag: v1
-
- SomeProjectWithoutChild: Fetched v1
+ SomeProjectWithoutChild:
+ > Fetched v1
"""
And 'MyProject' looks like:
"""
@@ -88,7 +88,8 @@ Feature: Updated project has dependencies
Then the output shows
"""
Dfetch (0.11.0)
- SomeProject : Fetched v1
+ SomeProject:
+ > Fetched v1
SomeProject/dfetch.yaml: Schema validation failed:
"very-invalid-manifest\n"
diff --git a/pyproject.toml b/pyproject.toml
index 4f7fc379..be3cfab7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -40,10 +40,8 @@ classifiers = [
]
dependencies = [
"PyYAML==6.0.3",
- "coloredlogs==15.0.1",
"strictyaml==1.7.3",
- "halo==0.0.31",
- "colorama==0.4.6",
+ "rich==14.2.0",
"typing-extensions==4.15.0",
"tldextract==5.3.0",
"sarif-om==1.0.4",
diff --git a/tests/test_report.py b/tests/test_report.py
index 86ea4d69..466f77d6 100644
--- a/tests/test_report.py
+++ b/tests/test_report.py
@@ -34,12 +34,12 @@ def test_report(name, projects):
fake_superproject.root_directory = Path("/tmp")
with patch("dfetch.commands.report.SuperProject", return_value=fake_superproject):
- with patch("dfetch.log.DLogger.print_info_line") as mocked_print_info_line:
+ with patch("dfetch.log.DLogger.print_report_line") as mocked_print_report_line:
report(DEFAULT_ARGS)
if projects:
for project in projects:
- mocked_print_info_line.assert_any_call("project", project["name"])
+ mocked_print_report_line.assert_any_call("project", project["name"])
else:
- mocked_print_info_line.assert_not_called()
+ mocked_print_report_line.assert_not_called()