Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 3.9 (unreleased)

- Added a per-source `recurse` option to override `--recurse` for a dependency's nested dependencies.

# 3.8.1 (2025-03-20)

- Fixed reapplication of sparse paths after install. (@fhamdi-bdai)
Expand Down
5 changes: 3 additions & 2 deletions gitman/models/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,10 +120,11 @@ def install_dependencies(
config = load_config(search=False)
if config:
common.indent()
source_recurse = recurse if source.recurse is None else source.recurse
count += config.install_dependencies(
depth=None if depth is None else max(0, depth - 1),
update=update and recurse,
recurse=recurse,
update=update and source_recurse,
recurse=source_recurse,
force=force,
fetch=fetch,
clean=clean,
Expand Down
15 changes: 15 additions & 0 deletions gitman/models/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Source:
| `links` | Creates symlinks within a project | No | `[]` |
| `scripts` | Shell commands to run after checkout | No | `[]` |
| `patches` | patches to be applied after checkout | No | `[]` |
| `recurse` | Overrides `--recurse` for this dependency's nested dependencies | No | `null` (inherit `--recurse`) |

<br>

Expand Down Expand Up @@ -76,6 +77,19 @@ class Source:
- patchdir/0002-add-more.patch
```

### Recurse

Overrides the command-line `--recurse` option for this specific dependency,
controlling whether its own nested dependencies are also updated when running
`gitman update`. When unset (`null`), the command-line value is used. Set to
`true` to always update this dependency's nested dependencies, or `false` to
never update them:

```
repo: "https://github.com/example/repo"
recurse: false
```

"""

repo: str = ""
Expand All @@ -89,6 +103,7 @@ class Source:

scripts: List[str] = field(default_factory=list)
patches: List[str] = field(default_factory=list)
recurse: Optional[bool] = None

DIRTY = "<dirty>"
UNKNOWN = "<unknown>"
Expand Down
51 changes: 50 additions & 1 deletion gitman/tests/test_models_config.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# pylint: disable=redefined-outer-name,unused-variable,expression-not-assigned,len-as-condition

import os
from unittest.mock import MagicMock, patch

import pytest
from expecter import expect

from gitman.models import Config, load_config
from gitman.models import Config, Source, load_config

from .conftest import FILES

Expand Down Expand Up @@ -136,6 +137,54 @@ def it_can_get_dependency_path(config):
)


class TestRecurse:
"""Verify a source's ``recurse`` overrides ``--recurse`` for its subtree."""

@staticmethod
def _record_propagated_recurse(command_recurse, source_recurse):
"""Install a single source and return the (update, recurse) it passes down."""
config = Config("mock/root")
source = Source(repo="https://example.com/repo", name="dep")
source.recurse = source_recurse

recorded = {}

def record(**kwargs):
recorded["update"] = kwargs.get("update")
recorded["recurse"] = kwargs.get("recurse")
return 0

child = MagicMock()
child.install_dependencies.side_effect = record

with patch.object(Config, "_get_sources", return_value=[source]), patch.object(
Config, "_get_sources_filter", return_value=["dep"]
), patch.object(Source, "update_files"), patch.object(
Source, "create_links"
), patch(
"gitman.models.config.shell"
), patch(
"gitman.models.config.os.path.isdir", return_value=True
), patch(
"gitman.models.config.load_config", return_value=child
):
config.install_dependencies(
"dep", update=True, recurse=command_recurse
)

return recorded["update"], recorded["recurse"]

def test_unset_inherits_command_recurse(self):
assert (False, False) == self._record_propagated_recurse(False, None)
assert (True, True) == self._record_propagated_recurse(True, None)

def test_true_forces_recurse_when_command_disables_it(self):
assert (True, True) == self._record_propagated_recurse(False, True)

def test_false_prevents_recurse_when_command_enables_it(self):
assert (False, False) == self._record_propagated_recurse(True, False)


class TestLoad:
def test_load_from_directory_with_config_file(self):
config = load_config(FILES)
Expand Down
Loading
Loading