Skip to content

Commit 4dfe2ac

Browse files
committed
Hash directory also hashes untracked files
Fixes #350
1 parent 4766004 commit 4dfe2ac

9 files changed

Lines changed: 81 additions & 12 deletions

File tree

CHANGELOG.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Release 0.11.0 (unreleased)
1919
* Fix unneeded project prefix in SVN diffs (#888)
2020
* Add more tests and documentation for patching (#888)
2121
* Restrict ``src`` to string only in schema (#889)
22+
* Don't consider ignored files for determining local changes (#350)
2223

2324
Release 0.10.0 (released 2025-03-12)
2425
====================================

dfetch/commands/check.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
import dfetch.manifest.manifest
2727
import dfetch.manifest.validate
2828
import dfetch.project
29-
from dfetch.commands.common import check_child_manifests
29+
from dfetch.commands.common import check_child_manifests, files_to_ignore
3030
from dfetch.log import get_logger
3131
from dfetch.manifest.manifest import Manifest
3232
from dfetch.reporting.check.code_climate_reporter import CodeClimateReporter
@@ -90,7 +90,9 @@ def __call__(self, args: argparse.Namespace) -> None:
9090
exceptions: list[str] = []
9191
for project in manifest.selected_projects(args.projects):
9292
with catch_runtime_exceptions(exceptions) as exceptions:
93-
dfetch.project.make(project).check_for_update(reporters)
93+
dfetch.project.make(project).check_for_update(
94+
reporters, files_to_ignore=files_to_ignore(project.destination)
95+
)
9496

9597
if not args.no_recommendations and os.path.isdir(project.destination):
9698
with in_directory(project.destination):

dfetch/commands/common.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
"""Module for common command operations."""
22

33
import os
4+
from collections.abc import Sequence
45

56
import yaml
67

78
from dfetch.log import get_logger
89
from dfetch.manifest.manifest import Manifest, get_childmanifests
910
from dfetch.manifest.project import ProjectEntry
11+
from dfetch.project.svn import SvnRepo
12+
from dfetch.vcs.git import GitLocalRepo
1013

1114
logger = get_logger(__name__)
1215

@@ -63,3 +66,14 @@ def _make_recommendation(
6366
for line in recommendation_json.splitlines():
6467
logger.warning(line)
6568
logger.warning("")
69+
70+
71+
def files_to_ignore(path: str) -> Sequence[str]:
72+
"""Return a list of files that can be ignored in a given path."""
73+
if GitLocalRepo().is_git():
74+
ignore_list = GitLocalRepo.ignored_files(path)
75+
elif SvnRepo.check_path():
76+
ignore_list = SvnRepo.ignored_files(path)
77+
else:
78+
ignore_list = []
79+
return ignore_list

dfetch/commands/update.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
import dfetch.manifest.validate
3030
import dfetch.project.git
3131
import dfetch.project.svn
32-
from dfetch.commands.common import check_child_manifests
32+
from dfetch.commands.common import check_child_manifests, files_to_ignore
3333
from dfetch.log import get_logger
3434
from dfetch.util.util import catch_runtime_exceptions, in_directory
3535

@@ -78,7 +78,10 @@ def __call__(self, args: argparse.Namespace) -> None:
7878
for project in manifest.selected_projects(args.projects):
7979
with catch_runtime_exceptions(exceptions) as exceptions:
8080
self._check_destination(project, destinations)
81-
dfetch.project.make(project).update(force=args.force)
81+
dfetch.project.make(project).update(
82+
force=args.force,
83+
files_to_ignore=files_to_ignore(project.destination),
84+
)
8285

8386
if not args.no_recommendations and os.path.isdir(
8487
project.destination

dfetch/project/svn.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,3 +430,21 @@ def _untracked_files(path: str, ignore: Sequence[str]) -> list[str]:
430430
):
431431
files.append(file_path)
432432
return files
433+
434+
@staticmethod
435+
def ignored_files(path: str) -> Sequence[str]:
436+
"""Get list of ignored files in the working copy."""
437+
if not pathlib.Path(path).exists():
438+
return []
439+
440+
with in_directory(path):
441+
result = (
442+
run_on_cmdline(
443+
logger,
444+
["svn", "status", "--no-ignore", "."],
445+
)
446+
.stdout.decode()
447+
.splitlines()
448+
)
449+
450+
return [line[1:].strip() for line in result if line.startswith("I")]

dfetch/project/vcs.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,19 +92,24 @@ def update_is_required(self, force: bool = False) -> Optional[Version]:
9292
logger.debug(f"{self.__project.name} Current ({current}), Available ({wanted})")
9393
return wanted
9494

95-
def update(self, force: bool = False) -> None:
95+
def update(
96+
self, force: bool = False, files_to_ignore: Optional[Sequence[str]] = None
97+
) -> None:
9698
"""Update this VCS if required.
9799
98100
Args:
99101
force (bool, optional): Ignore if version is ok or any local changes were done.
100102
Defaults to False.
103+
files_to_ignore (Sequence[str], optional): list of files that are ok to overwrite.
101104
"""
102105
to_fetch = self.update_is_required(force)
103106

104107
if not to_fetch:
105108
return
106109

107-
if not force and self._are_there_local_changes():
110+
files_to_ignore = files_to_ignore or []
111+
112+
if not force and self._are_there_local_changes(files_to_ignore):
108113
self._log_project(
109114
"skipped - local changes after last update (use --force to overwrite)"
110115
)
@@ -151,7 +156,9 @@ def apply_patch(self) -> None:
151156
else:
152157
raise RuntimeError(f'Applying patch "{self.__project.patch}" failed')
153158

154-
def check_for_update(self, reporters: Sequence[AbstractCheckReporter]) -> None:
159+
def check_for_update(
160+
self, reporters: Sequence[AbstractCheckReporter], files_to_ignore: Sequence[str]
161+
) -> None:
155162
"""Check if there is an update available."""
156163
on_disk_version = self.on_disk_version()
157164
with Halo(
@@ -177,7 +184,7 @@ def check_for_update(self, reporters: Sequence[AbstractCheckReporter]) -> None:
177184

178185
return
179186

180-
if self._are_there_local_changes():
187+
if self._are_there_local_changes(files_to_ignore):
181188
for reporter in reporters:
182189
reporter.local_changes(self.__project)
183190

@@ -339,7 +346,7 @@ def _check_for_newer_version(self) -> Optional[Version]:
339346
revision = self._latest_revision_on_branch(branch)
340347
return Version(revision=revision, branch=branch) if revision else None
341348

342-
def _are_there_local_changes(self) -> bool:
349+
def _are_there_local_changes(self, files_to_ignore: Sequence[str]) -> bool:
343350
"""Check if there are local changes.
344351
345352
Returns:
@@ -349,7 +356,8 @@ def _are_there_local_changes(self) -> bool:
349356
on_disk_hash = self._on_disk_hash()
350357

351358
return bool(on_disk_hash) and on_disk_hash != hash_directory(
352-
self.local_path, skiplist=[self.__metadata.FILENAME]
359+
self.local_path,
360+
skiplist=[self.__metadata.FILENAME] + list(files_to_ignore),
353361
)
354362

355363
@abstractmethod

dfetch/vcs/git.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,29 @@ def create_diff(
344344

345345
return str(result.stdout.decode())
346346

347+
@staticmethod
348+
def ignored_files(path: str) -> Sequence[str]:
349+
"""List of ignored files."""
350+
if not Path(path).exists():
351+
return []
352+
353+
with in_directory(path):
354+
return list(
355+
run_on_cmdline(
356+
logger,
357+
[
358+
"git",
359+
"ls-files",
360+
"--ignored",
361+
"--others",
362+
"--exclude-standard",
363+
".",
364+
],
365+
)
366+
.stdout.decode()
367+
.splitlines()
368+
)
369+
347370
def untracked_files_patch(self, ignore: Optional[Sequence[str]] = None) -> str:
348371
"""Create a diff for untracked files."""
349372
with in_directory(self._path):

tests/test_update.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def test_forced_update():
6565

6666
update(args)
6767
mocked_make.return_value.update.assert_called_once_with(
68-
force=True
68+
force=True, files_to_ignore=[]
6969
)
7070

7171

tests/test_vcs.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def test_are_there_local_changes(
137137
mocked_on_disk_hash.return_value = hash_in_metadata
138138
mocked_hash_directory.return_value = current_hash
139139

140-
assert expectation == vcs._are_there_local_changes()
140+
assert expectation == vcs._are_there_local_changes(files_to_ignore=[])
141141

142142

143143
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)