• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pantsbuild / pants / 19015773527

02 Nov 2025 05:33PM UTC coverage: 17.872% (-62.4%) from 80.3%
19015773527

Pull #22816

github

web-flow
Merge a12d75757 into 6c024e162
Pull Request #22816: Update Pants internal Python to 3.14

4 of 5 new or added lines in 3 files covered. (80.0%)

28452 existing lines in 683 files now uncovered.

9831 of 55007 relevant lines covered (17.87%)

0.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.22
/src/python/pants/vcs/changed.py
1
# Copyright 2016 Pants project contributors (see CONTRIBUTORS.md).
2
# Licensed under the Apache License, Version 2.0 (see LICENSE).
3

4
from __future__ import annotations
1✔
5

6
from collections.abc import Iterable
1✔
7
from dataclasses import dataclass
1✔
8
from enum import Enum
1✔
9

10
from pants.backend.project_info import dependents
1✔
11
from pants.backend.project_info.dependents import DependentsRequest, find_dependents
1✔
12
from pants.base.build_environment import get_buildroot
1✔
13
from pants.engine.addresses import Address, Addresses
1✔
14
from pants.engine.collection import Collection
1✔
15
from pants.engine.internals.graph import OwnersRequest, find_owners, resolve_unexpanded_targets
1✔
16
from pants.engine.internals.mapper import SpecsFilter
1✔
17
from pants.engine.rules import collect_rules, implicitly, rule
1✔
18
from pants.option.option_types import EnumOption, StrOption
1✔
19
from pants.option.option_value_container import OptionValueContainer
1✔
20
from pants.option.subsystem import Subsystem
1✔
21
from pants.util.docutil import doc_url
1✔
22
from pants.util.frozendict import FrozenDict
1✔
23
from pants.util.ordered_set import FrozenOrderedSet
1✔
24
from pants.util.strutil import help_text
1✔
25
from pants.vcs.git import GitWorktree
1✔
26
from pants.vcs.hunk import Hunk, TextBlocks
1✔
27

28

29
class DependentsOption(Enum):
1✔
30
    NONE = "none"
1✔
31
    DIRECT = "direct"
1✔
32
    TRANSITIVE = "transitive"
1✔
33

34

35
@dataclass(frozen=True)
1✔
36
class ChangedRequest:
1✔
37
    sources: tuple[str, ...]
1✔
38
    sources_blocks: FrozenDict[str, TextBlocks]
1✔
39
    dependents: DependentsOption
1✔
40

41

42
class ChangedAddresses(Collection[Address]):
1✔
43
    pass
1✔
44

45

46
@rule
1✔
47
async def find_changed_owners(
1✔
48
    request: ChangedRequest,
49
    specs_filter: SpecsFilter,
50
) -> ChangedAddresses:
51
    no_dependents = request.dependents == DependentsOption.NONE
×
52
    owners = await find_owners(
×
53
        OwnersRequest(
54
            request.sources,
55
            # If `--changed-dependents` is used, we cannot eagerly filter out root targets. We
56
            # need to first find their dependents, and only then should we filter. See
57
            # https://github.com/pantsbuild/pants/issues/15544
58
            filter_by_global_options=no_dependents,
59
            # Changing a BUILD file might impact the targets it defines.
60
            match_if_owning_build_file_included_in_sources=True,
61
            sources_blocks=request.sources_blocks,
62
        ),
63
        **implicitly(),
64
    )
65

66
    if no_dependents:
×
67
        return ChangedAddresses(owners)
×
68

69
    # See https://github.com/pantsbuild/pants/issues/15313. We filter out target generators because
70
    # they are not useful as aliases for their generated targets in the context of
71
    # `--changed-since`. Including them makes it look like all sibling targets from the same
72
    # target generator have also changed.
73
    #
74
    # However, we also must be careful to preserve if target generators are direct owners, which
75
    # happens when a generated file is deleted.
