diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8f9f6c49..d4a77735 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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)
diff --git a/gitman/models/config.py b/gitman/models/config.py
index 243aaf50..41c5e859 100644
--- a/gitman/models/config.py
+++ b/gitman/models/config.py
@@ -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,
diff --git a/gitman/models/source.py b/gitman/models/source.py
index 3376b181..115058de 100644
--- a/gitman/models/source.py
+++ b/gitman/models/source.py
@@ -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`) |
@@ -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 = ""
@@ -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 = ""
UNKNOWN = ""
diff --git a/gitman/tests/test_models_config.py b/gitman/tests/test_models_config.py
index 9c1c2ba4..ff6117b8 100644
--- a/gitman/tests/test_models_config.py
+++ b/gitman/tests/test_models_config.py
@@ -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
@@ -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)
diff --git a/tests/test_api.py b/tests/test_api.py
index 38ff8b87..d1e71964 100644
--- a/tests/test_api.py
+++ b/tests/test_api.py
@@ -36,6 +36,7 @@
-
patches:
-
+ recurse:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
rev: example-tag
@@ -49,6 +50,7 @@
-
patches:
-
+ recurse:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_3
rev: 9bf18e16b956041f0267c21baad555a23237b52e
@@ -62,6 +64,7 @@
-
patches:
-
+ recurse:
sources_locked:
-
groups:
@@ -114,6 +117,7 @@ def it_creates_a_new_config_file(tmpdir):
-
patches:
-
+ recurse:
sources_locked:
- repo: https://github.com/githubtraining/hellogitworld
name: sample_dependency
@@ -128,6 +132,7 @@ def it_creates_a_new_config_file(tmpdir):
-
patches:
-
+ recurse:
default_group: ''
groups:
-
@@ -167,6 +172,7 @@ def it_merges_sources(config):
-
patches:
-
+ recurse:
sources_locked:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
@@ -178,6 +184,7 @@ def it_merges_sources(config):
-
patches:
-
+ recurse:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_3
type: git
@@ -188,6 +195,7 @@ def it_merges_sources(config):
-
patches:
-
+ recurse:
""")
config.datafile.load()
@@ -217,6 +225,7 @@ def it_can_handle_missing_locked_sources(config):
-
patches:
-
+ recurse:
sources_locked:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
@@ -228,6 +237,7 @@ def it_can_handle_missing_locked_sources(config):
-
patches:
-
+ recurse:
""")
config.datafile.load()
@@ -514,6 +524,7 @@ def config_with_default_group(config):
-
patches:
-
+ recurse:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
type: git
@@ -527,6 +538,7 @@ def config_with_default_group(config):
-
patches:
-
+ recurse:
groups:
- name: main
members:
@@ -558,6 +570,7 @@ def config_without_default_group(config):
-
patches:
-
+ recurse:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
type: git
@@ -571,6 +584,7 @@ def config_without_default_group(config):
-
patches:
-
+ recurse:
groups:
- name: main
members:
@@ -701,6 +715,7 @@ def it_locks_previously_unlocked_dependencies(config):
-
patches:
-
+ recurse:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
type: git
@@ -714,6 +729,7 @@ def it_locks_previously_unlocked_dependencies(config):
-
patches:
-
+ recurse:
sources_locked:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
@@ -728,6 +744,7 @@ def it_locks_previously_unlocked_dependencies(config):
-
patches:
-
+ recurse:
groups:
-
""")
@@ -752,6 +769,7 @@ def it_locks_previously_unlocked_dependencies(config):
-
patches:
-
+ recurse:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
rev: example-tag
@@ -765,6 +783,7 @@ def it_locks_previously_unlocked_dependencies(config):
-
patches:
-
+ recurse:
sources_locked:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
@@ -779,6 +798,7 @@ def it_locks_previously_unlocked_dependencies(config):
-
patches:
-
+ recurse:
groups:
-
default_group: ''
@@ -801,6 +821,7 @@ def it_should_not_lock_dependencies_when_disabled(config):
-
patches:
-
+ recurse:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
type: git
@@ -814,6 +835,7 @@ def it_should_not_lock_dependencies_when_disabled(config):
-
patches:
-
+ recurse:
sources_locked:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
@@ -828,6 +850,7 @@ def it_should_not_lock_dependencies_when_disabled(config):
-
patches:
-
+ recurse:
groups:
-
""")
@@ -851,6 +874,7 @@ def it_should_not_lock_dependencies_when_disabled(config):
-
patches:
-
+ recurse:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
rev: example-tag
@@ -864,6 +888,7 @@ def it_should_not_lock_dependencies_when_disabled(config):
-
patches:
-
+ recurse:
sources_locked:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
@@ -878,6 +903,7 @@ def it_should_not_lock_dependencies_when_disabled(config):
-
patches:
-
+ recurse:
groups:
-
default_group: ''
@@ -925,6 +951,7 @@ def it_locks_previously_locked_dependencies_by_group_name(config):
-
patches:
-
+ recurse:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
type: git
@@ -938,6 +965,7 @@ def it_locks_previously_locked_dependencies_by_group_name(config):
-
patches:
-
+ recurse:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_3
type: git
@@ -951,6 +979,7 @@ def it_locks_previously_locked_dependencies_by_group_name(config):
-
patches:
-
+ recurse:
sources_locked:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_1
@@ -965,6 +994,7 @@ def it_locks_previously_locked_dependencies_by_group_name(config):
-
patches:
-
+ recurse:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
type: git
@@ -978,6 +1008,7 @@ def it_locks_previously_locked_dependencies_by_group_name(config):
-
patches:
-
+ recurse:
groups:
- name: group_a
members:
@@ -1005,6 +1036,7 @@ def it_locks_previously_locked_dependencies_by_group_name(config):
-
patches:
-
+ recurse:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
rev: example-tag
@@ -1018,6 +1050,7 @@ def it_locks_previously_locked_dependencies_by_group_name(config):
-
patches:
-
+ recurse:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_3
rev: example-tag
@@ -1031,6 +1064,7 @@ def it_locks_previously_locked_dependencies_by_group_name(config):
-
patches:
-
+ recurse:
sources_locked:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_1
@@ -1045,6 +1079,7 @@ def it_locks_previously_locked_dependencies_by_group_name(config):
-
patches:
-
+ recurse:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
rev: 7bd138fe7359561a8c2ff9d195dff238794ccc04
@@ -1058,6 +1093,7 @@ def it_locks_previously_locked_dependencies_by_group_name(config):
-
patches:
-
+ recurse:
groups:
- name: group_a
members:
@@ -1099,6 +1135,7 @@ def git_changes(
-
patches:
-
+ recurse:
sources_locked:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
@@ -1113,6 +1150,7 @@ def git_changes(
-
patches:
-
+ recurse:
groups:
-
""")
@@ -1136,6 +1174,7 @@ def git_changes(
-
patches:
-
+ recurse:
sources_locked:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
@@ -1150,6 +1189,7 @@ def git_changes(
-
patches:
-
+ recurse:
groups:
-
default_group: ''
@@ -1195,6 +1235,7 @@ def git_changes(
-
patches:
-
+ recurse:
sources_locked:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
@@ -1209,6 +1250,7 @@ def git_changes(
-
patches:
-
+ recurse:
groups:
-
""")
@@ -1233,6 +1275,7 @@ def git_changes(
-
patches:
-
+ recurse:
sources_locked:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
@@ -1247,6 +1290,7 @@ def git_changes(
-
patches:
-
+ recurse:
groups:
-
default_group: ''
@@ -1267,6 +1311,7 @@ def it_merges_sources(config):
-
patches:
-
+ recurse:
sources_locked:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
@@ -1278,6 +1323,7 @@ def it_merges_sources(config):
-
patches:
-
+ recurse:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_3
type: git
@@ -1288,6 +1334,7 @@ def it_merges_sources(config):
-
patches:
-
+ recurse:
""")
config.datafile.load()
@@ -1356,6 +1403,7 @@ def it_records_all_versions_when_no_arguments(config):
-
patches:
-
+ recurse:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_2
rev: 7bd138fe7359561a8c2ff9d195dff238794ccc04
@@ -1369,6 +1417,7 @@ def it_records_all_versions_when_no_arguments(config):
-
patches:
-
+ recurse:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_3
rev: 9bf18e16b956041f0267c21baad555a23237b52e
@@ -1382,6 +1431,7 @@ def it_records_all_versions_when_no_arguments(config):
-
patches:
-
+ recurse:
"""))
def it_records_specified_dependencies(config):
@@ -1404,6 +1454,7 @@ def it_records_specified_dependencies(config):
-
patches:
-
+ recurse:
- repo: https://github.com/jacebrowning/gitman-demo
name: gitman_3
rev: 9bf18e16b956041f0267c21baad555a23237b52e
@@ -1417,6 +1468,7 @@ def it_records_specified_dependencies(config):
-
patches:
-
+ recurse:
"""))
def it_should_fail_on_dirty_repositories(config):
@@ -1480,3 +1532,4 @@ def it_should_skip_lock_to_invalid_user_specified_reference(config):
expect(gitman.update(depth=1, lock=False)) == True
expect(gitman.lock("gitman_1@deadbeef_ref", depth=1)) == False
+