From b3c1c4236ce7c982b01781ab6a71150878c8d542 Mon Sep 17 00:00:00 2001 From: Paul Hovey Date: Sat, 18 Jul 2026 15:06:31 -0400 Subject: [PATCH] Adds a field to individual sources that allows overriding the --recurse flag --- CHANGELOG.md | 4 +++ gitman/models/config.py | 5 +-- gitman/models/source.py | 15 +++++++++ gitman/tests/test_models_config.py | 51 +++++++++++++++++++++++++++- tests/test_api.py | 53 ++++++++++++++++++++++++++++++ 5 files changed, 125 insertions(+), 3 deletions(-) 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 +