Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1#!/usr/bin/env python32# Copyright (c) 2021-present The Bitcoin Core developers3# Distributed under the MIT software license, see the accompanying4# file COPYING or http://www.opensource.org/licenses/mit-license.php.6"""7This checks that all files in the repository have correct filenames and permissions8"""10import os11import re12import sys13from subprocess import check_output14from typing import Optional, NoReturn16CMD_TOP_LEVEL = ["git", "rev-parse", "--show-toplevel"]17CMD_ALL_FILES = ["git", "ls-files", "-z", "--full-name", "--stage"]18CMD_SHEBANG_FILES = ["git", "grep", "--full-name", "--line-number", "-I", "^#!"]20ALL_SOURCE_FILENAMES_REGEXP = r"^.*\.(cpp|h|py|sh)$"21ALLOWED_FILENAME_REGEXP = "^[a-zA-Z0-9/_.@][a-zA-Z0-9/_.@-]*$"22ALLOWED_SOURCE_FILENAME_REGEXP = "^[a-z0-9_./-]+$"23ALLOWED_SOURCE_FILENAME_EXCEPTION_REGEXP = (24"^src/(secp256k1/|minisketch/|test/fuzz/FuzzedDataProvider.h)"25)26ALLOWED_PERMISSION_NON_EXECUTABLES = 0o64427ALLOWED_PERMISSION_EXECUTABLES = 0o75528ALLOWED_EXECUTABLE_SHEBANG = {29# https://github.com/dylanaraps/pure-bash-bible#shebang:30# `#!/bin/bash` assumes it is always installed to /bin/ which can cause issues;31# `#!/usr/bin/env bash` searches the user's PATH to find the bash binary.32"py": [b"#!/usr/bin/env python3"],33"sh": [b"#!/usr/bin/env bash", b"#!/bin/sh"],34}37class FileMeta(object):38def __init__(self, file_spec: str):39'''Parse a `git ls files --stage` output line.'''40# 100755 5a150d5f8031fcd75e80a4dd9843afa33655f579 0 ci/test/00_setup_env.sh41meta, self.file_path = file_spec.split('\t', 2)42meta = meta.split()43# The octal file permission of the file. Internally, git only44# keeps an 'executable' bit, so this will always be 0o644 or 0o755.45self.permissions = int(meta[0], 8) & 0o777746# We don't currently care about the other fields48@property49def extension(self) -> Optional[str]:50"""51Returns the file extension for a given filename string.52eg:53'ci/lint_run_all.sh' -> 'sh'54'ci/retry/retry' -> None55'contrib/devtools/split-debug.sh.in' -> 'in'56"""57return str(os.path.splitext(self.file_path)[1].strip(".") or None)59@property60def full_extension(self) -> Optional[str]:61"""62Returns the full file extension for a given filename string.63eg:64'ci/lint_run_all.sh' -> 'sh'65'ci/retry/retry' -> None66'contrib/devtools/split-debug.sh.in' -> 'sh.in'67"""68filename_parts = self.file_path.split(os.extsep, 1)69try:70return filename_parts[1]71except IndexError:72return None75def get_git_file_metadata() -> dict[str, FileMeta]:76'''77Return a dictionary mapping the name of all files in the repository to git tree metadata.78'''79files_raw = check_output(CMD_ALL_FILES, text=True).rstrip("\0").split("\0")80files = {}81for file_spec in files_raw:82meta = FileMeta(file_spec)83files[meta.file_path] = meta84return files86def check_all_filenames(files) -> int:87"""88Checks every file in the repository against an allowed regexp to make sure only lowercase or uppercase89alphanumerics (a-zA-Z0-9), underscores (_), hyphens (-), at (@) and dots (.) are used in repository filenames.90"""91filenames = files.keys()92filename_regex = re.compile(ALLOWED_FILENAME_REGEXP)93failed_tests = 094for filename in filenames:95if not filename_regex.match(filename):96print(97f"""File {repr(filename)} does not match the allowed filename regexp ('{ALLOWED_FILENAME_REGEXP}')."""98)99failed_tests += 1100return failed_tests103def check_source_filenames(files) -> int:104"""105Checks only source files (*.cpp, *.h, *.py, *.sh) against a stricter allowed regexp to make sure only lowercase106alphanumerics (a-z0-9), underscores (_), hyphens (-) and dots (.) are used in source code filenames.108Additionally there is an exception regexp for directories or files which are excepted from matching this regexp.109"""110filenames = [filename for filename in files.keys() if re.match(ALL_SOURCE_FILENAMES_REGEXP, filename, re.IGNORECASE)]111filename_regex = re.compile(ALLOWED_SOURCE_FILENAME_REGEXP)112filename_exception_regex = re.compile(ALLOWED_SOURCE_FILENAME_EXCEPTION_REGEXP)113failed_tests = 0114for filename in filenames:115if not filename_regex.match(filename) and not filename_exception_regex.match(filename):116print(117f"""File {repr(filename)} does not match the allowed source filename regexp ('{ALLOWED_SOURCE_FILENAME_REGEXP}'), or the exception regexp ({ALLOWED_SOURCE_FILENAME_EXCEPTION_REGEXP})."""118)119failed_tests += 1120return failed_tests123def check_all_file_permissions(files) -> int:124"""125Checks all files in the repository match an allowed executable or non-executable file permission octal.127Additionally checks that for executable files, the file contains a shebang line128"""129failed_tests = 0130for filename, file_meta in files.items():131if file_meta.permissions == ALLOWED_PERMISSION_EXECUTABLES:132with open(filename, "rb") as f:133shebang = f.readline().rstrip(b"\n")135# For any file with executable permissions the first line must contain a shebang136if not shebang.startswith(b"#!"):137print(138f"""File "{filename}" has permission {ALLOWED_PERMISSION_EXECUTABLES:03o} (executable) and is thus expected to contain a shebang '#!'. Add shebang or do "chmod {ALLOWED_PERMISSION_NON_EXECUTABLES:03o} {filename}" to make it non-executable."""139)140failed_tests += 1142# For certain file extensions that have been defined, we also check that the shebang conforms to a specific143# allowable set of shebangs144if file_meta.extension in ALLOWED_EXECUTABLE_SHEBANG.keys():145if shebang not in ALLOWED_EXECUTABLE_SHEBANG[file_meta.extension]:146print(147f"""File "{filename}" is missing expected shebang """148+ " or ".join(149[150x.decode("utf-8")151for x in ALLOWED_EXECUTABLE_SHEBANG[file_meta.extension]152]153)154)155failed_tests += 1157elif file_meta.permissions == ALLOWED_PERMISSION_NON_EXECUTABLES:158continue159else:160print(161f"""File "{filename}" has unexpected permission {file_meta.permissions:03o}. Do "chmod {ALLOWED_PERMISSION_NON_EXECUTABLES:03o} {filename}" (if non-executable) or "chmod {ALLOWED_PERMISSION_EXECUTABLES:03o} {filename}" (if executable)."""162)163failed_tests += 1165return failed_tests168def check_shebang_file_permissions(files_meta) -> int:169"""170Checks every file that contains a shebang line to ensure it has an executable permission171"""172filenames = check_output(CMD_SHEBANG_FILES, text=True).strip().split("\n")174# The git grep command we use returns files which contain a shebang on any line within the file175# so we need to filter the list to only files with the shebang on the first line176filenames = [filename.split(":1:")[0] for filename in filenames if ":1:" in filename]178failed_tests = 0179for filename in filenames:180file_meta = files_meta[filename]181if file_meta.permissions != ALLOWED_PERMISSION_EXECUTABLES:182# These file types are typically expected to be sourced and not executed directly183if file_meta.full_extension in ["bash", "init", "openrc", "sh.in"]:184continue186# *.py files which don't contain an `if __name__ == '__main__'` are not expected to be executed directly187if file_meta.extension == "py":188with open(filename, "r") as f:189file_data = f.read()190if not re.search("""if __name__ == ['"]__main__['"]:""", file_data):191continue193print(194f"""File "{filename}" contains a shebang line, but has the file permission {file_meta.permissions:03o} instead of the expected executable permission {ALLOWED_PERMISSION_EXECUTABLES:03o}. Do "chmod {ALLOWED_PERMISSION_EXECUTABLES:03o} {filename}" (or remove the shebang line)."""195)196failed_tests += 1197return failed_tests200def main() -> NoReturn:201root_dir = check_output(CMD_TOP_LEVEL, text=True).strip()202os.chdir(root_dir)204files = get_git_file_metadata()206failed_tests = 0207failed_tests += check_all_filenames(files)208failed_tests += check_source_filenames(files)209failed_tests += check_all_file_permissions(files)210failed_tests += check_shebang_file_permissions(files)212if failed_tests:213print(214f"ERROR: There were {failed_tests} failed tests in the lint-files.py lint test. Please resolve the above errors."215)216sys.exit(1)217else:218sys.exit(0)221if __name__ == "__main__":222main()
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.