Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
245c4fb
Restyle docs to match landing page and restructure per Diataxis
claude Mar 29, 2026
72147e4
Split add scenarios into separate feature files and wire up both casts
claude Mar 29, 2026
a265ea1
Style vendoring page with landing-page icons, grids, and cards
claude Mar 29, 2026
d413093
Align README and docs index messaging with landing page
claude Mar 29, 2026
af78f3e
Style flow diagrams to match the docs design system
claude Mar 29, 2026
9151aa4
Restructure manual into four logical groups
claude Mar 29, 2026
c3f8b6e
Restyle CLI Cheatsheet as a printable 2×2 card grid
claude Mar 29, 2026
aa3ae0d
Redesign CLI Cheatsheet as a beautiful dark terminal card
claude Mar 29, 2026
16e6b36
Add command mindmap and wire up flow diagrams in manual
claude Mar 29, 2026
7608a25
Improve command documentation clarity and completeness
claude Mar 29, 2026
ea104bd
Add visual Design Guide to contributing docs
claude Mar 29, 2026
2e0ec55
Add Diataxis section colouring to sidebar and page headers
claude Mar 29, 2026
1d2544e
Deduplicate docs
spoorcc Mar 29, 2026
460794b
remove outdated after confirmation mention
spoorcc Mar 29, 2026
92ad313
Vendor fonts using dfetch
spoorcc Mar 29, 2026
42d5139
Fix styling
spoorcc Mar 29, 2026
521c410
Split out cheatsheet
spoorcc Mar 29, 2026
c1103a4
Split out migration guide
spoorcc Mar 29, 2026
e2feff2
Split out patching guide
spoorcc Mar 29, 2026
e491a9d
Move cicd docs into howto
spoorcc Mar 29, 2026
7d5e06d
Move adding project to howto
spoorcc Mar 29, 2026
381f89c
Move updating project to howto
spoorcc Mar 29, 2026
baaf9e3
Use more active titles for howto
spoorcc Mar 29, 2026
91709e0
Rename manual to command
spoorcc Mar 29, 2026
8f0ae22
Fix cheatsheet
spoorcc Mar 29, 2026
baa843a
Fix pre format
spoorcc Mar 29, 2026
fc694a0
Reword commands
spoorcc Mar 29, 2026
d474874
Restructure docs
spoorcc Mar 29, 2026
63bbfe5
shorter titles
spoorcc Mar 29, 2026
3632eb1
Update doc/howto/sbom.rst
spoorcc Mar 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 20 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,37 +17,34 @@
[![OpenSSF Best Practices](https://www.bestpractices.dev/projects/11245/badge)](https://www.bestpractices.dev/projects/11245)


**DFetch can manage dependencies**
**Vendor dependencies without the pain.**

We make products that can last 15+ years; because of this we want to be able to have all sources available
to build the entire project from source without depending on external resources.
For this, we needed a dependency manager that was flexible enough to retrieve dependencies as plain text
from various sources. `svn externals`, `git submodules` and `git subtrees` solve a similar
problem, but not in a VCS-agnostic way or completely user-friendly way.
We want self-contained code repositories without any hassle for end-users.
Dfetch must promote upstreaming changes, but allow for local customizations.
The problem is described thoroughly in [managing external dependencies](https://embeddedartistry.com/blog/2020/06/22/qa-on-managing-external-dependencies/) and sometimes
is also known as [*vendoring*](https://dfetch.readthedocs.io/en/latest/vendoring.html).
**Dfetch** copies source code directly into your project — no Git submodules, no SVN externals,
no hidden external links. Fetch from Git, SVN, or plain archive URLs. Dependencies live as plain,
readable files inside your own repository. You stay in full control of every line.

Dfetch supports three source types: **Git**, **SVN**, and **archive files** (`.tar.gz`, `.tgz`, `.tar.bz2`, `.tar.xz`, `.zip`). Archives can be verified with a cryptographic hash (`sha256`, `sha384`, or `sha512`) to guarantee integrity on every fetch.
Dfetch supports **Git**, **SVN**, and **archive files** (`.tar.gz`, `.tgz`, `.tar.bz2`, `.tar.xz`, `.zip`).
Archives can be verified with a cryptographic hash (`sha256`, `sha384`, or `sha512`) to guarantee
integrity on every fetch. No proprietary formats, no lock-in — switch tools any time.

Other tools that do similar things are ``Zephyr's West``, ``CMake ExternalProject`` and other meta tools.
Other tools that do similar things are Zephyr's West, CMake ExternalProject, and other meta tools.
See [alternatives](https://dfetch.readthedocs.io/en/latest/alternatives.html) for a complete list.
The broader concept is known as [*vendoring*](https://dfetch.readthedocs.io/en/latest/vendoring.html).

[**Getting started**](https://dfetch.readthedocs.io/en/latest/getting_started.html) |
[**Manual**](https://dfetch.readthedocs.io/en/latest/manual.html) |
[**Commands**](https://dfetch.readthedocs.io/en/latest/commands.html) |
[**Troubleshooting**](https://dfetch.readthedocs.io/en/latest/troubleshooting.html) |
[**Contributing**](https://dfetch.readthedocs.io/en/latest/contributing.html)

## Problems DFetch Solves
## What Dfetch Does

* Declarative code reuse across projects ([inner sourcing](https://about.gitlab.com/topics/version-control/what-is-innersource/))
* Compose multi-repo code bases into a single working tree
* Vendoring dependencies for reproducible builds
* Apply local patches while keeping upstream syncable
* VCS-agnostic dependency management (Git, SVN, and plain archives)
* Vendor source-only dependencies — fully self-contained, no external links at build time
* VCS-agnostic: mix Git, SVN, and plain archive URLs freely in one manifest
* Fetch and verify archives with cryptographic integrity checks
* Self-contained exports for releases or audits
* Apply local patches while keeping upstream syncable (`dfetch diff` / `dfetch format-patch`)
* Supply-chain ready: SBOM generation, license detection, multi-format CI reports
* Migrate from Git submodules or SVN externals in seconds (`dfetch import`)
* Declarative code reuse across projects ([inner sourcing](https://about.gitlab.com/topics/version-control/what-is-innersource/))

## Install

Expand Down Expand Up @@ -122,10 +119,10 @@ manifest:
hash: sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
```

## Github Action
## GitHub Action

You can use DFetch in your Github Actions workflow to check your dependencies.
The results will be uploaded to Github. Add the following to your workflow file:
You can use Dfetch in your GitHub Actions workflow to check your dependencies.
The results will be uploaded to GitHub. Add the following to your workflow file:

```yaml
jobs:
Expand Down
27 changes: 27 additions & 0 deletions dfetch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,30 @@
patch:
- doc/_ext/patches/001-autoformat-sphinxcontrib.asciinema.patch
- doc/_ext/patches/002-fix-options-sphinxcontrib.asciinema.patch

- name: inter-font

Check warning

Code scanning / DFetch

Project was locally changed Warning

inter-font : inter-font has local changes, please create a patch file or upstream the changes.
remote: github
vcs: archive
src: web/
dst: doc/static/fonts/inter
repo-path: rsms/inter/releases/download/v4.1/Inter-4.1.zip
ignore:
- inter.css
- InterDisplay-*
- InterVariable*
- Inter*Italic*
- Inter-Black*

- name: jetbrains-mono-font

Check warning

Code scanning / DFetch

Project was locally changed Warning

jetbrains-mono-font : jetbrains-mono-font has local changes, please create a patch file or upstream the changes.
remote: github
vcs: archive
src: fonts/webfonts/
dst: doc/static/fonts/jetbrains-mono
repo-path: JetBrains/JetBrainsMono/releases/download/v2.304/JetBrainsMono-2.304.zip
ignore:
- JetBrainsMono-Bold*
- JetBrainsMono-Extra*
- JetBrainsMono-Light*
- JetBrainsMono-Semi*
- JetBrainsMono-Thin*
- JetBrainsMono-*Italic*
47 changes: 6 additions & 41 deletions dfetch/commands/add.py
Original file line number Diff line number Diff line change
@@ -1,48 +1,13 @@
"""*Dfetch* can add projects to the manifest through the CLI.
"""Add a project to the manifest from the command line.

Sometimes you want to add a project to your manifest, but you don't want to
edit the manifest by hand. With ``dfetch add`` you can add a project to your
manifest through the command line.
Use ``dfetch add <url>`` to append a project without prompts, or
``dfetch add -i <url>`` for the interactive wizard.
See :ref:`adding-a-project` for the full guide.

Non-interactive mode
--------------------
In the simplest form you just provide the URL::

dfetch add https://github.com/some-org/some-repo.git

Dfetch fetches remote metadata (branches, tags), picks the default branch,
guesses a destination path from your existing projects, shows a preview, and
appends the entry to ``dfetch.yaml`` immediately without prompting.

Override any field with explicit flags::

dfetch add --name mylib --dst ext/mylib --version v2.0 --src lib https://github.com/some-org/some-repo.git

Interactive mode
----------------
Use ``--interactive`` (``-i``) for a guided, step-by-step wizard::

dfetch add -i https://github.com/some-org/some-repo.git

Pre-fill individual fields to skip specific prompts::

dfetch add -i --version main --src lib/core https://github.com/some-org/some-repo.git

The wizard walks through:

* **name** - defaults to the repository name extracted from the URL
* **dst** - local destination; defaults to a path guessed from existing projects
* **version** - scrollable list of all branches and tags (arrow keys to
navigate, Enter to select, Esc to fall back to free-text input)
* **src** - optional sub-path; browse the remote tree with arrow keys,
expand/collapse folders with Enter/Right/Left
* **ignore** - optional list of paths to exclude; same tree browser with
Space to toggle multiple selections and Enter to confirm
.. scenario-include:: ../features/add-project-through-cli.feature

After confirming the add you are offered to run ``dfetch update`` immediately
so the dependency is materialised without a separate command.
.. scenario-include:: ../features/interactive-add.feature

.. scenario-include:: ../features/add-project-through-cli.feature
"""

from __future__ import annotations
Expand Down
14 changes: 9 additions & 5 deletions dfetch/commands/check.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,32 +64,36 @@ def create_menu(subparsers: dfetch.commands.command.SubparserActionType) -> None
"--no-recommendations",
"-N",
action="store_true",
help="Ignore recommendations from fetched projects.",
help=(
"Do not check sub-manifests found inside fetched projects. "
"By default, dfetch.yaml files discovered in fetched dependencies "
"are also checked for outdated entries."
),
)
parser.add_argument(
"projects",
metavar="<project>",
type=str,
nargs="*",
help="Specific project(s) to check",
help="Specific project(s) to check (default: all projects in manifest)",
)
parser.add_argument(
"--jenkins-json",
metavar="outfile",
type=str,
help="Generate a JSON that can be parsed by Jenkins.",
help="Write a Jenkins warnings-ng JSON report to <outfile>.",
)
parser.add_argument(
"--sarif",
metavar="outfile",
type=str,
help="Generate a Sarif JSON that can be parsed by Github.",
help="Write a SARIF 2.1.0 report to <outfile> (GitHub Advanced Security).",
)
parser.add_argument(
"--code-climate",
metavar="outfile",
type=str,
help="Generate a code-climate JSON that can be parsed by Gitlab.",
help="Write a Code Climate JSON report to <outfile> (GitLab pipelines).",
)

def __call__(self, args: argparse.Namespace) -> None:
Expand Down
13 changes: 11 additions & 2 deletions dfetch/commands/diff.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,24 @@ def create_menu(subparsers: dfetch.commands.command.SubparserActionType) -> None
metavar="<oldrev>[:<newrev>]",
type=str,
default="",
help="Revision(s) to generate diff from",
help=(
"Revision(s) to generate diff from. "
"Omit to diff from the last fetched revision to the working copy. "
"Supply one revision to use it as the start point. "
"Supply two revisions separated by ':' (e.g. abc123:def456) for an "
"explicit range."
),
)

parser.add_argument(
"projects",
metavar="<project>",
type=str,
nargs=1,
help="Project to generate diff from",
help=(
"Project to generate diff from. "
"Output is written to <project>.patch in the superproject root."
),
)

def __call__(self, args: argparse.Namespace) -> None:
Expand Down
13 changes: 12 additions & 1 deletion dfetch/commands/environment.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
"""*Dfetch* can generate output about its working environment."""
"""*Dfetch* can report information about its working environment.

``dfetch environment`` prints:

* **Platform** — operating system name and kernel version.
* **VCS tool versions** — the version of each supported VCS client
(Git, SVN) found on ``PATH``.

Run this command when setting up a new machine to confirm that the required
VCS tools are installed and discoverable, or include the output when filing
a bug report so that developers can reproduce your environment exactly.
"""

import argparse
import platform
Expand Down
6 changes: 5 additions & 1 deletion dfetch/commands/format_patch.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ def create_menu(subparsers: dfetch.commands.command.SubparserActionType) -> None
metavar="<output_directory>",
type=str,
default=".",
help="Output directory for formatted patches",
help=(
"Directory to write formatted patches to "
"(default: current working directory). "
"The output file is named formatted-<project>.patch."
),
Comment on lines +76 to +80
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Incorrect output filename pattern in help text.

The help text claims the output file is named formatted-<project>.patch, but the actual implementation at lines 130-132 uses pathlib.Path(patch_file).name, which preserves the original patch filename rather than following a formatted-<project>.patch pattern.

📝 Proposed fix for the help text
             help=(
                 "Directory to write formatted patches to "
                 "(default: current working directory). "
-                "The output file is named formatted-<project>.patch."
+                "The output filename is preserved from the input patch file."
             ),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
help=(
"Directory to write formatted patches to "
"(default: current working directory). "
"The output file is named formatted-<project>.patch."
),
help=(
"Directory to write formatted patches to "
"(default: current working directory). "
"The output filename is preserved from the input patch file."
),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@dfetch/commands/format_patch.py` around lines 76 - 80, The help string for
the output directory is incorrect: it states the output will be named
formatted-<project>.patch while the implementation actually uses
pathlib.Path(patch_file).name to preserve the original patch filename. Update
the help text (the help= argument in format_patch.py where the directory
argument is defined) to accurately describe that the formatted patch will be
written to the specified directory using the original patch's basename (from
pathlib.Path(patch_file).name), or alternatively change the implementation to
generate formatted-<project>.patch; pick one and make the help text and the code
(pathlib.Path(patch_file).name usage) consistent.

)

def __call__(self, args: argparse.Namespace) -> None:
Expand Down
72 changes: 1 addition & 71 deletions dfetch/commands/import_.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,77 +6,7 @@
After importing, you will need to remove the submodules or externals, and then you can let Dfetch
update by running :ref:`dfetch update <update>`.

Migrating from git submodules
=============================

* Make sure your repository is up-to-date.
* Make sure your submodules are up-to-date (``git submodules update --init``).
* Generate a manifest using :ref:`dfetch import<import>`.
* Remove all git submodules (see `How do I remove a submodule <https://stackoverflow.com/questions/1260748/>`_ ).
* Download all your projects using :ref:`dfetch update<update>`.
* Commit your projects as part of your project.

.. scenario-include:: ../features/import-from-git.feature

Switching branches
~~~~~~~~~~~~~~~~~~
After importing submodules into a manifest within a branch, you might encounter difficulties when switching branches.
If one branch has submodules located where your *DFetched* project dependencies should be, or vice versa.
In both situations below, assume a branch ``feature/use-dfetch`` with a manifest and ``master`` with the original
submodules in their place.

Switching from branch with submodules to branch with manifest
-------------------------------------------------------------
When switching from branch with submodules to one without, git will warn that the
*Dfetched* project dependencies will overwrite the submodule that is currently in the
same location.

.. code-block:: console

$ git checkout feature/use-dfetch
error: The following untracked working tree files would be overwritten by checkout:
MySubmodule/somefile.c
MySubmodule/someotherfile.c

However, ``git status`` will show nothing:

.. code-block:: console

$ git status
On branch master
Your branch is up to date with 'origin/master'.

nothing to commit, working tree clean

To overcome this, remove the submodule folder and checkout branch ``feature/use-dfetch``:

.. code-block:: console

$ rm -rf MySubmodule
$ git checkout feature/use-dfetch

Switching from branch with manifest to branch with submodules
-------------------------------------------------------------
This situation gives no problem, *but* the submodules are gone and need to be initialized again.
To solve this:

.. code-block:: console

$ git checkout master
$ git submodule update --init
Submodule path 'MySubmodule': checked out '08f95e01b297d8b8c1c9101bde58e75cd4d428ce'


Migrating from SVN externals
============================

* Make sure your repository is up-to-date.
* Generate a manifest using :ref:`dfetch import<import>`.
* Remove all svn externals (see `How do I remove svn::externals <https://stackoverflow.com/questions/1044649/>`_ ).
* Download all your projects using :ref:`dfetch update<update>`.
* Commit your projects as part of your project.

.. scenario-include:: ../features/import-from-svn.feature
For full step-by-step migration instructions see :ref:`migration`.

"""

Expand Down
9 changes: 8 additions & 1 deletion dfetch/commands/init.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
"""*Dfetch* can generate a starting manifest.

It will be created in the current folder.
Running ``dfetch init`` creates a ``dfetch.yaml`` file in the current
directory. The file contains a minimal template that you can open and edit
directly, or populate incrementally using :ref:`dfetch add <add>`.

Once you have listed your dependencies, fetch them with :ref:`dfetch update <update>`.

If a ``dfetch.yaml`` already exists in the current directory, *Dfetch*
prints a warning and exits without overwriting it.
"""

import argparse
Expand Down
28 changes: 25 additions & 3 deletions dfetch/commands/report.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,28 @@
"""*Dfetch* can generate multiple reports.

There are several report types that *DFetch* can generate.
"""*Dfetch* can generate reports about the projects in your manifest.

Two report types are available via the ``-t`` / ``--type`` flag:

``stdout`` (default)
Prints a dependency inventory to the terminal. For each project it shows
the remote URL, branch/tag/revision, last-fetch timestamp, applied patches,
and any licences detected in the fetched source tree.

``sbom``
Generates a `CycloneDX 1.6 <https://cyclonedx.org/>`_ Software Bill of
Materials (SBOM) as a JSON file (``report.json`` by default, override with
``-o``). The SBOM includes package URLs (PURLs), VCS references, licence
evidence, and — for archive projects — an optional SHA-256 integrity hash.

This can be uploaded to GitHub as a supply-chain asset or attached to a
GitLab pipeline as a ``cyclonedx`` artefact.

Licence detection
~~~~~~~~~~~~~~~~~
*Dfetch* scans each fetched project for common licence files (``LICENSE``,
``COPYING``, etc.) and uses a best-effort heuristic to identify the licence
type. Only matches with a confidence of 80 % or higher are reported; ambiguous
files are silently skipped. If no licence is detected, the field is left
empty rather than guessing.
"""

import argparse
Expand Down
Loading
Loading