-
Notifications
You must be signed in to change notification settings - Fork 5
Respect superproject .gitattributes eol rule when fetching git dependencies #1260
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
22cc176
dd9fb75
64691a1
a359b51
22de947
3198e0e
aebcf7a
07de941
f7640e8
e01b428
291817e
155675e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,3 +16,4 @@ venv* | |
| *.cdx.json | ||
| release_notes.txt | ||
| .hypothesis | ||
| .claude/worktrees/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,7 @@ | ||
| """Git specific implementation.""" | ||
|
|
||
| import pathlib | ||
| from functools import lru_cache | ||
| from pathlib import Path | ||
|
|
||
| from dfetch.log import get_logger | ||
| from dfetch.manifest.project import ProjectEntry | ||
|
|
@@ -67,12 +67,14 @@ def list_tool_info() -> None: | |
| ) | ||
| SubProject._log_tool("git", "<not found in PATH>") | ||
|
|
||
| def _fetch_impl(self, version: Version) -> tuple[Version, list[Dependency]]: | ||
| def _fetch_impl( | ||
| self, version: Version, eol_hint: str | None = None | ||
| ) -> tuple[Version, list[Dependency]]: | ||
| """Get the revision of the remote and place it at the local path.""" | ||
| rev_or_branch_or_tag = self._determine_what_to_fetch(version) | ||
|
|
||
| # When exporting a file, the destination directory must already exist | ||
| pathlib.Path(self.local_path).mkdir(parents=True, exist_ok=True) | ||
| Path(self.local_path).mkdir(parents=True, exist_ok=True) | ||
|
|
||
|
Comment on lines
76
to
78
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Create the parent directory, not the destination path, before file exports. At Line 77, Suggested fix- Path(self.local_path).mkdir(parents=True, exist_ok=True)
+ Path(self.local_path).parent.mkdir(parents=True, exist_ok=True)🤖 Prompt for AI Agents |
||
| license_globs = [f"/{name.lower()}" for name in LICENSE_GLOBS] + [ | ||
| f"/{name.upper()}" for name in LICENSE_GLOBS | ||
|
|
@@ -86,6 +88,7 @@ def _fetch_impl(self, version: Version) -> tuple[Version, list[Dependency]]: | |
| src=self.source, | ||
| must_keeps=license_globs + [".gitmodules"], | ||
| ignore=self.ignore, | ||
| eol=eol_hint, | ||
| ) | ||
| ) | ||
|
|
||
|
|
@@ -108,7 +111,7 @@ def _fetch_impl(self, version: Version) -> tuple[Version, list[Dependency]]: | |
|
|
||
| targets = {local_repo.METADATA_DIR, local_repo.GIT_MODULES_FILE} | ||
|
|
||
| for path in pathlib.Path(self.local_path).rglob("*"): | ||
| for path in Path(self.local_path).rglob("*"): | ||
| if path.name in targets: | ||
| safe_rm(path) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -92,6 +92,9 @@ def update( | |
| force: bool = False, | ||
| ignored_files_callback: Callable[[], Sequence[str]] | None = None, | ||
| patch_count: int = -1, | ||
| eol_preferences_callback: ( | ||
| Callable[[Sequence[str]], dict[str, str]] | None | ||
| ) = None, | ||
| ) -> None: | ||
| """Update this subproject if required. | ||
|
|
||
|
|
@@ -104,6 +107,10 @@ def update( | |
| hash). Calling it at both points ensures the stored hash and the check-time | ||
| hash use the same skiplist, preventing false "local changes" reports. | ||
| patch_count (int, optional): Number of patches to apply (-1 means all). | ||
| eol_preferences_callback (Callable, optional): Given a list of paths, returns | ||
| the line ending ("lf" or "crlf") the superproject requests per path (e.g. | ||
| from its gitattributes). Used to resolve the destination's preference, | ||
| which the VCS backend applies natively while fetching. | ||
| """ | ||
| to_fetch = self.update_is_required(force) | ||
|
|
||
|
|
@@ -131,7 +138,9 @@ def update( | |
| ): | ||
| if warning := plaintext_warning(self.__project.remote_url): | ||
| logger.print_warning_line(self.__project.name, warning) | ||
| actually_fetched, dependency = self._fetch_impl(to_fetch) | ||
| actually_fetched, dependency = self._fetch_impl( | ||
| to_fetch, self._destination_eol_hint(eol_preferences_callback) | ||
| ) | ||
| self._log_project(f"Fetched {actually_fetched}") | ||
|
|
||
| applied_patches = self._apply_patches(patch_count) | ||
|
|
@@ -153,6 +162,22 @@ def update( | |
| logger.debug(f"Writing repo metadata to: {self.__metadata.path}") | ||
| self.__metadata.dump() | ||
|
|
||
| def _destination_eol_hint( | ||
| self, callback: Callable[[Sequence[str]], dict[str, str]] | None | ||
| ) -> str | None: | ||
| """Get the directory-wide line-ending preference for the destination, if any. | ||
|
|
||
| Resolved with a hypothetical path inside the destination, so global and | ||
| directory-level rules can match. The VCS backend applies it natively | ||
| while fetching (git renormalisation, ``svn export --native-eol``). | ||
| """ | ||
| if not callback: | ||
| return None | ||
| exact = pathlib.PurePath(self.local_path).as_posix() | ||
| probe = f"{exact}/_" | ||
| result = callback([exact, probe]) | ||
| return result.get(probe) or result.get(exact) | ||
|
Comment on lines
+178
to
+179
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prefer exact-path EOL over probe fallback. At Line 179, precedence is reversed. If the callback returns both keys, Suggested fix- return result.get(probe) or result.get(exact)
+ return result.get(exact) or result.get(probe)🤖 Prompt for AI Agents |
||
|
|
||
| def _apply_patches(self, count: int = -1) -> list[str]: | ||
| """Apply the patches.""" | ||
| cwd = pathlib.Path(".").resolve() | ||
|
|
@@ -429,8 +454,18 @@ def _are_there_local_changes(self, files_to_ignore: Sequence[str]) -> bool: | |
| ) | ||
|
|
||
| @abstractmethod | ||
| def _fetch_impl(self, version: Version) -> tuple[Version, list[Dependency]]: | ||
| """Fetch the given version of the subproject, should be implemented by the child class.""" | ||
| def _fetch_impl( | ||
| self, version: Version, eol_hint: str | None = None | ||
| ) -> tuple[Version, list[Dependency]]: | ||
| """Fetch the given version of the subproject, should be implemented by the child class. | ||
|
|
||
| Args: | ||
| version: The version to fetch. | ||
| eol_hint: Directory-wide line ending ("lf" or "crlf") the superproject | ||
| requests for the destination. Backends apply it natively while | ||
| fetching where the VCS supports it (git renormalisation, | ||
| ``svn export --native-eol``). | ||
| """ | ||
|
|
||
| @abstractmethod | ||
| def get_default_branch(self) -> str: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,12 +40,15 @@ def run_on_cmdline( | |
| logger: logging.Logger, | ||
| cmd: list[str], | ||
| env: Mapping[str, str] | None = None, | ||
| input_data: bytes | None = None, | ||
| ) -> "subprocess.CompletedProcess[Any]": | ||
| """Run a command and log the output, and raise if something goes wrong.""" | ||
| logger.debug(f"Running {cmd}") | ||
|
|
||
| try: | ||
| proc = subprocess.run(cmd, env=env, capture_output=True, check=True) # nosec | ||
| proc = subprocess.run( # nosec | ||
| cmd, env=env, input=input_data, capture_output=True, check=True | ||
|
Comment on lines
+49
to
+50
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift Remove the inline Bandit suppression from the shared process boundary. This helper is the single subprocess runner for Git/SVN operations, so keeping As per coding guidelines, 🧰 Tools🪛 ast-grep (0.43.0)[error] 48-50: Command coming from incoming request (subprocess-from-request) [error] 48-50: Use of unsanitized data to create processes (os-system-unsanitized-data) 🤖 Prompt for AI AgentsSources: Coding guidelines, Linters/SAST tools |
||
| ) | ||
| except subprocess.CalledProcessError as exc: | ||
| raise SubprocessCommandError( | ||
| exc.cmd, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.