From b3658aca10b51549a83218101c2fd91dbf1b4a14 Mon Sep 17 00:00:00 2001 From: Blue Date: Fri, 26 Jun 2026 17:02:24 -0700 Subject: [PATCH 1/3] Add a script to validate & fix mismatches CRLF in the repo --- .pipelines/build-stage.yml | 3 ++ tools/devops/validate-line-endings.py | 61 +++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 tools/devops/validate-line-endings.py diff --git a/.pipelines/build-stage.yml b/.pipelines/build-stage.yml index 0d0c30b175..15130852c2 100644 --- a/.pipelines/build-stage.yml +++ b/.pipelines/build-stage.yml @@ -113,6 +113,9 @@ stages: - script: python tools\devops\validate-copyright-headers.py test displayName: Validate copyright headers (test/) + - script: python tools\devops\validate-copyright-headers.py + displayName: Validate line feeds + - task: CMake@1 displayName: "CMake ." inputs: diff --git a/tools/devops/validate-line-endings.py b/tools/devops/validate-line-endings.py new file mode 100644 index 0000000000..93e3bfedcd --- /dev/null +++ b/tools/devops/validate-line-endings.py @@ -0,0 +1,61 @@ +# pip install click gitpython + +# Usage: + +# python tools\devops\validate-line-endings.py src test +# python tools\devops\validate-line-endings.py src test --fix +# python tools\devops\validate-line-endings.py src/windows + +import click +import os.path +from git import Repo + +EXTENSIONS = ['.cpp', '.h', '.hpp', '.idl', '.resw'] + +def is_source_file(path: str) -> bool: + return any(path.casefold().endswith(e) for e in EXTENSIONS) + +def has_crlf_mismatch(content: bytes) -> bool: + # Strip all CRLF pairs, then any remaining lone '\n' or '\r' is a mismatch. + stripped = content.replace(b'\r\n', b'') + return b'\n' in stripped or b'\r' in stripped + +def to_crlf(content: bytes) -> bytes: + # Normalize every line ending (CRLF, lone CR, lone LF) to a single CRLF. + return content.replace(b'\r\n', b'\n').replace(b'\r', b'\n').replace(b'\n', b'\r\n') + +@click.command() +@click.argument('repo', required=True, type=click.Path(exists=True)) +@click.option('--fix', is_flag=True, help='Convert mismatching files to CRLF line endings.') +def main(repo: str, fix: bool): + repo = Repo(repo, search_parent_directories=True) + + tracked = repo.git.ls_files('-z').split('\0') + source_files = [os.path.join(repo.working_tree_dir, e) for e in tracked if e and is_source_file(e)] + + mismatches = [] + for e in source_files: + with open(e, 'rb') as fd: + content = fd.read() + + if not has_crlf_mismatch(content): + continue + + mismatches.append(e) + if fix: + with open(e, 'wb') as fd: + fd.write(to_crlf(content)) + + if not mismatches: + click.secho('All files use CRLF line endings', fg='green') + return + + listed = '\n'.join(mismatches) + if fix: + click.secho(f'Converted {len(mismatches)} files to CRLF:\n{listed}', fg='yellow') + else: + click.secho(f'{len(mismatches)} files have non-CRLF line endings:\n{listed}', fg='red') + raise SystemExit(1) + +if __name__ == '__main__': + main() From 699c6b29850693e61ddc6c1ff8b7120d05a8c438 Mon Sep 17 00:00:00 2001 From: Blue Date: Fri, 26 Jun 2026 17:04:55 -0700 Subject: [PATCH 2/3] Remove comments --- tools/devops/validate-line-endings.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tools/devops/validate-line-endings.py b/tools/devops/validate-line-endings.py index 93e3bfedcd..b893019bdf 100644 --- a/tools/devops/validate-line-endings.py +++ b/tools/devops/validate-line-endings.py @@ -1,11 +1,3 @@ -# pip install click gitpython - -# Usage: - -# python tools\devops\validate-line-endings.py src test -# python tools\devops\validate-line-endings.py src test --fix -# python tools\devops\validate-line-endings.py src/windows - import click import os.path from git import Repo From 3dcfaf1af51489ee3031890556a4d9aa9d9dfd4a Mon Sep 17 00:00:00 2001 From: Blue Date: Fri, 26 Jun 2026 17:17:09 -0700 Subject: [PATCH 3/3] Apply PR feedback --- .pipelines/build-stage.yml | 2 +- tools/devops/validate-line-endings.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.pipelines/build-stage.yml b/.pipelines/build-stage.yml index 15130852c2..9492cb104f 100644 --- a/.pipelines/build-stage.yml +++ b/.pipelines/build-stage.yml @@ -113,7 +113,7 @@ stages: - script: python tools\devops\validate-copyright-headers.py test displayName: Validate copyright headers (test/) - - script: python tools\devops\validate-copyright-headers.py + - script: python tools\devops\validate-line-endings.py . displayName: Validate line feeds - task: CMake@1 diff --git a/tools/devops/validate-line-endings.py b/tools/devops/validate-line-endings.py index b893019bdf..e099528fc6 100644 --- a/tools/devops/validate-line-endings.py +++ b/tools/devops/validate-line-endings.py @@ -2,7 +2,7 @@ import os.path from git import Repo -EXTENSIONS = ['.cpp', '.h', '.hpp', '.idl', '.resw'] +EXTENSIONS = ['.c', '.cpp', '.h', '.hpp', '.idl', '.resw'] def is_source_file(path: str) -> bool: return any(path.casefold().endswith(e) for e in EXTENSIONS) @@ -17,10 +17,10 @@ def to_crlf(content: bytes) -> bytes: return content.replace(b'\r\n', b'\n').replace(b'\r', b'\n').replace(b'\n', b'\r\n') @click.command() -@click.argument('repo', required=True, type=click.Path(exists=True)) +@click.argument('path', required=True, type=click.Path(exists=True)) @click.option('--fix', is_flag=True, help='Convert mismatching files to CRLF line endings.') -def main(repo: str, fix: bool): - repo = Repo(repo, search_parent_directories=True) +def main(path: str, fix: bool): + repo = Repo(path, search_parent_directories=True) tracked = repo.git.ls_files('-z').split('\0') source_files = [os.path.join(repo.working_tree_dir, e) for e in tracked if e and is_source_file(e)]