76
    owner_target_generators = FrozenOrderedSet(
×
77
        addr.maybe_convert_to_target_generator() for addr in owners if addr.is_generated_target
78
    )
79
    dependents = await find_dependents(
×
80
        DependentsRequest(
81
            owners,
82
            transitive=request.dependents == DependentsOption.TRANSITIVE,
83
            include_roots=False,
84
        ),
85
        **implicitly(),
86
    )
87
    result = FrozenOrderedSet(owners) | (dependents - owner_target_generators)
×
88
    if specs_filter.is_specified:
×
89
        # Finally, we must now filter out the result to only include what matches our tags, as the
90
        # last step of https://github.com/pantsbuild/pants/issues/15544.
91
        #
92
        # Note that we use `UnexpandedTargets` rather than `Targets` or `FilteredTargets` so that
93
        # we preserve target generators.
94
        result_as_tgts = await resolve_unexpanded_targets(Addresses(result))
×
95
        result = FrozenOrderedSet(
×
96
            tgt.address for tgt in result_as_tgts if specs_filter.matches(tgt)
97
        )
98

99
    return ChangedAddresses(result)
×
100

101

102
@dataclass(frozen=True)
1✔
103
class ChangedOptions:
1✔
104
    """A wrapper for the options from the `Changed` Subsystem.
105

106
    This is necessary because parsing of these options happens before conventional subsystems are
107
    configured, so the normal mechanisms like `Subsystem.rules()` would not work properly.
108
    """
109

110
    since: str | None
1✔
111
    diffspec: str | None
1✔
112
    dependents: DependentsOption
1✔
113

114
    @classmethod
1✔
115
    def from_options(cls, options: OptionValueContainer) -> ChangedOptions:
1✔
116
        return cls(options.since, options.diffspec, options.dependents)
×
117

118
    @property
1✔
119
    def provided(self) -> bool:
1✔
120
        return bool(self.since) or bool(self.diffspec)
×
121

122
    def changed_files(self, git_worktree: GitWorktree) -> set[str]:
1✔
123
        """Determines the files changed according to SCM/workspace and options."""
124
        if self.diffspec:
×
125
            return git_worktree.changes_in(self.diffspec, relative_to=get_buildroot())
×
126

127
        changes_since = self.since or git_worktree.current_rev_identifier
×
128
        return git_worktree.changed_files(
×
129
            from_commit=changes_since,
130
            include_untracked=True,
131
            relative_to=get_buildroot(),
132
        )
133

134
    def diff_hunks(
1✔
135
        self, git_worktree: GitWorktree, paths: Iterable[str]
136
    ) -> dict[str, tuple[Hunk, ...]]:
137
        """Determines the unified diff hunks changed according to SCM/workspace and options.
138

139
        More info on unified diff: https://www.gnu.org/software/diffutils/manual/html_node/Detailed-Unified.html
140
        """
141
        changes_since = self.since or git_worktree.current_rev_identifier
×
142
        return git_worktree.changed_files_lines(
×
143
            paths,
144
            from_commit=changes_since,
145
            include_untracked=True,
146
            relative_to=get_buildroot(),
147
        )
148

149

150
class Changed(Subsystem):
1✔
151
    options_scope = "changed"
1✔
152
    help = help_text(
1✔
153
        f"""
154
        Tell Pants to detect what files and targets have changed from Git.
155

156
        See {doc_url("docs/using-pants/advanced-target-selection")}.
157
        """
158
    )
159

160
    since = StrOption(
1✔
161
        default=None,
162
        help="Calculate changes since this Git spec (commit range/SHA/ref).",
163
    )
164
    diffspec = StrOption(
1✔
165
        default=None,
166
        help="Calculate changes contained within a given Git spec (commit range/SHA/ref).",
167
    )
168
    dependents = EnumOption(
1✔
169
        default=DependentsOption.NONE,
170
        help="Include direct or transitive dependents of changed targets.",
171
    )
172

173

174
def rules():
1✔
UNCOV
175
    return [*collect_rules(), *dependents.rules()]
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc